diff options
-rw-r--r-- | examples/integration/src/controls.rs | 2 | ||||
-rw-r--r-- | examples/integration/src/main.rs | 21 | ||||
-rw-r--r-- | examples/integration/src/scene.rs | 29 | ||||
-rw-r--r-- | examples/pane_grid/src/main.rs | 2 | ||||
-rw-r--r-- | glow/src/widget/progress_bar.rs | 8 | ||||
-rw-r--r-- | glutin/src/application.rs | 4 | ||||
-rw-r--r-- | native/src/program/state.rs | 11 | ||||
-rw-r--r-- | native/src/widget/pane_grid.rs | 233 | ||||
-rw-r--r-- | native/src/widget/pane_grid/axis.rs | 24 | ||||
-rw-r--r-- | native/src/widget/slider.rs | 25 | ||||
-rw-r--r-- | winit/src/application.rs | 4 |
11 files changed, 215 insertions, 148 deletions
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 81c35072..e6e74995 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -9,7 +9,7 @@ pub struct Controls { sliders: [slider::State; 3], } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Message { BackgroundColorChanged(Color), } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index db8b4366..4c4b6d84 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -119,16 +119,19 @@ pub fn main() { } } Event::MainEventsCleared => { - // We update iced - let _ = state.update( - None, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + None, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); - // and request a redraw - window.request_redraw(); + // and request a redraw + window.request_redraw(); + } } Event::RedrawRequested(_) => { if resized { diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 6c1a4581..74cbb925 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -3,17 +3,13 @@ use iced_winit::Color; pub struct Scene { pipeline: wgpu::RenderPipeline, - bind_group: wgpu::BindGroup, } impl Scene { pub fn new(device: &wgpu::Device) -> Scene { - let (pipeline, bind_group) = build_pipeline(device); + let pipeline = build_pipeline(device); - Scene { - pipeline, - bind_group, - } + Scene { pipeline } } pub fn clear<'a>( @@ -45,14 +41,11 @@ impl Scene { pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.bind_group, &[]); render_pass.draw(0..3, 0..1); } } -fn build_pipeline( - device: &wgpu::Device, -) -> (wgpu::RenderPipeline, wgpu::BindGroup) { +fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { let vs = include_bytes!("shader/vert.spv"); let fs = include_bytes!("shader/frag.spv"); @@ -64,21 +57,9 @@ fn build_pipeline( &wgpu::read_spirv(std::io::Cursor::new(&fs[..])).unwrap(), ); - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - bindings: &[], - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - bindings: &[], - }); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[], }); let pipeline = @@ -116,5 +97,5 @@ fn build_pipeline( alpha_to_coverage_enabled: false, }); - (pipeline, bind_group) + pipeline } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b4bbd68f..a821072f 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -103,7 +103,7 @@ impl Sandbox for Example { .height(Length::Fill) .spacing(10) .on_drag(Message::Dragged) - .on_resize(Message::Resized) + .on_resize(10, Message::Resized) .on_key_press(handle_hotkey); Container::new(pane_grid) diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs index 5782103c..a636a3a6 100644 --- a/glow/src/widget/progress_bar.rs +++ b/glow/src/widget/progress_bar.rs @@ -1,9 +1,9 @@ -//! Allow your users to perform actions by pressing a button. +//! Allow your users to visually track the progress of a computation. //! -//! A [`Button`] has some local [`State`]. +//! A [`ProgressBar`] has a range of possible values and a current value, +//! as well as a length, height and style. //! -//! [`Button`]: type.Button.html -//! [`State`]: struct.State.html +//! [`ProgressBar`]: type.ProgressBar.html use crate::Renderer; pub use iced_graphics::progress_bar::{Style, StyleSheet}; diff --git a/glutin/src/application.rs b/glutin/src/application.rs index c777a13b..4f36114c 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -95,6 +95,10 @@ pub fn run<A, E, C>( event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { + if state.is_queue_empty() { + return; + } + let command = runtime.enter(|| { state.update( clipboard.as_ref().map(|c| c as _), diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 8716d8b9..bb428198 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -88,6 +88,13 @@ where self.queued_messages.push(message); } + /// Returns whether the event queue of the [`State`] is empty or not. + /// + /// [`State`]: struct.State.html + pub fn is_queue_empty(&self) -> bool { + self.queued_events.is_empty() && self.queued_messages.is_empty() + } + /// Processes all the queued events and messages, rebuilding and redrawing /// the widgets of the linked [`Program`] if necessary. /// @@ -102,10 +109,6 @@ where renderer: &mut P::Renderer, debug: &mut Debug, ) -> Option<Command<P::Message>> { - if self.queued_events.is_empty() && self.queued_messages.is_empty() { - return None; - } - let mut user_interface = build_user_interface( &mut self.program, self.cache.take().unwrap(), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 076ae76f..2d21a968 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -26,7 +26,7 @@ pub use state::{Focus, State}; use crate::{ keyboard, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Size, Widget, + Point, Rectangle, Size, Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -76,7 +76,7 @@ use crate::{ /// }.into() /// }) /// .on_drag(Message::PaneDragged) -/// .on_resize(Message::PaneResized); +/// .on_resize(10, Message::PaneResized); /// ``` /// /// [`PaneGrid`]: struct.PaneGrid.html @@ -91,7 +91,7 @@ pub struct PaneGrid<'a, Message, Renderer> { spacing: u16, modifier_keys: keyboard::ModifiersState, on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, - on_resize: Option<Box<dyn Fn(ResizeEvent) -> Message + 'a>>, + on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>, } @@ -177,8 +177,8 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the modifier keys of the [`PaneGrid`]. /// - /// The modifier keys will need to be pressed to trigger dragging, resizing, - /// and key events. + /// The modifier keys will need to be pressed to trigger dragging, and key + /// events. /// /// The default modifier key is `Ctrl`. /// @@ -208,14 +208,19 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Enables the resize interactions of the [`PaneGrid`], which will /// use the provided function to produce messages. /// - /// Panes can be resized using `Modifier keys + Right click`. + /// The `leeway` describes the amount of space around a split that can be + /// used to grab it. + /// + /// The grabbable area of a split will have a length of `spacing + leeway`, + /// properly centered. In other words, a length of + /// `(spacing + leeway) / 2.0` on either side of the split line. /// /// [`PaneGrid`]: struct.PaneGrid.html - pub fn on_resize<F>(mut self, f: F) -> Self + pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self where F: 'a + Fn(ResizeEvent) -> Message, { - self.on_resize = Some(Box::new(f)); + self.on_resize = Some((leeway, Box::new(f))); self } @@ -244,13 +249,42 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + fn click_pane( + &mut self, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec<Message>, + ) { + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| layout.bounds().contains(cursor_position), + ); + + if let Some(((pane, _), _)) = clicked_region.next() { + match &self.on_drag { + Some(on_drag) + if self.pressed_modifiers.matches(self.modifier_keys) => + { + self.state.pick_pane(pane); + + messages.push(on_drag(DragEvent::Picked { pane: *pane })); + } + _ => { + self.state.focus(pane); + } + } + } else { + self.state.unfocus(); + } + } + fn trigger_resize( &mut self, layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, ) { - if let Some(on_resize) = &self.on_resize { + if let Some((_, on_resize)) = &self.on_resize { if let Some((split, _)) = self.state.picked_split() { let bounds = layout.bounds(); @@ -409,32 +443,45 @@ where match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| { - layout.bounds().contains(cursor_position) - }, - ); - - if let Some(((pane, _), _)) = clicked_region.next() { - match &self.on_drag { - Some(on_drag) - if self - .pressed_modifiers - .matches(self.modifier_keys) => - { - self.state.pick_pane(pane); - - messages.push(on_drag(DragEvent::Picked { - pane: *pane, - })); + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + match self.on_resize { + Some((leeway, _)) => { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + let clicked_split = hovered_split( + splits.iter(), + f32::from(self.spacing + leeway), + relative_cursor, + ); + + if let Some((split, axis)) = clicked_split { + self.state.pick_split(&split, axis); + } else { + self.click_pane( + layout, + cursor_position, + messages, + ); + } } - _ => { - self.state.focus(pane); + None => { + self.click_pane( + layout, + cursor_position, + messages, + ); } } - } else { - self.state.unfocus(); } } mouse::Event::ButtonReleased(mouse::Button::Left) => { @@ -462,78 +509,10 @@ where messages.push(on_drag(event)); } + } else if self.state.picked_split().is_some() { + self.state.drop_split(); } } - mouse::Event::ButtonPressed(mouse::Button::Right) - if self.on_resize.is_some() - && self.state.picked_pane().is_none() - && self - .pressed_modifiers - .matches(self.modifier_keys) => - { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); - - let mut sorted_splits: Vec<_> = splits - .iter() - .filter(|(_, (axis, rectangle, _))| match axis { - Axis::Horizontal => { - relative_cursor.x > rectangle.x - && relative_cursor.x - < rectangle.x + rectangle.width - } - Axis::Vertical => { - relative_cursor.y > rectangle.y - && relative_cursor.y - < rectangle.y + rectangle.height - } - }) - .collect(); - - sorted_splits.sort_by_key( - |(_, (axis, rectangle, ratio))| { - let distance = match axis { - Axis::Horizontal => (relative_cursor.y - - (rectangle.y - + rectangle.height * ratio)) - .abs(), - Axis::Vertical => (relative_cursor.x - - (rectangle.x - + rectangle.width * ratio)) - .abs(), - }; - - distance.round() as u32 - }, - ); - - if let Some((split, (axis, _, _))) = - sorted_splits.first() - { - self.state.pick_split(split, *axis); - self.trigger_resize( - layout, - cursor_position, - messages, - ); - } - } - } - mouse::Event::ButtonReleased(mouse::Button::Right) - if self.state.picked_split().is_some() => - { - self.state.drop_split(); - } mouse::Event::CursorMoved { .. } => { self.trigger_resize(layout, cursor_position, messages); } @@ -597,11 +576,37 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { + let picked_split = self + .state + .picked_split() + .or_else(|| match self.on_resize { + Some((leeway, _)) => { + let bounds = layout.bounds(); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = self + .state + .splits(f32::from(self.spacing), bounds.size()); + + hovered_split( + splits.iter(), + f32::from(self.spacing + leeway), + relative_cursor, + ) + } + None => None, + }) + .map(|(_, axis)| axis); + renderer.draw( defaults, &self.elements, self.state.picked_pane(), - self.state.picked_split().map(|(_, axis)| axis), + picked_split, layout, cursor_position, ) @@ -665,3 +670,25 @@ where Element::new(pane_grid) } } + +/* + * Helpers + */ +fn hovered_split<'a>( + splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>, + spacing: f32, + cursor_position: Point, +) -> Option<(Split, Axis)> { + splits + .filter_map(|(split, (axis, region, ratio))| { + let bounds = + axis.split_line_bounds(*region, *ratio, f32::from(spacing)); + + if bounds.contains(cursor_position) { + Some((*split, *axis)) + } else { + None + } + }) + .next() +} diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index b3a306d5..2320cb7c 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -53,6 +53,30 @@ impl Axis { } } } + + pub(super) fn split_line_bounds( + &self, + rectangle: Rectangle, + ratio: f32, + spacing: f32, + ) -> Rectangle { + match self { + Axis::Horizontal => Rectangle { + x: rectangle.x, + y: (rectangle.y + rectangle.height * ratio - spacing / 2.0) + .round(), + width: rectangle.width, + height: spacing, + }, + Axis::Vertical => Rectangle { + x: (rectangle.x + rectangle.width * ratio - spacing / 2.0) + .round(), + y: rectangle.y, + width: spacing, + height: rectangle.height, + }, + } + } } #[cfg(test)] diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 8cdfc3de..753a49fe 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -40,6 +40,7 @@ pub struct Slider<'a, Message, Renderer: self::Renderer> { range: RangeInclusive<f32>, value: f32, on_change: Box<dyn Fn(f32) -> Message>, + on_release: Option<Message>, width: Length, style: Renderer::Style, } @@ -71,11 +72,25 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { value: value.max(*range.start()).min(*range.end()), range, on_change: Box::new(on_change), + on_release: None, width: Length::Fill, style: Renderer::Style::default(), } } + /// Sets the release message of the [`Slider`]. + /// This is called when the mouse is released from the slider. + /// + /// Typically, the user's interaction with the slider is finished when this message is produced. + /// This is useful if you need to spawn a long-running task from the slider's result, where + /// the default on_change message could create too many events. + /// + /// [`Slider`]: struct.Slider.html + pub fn on_release(mut self, on_release: Message) -> Self { + self.on_release = Some(on_release); + self + } + /// Sets the width of the [`Slider`]. /// /// [`Slider`]: struct.Slider.html @@ -114,6 +129,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message, Renderer> where Renderer: self::Renderer, + Message: Clone, { fn width(&self) -> Length { self.width @@ -171,7 +187,12 @@ where } } mouse::Event::ButtonReleased(mouse::Button::Left) => { - self.state.is_dragging = false; + if self.state.is_dragging { + if let Some(on_release) = self.on_release.clone() { + messages.push(on_release); + } + self.state.is_dragging = false; + } } mouse::Event::CursorMoved { .. } => { if self.state.is_dragging { @@ -252,7 +273,7 @@ impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Renderer: 'a + self::Renderer, - Message: 'a, + Message: 'a + Clone, { fn from( slider: Slider<'a, Message, Renderer>, diff --git a/winit/src/application.rs b/winit/src/application.rs index 73ac72b2..a5d00407 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -150,6 +150,10 @@ pub fn run<A, E, C>( event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { + if state.is_queue_empty() { + return; + } + let command = runtime.enter(|| { state.update( clipboard.as_ref().map(|c| c as _), |