summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/layout.rs2
-rw-r--r--core/src/mouse/interaction.rs1
-rw-r--r--core/src/overlay.rs2
-rw-r--r--core/src/widget.rs2
-rw-r--r--examples/bezier_tool/src/main.rs53
-rw-r--r--examples/loupe/src/main.rs2
-rw-r--r--examples/modal/src/main.rs363
-rw-r--r--runtime/src/multi_window/state.rs2
-rw-r--r--runtime/src/program/state.rs2
-rw-r--r--widget/src/helpers.rs196
-rw-r--r--widget/src/image/viewer.rs2
-rw-r--r--widget/src/lib.rs3
-rw-r--r--widget/src/mouse_area.rs2
-rw-r--r--widget/src/scrollable.rs2
-rw-r--r--widget/src/stack.rs328
-rw-r--r--winit/src/conversion.rs4
-rw-r--r--winit/src/multi_window/window_manager.rs2
17 files changed, 609 insertions, 359 deletions
diff --git a/core/src/layout.rs b/core/src/layout.rs
index 95720aba..98d05602 100644
--- a/core/src/layout.rs
+++ b/core/src/layout.rs
@@ -54,7 +54,7 @@ impl<'a> Layout<'a> {
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
- pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
+ pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
Vector::new(self.position.x, self.position.y),
diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs
index 6ad66229..065eb8e7 100644
--- a/core/src/mouse/interaction.rs
+++ b/core/src/mouse/interaction.rs
@@ -3,6 +3,7 @@
#[allow(missing_docs)]
pub enum Interaction {
#[default]
+ None,
Idle,
Pointer,
Grab,
diff --git a/core/src/overlay.rs b/core/src/overlay.rs
index 03076a30..3a57fe16 100644
--- a/core/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -79,7 +79,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
/// Returns true if the cursor is over the [`Overlay`].
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 58a9f19b..b02e3a4f 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -137,7 +137,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
/// Returns the overlay of the [`Widget`], if there is any.
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 289c919b..ba51a00e 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -1,9 +1,11 @@
//! This example showcases an interactive `Canvas` for drawing BĂ©zier curves.
-use iced::widget::{button, column, text};
-use iced::{Alignment, Element, Length};
+use iced::alignment;
+use iced::widget::{button, container, stack};
+use iced::{Element, Length, Theme};
pub fn main() -> iced::Result {
iced::program("Bezier Tool - Iced", Example::update, Example::view)
+ .theme(|_| Theme::CatppuccinMocha)
.antialiasing(true)
.run()
}
@@ -35,16 +37,18 @@ impl Example {
}
fn view(&self) -> Element<Message> {
- column![
- text("Bezier tool example").width(Length::Shrink).size(50),
+ container(stack![
self.bezier.view(&self.curves).map(Message::AddCurve),
- button("Clear")
- .style(button::danger)
- .on_press(Message::Clear),
- ]
+ container(
+ button("Clear")
+ .style(button::danger)
+ .on_press(Message::Clear)
+ )
+ .padding(10)
+ .width(Length::Fill)
+ .align_x(alignment::Horizontal::Right),
+ ])
.padding(20)
- .spacing(20)
- .align_items(Alignment::Center)
.into()
}
}
@@ -139,22 +143,24 @@ mod bezier {
&self,
state: &Self::State,
renderer: &Renderer,
- _theme: &Theme,
+ theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let content =
self.state.cache.draw(renderer, bounds.size(), |frame| {
- Curve::draw_all(self.curves, frame);
+ Curve::draw_all(self.curves, frame, theme);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
- Stroke::default().with_width(2.0),
+ Stroke::default()
+ .with_width(2.0)
+ .with_color(theme.palette().text),
);
});
if let Some(pending) = state {
- vec![content, pending.draw(renderer, bounds, cursor)]
+ vec![content, pending.draw(renderer, theme, bounds, cursor)]
} else {
vec![content]
}
@@ -182,7 +188,7 @@ mod bezier {
}
impl Curve {
- fn draw_all(curves: &[Curve], frame: &mut Frame) {
+ fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) {
let curves = Path::new(|p| {
for curve in curves {
p.move_to(curve.from);
@@ -190,7 +196,12 @@ mod bezier {
}
});
- frame.stroke(&curves, Stroke::default().with_width(2.0));
+ frame.stroke(
+ &curves,
+ Stroke::default()
+ .with_width(2.0)
+ .with_color(theme.palette().text),
+ );
}
}
@@ -204,6 +215,7 @@ mod bezier {
fn draw(
&self,
renderer: &Renderer,
+ theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Geometry {
@@ -213,7 +225,12 @@ mod bezier {
match *self {
Pending::One { from } => {
let line = Path::line(from, cursor_position);
- frame.stroke(&line, Stroke::default().with_width(2.0));
+ frame.stroke(
+ &line,
+ Stroke::default()
+ .with_width(2.0)
+ .with_color(theme.palette().text),
+ );
}
Pending::Two { from, to } => {
let curve = Curve {
@@ -222,7 +239,7 @@ mod bezier {
control: cursor_position,
};
- Curve::draw_all(&[curve], &mut frame);
+ Curve::draw_all(&[curve], &mut frame, theme);
}
};
}
diff --git a/examples/loupe/src/main.rs b/examples/loupe/src/main.rs
index 6a5ff123..42b7f471 100644
--- a/examples/loupe/src/main.rs
+++ b/examples/loupe/src/main.rs
@@ -159,7 +159,7 @@ mod loupe {
if cursor.is_over(layout.bounds()) {
mouse::Interaction::ZoomIn
} else {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
}
}
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index 398728e0..a345924d 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -2,12 +2,11 @@ use iced::event::{self, Event};
use iced::keyboard;
use iced::keyboard::key;
use iced::widget::{
- self, button, column, container, horizontal_space, pick_list, row, text,
- text_input,
+ self, button, column, container, horizontal_space, mouse_area, opaque,
+ pick_list, row, stack, text, text_input,
};
-use iced::{Alignment, Command, Element, Length, Subscription};
+use iced::{Alignment, Color, Command, Element, Length, Subscription};
-use modal::Modal;
use std::fmt;
pub fn main() -> iced::Result {
@@ -121,7 +120,7 @@ impl App {
.height(Length::Fill);
if self.show_modal {
- let modal = container(
+ let signup = container(
column![
text("Sign Up").size(24),
column![
@@ -162,9 +161,7 @@ impl App {
.padding(10)
.style(container::rounded_box);
- Modal::new(content, modal)
- .on_blur(Message::HideModal)
- .into()
+ modal(content, signup, Message::HideModal)
} else {
content.into()
}
@@ -203,326 +200,36 @@ impl fmt::Display for Plan {
}
}
-mod modal {
- use iced::advanced::layout::{self, Layout};
- use iced::advanced::overlay;
- use iced::advanced::renderer;
- use iced::advanced::widget::{self, Widget};
- use iced::advanced::{self, Clipboard, Shell};
- use iced::alignment::Alignment;
- use iced::event;
- use iced::mouse;
- use iced::{Color, Element, Event, Length, Point, Rectangle, Size, Vector};
-
- /// A widget that centers a modal element over some base element
- pub struct Modal<'a, Message, Theme, Renderer> {
- base: Element<'a, Message, Theme, Renderer>,
- modal: Element<'a, Message, Theme, Renderer>,
- on_blur: Option<Message>,
- }
-
- impl<'a, Message, Theme, Renderer> Modal<'a, Message, Theme, Renderer> {
- /// Returns a new [`Modal`]
- pub fn new(
- base: impl Into<Element<'a, Message, Theme, Renderer>>,
- modal: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self {
- Self {
- base: base.into(),
- modal: modal.into(),
- on_blur: None,
- }
- }
-
- /// Sets the message that will be produces when the background
- /// of the [`Modal`] is pressed
- pub fn on_blur(self, on_blur: Message) -> Self {
- Self {
- on_blur: Some(on_blur),
- ..self
- }
- }
- }
-
- impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Modal<'a, Message, Theme, Renderer>
- where
- Renderer: advanced::Renderer,
- Message: Clone,
- {
- fn children(&self) -> Vec<widget::Tree> {
- vec![
- widget::Tree::new(&self.base),
- widget::Tree::new(&self.modal),
- ]
- }
-
- fn diff(&self, tree: &mut widget::Tree) {
- tree.diff_children(&[&self.base, &self.modal]);
- }
-
- fn size(&self) -> Size<Length> {
- self.base.as_widget().size()
- }
-
- fn layout(
- &self,
- tree: &mut widget::Tree,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- self.base.as_widget().layout(
- &mut tree.children[0],
- renderer,
- limits,
- )
- }
-
- fn on_event(
- &mut self,
- state: &mut widget::Tree,
- event: Event,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- viewport: &Rectangle,
- ) -> event::Status {
- self.base.as_widget_mut().on_event(
- &mut state.children[0],
- event,
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- )
- }
-
- fn draw(
- &self,
- state: &widget::Tree,
- renderer: &mut Renderer,
- theme: &Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- viewport: &Rectangle,
- ) {
- self.base.as_widget().draw(
- &state.children[0],
- renderer,
- theme,
- style,
- layout,
- cursor,
- viewport,
- );
- }
-
- fn overlay<'b>(
- &'b mut self,
- state: &'b mut widget::Tree,
- layout: Layout<'_>,
- _renderer: &Renderer,
- translation: Vector,
- ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
- Some(overlay::Element::new(Box::new(Overlay {
- position: layout.position() + translation,
- content: &mut self.modal,
- tree: &mut state.children[1],
- size: layout.bounds().size(),
- on_blur: self.on_blur.clone(),
- })))
- }
-
- fn mouse_interaction(
- &self,
- state: &widget::Tree,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- self.base.as_widget().mouse_interaction(
- &state.children[0],
- layout,
- cursor,
- viewport,
- renderer,
- )
- }
-
- fn operate(
- &self,
- state: &mut widget::Tree,
- layout: Layout<'_>,
- renderer: &Renderer,
- operation: &mut dyn widget::Operation<Message>,
- ) {
- self.base.as_widget().operate(
- &mut state.children[0],
- layout,
- renderer,
- operation,
- );
- }
- }
-
- struct Overlay<'a, 'b, Message, Theme, Renderer> {
- position: Point,
- content: &'b mut Element<'a, Message, Theme, Renderer>,
- tree: &'b mut widget::Tree,
- size: Size,
- on_blur: Option<Message>,
- }
-
- impl<'a, 'b, Message, Theme, Renderer>
- overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, 'b, Message, Theme, Renderer>
- where
- Renderer: advanced::Renderer,
- Message: Clone,
- {
- fn layout(
- &mut self,
- renderer: &Renderer,
- _bounds: Size,
- ) -> layout::Node {
- let limits = layout::Limits::new(Size::ZERO, self.size)
+fn modal<'a, Message>(
+ base: impl Into<Element<'a, Message>>,
+ content: impl Into<Element<'a, Message>>,
+ on_blur: Message,
+) -> Element<'a, Message>
+where
+ Message: Clone + 'a,
+{
+ stack![
+ base.into(),
+ mouse_area(
+ container(opaque(content))
.width(Length::Fill)
- .height(Length::Fill);
-
- let child = self
- .content
- .as_widget()
- .layout(self.tree, renderer, &limits)
- .align(Alignment::Center, Alignment::Center, limits.max());
-
- layout::Node::with_children(self.size, vec![child])
- .move_to(self.position)
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- let content_bounds = layout.children().next().unwrap().bounds();
-
- if let Some(message) = self.on_blur.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonPressed(
- mouse::Button::Left,
- )) = &event
- {
- if !cursor.is_over(content_bounds) {
- shell.publish(message.clone());
- return event::Status::Captured;
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .style(|_theme| {
+ container::Style {
+ background: Some(
+ Color {
+ a: 0.8,
+ ..Color::BLACK
+ }
+ .into(),
+ ),
+ ..container::Style::default()
}
- }
- }
-
- self.content.as_widget_mut().on_event(
- self.tree,
- event,
- layout.children().next().unwrap(),
- cursor,
- renderer,
- clipboard,
- shell,
- &layout.bounds(),
- )
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- ) {
- renderer.fill_quad(
- renderer::Quad {
- bounds: layout.bounds(),
- ..renderer::Quad::default()
- },
- Color {
- a: 0.80,
- ..Color::BLACK
- },
- );
-
- self.content.as_widget().draw(
- self.tree,
- renderer,
- theme,
- style,
- layout.children().next().unwrap(),
- cursor,
- &layout.bounds(),
- );
- }
-
- fn operate(
- &mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- operation: &mut dyn widget::Operation<Message>,
- ) {
- self.content.as_widget().operate(
- self.tree,
- layout.children().next().unwrap(),
- renderer,
- operation,
- );
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- self.content.as_widget().mouse_interaction(
- self.tree,
- layout.children().next().unwrap(),
- cursor,
- viewport,
- renderer,
- )
- }
-
- fn overlay<'c>(
- &'c mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
- self.content.as_widget_mut().overlay(
- self.tree,
- layout.children().next().unwrap(),
- renderer,
- Vector::ZERO,
- )
- }
- }
-
- impl<'a, Message, Theme, Renderer> From<Modal<'a, Message, Theme, Renderer>>
- for Element<'a, Message, Theme, Renderer>
- where
- Theme: 'a,
- Message: 'a + Clone,
- Renderer: 'a + advanced::Renderer,
- {
- fn from(modal: Modal<'a, Message, Theme, Renderer>) -> Self {
- Element::new(modal)
- }
- }
+ })
+ )
+ .on_press(on_blur)
+ ]
+ .into()
}
diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs
index afd04519..10366ec0 100644
--- a/runtime/src/multi_window/state.rs
+++ b/runtime/src/multi_window/state.rs
@@ -48,7 +48,7 @@ where
caches,
queued_events: Vec::new(),
queued_messages: Vec::new(),
- mouse_interaction: mouse::Interaction::Idle,
+ mouse_interaction: mouse::Interaction::None,
}
}
diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs
index d685b07c..c6589c22 100644
--- a/runtime/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -47,7 +47,7 @@ where
cache,
queued_events: Vec::new(),
queued_messages: Vec::new(),
- mouse_interaction: mouse::Interaction::Idle,
+ mouse_interaction: mouse::Interaction::None,
}
}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 61789c19..fd345251 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -5,7 +5,7 @@ use crate::combo_box::{self, ComboBox};
use crate::container::{self, Container};
use crate::core;
use crate::core::widget::operation;
-use crate::core::{Element, Length, Pixels};
+use crate::core::{Element, Length, Pixels, Widget};
use crate::keyed;
use crate::overlay;
use crate::pick_list::{self, PickList};
@@ -21,7 +21,7 @@ use crate::text_input::{self, TextInput};
use crate::toggler::{self, Toggler};
use crate::tooltip::{self, Tooltip};
use crate::vertical_slider::{self, VerticalSlider};
-use crate::{Column, MouseArea, Row, Space, Themer};
+use crate::{Column, MouseArea, Row, Space, Stack, Themer};
use std::borrow::Borrow;
use std::ops::RangeInclusive;
@@ -52,6 +52,19 @@ macro_rules! row {
);
}
+/// Creates a [`Stack`] with the given children.
+///
+/// [`Stack`]: crate::Stack
+#[macro_export]
+macro_rules! stack {
+ () => (
+ $crate::Stack::new()
+ );
+ ($($x:expr),+ $(,)?) => (
+ $crate::Stack::with_children([$($crate::core::Element::from($x)),+])
+ );
+}
+
/// Creates a new [`Container`] with the provided content.
///
/// [`Container`]: crate::Container
@@ -98,6 +111,185 @@ where
Row::with_children(children)
}
+/// Creates a new [`Stack`] with the given children.
+///
+/// [`Stack`]: crate::Stack
+pub fn stack<'a, Message, Theme, Renderer>(
+ children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+) -> Stack<'a, Message, Theme, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ Stack::with_children(children)
+}
+
+/// Wraps the given widget and captures any mouse button presses inside the bounds of
+/// the widget—therefore making it _opaque_.
+///
+/// This helper is meant to be used to mark elements in a [`Stack`] to avoid mouse
+/// events from passing through layers.
+///
+/// [`Stack`]: crate::Stack
+pub fn opaque<'a, Message, Theme, Renderer>(
+ content: impl Into<Element<'a, Message, Theme, Renderer>>,
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: 'a,
+ Renderer: core::Renderer + 'a,
+{
+ use crate::core::event::{self, Event};
+ use crate::core::layout::{self, Layout};
+ use crate::core::mouse;
+ use crate::core::renderer;
+ use crate::core::widget::tree::{self, Tree};
+ use crate::core::{Rectangle, Shell, Size};
+
+ struct Opaque<'a, Message, Theme, Renderer> {
+ content: Element<'a, Message, Theme, Renderer>,
+ }
+
+ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Opaque<'a, Message, Theme, Renderer>
+ where
+ Renderer: core::Renderer,
+ {
+ fn tag(&self) -> tree::Tag {
+ self.content.as_widget().tag()
+ }
+
+ fn state(&self) -> tree::State {
+ self.content.as_widget().state()
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ self.content.as_widget().children()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ self.content.as_widget().diff(tree);
+ }
+
+ fn size(&self) -> Size<Length> {
+ self.content.as_widget().size()
+ }
+
+ fn size_hint(&self) -> Size<Length> {
+ self.content.as_widget().size_hint()
+ }
+
+ fn layout(
+ &self,
+ tree: &mut Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget().layout(tree, renderer, limits)
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ self.content
+ .as_widget()
+ .draw(tree, renderer, theme, style, layout, cursor, viewport);
+ }
+
+ fn operate(
+ &self,
+ state: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn operation::Operation<Message>,
+ ) {
+ self.content
+ .as_widget()
+ .operate(state, layout, renderer, operation);
+ }
+
+ fn on_event(
+ &mut self,
+ state: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn core::Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) -> event::Status {
+ let is_mouse_press = matches!(
+ event,
+ core::Event::Mouse(mouse::Event::ButtonPressed(_))
+ );
+
+ if let core::event::Status::Captured =
+ self.content.as_widget_mut().on_event(
+ state, event, layout, cursor, renderer, clipboard, shell,
+ viewport,
+ )
+ {
+ return event::Status::Captured;
+ }
+
+ if is_mouse_press && cursor.is_over(layout.bounds()) {
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ state: &core::widget::Tree,
+ layout: core::Layout<'_>,
+ cursor: core::mouse::Cursor,
+ viewport: &core::Rectangle,
+ renderer: &Renderer,
+ ) -> core::mouse::Interaction {
+ let interaction = self
+ .content
+ .as_widget()
+ .mouse_interaction(state, layout, cursor, viewport, renderer);
+
+ if interaction == mouse::Interaction::None
+ && cursor.is_over(layout.bounds())
+ {
+ mouse::Interaction::Idle
+ } else {
+ interaction
+ }
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ state: &'b mut core::widget::Tree,
+ layout: core::Layout<'_>,
+ renderer: &Renderer,
+ translation: core::Vector,
+ ) -> Option<core::overlay::Element<'b, Message, Theme, Renderer>>
+ {
+ self.content.as_widget_mut().overlay(
+ state,
+ layout,
+ renderer,
+ translation,
+ )
+ }
+ }
+
+ Element::new(Opaque {
+ content: content.into(),
+ })
+}
+
/// Creates a new [`Scrollable`] with the provided content.
///
/// [`Scrollable`]: crate::Scrollable
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 5f7bb345..75d73b19 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -304,7 +304,7 @@ where
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
- mouse::Interaction::Idle
+ mouse::Interaction::None
}
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index 1eeacbae..00e9aaa4 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -12,6 +12,7 @@ mod column;
mod mouse_area;
mod row;
mod space;
+mod stack;
mod themer;
pub mod button;
@@ -78,6 +79,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use space::Space;
#[doc(no_inline)]
+pub use stack::Stack;
+#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_editor::TextEditor;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
index 9634e477..d7235cf6 100644
--- a/widget/src/mouse_area.rs
+++ b/widget/src/mouse_area.rs
@@ -232,7 +232,7 @@ where
);
match (self.interaction, content_interaction) {
- (Some(interaction), mouse::Interaction::Idle)
+ (Some(interaction), mouse::Interaction::None)
if cursor.is_over(layout.bounds()) =>
{
interaction
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index cdc143a2..10e81cee 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -857,7 +857,7 @@ where
if (mouse_over_x_scrollbar || mouse_over_y_scrollbar)
|| state.scrollers_grabbed()
{
- mouse::Interaction::Idle
+ mouse::Interaction::None
} else {
let translation =
state.translation(self.direction, bounds, content_bounds);
diff --git a/widget/src/stack.rs b/widget/src/stack.rs
new file mode 100644
index 00000000..8a0ea2eb
--- /dev/null
+++ b/widget/src/stack.rs
@@ -0,0 +1,328 @@
+//! Display content on top of other content.
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget::{Operation, Tree};
+use crate::core::{
+ Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
+};
+
+/// A container that displays children on top of each other.
+///
+/// The first [`Element`] dictates the intrinsic [`Size`] of a [`Stack`] and
+/// will be displayed as the base layer. Every consecutive [`Element`] will be
+/// renderer on top; on its own layer.
+///
+/// Keep in mind that too much layering will normally produce bad UX as well as
+/// introduce certain rendering overhead. Use this widget sparingly!
+#[allow(missing_debug_implementations)]
+pub struct Stack<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
+{
+ width: Length,
+ height: Length,
+ children: Vec<Element<'a, Message, Theme, Renderer>>,
+}
+
+impl<'a, Message, Theme, Renderer> Stack<'a, Message, Theme, Renderer>
+where
+ Renderer: crate::core::Renderer,
+{
+ /// Creates an empty [`Stack`].
+ pub fn new() -> Self {
+ Self::from_vec(Vec::new())
+ }
+
+ /// Creates a [`Stack`] with the given capacity.
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self::from_vec(Vec::with_capacity(capacity))
+ }
+
+ /// Creates a [`Stack`] with the given elements.
+ pub fn with_children(
+ children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ let iterator = children.into_iter();
+
+ Self::with_capacity(iterator.size_hint().0).extend(iterator)
+ }
+
+ /// Creates a [`Stack`] from an already allocated [`Vec`].
+ ///
+ /// Keep in mind that the [`Stack`] will not inspect the [`Vec`], which means
+ /// it won't automatically adapt to the sizing strategy of its contents.
+ ///
+ /// If any of the children have a [`Length::Fill`] strategy, you will need to
+ /// call [`Stack::width`] or [`Stack::height`] accordingly.
+ pub fn from_vec(
+ children: Vec<Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ Self {
+ width: Length::Shrink,
+ height: Length::Shrink,
+ children,
+ }
+ }
+
+ /// Sets the width of the [`Stack`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
+ self
+ }
+
+ /// Sets the height of the [`Stack`].
+ pub fn height(mut self, height: impl Into<Length>) -> Self {
+ self.height = height.into();
+ self
+ }
+
+ /// Adds an element to the [`Stack`].
+ pub fn push(
+ mut self,
+ child: impl Into<Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ let child = child.into();
+
+ if self.children.is_empty() {
+ let child_size = child.as_widget().size_hint();
+
+ self.width = self.width.enclose(child_size.width);
+ self.height = self.height.enclose(child_size.height);
+ }
+
+ self.children.push(child);
+ self
+ }
+
+ /// Adds an element to the [`Stack`], if `Some`.
+ pub fn push_maybe(
+ self,
+ child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
+ ) -> Self {
+ if let Some(child) = child {
+ self.push(child)
+ } else {
+ self
+ }
+ }
+
+ /// Extends the [`Stack`] with the given children.
+ pub fn extend(
+ self,
+ children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ children.into_iter().fold(self, Self::push)
+ }
+}
+
+impl<'a, Message, Renderer> Default for Stack<'a, Message, Renderer>
+where
+ Renderer: crate::core::Renderer,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Stack<'a, Message, Theme, Renderer>
+where
+ Renderer: crate::core::Renderer,
+{
+ fn children(&self) -> Vec<Tree> {
+ self.children.iter().map(Tree::new).collect()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(&self.children);
+ }
+
+ fn size(&self) -> Size<Length> {
+ Size {
+ width: self.width,
+ height: self.height,
+ }
+ }
+
+ fn layout(
+ &self,
+ tree: &mut Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ if self.children.is_empty() {
+ return layout::Node::new(Size::ZERO);
+ }
+
+ let limits = limits.width(self.width).height(self.height);
+ let base = self.children[0].as_widget().layout(
+ &mut tree.children[0],
+ renderer,
+ &limits,
+ );
+
+ let size = limits.resolve(self.width, self.height, base.size());
+ let limits = layout::Limits::new(Size::ZERO, size);
+
+ let nodes = std::iter::once(base)
+ .chain(self.children[1..].iter().zip(&mut tree.children[1..]).map(
+ |(layer, tree)| {
+ let node =
+ layer.as_widget().layout(tree, renderer, &limits);
+
+ node
+ },
+ ))
+ .collect();
+
+ layout::Node::with_children(size, nodes)
+ }
+
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn Operation<Message>,
+ ) {
+ operation.container(None, layout.bounds(), &mut |operation| {
+ self.children
+ .iter()
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .for_each(|((child, state), layout)| {
+ child
+ .as_widget()
+ .operate(state, layout, renderer, operation);
+ });
+ });
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .rev()
+ .zip(tree.children.iter_mut().rev())
+ .zip(layout.children().rev())
+ .map(|((child, state), layout)| {
+ child.as_widget_mut().on_event(
+ state,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ )
+ })
+ .find(|&status| status == event::Status::Captured)
+ .unwrap_or(event::Status::Ignored)
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .rev()
+ .zip(tree.children.iter().rev())
+ .zip(layout.children().rev())
+ .map(|((child, state), layout)| {
+ child.as_widget().mouse_interaction(
+ state, layout, cursor, viewport, renderer,
+ )
+ })
+ .find(|&interaction| interaction != mouse::Interaction::None)
+ .unwrap_or_default()
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
+ for (i, ((layer, state), layout)) in self
+ .children
+ .iter()
+ .zip(&tree.children)
+ .zip(layout.children())
+ .enumerate()
+ {
+ if i > 0 {
+ renderer.with_layer(clipped_viewport, |renderer| {
+ layer.as_widget().draw(
+ state,
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor,
+ &clipped_viewport,
+ );
+ });
+ } else {
+ layer.as_widget().draw(
+ state,
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor,
+ &clipped_viewport,
+ );
+ }
+ }
+ }
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ translation: Vector,
+ ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
+ overlay::from_children(
+ &mut self.children,
+ tree,
+ layout,
+ renderer,
+ translation,
+ )
+ }
+}
+
+impl<'a, Message, Theme, Renderer> From<Stack<'a, Message, Theme, Renderer>>
+ for Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: 'a,
+ Renderer: crate::core::Renderer + 'a,
+{
+ fn from(stack: Stack<'a, Message, Theme, Renderer>) -> Self {
+ Self::new(stack)
+ }
+}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index fc3d1c08..0f83dac3 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -396,7 +396,9 @@ pub fn mouse_interaction(
use mouse::Interaction;
match interaction {
- Interaction::Idle => winit::window::CursorIcon::Default,
+ Interaction::None | Interaction::Idle => {
+ winit::window::CursorIcon::Default
+ }
Interaction::Pointer => winit::window::CursorIcon::Pointer,
Interaction::Working => winit::window::CursorIcon::Progress,
Interaction::Grab => winit::window::CursorIcon::Grab,
diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/multi_window/window_manager.rs
index 71c1688b..3a0c8556 100644
--- a/winit/src/multi_window/window_manager.rs
+++ b/winit/src/multi_window/window_manager.rs
@@ -60,7 +60,7 @@ where
exit_on_close_request,
surface,
renderer,
- mouse_interaction: mouse::Interaction::Idle,
+ mouse_interaction: mouse::Interaction::None,
},
);