diff options
Diffstat (limited to 'widget/src/helpers.rs')
-rw-r--r-- | widget/src/helpers.rs | 460 |
1 files changed, 458 insertions, 2 deletions
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 61789c19..fd8614f5 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 @@ -65,6 +78,27 @@ where Container::new(content) } +/// Creates a new [`Container`] that fills all the available space +/// and centers its contents inside. +/// +/// This is equivalent to: +/// ```rust,no_run +/// # use iced_widget::Container; +/// # fn container<A>(x: A) -> Container<'static, ()> { unreachable!() } +/// let centered = container("Centered!").center(); +/// ``` +/// +/// [`Container`]: crate::Container +pub fn center<'a, Message, Theme, Renderer>( + content: impl Into<Element<'a, Message, Theme, Renderer>>, +) -> Container<'a, Message, Theme, Renderer> +where + Theme: container::Catalog + 'a, + Renderer: core::Renderer, +{ + container(content).fill().center() +} + /// Creates a new [`Column`] with the given children. pub fn column<'a, Message, Theme, Renderer>( children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>, @@ -98,6 +132,428 @@ 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—effectively 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(), + }) +} + +/// Displays a widget on top of another one, only when the base widget is hovered. +/// +/// This works analogously to a [`stack`], but it will only display the layer on top +/// when the cursor is over the base. It can be useful for removing visual clutter. +/// +/// [`stack`]: stack() +pub fn hover<'a, Message, Theme, Renderer>( + base: impl Into<Element<'a, Message, Theme, Renderer>>, + top: 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 Hover<'a, Message, Theme, Renderer> { + base: Element<'a, Message, Theme, Renderer>, + top: Element<'a, Message, Theme, Renderer>, + is_top_overlay_active: bool, + } + + impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Hover<'a, Message, Theme, Renderer> + where + Renderer: core::Renderer, + { + fn tag(&self) -> tree::Tag { + struct Tag; + tree::Tag::of::<Tag>() + } + + fn children(&self) -> Vec<Tree> { + vec![Tree::new(&self.base), Tree::new(&self.top)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.base, &self.top]); + } + + fn size(&self) -> Size<Length> { + self.base.as_widget().size() + } + + fn size_hint(&self) -> Size<Length> { + self.base.as_widget().size_hint() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let base = self.base.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ); + + let top = self.top.as_widget().layout( + &mut tree.children[1], + renderer, + &layout::Limits::new(Size::ZERO, base.size()), + ); + + layout::Node::with_children(base.size(), vec![base, top]) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(bounds) = layout.bounds().intersection(viewport) { + let mut children = layout.children().zip(&tree.children); + + let (base_layout, base_tree) = children.next().unwrap(); + + self.base.as_widget().draw( + base_tree, + renderer, + theme, + style, + base_layout, + cursor, + viewport, + ); + + if cursor.is_over(layout.bounds()) || self.is_top_overlay_active + { + let (top_layout, top_tree) = children.next().unwrap(); + + renderer.with_layer(bounds, |renderer| { + self.top.as_widget().draw( + top_tree, renderer, theme, style, top_layout, + cursor, viewport, + ); + }); + } + } + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn operation::Operation<Message>, + ) { + let children = [&self.base, &self.top] + .into_iter() + .zip(layout.children().zip(&mut tree.children)); + + for (child, (layout, tree)) in children { + child.as_widget().operate(tree, layout, renderer, operation); + } + } + + fn on_event( + &mut self, + tree: &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 mut children = layout.children().zip(&mut tree.children); + let (base_layout, base_tree) = children.next().unwrap(); + + let top_status = if matches!( + event, + Event::Mouse( + mouse::Event::CursorMoved { .. } + | mouse::Event::ButtonReleased(_) + ) + ) || cursor.is_over(layout.bounds()) + { + let (top_layout, top_tree) = children.next().unwrap(); + + self.top.as_widget_mut().on_event( + top_tree, + event.clone(), + top_layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + } else { + event::Status::Ignored + }; + + if top_status == event::Status::Captured { + return top_status; + } + + self.base.as_widget_mut().on_event( + base_tree, + event.clone(), + base_layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + [&self.base, &self.top] + .into_iter() + .rev() + .zip(layout.children().rev().zip(tree.children.iter().rev())) + .map(|(child, (layout, tree))| { + child.as_widget().mouse_interaction( + tree, layout, cursor, viewport, renderer, + ) + }) + .find(|&interaction| interaction != mouse::Interaction::None) + .unwrap_or_default() + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut core::widget::Tree, + layout: core::Layout<'_>, + renderer: &Renderer, + translation: core::Vector, + ) -> Option<core::overlay::Element<'b, Message, Theme, Renderer>> + { + let mut overlays = [&mut self.base, &mut self.top] + .into_iter() + .zip(layout.children().zip(tree.children.iter_mut())) + .map(|(child, (layout, tree))| { + child.as_widget_mut().overlay( + tree, + layout, + renderer, + translation, + ) + }); + + if let Some(base_overlay) = overlays.next()? { + return Some(base_overlay); + } + + let top_overlay = overlays.next()?; + self.is_top_overlay_active = top_overlay.is_some(); + + top_overlay + } + } + + Element::new(Hover { + base: base.into(), + top: top.into(), + is_top_overlay_active: false, + }) +} + /// Creates a new [`Scrollable`] with the provided content. /// /// [`Scrollable`]: crate::Scrollable |