diff options
author | 2022-03-10 19:25:57 +0700 | |
---|---|---|
committer | 2022-03-14 17:44:25 +0700 | |
commit | 6dd187ff0822230f084e43636b1aabeb1baf06f6 (patch) | |
tree | 27426980b2882025a3c812ba2401d3bf8f6dca8d /native/src/widget/pane_grid.rs | |
parent | 9f27969d14232355ad628431fb67aa07e42e768f (diff) | |
download | iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.tar.gz iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.tar.bz2 iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.zip |
Implement `pure` version of `PaneGrid` widget
Diffstat (limited to 'native/src/widget/pane_grid.rs')
-rw-r--r-- | native/src/widget/pane_grid.rs | 781 |
1 files changed, 446 insertions, 335 deletions
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8ad63cf1..2093886e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -11,16 +11,19 @@ mod axis; mod configuration; mod content; mod direction; +mod draggable; mod node; mod pane; mod split; -mod state; mod title_bar; +pub mod state; + pub use axis::Axis; pub use configuration::Configuration; pub use content::Content; pub use direction::Direction; +pub use draggable::Draggable; pub use node::Node; pub use pane::Pane; pub use split::Split; @@ -92,6 +95,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet}; #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { state: &'a mut state::Internal, + action: &'a mut state::Action, elements: Vec<(Pane, Content<'a, Message, Renderer>)>, width: Length, height: Length, @@ -124,6 +128,7 @@ where Self { state: &mut state.internal, + action: &mut state.action, elements, width: Length::Fill, height: Length::Fill, @@ -197,80 +202,407 @@ where } } -impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn click_pane( - &mut self, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - ) { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| layout.bounds().contains(cursor_position), +/// Calculates the [`Layout`] of a [`PaneGrid`]. +pub fn layout<Renderer, T>( + renderer: &Renderer, + limits: &layout::Limits, + state: &state::Internal, + width: Length, + height: Length, + spacing: u16, + elements: impl Iterator<Item = (Pane, T)>, + layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { + let limits = limits.width(width).height(height); + let size = limits.resolve(Size::ZERO); + + let regions = state.pane_regions(f32::from(spacing), size); + let children = elements + .filter_map(|(pane, element)| { + let region = regions.get(&pane)?; + let size = Size::new(region.width, region.height); + + let mut node = layout_element( + element, + renderer, + &layout::Limits::new(size, size), ); - if let Some(((pane, content), layout)) = clicked_region.next() { - if let Some(on_click) = &self.on_click { - shell.publish(on_click(*pane)); + node.move_to(Point::new(region.x, region.y)); + + Some(node) + }) + .collect(); + + layout::Node::with_children(size, children) +} + +/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`] +/// accordingly. +pub fn update<'a, Message, T: Draggable>( + action: &mut state::Action, + state: &state::Internal, + event: &Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + spacing: u16, + elements: impl Iterator<Item = (Pane, T)>, + on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, + on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, + on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, +) -> event::Status { + let mut event_status = event::Status::Ignored; + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + event_status = event::Status::Captured; + + match on_resize { + Some((leeway, _)) => { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = state.split_regions( + f32::from(spacing), + Size::new(bounds.width, bounds.height), + ); + + let clicked_split = hovered_split( + splits.iter(), + f32::from(spacing + leeway), + relative_cursor, + ); + + if let Some((split, axis, _)) = clicked_split { + if action.picked_pane().is_none() { + *action = + state::Action::Resizing { split, axis }; + } + } else { + click_pane( + action, + layout, + cursor_position, + shell, + elements, + on_click, + on_drag, + ); + } + } + None => { + click_pane( + action, + layout, + cursor_position, + shell, + elements, + on_click, + on_drag, + ); + } + } } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if let Some((pane, _)) = action.picked_pane() { + if let Some(on_drag) = on_drag { + let mut dropped_region = elements + .zip(layout.children()) + .filter(|(_, layout)| { + layout.bounds().contains(cursor_position) + }); + + let event = match dropped_region.next() { + Some(((target, _), _)) if pane != target => { + DragEvent::Dropped { pane, target } + } + _ => DragEvent::Canceled { pane }, + }; + + shell.publish(on_drag(event)); + } + + *action = state::Action::Idle; + + event_status = event::Status::Captured; + } else if action.picked_split().is_some() { + *action = state::Action::Idle; + + event_status = event::Status::Captured; + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some((_, on_resize)) = on_resize { + if let Some((split, _)) = action.picked_split() { + let bounds = layout.bounds(); + + let splits = state.split_regions( + f32::from(spacing), + Size::new(bounds.width, bounds.height), + ); - if let Some(on_drag) = &self.on_drag { - if content.can_be_picked_at(layout, cursor_position) { - let pane_position = layout.position(); + if let Some((axis, rectangle, _)) = splits.get(&split) { + let ratio = match axis { + Axis::Horizontal => { + let position = + cursor_position.y - bounds.y - rectangle.y; - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); + (position / rectangle.height).max(0.1).min(0.9) + } + Axis::Vertical => { + let position = + cursor_position.x - bounds.x - rectangle.x; - self.state.pick_pane(pane, origin); + (position / rectangle.width).max(0.1).min(0.9) + } + }; - shell.publish(on_drag(DragEvent::Picked { pane: *pane })); + shell.publish(on_resize(ResizeEvent { split, ratio })); + + event_status = event::Status::Captured; + } } } } + _ => {} } - fn trigger_resize( - &mut self, - layout: Layout<'_>, - cursor_position: Point, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - if let Some((_, on_resize)) = &self.on_resize { - if let Some((split, _)) = self.state.picked_split() { + event_status +} + +fn click_pane<'a, Message, T>( + action: &mut state::Action, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + elements: impl Iterator<Item = (Pane, T)>, + on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, + on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, +) where + T: Draggable, +{ + let mut clicked_region = elements + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().contains(cursor_position)); + + if let Some(((pane, content), layout)) = clicked_region.next() { + if let Some(on_click) = &on_click { + shell.publish(on_click(pane)); + } + + if let Some(on_drag) = &on_drag { + if content.can_be_dragged_at(layout, cursor_position) { + let pane_position = layout.position(); + + let origin = cursor_position + - Vector::new(pane_position.x, pane_position.y); + + *action = state::Action::Dragging { pane, origin }; + + shell.publish(on_drag(DragEvent::Picked { pane })); + } + } + } +} + +/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. +pub fn mouse_interaction( + action: &state::Action, + state: &state::Internal, + layout: Layout<'_>, + cursor_position: Point, + spacing: u16, + resize_leeway: Option<u16>, +) -> Option<mouse::Interaction> { + if action.picked_pane().is_some() { + return Some(mouse::Interaction::Grab); + } + + let resize_axis = + action.picked_split().map(|(_, axis)| axis).or_else(|| { + resize_leeway.and_then(|leeway| { let bounds = layout.bounds(); - let splits = self.state.split_regions( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), + let splits = + state.split_regions(f32::from(spacing), bounds.size()); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, ); - if let Some((axis, rectangle, _)) = splits.get(&split) { - let ratio = match axis { - Axis::Horizontal => { - let position = - cursor_position.y - bounds.y - rectangle.y; + hovered_split( + splits.iter(), + f32::from(spacing + leeway), + relative_cursor, + ) + .map(|(_, axis, _)| axis) + }) + }); - (position / rectangle.height).max(0.1).min(0.9) - } - Axis::Vertical => { - let position = - cursor_position.x - bounds.x - rectangle.x; + if let Some(resize_axis) = resize_axis { + return Some(match resize_axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + }); + } - (position / rectangle.width).max(0.1).min(0.9) - } - }; + None +} + +/// Draws a [`PaneGrid`]. +pub fn draw<Renderer, T>( + action: &state::Action, + state: &state::Internal, + layout: Layout<'_>, + cursor_position: Point, + renderer: &mut Renderer, + style: &renderer::Style, + viewport: &Rectangle, + spacing: u16, + resize_leeway: Option<u16>, + style_sheet: &dyn StyleSheet, + elements: impl Iterator<Item = (Pane, T)>, + draw_pane: impl Fn( + T, + &mut Renderer, + &renderer::Style, + Layout<'_>, + Point, + &Rectangle, + ), +) where + Renderer: crate::Renderer, +{ + let picked_pane = action.picked_pane(); - shell.publish(on_resize(ResizeEvent { split, ratio })); + let picked_split = action + .picked_split() + .and_then(|(split, axis)| { + let bounds = layout.bounds(); - return event::Status::Captured; - } + let splits = state.split_regions(f32::from(spacing), bounds.size()); + + let (_axis, region, ratio) = splits.get(&split)?; + + let region = + axis.split_line_bounds(*region, *ratio, f32::from(spacing)); + + Some((axis, region + Vector::new(bounds.x, bounds.y), true)) + }) + .or_else(|| match resize_leeway { + Some(leeway) => { + let bounds = layout.bounds(); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = + state.split_regions(f32::from(spacing), bounds.size()); + + let (_split, axis, region) = hovered_split( + splits.iter(), + f32::from(spacing + leeway), + relative_cursor, + )?; + + Some((axis, region + Vector::new(bounds.x, bounds.y), false)) + } + None => None, + }); + + let pane_cursor_position = if picked_pane.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + for ((id, pane), layout) in elements.zip(layout.children()) { + match picked_pane { + Some((dragging, origin)) if id == dragging => { + let bounds = layout.bounds(); + + renderer.with_translation( + cursor_position + - Point::new(bounds.x + origin.x, bounds.y + origin.y), + |renderer| { + renderer.with_layer(bounds, |renderer| { + draw_pane( + pane, + renderer, + style, + layout, + pane_cursor_position, + viewport, + ); + }); + }, + ); + } + _ => { + draw_pane( + pane, + renderer, + style, + layout, + pane_cursor_position, + viewport, + ); } } + } - event::Status::Ignored + if let Some((axis, split_region, is_picked)) = picked_split { + let highlight = if is_picked { + style_sheet.picked_split() + } else { + style_sheet.hovered_split() + }; + + if let Some(highlight) = highlight { + renderer.fill_quad( + renderer::Quad { + bounds: match axis { + Axis::Horizontal => Rectangle { + x: split_region.x, + y: (split_region.y + + (split_region.height - highlight.width) + / 2.0) + .round(), + width: split_region.width, + height: highlight.width, + }, + Axis::Vertical => Rectangle { + x: (split_region.x + + (split_region.width - highlight.width) / 2.0) + .round(), + y: split_region.y, + width: highlight.width, + height: split_region.height, + }, + }, + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + highlight.color, + ); + } } } @@ -331,28 +663,16 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - let regions = self.state.pane_regions(f32::from(self.spacing), size); - - let children = self - .elements - .iter() - .filter_map(|(pane, element)| { - let region = regions.get(pane)?; - let size = Size::new(region.width, region.height); - - let mut node = - element.layout(renderer, &layout::Limits::new(size, size)); - - node.move_to(Point::new(region.x, region.y)); - - Some(node) - }) - .collect(); - - layout::Node::with_children(size, children) + layout( + renderer, + limits, + self.state, + self.width, + self.height, + self.spacing, + self.elements.iter().map(|(pane, content)| (*pane, content)), + |element, renderer, limits| element.layout(renderer, limits), + ) } fn on_event( @@ -364,89 +684,21 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - let mut event_status = event::Status::Ignored; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - event_status = event::Status::Captured; - - 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.split_regions( - 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, shell); - } - } - None => { - self.click_pane(layout, cursor_position, shell); - } - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if let Some((pane, _)) = self.state.picked_pane() { - if let Some(on_drag) = &self.on_drag { - let mut dropped_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| { - layout.bounds().contains(cursor_position) - }, - ); - - let event = match dropped_region.next() { - Some(((target, _), _)) if pane != *target => { - DragEvent::Dropped { - pane, - target: *target, - } - } - _ => DragEvent::Canceled { pane }, - }; - - shell.publish(on_drag(event)); - } - - self.state.idle(); - - event_status = event::Status::Captured; - } else if self.state.picked_split().is_some() { - self.state.idle(); - - event_status = event::Status::Captured; - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - event_status = - self.trigger_resize(layout, cursor_position, shell); - } - _ => {} - } - - let picked_pane = self.state.picked_pane().map(|(pane, _)| pane); + let event_status = update( + self.action, + self.state, + &event, + layout, + cursor_position, + shell, + self.spacing, + self.elements.iter().map(|(pane, content)| (*pane, content)), + &self.on_click, + &self.on_drag, + &self.on_resize, + ); + + let picked_pane = self.action.picked_pane().map(|(pane, _)| pane); self.elements .iter_mut() @@ -474,53 +726,29 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - if self.state.picked_pane().is_some() { - return mouse::Interaction::Grab; - } - - let resize_axis = - self.state.picked_split().map(|(_, axis)| axis).or_else(|| { - self.on_resize.as_ref().and_then(|(leeway, _)| { - let bounds = layout.bounds(); - - let splits = self - .state - .split_regions(f32::from(self.spacing), bounds.size()); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - hovered_split( - splits.iter(), - f32::from(self.spacing + leeway), - relative_cursor, + mouse_interaction( + self.action, + self.state, + layout, + cursor_position, + self.spacing, + self.on_resize.as_ref().map(|(leeway, _)| *leeway), + ) + .unwrap_or_else(|| { + self.elements + .iter() + .zip(layout.children()) + .map(|((_pane, content), layout)| { + content.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, ) - .map(|(_, axis, _)| axis) }) - }); - - if let Some(resize_axis) = resize_axis { - return match resize_axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - }; - } - - self.elements - .iter() - .zip(layout.children()) - .map(|((_pane, content), layout)| { - content.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - }) - .max() - .unwrap_or_default() + .max() + .unwrap_or_default() + }) } fn draw( @@ -531,139 +759,22 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let picked_pane = self.state.picked_pane(); - - let picked_split = self - .state - .picked_split() - .and_then(|(split, axis)| { - let bounds = layout.bounds(); - - let splits = self - .state - .split_regions(f32::from(self.spacing), bounds.size()); - - let (_axis, region, ratio) = splits.get(&split)?; - - let region = axis.split_line_bounds( - *region, - *ratio, - f32::from(self.spacing), - ); - - Some((axis, region + Vector::new(bounds.x, bounds.y), true)) - }) - .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 - .split_regions(f32::from(self.spacing), bounds.size()); - - let (_split, axis, region) = hovered_split( - splits.iter(), - f32::from(self.spacing + leeway), - relative_cursor, - )?; - - Some(( - axis, - region + Vector::new(bounds.x, bounds.y), - false, - )) - } - None => None, - }); - - let pane_cursor_position = if picked_pane.is_some() { - // TODO: Remove once cursor availability is encoded in the type - // system - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - - for ((id, pane), layout) in self.elements.iter().zip(layout.children()) - { - match picked_pane { - Some((dragging, origin)) if *id == dragging => { - let bounds = layout.bounds(); - - renderer.with_translation( - cursor_position - - Point::new( - bounds.x + origin.x, - bounds.y + origin.y, - ), - |renderer| { - renderer.with_layer(bounds, |renderer| { - pane.draw( - renderer, - style, - layout, - pane_cursor_position, - viewport, - ); - }); - }, - ); - } - _ => { - pane.draw( - renderer, - style, - layout, - pane_cursor_position, - viewport, - ); - } - } - } - - if let Some((axis, split_region, is_picked)) = picked_split { - let highlight = if is_picked { - self.style_sheet.picked_split() - } else { - self.style_sheet.hovered_split() - }; - - if let Some(highlight) = highlight { - renderer.fill_quad( - renderer::Quad { - bounds: match axis { - Axis::Horizontal => Rectangle { - x: split_region.x, - y: (split_region.y - + (split_region.height - highlight.width) - / 2.0) - .round(), - width: split_region.width, - height: highlight.width, - }, - Axis::Vertical => Rectangle { - x: (split_region.x - + (split_region.width - highlight.width) - / 2.0) - .round(), - y: split_region.y, - width: highlight.width, - height: split_region.height, - }, - }, - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - highlight.color, - ); - } - } + draw( + self.action, + self.state, + layout, + cursor_position, + renderer, + style, + viewport, + self.spacing, + self.on_resize.as_ref().map(|(leeway, _)| *leeway), + self.style_sheet.as_ref(), + self.elements.iter().map(|(pane, content)| (*pane, content)), + |pane, renderer, style, layout, cursor_position, rectangle| { + pane.draw(renderer, style, layout, cursor_position, rectangle); + }, + ) } fn overlay( |