From 9b2fd6416775cb27af69e34fb20063d28b4314eb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 15:41:57 +0100 Subject: Simplify theming for `PaneGrid` widget --- widget/src/pane_grid.rs | 1073 +++++++++++++++++++++++------------------------ 1 file changed, 522 insertions(+), 551 deletions(-) (limited to 'widget/src/pane_grid.rs') diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index a18d0fbf..62067e66 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -30,8 +30,6 @@ pub use split::Split; pub use state::State; pub use title_bar::TitleBar; -pub use crate::style::pane_grid::{Appearance, Line, StyleSheet}; - use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -41,9 +39,10 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, + Point, Rectangle, Shell, Size, Vector, Widget, }; +use crate::style::Theme; const DRAG_DEADBAND_DISTANCE: f32 = 10.0; const THICKNESS_RATIO: f32 = 25.0; @@ -104,7 +103,6 @@ pub struct PaneGrid< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: crate::core::Renderer, { contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, @@ -114,12 +112,11 @@ pub struct PaneGrid< on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, - style: ::Style, + style: fn(&Theme) -> Appearance, } impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { /// Creates a [`PaneGrid`] with the given [`State`] and view function. @@ -129,7 +126,10 @@ where pub fn new( state: &'a State, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, - ) -> Self { + ) -> Self + where + Theme: Style, + { let contents = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { state.panes.get(&pane).map(|pane_state| (pane, pane_state)) @@ -160,7 +160,7 @@ where on_click: None, on_drag: None, on_resize: None, - style: Default::default(), + style: Theme::style(), } } @@ -220,11 +220,8 @@ where } /// Sets the style of the [`PaneGrid`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { + self.style = style; self } @@ -239,7 +236,6 @@ impl<'a, Message, Theme, Renderer> Widget for PaneGrid<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, - Theme: StyleSheet, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -284,19 +280,29 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - tree, - renderer, - limits, - self.contents.layout(), - self.width, - self.height, - self.spacing, - self.contents.iter(), - |content, tree, renderer, limits| { - content.layout(tree, renderer, limits) - }, - ) + let size = limits.resolve(self.width, self.height, Size::ZERO); + let node = self.contents.layout(); + let regions = node.pane_regions(self.spacing, size); + + let children = self + .contents + .iter() + .zip(tree.children.iter_mut()) + .filter_map(|((pane, content), tree)| { + let region = regions.get(&pane)?; + let size = Size::new(region.width, region.height); + + let node = content.layout( + tree, + renderer, + &layout::Limits::new(size, size), + ); + + Some(node.move_to(Point::new(region.x, region.y))) + }) + .collect(); + + layout::Node::with_children(size, children) } fn operate( @@ -328,7 +334,10 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { + let mut event_status = event::Status::Ignored; + let action = tree.state.downcast_mut::(); + let node = self.contents.layout(); let on_drag = if self.drag_enabled() { &self.on_drag @@ -336,19 +345,164 @@ where &None }; - let event_status = update( - action, - self.contents.layout(), - &event, - layout, - cursor, - shell, - self.spacing, - self.contents.iter(), - &self.on_click, - on_drag, - &self.on_resize, - ); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let bounds = layout.bounds(); + + if let Some(cursor_position) = cursor.position_over(bounds) { + 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 = node.split_regions( + self.spacing, + Size::new(bounds.width, bounds.height), + ); + + let clicked_split = hovered_split( + splits.iter(), + self.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, + self.contents.iter(), + &self.on_click, + on_drag, + ); + } + } + None => { + click_pane( + action, + layout, + cursor_position, + shell, + self.contents.iter(), + &self.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, origin)) = action.picked_pane() { + if let Some(on_drag) = on_drag { + if let Some(cursor_position) = cursor.position() { + if cursor_position.distance(origin) + > DRAG_DEADBAND_DISTANCE + { + let event = if let Some(edge) = + in_edge(layout, cursor_position) + { + DragEvent::Dropped { + pane, + target: Target::Edge(edge), + } + } else { + let dropped_region = self + .contents + .iter() + .zip(layout.children()) + .find_map(|(target, layout)| { + layout_region( + layout, + cursor_position, + ) + .map(|region| (target, region)) + }); + + match dropped_region { + Some(((target, _), region)) + if pane != target => + { + DragEvent::Dropped { + pane, + target: Target::Pane( + target, region, + ), + } + } + _ => DragEvent::Canceled { pane }, + } + }; + + shell.publish(on_drag(event)); + } + } + } + + event_status = event::Status::Captured; + } else if action.picked_split().is_some() { + event_status = event::Status::Captured; + } + + *action = state::Action::Idle; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some((_, on_resize)) = &self.on_resize { + if let Some((split, _)) = action.picked_split() { + let bounds = layout.bounds(); + + let splits = node.split_regions( + self.spacing, + Size::new(bounds.width, bounds.height), + ); + + if let Some((axis, rectangle, _)) = splits.get(&split) { + if let Some(cursor_position) = cursor.position() { + let ratio = match axis { + Axis::Horizontal => { + let position = cursor_position.y + - bounds.y + - rectangle.y; + + (position / rectangle.height) + .clamp(0.1, 0.9) + } + Axis::Vertical => { + let position = cursor_position.x + - bounds.x + - rectangle.x; + + (position / rectangle.width) + .clamp(0.1, 0.9) + } + }; + + shell.publish(on_resize(ResizeEvent { + split, + ratio, + })); + + event_status = event::Status::Captured; + } + } + } + } + } + _ => {} + } let picked_pane = action.picked_pane().map(|(pane, _)| pane); @@ -382,32 +536,61 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - ) - .unwrap_or_else(|| { - self.contents - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|(((_pane, content), tree), layout)| { - content.mouse_interaction( - tree, - layout, - cursor, - viewport, - renderer, - self.drag_enabled(), + let action = tree.state.downcast_ref::(); + + if action.picked_pane().is_some() { + return mouse::Interaction::Grabbing; + } + + let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); + let node = self.contents.layout(); + + let resize_axis = + action.picked_split().map(|(_, axis)| axis).or_else(|| { + resize_leeway.and_then(|leeway| { + let cursor_position = cursor.position()?; + let bounds = layout.bounds(); + + let splits = + node.split_regions(self.spacing, bounds.size()); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, ) + .map(|(_, axis, _)| axis) }) - .max() - .unwrap_or_default() - }) + }); + + if let Some(resize_axis) = resize_axis { + return match resize_axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + }; + } + + self.contents + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|(((_pane, content), tree), layout)| { + content.mouse_interaction( + tree, + layout, + cursor, + viewport, + renderer, + self.drag_enabled(), + ) + }) + .max() + .unwrap_or_default() } fn draw( @@ -420,28 +603,210 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - draw( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor, - renderer, - theme, - style, - viewport, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - &self.style, - self.contents - .iter() - .zip(&tree.children) - .map(|((pane, content), tree)| (pane, (content, tree))), - |(content, tree), renderer, style, layout, cursor, rectangle| { - content.draw( - tree, renderer, theme, style, layout, cursor, rectangle, + let action = tree.state.downcast_ref::(); + let node = self.contents.layout(); + let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); + + let contents = self + .contents + .iter() + .zip(&tree.children) + .map(|((pane, content), tree)| (pane, (content, tree))); + + let picked_pane = action.picked_pane().filter(|(_, origin)| { + cursor + .position() + .map(|position| position.distance(*origin)) + .unwrap_or_default() + > DRAG_DEADBAND_DISTANCE + }); + + let picked_split = action + .picked_split() + .and_then(|(split, axis)| { + let bounds = layout.bounds(); + + let splits = node.split_regions(self.spacing, bounds.size()); + + let (_axis, region, ratio) = splits.get(&split)?; + + let region = + axis.split_line_bounds(*region, *ratio, self.spacing); + + Some((axis, region + Vector::new(bounds.x, bounds.y), true)) + }) + .or_else(|| match resize_leeway { + Some(leeway) => { + let cursor_position = cursor.position()?; + let bounds = layout.bounds(); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = + node.split_regions(self.spacing, bounds.size()); + + let (_split, axis, region) = hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, + )?; + + Some(( + axis, + region + Vector::new(bounds.x, bounds.y), + false, + )) + } + None => None, + }); + + let pane_cursor = if picked_pane.is_some() { + mouse::Cursor::Unavailable + } else { + cursor + }; + + let mut render_picked_pane = None; + + let pane_in_edge = if picked_pane.is_some() { + cursor + .position() + .and_then(|cursor_position| in_edge(layout, cursor_position)) + } else { + None + }; + + let appearance = (self.style)(theme); + + for ((id, (content, tree)), pane_layout) in + contents.zip(layout.children()) + { + match picked_pane { + Some((dragging, origin)) if id == dragging => { + render_picked_pane = + Some(((content, tree), origin, pane_layout)); + } + Some((dragging, _)) if id != dragging => { + content.draw( + tree, + renderer, + theme, + style, + pane_layout, + pane_cursor, + viewport, + ); + + if picked_pane.is_some() && pane_in_edge.is_none() { + if let Some(region) = + cursor.position().and_then(|cursor_position| { + layout_region(pane_layout, cursor_position) + }) + { + let bounds = + layout_region_bounds(pane_layout, region); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.hovered_region.border, + ..renderer::Quad::default() + }, + appearance.hovered_region.background, + ); + } + } + } + _ => { + content.draw( + tree, + renderer, + theme, + style, + pane_layout, + pane_cursor, + viewport, + ); + } + } + } + + if let Some(edge) = pane_in_edge { + let bounds = edge_bounds(layout, edge); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.hovered_region.border, + ..renderer::Quad::default() + }, + appearance.hovered_region.background, + ); + } + + // Render picked pane last + if let Some(((content, tree), origin, layout)) = render_picked_pane { + if let Some(cursor_position) = cursor.position() { + let bounds = layout.bounds(); + + let translation = + cursor_position - Point::new(origin.x, origin.y); + + renderer.with_translation(translation, |renderer| { + renderer.with_layer(bounds, |renderer| { + content.draw( + tree, + renderer, + theme, + style, + layout, + pane_cursor, + viewport, + ); + }); + }); + } + } + + if picked_pane.is_none() { + if let Some((axis, split_region, is_picked)) = picked_split { + let highlight = if is_picked { + appearance.picked_split + } else { + appearance.hovered_split + }; + + 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, + }, + }, + ..renderer::Quad::default() + }, + highlight.color, ); - }, - ); + } + } } fn overlay<'b>( @@ -469,7 +834,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: crate::core::Renderer + 'a, { fn from( @@ -479,219 +844,6 @@ where } } -/// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout( - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - node: &Node, - width: Length, - height: Length, - spacing: f32, - contents: impl Iterator, - layout_content: impl Fn( - T, - &mut Tree, - &Renderer, - &layout::Limits, - ) -> layout::Node, -) -> layout::Node { - let size = limits.resolve(width, height, Size::ZERO); - - let regions = node.pane_regions(spacing, size); - let children = contents - .zip(tree.children.iter_mut()) - .filter_map(|((pane, content), tree)| { - let region = regions.get(&pane)?; - let size = Size::new(region.width, region.height); - - let node = layout_content( - content, - tree, - renderer, - &layout::Limits::new(size, size), - ); - - Some(node.move_to(Point::new(region.x, region.y))) - }) - .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, - node: &Node, - event: &Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - spacing: f32, - contents: impl Iterator, - on_click: &Option Message + 'a>>, - on_drag: &Option Message + 'a>>, - on_resize: &Option<(f32, Box 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 let Some(cursor_position) = cursor.position_over(bounds) { - 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 = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - let clicked_split = hovered_split( - splits.iter(), - 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, - contents, - on_click, - on_drag, - ); - } - } - None => { - click_pane( - action, - layout, - cursor_position, - shell, - contents, - 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, origin)) = action.picked_pane() { - if let Some(on_drag) = on_drag { - if let Some(cursor_position) = cursor.position() { - if cursor_position.distance(origin) - > DRAG_DEADBAND_DISTANCE - { - let event = if let Some(edge) = - in_edge(layout, cursor_position) - { - DragEvent::Dropped { - pane, - target: Target::Edge(edge), - } - } else { - let dropped_region = contents - .zip(layout.children()) - .find_map(|(target, layout)| { - layout_region(layout, cursor_position) - .map(|region| (target, region)) - }); - - match dropped_region { - Some(((target, _), region)) - if pane != target => - { - DragEvent::Dropped { - pane, - target: Target::Pane( - target, region, - ), - } - } - _ => DragEvent::Canceled { pane }, - } - }; - - shell.publish(on_drag(event)); - } - } - } - - event_status = event::Status::Captured; - } else if action.picked_split().is_some() { - event_status = event::Status::Captured; - } - - *action = state::Action::Idle; - } - 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 = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - if let Some((axis, rectangle, _)) = splits.get(&split) { - if let Some(cursor_position) = cursor.position() { - let ratio = match axis { - Axis::Horizontal => { - let position = cursor_position.y - - bounds.y - - rectangle.y; - - (position / rectangle.height) - .clamp(0.1, 0.9) - } - Axis::Vertical => { - let position = cursor_position.x - - bounds.x - - rectangle.x; - - (position / rectangle.width).clamp(0.1, 0.9) - } - }; - - shell.publish(on_resize(ResizeEvent { - split, - ratio, - })); - - event_status = event::Status::Captured; - } - } - } - } - } - _ => {} - } - - event_status -} - fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option { let bounds = layout.bounds(); @@ -747,257 +899,6 @@ fn click_pane<'a, Message, T>( } } -/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor: mouse::Cursor, - spacing: f32, - resize_leeway: Option, -) -> Option { - if action.picked_pane().is_some() { - return Some(mouse::Interaction::Grabbing); - } - - let resize_axis = - action.picked_split().map(|(_, axis)| axis).or_else(|| { - resize_leeway.and_then(|leeway| { - let cursor_position = cursor.position()?; - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - hovered_split(splits.iter(), spacing + leeway, relative_cursor) - .map(|(_, axis, _)| axis) - }) - }); - - if let Some(resize_axis) = resize_axis { - return Some(match resize_axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - }); - } - - None -} - -/// Draws a [`PaneGrid`]. -pub fn draw( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &mut Renderer, - theme: &Theme, - default_style: &renderer::Style, - viewport: &Rectangle, - spacing: f32, - resize_leeway: Option, - style: &Theme::Style, - contents: impl Iterator, - draw_pane: impl Fn( - T, - &mut Renderer, - &renderer::Style, - Layout<'_>, - mouse::Cursor, - &Rectangle, - ), -) where - Theme: StyleSheet, - Renderer: crate::core::Renderer, -{ - let picked_pane = action.picked_pane().filter(|(_, origin)| { - cursor - .position() - .map(|position| position.distance(*origin)) - .unwrap_or_default() - > DRAG_DEADBAND_DISTANCE - }); - - let picked_split = action - .picked_split() - .and_then(|(split, axis)| { - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_axis, region, ratio) = splits.get(&split)?; - - let region = axis.split_line_bounds(*region, *ratio, spacing); - - Some((axis, region + Vector::new(bounds.x, bounds.y), true)) - }) - .or_else(|| match resize_leeway { - Some(leeway) => { - let cursor_position = cursor.position()?; - let bounds = layout.bounds(); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_split, axis, region) = hovered_split( - splits.iter(), - spacing + leeway, - relative_cursor, - )?; - - Some((axis, region + Vector::new(bounds.x, bounds.y), false)) - } - None => None, - }); - - let pane_cursor = if picked_pane.is_some() { - mouse::Cursor::Unavailable - } else { - cursor - }; - - let mut render_picked_pane = None; - - let pane_in_edge = if picked_pane.is_some() { - cursor - .position() - .and_then(|cursor_position| in_edge(layout, cursor_position)) - } else { - None - }; - - for ((id, pane), pane_layout) in contents.zip(layout.children()) { - match picked_pane { - Some((dragging, origin)) if id == dragging => { - render_picked_pane = Some((pane, origin, pane_layout)); - } - Some((dragging, _)) if id != dragging => { - draw_pane( - pane, - renderer, - default_style, - pane_layout, - pane_cursor, - viewport, - ); - - if picked_pane.is_some() && pane_in_edge.is_none() { - if let Some(region) = - cursor.position().and_then(|cursor_position| { - layout_region(pane_layout, cursor_position) - }) - { - let bounds = layout_region_bounds(pane_layout, region); - let hovered_region_style = theme.hovered_region(style); - - renderer.fill_quad( - renderer::Quad { - bounds, - border: hovered_region_style.border, - ..renderer::Quad::default() - }, - theme.hovered_region(style).background, - ); - } - } - } - _ => { - draw_pane( - pane, - renderer, - default_style, - pane_layout, - pane_cursor, - viewport, - ); - } - } - } - - if let Some(edge) = pane_in_edge { - let hovered_region_style = theme.hovered_region(style); - let bounds = edge_bounds(layout, edge); - - renderer.fill_quad( - renderer::Quad { - bounds, - border: hovered_region_style.border, - ..renderer::Quad::default() - }, - theme.hovered_region(style).background, - ); - } - - // Render picked pane last - if let Some((pane, origin, layout)) = render_picked_pane { - if let Some(cursor_position) = cursor.position() { - let bounds = layout.bounds(); - - let translation = cursor_position - Point::new(origin.x, origin.y); - - renderer.with_translation(translation, |renderer| { - renderer.with_layer(bounds, |renderer| { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor, - viewport, - ); - }); - }); - } - } - - if picked_pane.is_none() { - if let Some((axis, split_region, is_picked)) = picked_split { - let highlight = if is_picked { - theme.picked_split(style) - } else { - theme.hovered_split(style) - }; - - 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, - }, - }, - ..renderer::Quad::default() - }, - highlight.color, - ); - } - } - } -} - fn in_edge(layout: Layout<'_>, cursor: Point) -> Option { let bounds = layout.bounds(); @@ -1214,3 +1115,73 @@ impl<'a, T> Contents<'a, T> { matches!(self, Self::Maximized(..)) } } + +/// The appearance of a [`PaneGrid`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The appearance of a hovered region highlight. + hovered_region: Highlight, + /// The appearance of a picked split. + picked_split: Line, + /// The appearance of a hovered split. + hovered_split: Line, +} + +/// The appearance of a highlight of the [`PaneGrid`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Highlight { + /// The [`Background`] of the pane region. + pub background: Background, + /// The [`Border`] of the pane region. + pub border: Border, +} + +/// A line. +/// +/// It is normally used to define the highlight of something, like a split. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Line { + /// The [`Color`] of the [`Line`]. + pub color: Color, + /// The width of the [`Line`]. + pub width: f32, +} + +/// The definiton of the default style of a [`PaneGrid`]. +pub trait Style { + /// Returns the default style of a [`PaneGrid`]. + fn style() -> fn(&Self) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self) -> Appearance { + default + } +} + +/// The default style of a [`PaneGrid`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + hovered_region: Highlight { + background: Background::Color(Color { + a: 0.5, + ..palette.primary.base.color + }), + border: Border { + width: 2.0, + color: palette.primary.strong.color, + radius: 0.0.into(), + }, + }, + hovered_split: Line { + color: palette.primary.base.color, + width: 2.0, + }, + picked_split: Line { + color: palette.primary.strong.color, + width: 2.0, + }, + } +} -- cgit