summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/pane_grid/src/main.rs3
-rw-r--r--runtime/src/window.rs12
-rw-r--r--runtime/src/window/action.rs19
-rw-r--r--style/src/pane_grid.rs2
-rw-r--r--tiny_skia/Cargo.toml4
-rw-r--r--tiny_skia/src/backend.rs10
-rw-r--r--tiny_skia/src/raster.rs6
-rw-r--r--tiny_skia/src/text.rs34
-rw-r--r--tiny_skia/src/vector.rs48
-rw-r--r--wgpu/Cargo.toml2
-rw-r--r--wgpu/src/image/vector.rs32
-rw-r--r--widget/src/lazy/component.rs4
-rw-r--r--widget/src/pane_grid.rs275
-rw-r--r--widget/src/pane_grid/node.rs10
-rw-r--r--widget/src/pane_grid/state.rs108
-rw-r--r--widget/src/tooltip.rs269
-rw-r--r--winit/src/application.rs16
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);
}