Merge pull request #2405 from iced-rs/feature/stack-widget
`Stack` widget
This commit is contained in:
commit
2d01d55cbc
17 changed files with 609 additions and 359 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#[allow(missing_docs)]
|
||||
pub enum Interaction {
|
||||
#[default]
|
||||
None,
|
||||
Idle,
|
||||
Pointer,
|
||||
Grab,
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ mod loupe {
|
|||
if cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::ZoomIn
|
||||
} else {
|
||||
mouse::Interaction::Idle
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ where
|
|||
caches,
|
||||
queued_events: Vec::new(),
|
||||
queued_messages: Vec::new(),
|
||||
mouse_interaction: mouse::Interaction::Idle,
|
||||
mouse_interaction: mouse::Interaction::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ where
|
|||
cache,
|
||||
queued_events: Vec::new(),
|
||||
queued_messages: Vec::new(),
|
||||
mouse_interaction: mouse::Interaction::Idle,
|
||||
mouse_interaction: mouse::Interaction::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ where
|
|||
} else if is_mouse_over {
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
mouse::Interaction::Idle
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
328
widget/src/stack.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ where
|
|||
exit_on_close_request,
|
||||
surface,
|
||||
renderer,
|
||||
mouse_interaction: mouse::Interaction::Idle,
|
||||
mouse_interaction: mouse::Interaction::None,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue