Merge pull request #2405 from iced-rs/feature/stack-widget

`Stack` widget
This commit is contained in:
Héctor Ramón 2024-04-25 23:19:39 +02:00 committed by GitHub
commit 2d01d55cbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 609 additions and 359 deletions

View file

@ -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),

View file

@ -3,6 +3,7 @@
#[allow(missing_docs)]
pub enum Interaction {
#[default]
None,
Idle,
Pointer,
Grab,

View file

@ -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`].

View file

@ -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.

View file

@ -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);
}
};
}

View file

@ -159,7 +159,7 @@ mod loupe {
if cursor.is_over(layout.bounds()) {
mouse::Interaction::ZoomIn
} else {
mouse::Interaction::Idle
mouse::Interaction::None
}
}
}

View file

@ -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()
}

View file

@ -48,7 +48,7 @@ where
caches,
queued_events: Vec::new(),
queued_messages: Vec::new(),
mouse_interaction: mouse::Interaction::Idle,
mouse_interaction: mouse::Interaction::None,
}
}

View file

@ -47,7 +47,7 @@ where
cache,
queued_events: Vec::new(),
queued_messages: Vec::new(),
mouse_interaction: mouse::Interaction::Idle,
mouse_interaction: mouse::Interaction::None,
}
}

View file

@ -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

View file

@ -304,7 +304,7 @@ where
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::Idle
mouse::Interaction::None
}
}

View file

@ -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;

View file

@ -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

View file

@ -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);

328
widget/src/stack.rs Normal file
View file

@ -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)
}
}

View file

@ -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,

View file

@ -60,7 +60,7 @@ where
exit_on_close_request,
surface,
renderer,
mouse_interaction: mouse::Interaction::Idle,
mouse_interaction: mouse::Interaction::None,
},
);