diff options
-rw-r--r-- | examples/pane_grid/src/main.rs | 3 | ||||
-rw-r--r-- | runtime/src/window.rs | 12 | ||||
-rw-r--r-- | runtime/src/window/action.rs | 19 | ||||
-rw-r--r-- | style/src/pane_grid.rs | 2 | ||||
-rw-r--r-- | tiny_skia/Cargo.toml | 4 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 10 | ||||
-rw-r--r-- | tiny_skia/src/raster.rs | 6 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 34 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 48 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 2 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 32 | ||||
-rw-r--r-- | widget/src/lazy/component.rs | 4 | ||||
-rw-r--r-- | widget/src/pane_grid.rs | 275 | ||||
-rw-r--r-- | widget/src/pane_grid/node.rs | 10 | ||||
-rw-r--r-- | widget/src/pane_grid/state.rs | 108 | ||||
-rw-r--r-- | widget/src/tooltip.rs | 269 | ||||
-rw-r--r-- | winit/src/application.rs | 16 |
17 files changed, 572 insertions, 282 deletions
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 54c36d69..04896e20 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -108,9 +108,8 @@ impl Application for Example { Message::Dragged(pane_grid::DragEvent::Dropped { pane, target, - region, }) => { - self.panes.split_with(&target, &pane, region); + self.panes.drop(&pane, target); } Message::Dragged(_) => {} Message::TogglePin(pane) => { diff --git a/runtime/src/window.rs b/runtime/src/window.rs index e448edef..5219fbfd 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -9,6 +9,7 @@ pub use screenshot::Screenshot; use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; +use crate::core::Size; use crate::futures::subscription::{self, Subscription}; /// Subscribes to the frames of the window of the running application. @@ -37,8 +38,15 @@ pub fn drag<Message>() -> Command<Message> { } /// Resizes the window to the given logical dimensions. -pub fn resize<Message>(width: u32, height: u32) -> Command<Message> { - Command::single(command::Action::Window(Action::Resize { width, height })) +pub fn resize<Message>(new_size: Size<u32>) -> Command<Message> { + Command::single(command::Action::Window(Action::Resize(new_size))) +} + +/// Fetches the current window size in logical dimensions. +pub fn fetch_size<Message>( + f: impl FnOnce(Size<u32>) -> Message + 'static, +) -> Command<Message> { + Command::single(command::Action::Window(Action::FetchSize(Box::new(f)))) } /// Maximizes the window. diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 09be1810..b6964e36 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,4 +1,5 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; +use crate::core::Size; use crate::futures::MaybeSend; use crate::window::Screenshot; @@ -15,12 +16,9 @@ pub enum Action<T> { /// button was pressed immediately before this function is called. Drag, /// Resize the window. - Resize { - /// The new logical width of the window - width: u32, - /// The new logical height of the window - height: u32, - }, + Resize(Size<u32>), + /// Fetch the current size of the window. + FetchSize(Box<dyn FnOnce(Size<u32>) -> T + 'static>), /// Set the window to maximized or back Maximize(bool), /// Set the window to minimized or back @@ -106,7 +104,8 @@ impl<T> Action<T> { match self { Self::Close => Action::Close, Self::Drag => Action::Drag, - Self::Resize { width, height } => Action::Resize { width, height }, + Self::Resize(size) => Action::Resize(size), + Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))), Self::Maximize(maximized) => Action::Maximize(maximized), Self::Minimize(minimized) => Action::Minimize(minimized), Self::Move { x, y } => Action::Move { x, y }, @@ -135,10 +134,8 @@ impl<T> fmt::Debug for Action<T> { match self { Self::Close => write!(f, "Action::Close"), Self::Drag => write!(f, "Action::Drag"), - Self::Resize { width, height } => write!( - f, - "Action::Resize {{ widget: {width}, height: {height} }}" - ), + Self::Resize(size) => write!(f, "Action::Resize({size:?})"), + Self::FetchSize(_) => write!(f, "Action::FetchSize"), Self::Maximize(maximized) => { write!(f, "Action::Maximize({maximized})") } diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs index b99af955..dfdc9186 100644 --- a/style/src/pane_grid.rs +++ b/style/src/pane_grid.rs @@ -31,7 +31,7 @@ pub trait StyleSheet { /// The supported style of the [`StyleSheet`]. type Style: Default; - /// The [`Region`] to draw when a pane is hovered. + /// The [`Appearance`] to draw when a pane is hovered. fn hovered_region(&self, style: &Self::Style) -> Appearance; /// The [`Line`] to draw when a split is picked. diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 431f324b..d9276ea5 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -11,7 +11,7 @@ geometry = ["iced_graphics/geometry"] [dependencies] raw-window-handle = "0.5" softbuffer = "0.2" -tiny-skia = "0.9" +tiny-skia = "0.10" bytemuck = "1" rustc-hash = "1.1" kurbo = "0.9" @@ -34,5 +34,5 @@ version = "1.6.1" features = ["std"] [dependencies.resvg] -version = "0.32" +version = "0.35" optional = true diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index e0134220..a8add70b 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -753,7 +753,15 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { let path = { let mut builder = tiny_skia::PathBuilder::new(); - builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); + builder.push_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .unwrap(), + ); builder.finish().unwrap() }; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 3887ec8d..dedb127c 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -80,9 +80,9 @@ impl Cache { for (i, pixel) in image.pixels().enumerate() { let [r, g, b, a] = pixel.0; - buffer[i] = tiny_skia::ColorU8::from_rgba(b, g, r, a) - .premultiply() - .get(); + buffer[i] = bytemuck::cast( + tiny_skia::ColorU8::from_rgba(b, g, r, a).premultiply(), + ); } entry.insert(Some(Entry { diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 8f494650..15f25740 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -288,14 +288,15 @@ impl GlyphCache { for _y in 0..image.placement.height { for _x in 0..image.placement.width { - buffer[i] = tiny_skia::ColorU8::from_rgba( - b, - g, - r, - image.data[i], - ) - .premultiply() - .get(); + buffer[i] = bytemuck::cast( + tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply(), + ); i += 1; } @@ -307,14 +308,15 @@ impl GlyphCache { for _y in 0..image.placement.height { for _x in 0..image.placement.width { // TODO: Blend alpha - buffer[i >> 2] = tiny_skia::ColorU8::from_rgba( - image.data[i + 2], - image.data[i + 1], - image.data[i], - image.data[i + 3], - ) - .premultiply() - .get(); + buffer[i >> 2] = bytemuck::cast( + tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply(), + ); i += 4; } diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index a3f3c2e3..433ca0f5 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -130,30 +130,42 @@ impl Cache { let mut image = tiny_skia::Pixmap::new(size.width, size.height)?; - resvg::render( - tree, - if size.width > size.height { - resvg::FitTo::Width(size.width) - } else { - resvg::FitTo::Height(size.height) - }, - tiny_skia::Transform::default(), - image.as_mut(), - )?; + let tree_size = tree.size.to_int_size(); + + let target_size = if size.width > size.height { + tree_size.scale_to_width(size.width) + } else { + tree_size.scale_to_height(size.height) + }; + + let transform = if let Some(target_size) = target_size { + let tree_size = tree_size.to_size(); + let target_size = target_size.to_size(); + + tiny_skia::Transform::from_scale( + target_size.width() / tree_size.width(), + target_size.height() / tree_size.height(), + ) + } else { + tiny_skia::Transform::default() + }; + + resvg::Tree::from_usvg(tree).render(transform, &mut image.as_mut()); if let Some([r, g, b, _]) = key.color { // Apply color filter for pixel in bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) { - *pixel = tiny_skia::ColorU8::from_rgba( - b, - g, - r, - (*pixel >> 24) as u8, - ) - .premultiply() - .get(); + *pixel = bytemuck::cast( + tiny_skia::ColorU8::from_rgba( + b, + g, + r, + (*pixel >> 24) as u8, + ) + .premultiply(), + ); } } else { // Swap R and B channels for `softbuffer` presentation diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 15db5b5d..22cfad55 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -55,7 +55,7 @@ version = "1.0" optional = true [dependencies.resvg] -version = "0.32" +version = "0.35" optional = true [dependencies.tracing] diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6b9be651..2c03d36b 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -114,16 +114,28 @@ impl Cache { // It would be cool to be able to smooth resize the `svg` example. let mut img = tiny_skia::Pixmap::new(width, height)?; - resvg::render( - tree, - if width > height { - resvg::FitTo::Width(width) - } else { - resvg::FitTo::Height(height) - }, - tiny_skia::Transform::default(), - img.as_mut(), - )?; + let tree_size = tree.size.to_int_size(); + + let target_size = if width > height { + tree_size.scale_to_width(width) + } else { + tree_size.scale_to_height(height) + }; + + let transform = if let Some(target_size) = target_size { + let tree_size = tree_size.to_size(); + let target_size = target_size.to_size(); + + tiny_skia::Transform::from_scale( + target_size.width() / tree_size.width(), + target_size.height() / tree_size.height(), + ) + } else { + tiny_skia::Transform::default() + }; + + resvg::Tree::from_usvg(tree) + .render(transform, &mut img.as_mut()); let mut rgba = img.take(); diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index f955d9dd..c7814966 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -624,6 +624,10 @@ where local_shell.revalidate_layout(|| shell.invalidate_layout()); + if let Some(redraw_request) = local_shell.redraw_request() { + shell.request_redraw(redraw_request); + } + if !local_messages.is_empty() { let mut inner = self.overlay.take().unwrap().0.take().unwrap().into_heads(); diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 040d6bb3..31bb0e86 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -581,39 +581,46 @@ pub fn update<'a, Message, T: Draggable>( | Event::Touch(touch::Event::FingerLost { .. }) => { if let Some((pane, _)) = action.picked_pane() { if let Some(on_drag) = on_drag { - let dropped_region = - cursor.position().and_then(|cursor_position| { - contents + if let Some(cursor_position) = cursor.position() { + 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()) .filter_map(|(target, layout)| { layout_region(layout, cursor_position) .map(|region| (target, region)) }) - .next() - }); - - let event = match dropped_region { - Some(((target, _), region)) if pane != target => { - DragEvent::Dropped { - pane, - target, - region, + .next(); + + match dropped_region { + Some(((target, _), region)) + if pane != target => + { + DragEvent::Dropped { + pane, + target: Target::Pane(target, region), + } + } + _ => DragEvent::Canceled { pane }, } - } - _ => DragEvent::Canceled { pane }, - }; + }; - shell.publish(on_drag(event)); + 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; } + + *action = state::Action::Idle; } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { @@ -671,13 +678,13 @@ fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> { } let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) { - Region::Left + Region::Edge(Edge::Left) } else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) { - Region::Right + Region::Edge(Edge::Right) } else if cursor_position.y < (bounds.y + bounds.height / 3.0) { - Region::Top + Region::Edge(Edge::Top) } else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) { - Region::Bottom + Region::Edge(Edge::Bottom) } else { Region::Center }; @@ -833,28 +840,36 @@ pub fn draw<Renderer, T>( let mut render_picked_pane = None; - for ((id, pane), layout) in contents.zip(layout.children()) { + 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, layout)); + render_picked_pane = Some((pane, origin, pane_layout)); } Some((dragging, _)) if id != dragging => { draw_pane( pane, renderer, default_style, - layout, + pane_layout, pane_cursor, viewport, ); - if picked_pane.is_some() { + if picked_pane.is_some() && pane_in_edge.is_none() { if let Some(region) = cursor.position().and_then(|cursor_position| { - layout_region(layout, cursor_position) + layout_region(pane_layout, cursor_position) }) { - let bounds = layout_region_bounds(layout, region); + let bounds = layout_region_bounds(pane_layout, region); let hovered_region_style = theme.hovered_region(style); renderer.fill_quad( @@ -875,7 +890,7 @@ pub fn draw<Renderer, T>( pane, renderer, default_style, - layout, + pane_layout, pane_cursor, viewport, ); @@ -883,6 +898,21 @@ pub fn draw<Renderer, T>( } } + 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_radius: hovered_region_style.border_radius, + border_width: hovered_region_style.border_width, + border_color: hovered_region_style.border_color, + }, + 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() { @@ -907,71 +937,131 @@ pub fn draw<Renderer, T>( } } - 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, + 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, + }, }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - highlight.color, - ); + highlight.color, + ); + } } } } -fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle { +const THICKNESS_RATIO: f32 = 25.0; + +fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> { let bounds = layout.bounds(); - match region { - Region::Center => bounds, - Region::Top => Rectangle { - height: bounds.height / 2.0, + let height_thickness = bounds.height / THICKNESS_RATIO; + let width_thickness = bounds.width / THICKNESS_RATIO; + let thickness = height_thickness.min(width_thickness); + + if cursor.x > bounds.x && cursor.x < bounds.x + thickness { + Some(Edge::Left) + } else if cursor.x > bounds.x + bounds.width - thickness + && cursor.x < bounds.x + bounds.width + { + Some(Edge::Right) + } else if cursor.y > bounds.y && cursor.y < bounds.y + thickness { + Some(Edge::Top) + } else if cursor.y > bounds.y + bounds.height - thickness + && cursor.y < bounds.y + bounds.height + { + Some(Edge::Bottom) + } else { + None + } +} + +fn edge_bounds(layout: Layout<'_>, edge: Edge) -> Rectangle { + let bounds = layout.bounds(); + + let height_thickness = bounds.height / THICKNESS_RATIO; + let width_thickness = bounds.width / THICKNESS_RATIO; + let thickness = height_thickness.min(width_thickness); + + match edge { + Edge::Top => Rectangle { + height: thickness, ..bounds }, - Region::Left => Rectangle { - width: bounds.width / 2.0, + Edge::Left => Rectangle { + width: thickness, ..bounds }, - Region::Right => Rectangle { - x: bounds.x + bounds.width / 2.0, - width: bounds.width / 2.0, + Edge::Right => Rectangle { + x: bounds.x + bounds.width - thickness, + width: thickness, ..bounds }, - Region::Bottom => Rectangle { - y: bounds.y + bounds.height / 2.0, - height: bounds.height / 2.0, + Edge::Bottom => Rectangle { + y: bounds.y + bounds.height - thickness, + height: thickness, ..bounds }, } } +fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle { + let bounds = layout.bounds(); + + match region { + Region::Center => bounds, + Region::Edge(edge) => match edge { + Edge::Top => Rectangle { + height: bounds.height / 2.0, + ..bounds + }, + Edge::Left => Rectangle { + width: bounds.width / 2.0, + ..bounds + }, + Edge::Right => Rectangle { + x: bounds.x + bounds.width / 2.0, + width: bounds.width / 2.0, + ..bounds + }, + Edge::Bottom => Rectangle { + y: bounds.y + bounds.height / 2.0, + height: bounds.height / 2.0, + ..bounds + }, + }, + } +} + /// An event produced during a drag and drop interaction of a [`PaneGrid`]. #[derive(Debug, Clone, Copy)] pub enum DragEvent { @@ -986,11 +1076,8 @@ pub enum DragEvent { /// The picked [`Pane`]. pane: Pane, - /// The [`Pane`] where the picked one was dropped on. - target: Pane, - - /// The [`Region`] of the target [`Pane`] where the picked one was dropped on. - region: Region, + /// The [`Target`] where the picked [`Pane`] was dropped on. + target: Target, }, /// A [`Pane`] was picked and then dropped outside of other [`Pane`] @@ -1001,19 +1088,35 @@ pub enum DragEvent { }, } +/// The [`Target`] area a pane can be dropped on. +#[derive(Debug, Clone, Copy)] +pub enum Target { + /// An [`Edge`] of the full [`PaneGrid`]. + Edge(Edge), + /// A single [`Pane`] of the [`PaneGrid`]. + Pane(Pane, Region), +} + /// The region of a [`Pane`]. #[derive(Debug, Clone, Copy, Default)] pub enum Region { /// Center region. #[default] Center, - /// Top region. + /// Edge region. + Edge(Edge), +} + +/// The edges of an area. +#[derive(Debug, Clone, Copy)] +pub enum Edge { + /// Top edge. Top, - /// Left region. + /// Left edge. Left, - /// Right region. + /// Right edge. Right, - /// Bottom region. + /// Bottom edge. Bottom, } diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs index 3976acd8..6de5920f 100644 --- a/widget/src/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -120,6 +120,16 @@ impl Node { }; } + pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) { + *self = Node::Split { + id, + axis, + ratio: 0.5, + a: Box::new(Node::Pane(pane)), + b: Box::new(self.clone()), + }; + } + pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) { if let Node::Split { a, b, .. } = self { a.update(f); diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 1f034ca3..6fd15890 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -3,7 +3,7 @@ //! [`PaneGrid`]: crate::widget::PaneGrid use crate::core::{Point, Size}; use crate::pane_grid::{ - Axis, Configuration, Direction, Node, Pane, Region, Split, + Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, }; use std::collections::HashMap; @@ -145,7 +145,55 @@ impl<T> State<T> { pane: &Pane, state: T, ) -> Option<(Pane, Split)> { - let node = self.internal.layout.find(pane)?; + self.split_node(axis, Some(pane), state, false) + } + + /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. + /// + /// Panes will be swapped by default for [`Region::Center`]. + pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { + match region { + Region::Center => self.swap(pane, target), + Region::Edge(edge) => match edge { + Edge::Top => { + self.split_and_swap(Axis::Horizontal, target, pane, true) + } + Edge::Bottom => { + self.split_and_swap(Axis::Horizontal, target, pane, false) + } + Edge::Left => { + self.split_and_swap(Axis::Vertical, target, pane, true) + } + Edge::Right => { + self.split_and_swap(Axis::Vertical, target, pane, false) + } + }, + } + } + + /// Drops the given [`Pane`] into the provided [`Target`]. + pub fn drop(&mut self, pane: &Pane, target: Target) { + match target { + Target::Edge(edge) => self.move_to_edge(pane, edge), + Target::Pane(target, region) => { + self.split_with(&target, pane, region) + } + } + } + + fn split_node( + &mut self, + axis: Axis, + pane: Option<&Pane>, + state: T, + inverse: bool, + ) -> Option<(Pane, Split)> { + let node = if let Some(pane) = pane { + self.internal.layout.find(pane)? + } else { + // Major node + &mut self.internal.layout + }; let new_pane = { self.internal.last_id = self.internal.last_id.checked_add(1)?; @@ -159,7 +207,11 @@ impl<T> State<T> { Split(self.internal.last_id) }; - node.split(new_split, axis, new_pane); + if inverse { + node.split_inverse(new_split, axis, new_pane); + } else { + node.split(new_split, axis, new_pane); + } let _ = self.panes.insert(new_pane, state); let _ = self.maximized.take(); @@ -167,27 +219,6 @@ impl<T> State<T> { Some((new_pane, new_split)) } - /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. - /// - /// Panes will be swapped by default for [`Region::Center`]. - pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { - match region { - Region::Center => self.swap(pane, target), - Region::Top => { - self.split_and_swap(Axis::Horizontal, target, pane, true) - } - Region::Bottom => { - self.split_and_swap(Axis::Horizontal, target, pane, false) - } - Region::Left => { - self.split_and_swap(Axis::Vertical, target, pane, true) - } - Region::Right => { - self.split_and_swap(Axis::Vertical, target, pane, false) - } - } - } - fn split_and_swap( &mut self, axis: Axis, @@ -204,6 +235,35 @@ impl<T> State<T> { } } + /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`]. + pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) { + match edge { + Edge::Top => { + self.split_major_node_and_swap(Axis::Horizontal, pane, true) + } + Edge::Bottom => { + self.split_major_node_and_swap(Axis::Horizontal, pane, false) + } + Edge::Left => { + self.split_major_node_and_swap(Axis::Vertical, pane, true) + } + Edge::Right => { + self.split_major_node_and_swap(Axis::Vertical, pane, false) + } + } + } + + fn split_major_node_and_swap( + &mut self, + axis: Axis, + pane: &Pane, + swap: bool, + ) { + if let Some((state, _)) = self.close(pane) { + let _ = self.split_node(axis, None, state, swap); + } + } + /// Swaps the position of the provided panes in the [`State`]. /// /// If you want to swap panes on drag and drop in your [`PaneGrid`], you diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index d425de01..2dc3da01 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -1,16 +1,15 @@ //! Display a widget over another. use crate::container; -use crate::core; use crate::core::event::{self, Event}; use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::text; -use crate::core::widget::Tree; +use crate::core::widget::{self, Widget}; use crate::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, - Vector, Widget, + Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, + Vector, }; use crate::Text; @@ -107,14 +106,22 @@ where Renderer: text::Renderer, Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { - fn children(&self) -> Vec<Tree> { - vec![Tree::new(&self.content)] + fn children(&self) -> Vec<widget::Tree> { + vec![widget::Tree::new(&self.content)] } - fn diff(&self, tree: &mut Tree) { + fn diff(&self, tree: &mut widget::Tree) { tree.diff_children(std::slice::from_ref(&self.content)) } + fn state(&self) -> widget::tree::State { + widget::tree::State::new(State::default()) + } + + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::<State>() + } + fn width(&self) -> Length { self.content.as_widget().width() } @@ -133,7 +140,7 @@ where fn on_event( &mut self, - tree: &mut Tree, + tree: &mut widget::Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, @@ -141,6 +148,13 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { + let state = tree.state.downcast_mut::<State>(); + + *state = cursor + .position_over(layout.bounds()) + .map(|cursor_position| State::Hovered { cursor_position }) + .unwrap_or_default(); + self.content.as_widget_mut().on_event( &mut tree.children[0], event, @@ -154,7 +168,7 @@ where fn mouse_interaction( &self, - tree: &Tree, + tree: &widget::Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -171,7 +185,7 @@ where fn draw( &self, - tree: &Tree, + tree: &widget::Tree, renderer: &mut Renderer, theme: &Renderer::Theme, inherited_style: &renderer::Style, @@ -188,50 +202,50 @@ where cursor, viewport, ); - - let tooltip = &self.tooltip; - - draw( - renderer, - theme, - inherited_style, - layout, - cursor, - viewport, - self.position, - self.gap, - self.padding, - self.snap_within_viewport, - &self.style, - |renderer, limits| { - Widget::<(), Renderer>::layout(tooltip, renderer, limits) - }, - |renderer, defaults, layout, viewport| { - Widget::<(), Renderer>::draw( - tooltip, - &Tree::empty(), - renderer, - theme, - defaults, - layout, - cursor, - viewport, - ); - }, - ); } fn overlay<'b>( &'b mut self, - tree: &'b mut Tree, + tree: &'b mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget_mut().overlay( + let state = tree.state.downcast_ref::<State>(); + + let content = self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, - ) + ); + + let tooltip = if let State::Hovered { cursor_position } = *state { + Some(overlay::Element::new( + layout.position(), + Box::new(Overlay { + tooltip: &self.tooltip, + cursor_position, + content_bounds: layout.bounds(), + snap_within_viewport: self.snap_within_viewport, + position: self.position, + gap: self.gap, + padding: self.padding, + style: &self.style, + }), + )) + } else { + None + }; + + if content.is_some() || tooltip.is_some() { + Some( + overlay::Group::with_children( + content.into_iter().chain(tooltip).collect(), + ) + .overlay(), + ) + } else { + None + } } } @@ -264,84 +278,107 @@ pub enum Position { Right, } -/// Draws a [`Tooltip`]. -pub fn draw<Renderer>( - renderer: &mut Renderer, - theme: &Renderer::Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, +#[derive(Debug, Clone, Copy, Default)] +enum State { + #[default] + Idle, + Hovered { + cursor_position: Point, + }, +} + +struct Overlay<'a, 'b, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, +{ + tooltip: &'b Text<'a, Renderer>, + cursor_position: Point, + content_bounds: Rectangle, + snap_within_viewport: bool, position: Position, gap: f32, padding: f32, - snap_within_viewport: bool, - style: &<Renderer::Theme as container::StyleSheet>::Style, - layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, - draw_text: impl FnOnce(&mut Renderer, &renderer::Style, Layout<'_>, &Rectangle), -) where - Renderer: core::Renderer, - Renderer::Theme: container::StyleSheet, -{ - use container::StyleSheet; - - let bounds = layout.bounds(); - - if let Some(cursor_position) = cursor.position_over(bounds) { - let style = theme.appearance(style); + style: &'b <Renderer::Theme as container::StyleSheet>::Style, +} - let defaults = renderer::Style { - text_color: style.text_color.unwrap_or(inherited_style.text_color), - }; +impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> + for Overlay<'a, 'b, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + _position: Point, + ) -> layout::Node { + let viewport = Rectangle::with_size(bounds); - let text_layout = layout_text( + let text_layout = Widget::<(), Renderer>::layout( + self.tooltip, renderer, &layout::Limits::new( Size::ZERO, - snap_within_viewport + self.snap_within_viewport .then(|| viewport.size()) .unwrap_or(Size::INFINITY), ) - .pad(Padding::new(padding)), + .pad(Padding::new(self.padding)), ); let text_bounds = text_layout.bounds(); - let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; - let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0; + let x_center = self.content_bounds.x + + (self.content_bounds.width - text_bounds.width) / 2.0; + let y_center = self.content_bounds.y + + (self.content_bounds.height - text_bounds.height) / 2.0; let mut tooltip_bounds = { - let offset = match position { + let offset = match self.position { Position::Top => Vector::new( x_center, - bounds.y - text_bounds.height - gap - padding, + self.content_bounds.y + - text_bounds.height + - self.gap + - self.padding, ), Position::Bottom => Vector::new( x_center, - bounds.y + bounds.height + gap + padding, + self.content_bounds.y + + self.content_bounds.height + + self.gap + + self.padding, ), Position::Left => Vector::new( - bounds.x - text_bounds.width - gap - padding, + self.content_bounds.x + - text_bounds.width + - self.gap + - self.padding, y_center, ), Position::Right => Vector::new( - bounds.x + bounds.width + gap + padding, + self.content_bounds.x + + self.content_bounds.width + + self.gap + + self.padding, y_center, ), Position::FollowCursor => Vector::new( - cursor_position.x, - cursor_position.y - text_bounds.height, + self.cursor_position.x, + self.cursor_position.y - text_bounds.height, ), }; Rectangle { - x: offset.x - padding, - y: offset.y - padding, - width: text_bounds.width + padding * 2.0, - height: text_bounds.height + padding * 2.0, + x: offset.x - self.padding, + y: offset.y - self.padding, + width: text_bounds.width + self.padding * 2.0, + height: text_bounds.height + self.padding * 2.0, } }; - if snap_within_viewport { + if self.snap_within_viewport { if tooltip_bounds.x < viewport.x { tooltip_bounds.x = viewport.x; } else if viewport.x + viewport.width @@ -361,21 +398,49 @@ pub fn draw<Renderer>( } } - renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| { - container::draw_background(renderer, &style, tooltip_bounds); - - draw_text( - renderer, - &defaults, - Layout::with_offset( - Vector::new( - tooltip_bounds.x + padding, - tooltip_bounds.y + padding, - ), - &text_layout, - ), - viewport, - ) - }); + layout::Node::with_children( + tooltip_bounds.size(), + vec![text_layout.translate(Vector::new(self.padding, self.padding))], + ) + .translate(Vector::new(tooltip_bounds.x, tooltip_bounds.y)) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &<Renderer as renderer::Renderer>::Theme, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + ) { + let style = <Renderer::Theme as container::StyleSheet>::appearance( + theme, self.style, + ); + + container::draw_background(renderer, &style, layout.bounds()); + + let defaults = renderer::Style { + text_color: style.text_color.unwrap_or(inherited_style.text_color), + }; + + Widget::<(), Renderer>::draw( + self.tooltip, + &widget::Tree::empty(), + renderer, + theme, + &defaults, + layout.children().next().unwrap(), + cursor_position, + &Rectangle::with_size(Size::INFINITY), + ); + } + + fn is_over( + &self, + _layout: Layout<'_>, + _renderer: &Renderer, + _cursor_position: Point, + ) -> bool { + false } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 6e7b94ef..d1689452 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -748,12 +748,22 @@ pub fn run_command<A, C, E>( window::Action::Drag => { let _res = window.drag_window(); } - window::Action::Resize { width, height } => { + window::Action::Resize(size) => { window.set_inner_size(winit::dpi::LogicalSize { - width, - height, + width: size.width, + height: size.height, }); } + window::Action::FetchSize(callback) => { + let size = window.inner_size(); + + proxy + .send_event(callback(Size::new( + size.width, + size.height, + ))) + .expect("Send message to event loop") + } window::Action::Maximize(maximized) => { window.set_maximized(maximized); } |