diff options
| author | 2023-07-27 18:48:06 +0200 | |
|---|---|---|
| committer | 2023-07-27 18:48:06 +0200 | |
| commit | 18f1ab551be6fe9a603eb07dceddf231c01b5c7f (patch) | |
| tree | a4fd30a4252c3a1469a618ac4754ee8a6d81a518 | |
| parent | e4b75ec9c76402dda7c6b6631918da240bb1c7ed (diff) | |
| parent | cbb5fcc8829e6fbe60f97cad8597c86ffd4f5b1a (diff) | |
| download | iced-18f1ab551be6fe9a603eb07dceddf231c01b5c7f.tar.gz iced-18f1ab551be6fe9a603eb07dceddf231c01b5c7f.tar.bz2 iced-18f1ab551be6fe9a603eb07dceddf231c01b5c7f.zip  | |
Merge pull request #1971 from iced-rs/visible-bounds-operation
`visible_bounds` operation for `Container`
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | core/src/element.rs | 11 | ||||
| -rw-r--r-- | core/src/overlay/element.rs | 7 | ||||
| -rw-r--r-- | core/src/overlay/group.rs | 2 | ||||
| -rw-r--r-- | core/src/rectangle.rs | 15 | ||||
| -rw-r--r-- | core/src/widget/operation.rs | 32 | ||||
| -rw-r--r-- | core/src/widget/operation/focusable.rs | 6 | ||||
| -rw-r--r-- | core/src/widget/operation/scrollable.rs | 19 | ||||
| -rw-r--r-- | core/src/widget/operation/text_input.rs | 5 | ||||
| -rw-r--r-- | examples/toast/src/main.rs | 4 | ||||
| -rw-r--r-- | examples/visible_bounds/Cargo.toml | 10 | ||||
| -rw-r--r-- | examples/visible_bounds/src/main.rs | 187 | ||||
| -rw-r--r-- | widget/src/button.rs | 2 | ||||
| -rw-r--r-- | widget/src/column.rs | 2 | ||||
| -rw-r--r-- | widget/src/container.rs | 93 | ||||
| -rw-r--r-- | widget/src/lazy/component.rs | 10 | ||||
| -rw-r--r-- | widget/src/pane_grid.rs | 2 | ||||
| -rw-r--r-- | widget/src/row.rs | 2 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 14 | 
19 files changed, 399 insertions, 25 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a989f6..c68ad2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0  - Helpers to change viewport alignment of a `Scrollable`. [#1953](https://github.com/iced-rs/iced/pull/1953)  - `scroll_to` widget operation. [#1796](https://github.com/iced-rs/iced/pull/1796)  - `scroll_to` helper. [#1804](https://github.com/iced-rs/iced/pull/1804) +- `visible_bounds` widget operation for `Container`. [#1971](https://github.com/iced-rs/iced/pull/1971)  - Command to fetch window size. [#1927](https://github.com/iced-rs/iced/pull/1927)  - Conversion support from `Fn` trait to custom theme. [#1861](https://github.com/iced-rs/iced/pull/1861)  - Configurable border radii on relevant widgets. [#1869](https://github.com/iced-rs/iced/pull/1869) diff --git a/core/src/element.rs b/core/src/element.rs index b9b76247..d2c6358b 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -5,7 +5,9 @@ use crate::overlay;  use crate::renderer;  use crate::widget;  use crate::widget::tree::{self, Tree}; -use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget}; +use crate::{ +    Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget, +};  use std::any::Any;  use std::borrow::Borrow; @@ -325,11 +327,12 @@ where              fn container(                  &mut self,                  id: Option<&widget::Id>, +                bounds: Rectangle,                  operate_on_children: &mut dyn FnMut(                      &mut dyn widget::Operation<T>,                  ),              ) { -                self.operation.container(id, &mut |operation| { +                self.operation.container(id, bounds, &mut |operation| {                      operate_on_children(&mut MapOperation { operation });                  });              } @@ -346,8 +349,10 @@ where                  &mut self,                  state: &mut dyn widget::operation::Scrollable,                  id: Option<&widget::Id>, +                bounds: Rectangle, +                translation: Vector,              ) { -                self.operation.scrollable(state, id); +                self.operation.scrollable(state, id, bounds, translation);              }              fn text_input( diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index c2134343..29b404b8 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -172,11 +172,12 @@ where              fn container(                  &mut self,                  id: Option<&widget::Id>, +                bounds: Rectangle,                  operate_on_children: &mut dyn FnMut(                      &mut dyn widget::Operation<T>,                  ),              ) { -                self.operation.container(id, &mut |operation| { +                self.operation.container(id, bounds, &mut |operation| {                      operate_on_children(&mut MapOperation { operation });                  });              } @@ -193,8 +194,10 @@ where                  &mut self,                  state: &mut dyn widget::operation::Scrollable,                  id: Option<&widget::Id>, +                bounds: Rectangle, +                translation: Vector,              ) { -                self.operation.scrollable(state, id); +                self.operation.scrollable(state, id, bounds, translation);              }              fn text_input( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index deffaad0..691686cd 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -138,7 +138,7 @@ where          renderer: &Renderer,          operation: &mut dyn widget::Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.children.iter_mut().zip(layout.children()).for_each(                  |(child, layout)| {                      child.operate(layout, renderer, operation); diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 7ff324cb..db56aa18 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -197,3 +197,18 @@ where          }      }  } + +impl<T> std::ops::Sub<Vector<T>> for Rectangle<T> +where +    T: std::ops::Sub<Output = T>, +{ +    type Output = Rectangle<T>; + +    fn sub(self, translation: Vector<T>) -> Self { +        Rectangle { +            x: self.x - translation.x, +            y: self.y - translation.y, +            ..self +        } +    } +} diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index ad188c36..b91cf9ac 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -8,6 +8,7 @@ pub use scrollable::Scrollable;  pub use text_input::TextInput;  use crate::widget::Id; +use crate::{Rectangle, Vector};  use std::any::Any;  use std::fmt; @@ -23,6 +24,7 @@ pub trait Operation<T> {      fn container(          &mut self,          id: Option<&Id>, +        bounds: Rectangle,          operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),      ); @@ -30,7 +32,14 @@ pub trait Operation<T> {      fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}      /// Operates on a widget that can be scrolled. -    fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} +    fn scrollable( +        &mut self, +        _state: &mut dyn Scrollable, +        _id: Option<&Id>, +        _bounds: Rectangle, +        _translation: Vector, +    ) { +    }      /// Operates on a widget that has text input.      fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} @@ -92,6 +101,7 @@ where          fn container(              &mut self,              id: Option<&Id>, +            bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),          ) {              struct MapRef<'a, A> { @@ -102,11 +112,12 @@ where                  fn container(                      &mut self,                      id: Option<&Id>, +                    bounds: Rectangle,                      operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),                  ) {                      let Self { operation, .. } = self; -                    operation.container(id, &mut |operation| { +                    operation.container(id, bounds, &mut |operation| {                          operate_on_children(&mut MapRef { operation });                      });                  } @@ -115,8 +126,10 @@ where                      &mut self,                      state: &mut dyn Scrollable,                      id: Option<&Id>, +                    bounds: Rectangle, +                    translation: Vector,                  ) { -                    self.operation.scrollable(state, id); +                    self.operation.scrollable(state, id, bounds, translation);                  }                  fn focusable( @@ -145,15 +158,21 @@ where              MapRef {                  operation: operation.as_mut(),              } -            .container(id, operate_on_children); +            .container(id, bounds, operate_on_children);          }          fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {              self.operation.focusable(state, id);          } -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { -            self.operation.scrollable(state, id); +        fn scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            bounds: Rectangle, +            translation: Vector, +        ) { +            self.operation.scrollable(state, id, bounds, translation);          }          fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { @@ -197,6 +216,7 @@ pub fn scope<T: 'static>(          fn container(              &mut self,              id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),          ) {              if id == Some(&self.target) { diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 312e4894..ab1b677e 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -1,6 +1,7 @@  //! Operate on widgets that can be focused.  use crate::widget::operation::{Operation, Outcome};  use crate::widget::Id; +use crate::Rectangle;  /// The internal state of a widget that can be focused.  pub trait Focusable { @@ -45,6 +46,7 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -80,6 +82,7 @@ where          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -126,6 +129,7 @@ pub fn focus_previous<T>() -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -159,6 +163,7 @@ pub fn focus_next<T>() -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -185,6 +190,7 @@ pub fn find_focused() -> impl Operation<Id> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),          ) {              operate_on_children(self) diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index f947344d..4f8b2a98 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -1,5 +1,6 @@  //! Operate on widgets that can be scrolled.  use crate::widget::{Id, Operation}; +use crate::{Rectangle, Vector};  /// The internal state of a widget that can be scrolled.  pub trait Scrollable { @@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self)          } -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +        fn scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            _bounds: Rectangle, +            _translation: Vector, +        ) {              if Some(&self.target) == id {                  state.snap_to(self.offset);              } @@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self)          } -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +        fn scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            _bounds: Rectangle, +            _translation: Vector, +        ) {              if Some(&self.target) == id {                  state.scroll_to(self.offset);              } diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 4c773e99..a9ea2e81 100644 --- a/core/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs @@ -1,6 +1,7 @@  //! Operate on widgets that have text input.  use crate::widget::operation::Operation;  use crate::widget::Id; +use crate::Rectangle;  /// The internal state of a widget that has text input.  pub trait TextInput { @@ -34,6 +35,7 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -63,6 +65,7 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -93,6 +96,7 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) @@ -121,6 +125,7 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) {              operate_on_children(self) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 5d29e895..42f6c348 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -381,7 +381,7 @@ mod toast {              renderer: &Renderer,              operation: &mut dyn Operation<Message>,          ) { -            operation.container(None, &mut |operation| { +            operation.container(None, layout.bounds(), &mut |operation| {                  self.content.as_widget().operate(                      &mut state.children[0],                      layout, @@ -622,7 +622,7 @@ mod toast {              renderer: &Renderer,              operation: &mut dyn widget::Operation<Message>,          ) { -            operation.container(None, &mut |operation| { +            operation.container(None, layout.bounds(), &mut |operation| {                  self.toasts                      .iter()                      .zip(self.state.iter_mut()) diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml new file mode 100644 index 00000000..cfa56dd2 --- /dev/null +++ b/examples/visible_bounds/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "visible_bounds" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +once_cell = "1" diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs new file mode 100644 index 00000000..8b684514 --- /dev/null +++ b/examples/visible_bounds/src/main.rs @@ -0,0 +1,187 @@ +use iced::executor; +use iced::mouse; +use iced::subscription::{self, Subscription}; +use iced::theme::{self, Theme}; +use iced::widget::{ +    column, container, horizontal_space, row, scrollable, text, vertical_space, +}; +use iced::window; +use iced::{ +    Alignment, Application, Color, Command, Element, Event, Font, Length, +    Point, Rectangle, Settings, +}; + +pub fn main() -> iced::Result { +    Example::run(Settings::default()) +} + +struct Example { +    mouse_position: Option<Point>, +    outer_bounds: Option<Rectangle>, +    inner_bounds: Option<Rectangle>, +} + +#[derive(Debug, Clone, Copy)] +enum Message { +    MouseMoved(Point), +    WindowResized, +    Scrolled(scrollable::Viewport), +    OuterBoundsFetched(Option<Rectangle>), +    InnerBoundsFetched(Option<Rectangle>), +} + +impl Application for Example { +    type Message = Message; +    type Theme = Theme; +    type Flags = (); +    type Executor = executor::Default; + +    fn new(_flags: Self::Flags) -> (Self, Command<Message>) { +        ( +            Self { +                mouse_position: None, +                outer_bounds: None, +                inner_bounds: None, +            }, +            Command::none(), +        ) +    } + +    fn title(&self) -> String { +        String::from("Visible bounds - Iced") +    } + +    fn update(&mut self, message: Message) -> Command<Message> { +        match message { +            Message::MouseMoved(position) => { +                self.mouse_position = Some(position); + +                Command::none() +            } +            Message::Scrolled(_) | Message::WindowResized => { +                Command::batch(vec![ +                    container::visible_bounds(OUTER_CONTAINER.clone()) +                        .map(Message::OuterBoundsFetched), +                    container::visible_bounds(INNER_CONTAINER.clone()) +                        .map(Message::InnerBoundsFetched), +                ]) +            } +            Message::OuterBoundsFetched(outer_bounds) => { +                self.outer_bounds = outer_bounds; + +                Command::none() +            } +            Message::InnerBoundsFetched(inner_bounds) => { +                self.inner_bounds = inner_bounds; + +                Command::none() +            } +        } +    } + +    fn view(&self) -> Element<Message> { +        let data_row = |label, value, color| { +            row![ +                text(label), +                horizontal_space(Length::Fill), +                text(value).font(Font::MONOSPACE).size(14).style(color), +            ] +            .height(40) +            .align_items(Alignment::Center) +        }; + +        let view_bounds = |label, bounds: Option<Rectangle>| { +            data_row( +                label, +                match bounds { +                    Some(bounds) => format!("{:?}", bounds), +                    None => "not visible".to_string(), +                }, +                if bounds +                    .zip(self.mouse_position) +                    .map(|(bounds, mouse_position)| { +                        bounds.contains(mouse_position) +                    }) +                    .unwrap_or_default() +                { +                    Color { +                        g: 1.0, +                        ..Color::BLACK +                    } +                    .into() +                } else { +                    theme::Text::Default +                }, +            ) +        }; + +        column![ +            data_row( +                "Mouse position", +                match self.mouse_position { +                    Some(Point { x, y }) => format!("({x}, {y})"), +                    None => "unknown".to_string(), +                }, +                theme::Text::Default, +            ), +            view_bounds("Outer container", self.outer_bounds), +            view_bounds("Inner container", self.inner_bounds), +            scrollable( +                column![ +                    text("Scroll me!"), +                    vertical_space(400), +                    container(text("I am the outer container!")) +                        .id(OUTER_CONTAINER.clone()) +                        .padding(40) +                        .style(theme::Container::Box), +                    vertical_space(400), +                    scrollable( +                        column![ +                            text("Scroll me!"), +                            vertical_space(400), +                            container(text("I am the inner container!")) +                                .id(INNER_CONTAINER.clone()) +                                .padding(40) +                                .style(theme::Container::Box), +                            vertical_space(400) +                        ] +                        .padding(20) +                    ) +                    .on_scroll(Message::Scrolled) +                    .width(Length::Fill) +                    .height(300), +                ] +                .padding(20) +            ) +            .on_scroll(Message::Scrolled) +            .width(Length::Fill) +            .height(300), +        ] +        .spacing(10) +        .padding(20) +        .into() +    } + +    fn subscription(&self) -> Subscription<Message> { +        subscription::events_with(|event, _| match event { +            Event::Mouse(mouse::Event::CursorMoved { position }) => { +                Some(Message::MouseMoved(position)) +            } +            Event::Window(window::Event::Resized { .. }) => { +                Some(Message::WindowResized) +            } +            _ => None, +        }) +    } + +    fn theme(&self) -> Theme { +        Theme::Dark +    } +} + +use once_cell::sync::Lazy; + +static OUTER_CONTAINER: Lazy<container::Id> = +    Lazy::new(|| container::Id::new("outer")); +static INNER_CONTAINER: Lazy<container::Id> = +    Lazy::new(|| container::Id::new("inner")); diff --git a/widget/src/button.rs b/widget/src/button.rs index 1312095f..5727c631 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -181,7 +181,7 @@ where          renderer: &Renderer,          operation: &mut dyn Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.content.as_widget().operate(                  &mut tree.children[0],                  layout.children().next().unwrap(), diff --git a/widget/src/column.rs b/widget/src/column.rs index 9271d5ef..c16477f3 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -148,7 +148,7 @@ where          renderer: &Renderer,          operation: &mut dyn Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.children                  .iter()                  .zip(&mut tree.children) diff --git a/widget/src/container.rs b/widget/src/container.rs index 64cf5cd5..1f1df861 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -8,8 +8,9 @@ use crate::core::renderer;  use crate::core::widget::{self, Operation, Tree};  use crate::core::{      Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, -    Point, Rectangle, Shell, Widget, +    Point, Rectangle, Shell, Size, Vector, Widget,  }; +use crate::runtime::Command;  pub use iced_style::container::{Appearance, StyleSheet}; @@ -180,6 +181,7 @@ where      ) {          operation.container(              self.id.as_ref().map(|id| &id.0), +            layout.bounds(),              &mut |operation| {                  self.content.as_widget().operate(                      &mut tree.children[0], @@ -368,3 +370,92 @@ impl From<Id> for widget::Id {          id.0      }  } + +/// Produces a [`Command`] that queries the visible screen bounds of the +/// [`Container`] with the given [`Id`]. +pub fn visible_bounds(id: Id) -> Command<Option<Rectangle>> { +    struct VisibleBounds { +        target: widget::Id, +        depth: usize, +        scrollables: Vec<(Vector, Rectangle, usize)>, +        bounds: Option<Rectangle>, +    } + +    impl Operation<Option<Rectangle>> for VisibleBounds { +        fn scrollable( +            &mut self, +            _state: &mut dyn widget::operation::Scrollable, +            _id: Option<&widget::Id>, +            bounds: Rectangle, +            translation: Vector, +        ) { +            match self.scrollables.last() { +                Some((last_translation, last_viewport, _depth)) => { +                    let viewport = last_viewport +                        .intersection(&(bounds - *last_translation)) +                        .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO)); + +                    self.scrollables.push(( +                        translation + *last_translation, +                        viewport, +                        self.depth, +                    )); +                } +                None => { +                    self.scrollables.push((translation, bounds, self.depth)); +                } +            } +        } + +        fn container( +            &mut self, +            id: Option<&widget::Id>, +            bounds: Rectangle, +            operate_on_children: &mut dyn FnMut( +                &mut dyn Operation<Option<Rectangle>>, +            ), +        ) { +            if self.bounds.is_some() { +                return; +            } + +            if id == Some(&self.target) { +                match self.scrollables.last() { +                    Some((translation, viewport, _)) => { +                        self.bounds = +                            viewport.intersection(&(bounds - *translation)); +                    } +                    None => { +                        self.bounds = Some(bounds); +                    } +                } + +                return; +            } + +            self.depth += 1; + +            operate_on_children(self); + +            self.depth -= 1; + +            match self.scrollables.last() { +                Some((_, _, depth)) if self.depth == *depth => { +                    let _ = self.scrollables.pop(); +                } +                _ => {} +            } +        } + +        fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> { +            widget::operation::Outcome::Some(self.bounds) +        } +    } + +    Command::widget(VisibleBounds { +        target: id.into(), +        depth: 0, +        scrollables: Vec::new(), +        bounds: None, +    }) +} diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index bc0e23df..19df2792 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -7,7 +7,8 @@ use crate::core::renderer;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, +    self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, +    Widget,  };  use crate::runtime::overlay::Nested; @@ -340,11 +341,12 @@ where              fn container(                  &mut self,                  id: Option<&widget::Id>, +                bounds: Rectangle,                  operate_on_children: &mut dyn FnMut(                      &mut dyn widget::Operation<T>,                  ),              ) { -                self.operation.container(id, &mut |operation| { +                self.operation.container(id, bounds, &mut |operation| {                      operate_on_children(&mut MapOperation { operation });                  });              } @@ -369,8 +371,10 @@ where                  &mut self,                  state: &mut dyn widget::operation::Scrollable,                  id: Option<&widget::Id>, +                bounds: Rectangle, +                translation: Vector,              ) { -                self.operation.scrollable(state, id); +                self.operation.scrollable(state, id, bounds, translation);              }              fn custom( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 4f6dfbe8..0f4ab9eb 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -297,7 +297,7 @@ where          renderer: &Renderer,          operation: &mut dyn widget::Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.contents                  .iter()                  .zip(&mut tree.children) diff --git a/widget/src/row.rs b/widget/src/row.rs index 7baaaae3..99b2a0bf 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -137,7 +137,7 @@ where          renderer: &Renderer,          operation: &mut dyn Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.children                  .iter()                  .zip(&mut tree.children) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index f621fb26..103e3944 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -254,10 +254,22 @@ where      ) {          let state = tree.state.downcast_mut::<State>(); -        operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); +        let bounds = layout.bounds(); +        let content_layout = layout.children().next().unwrap(); +        let content_bounds = content_layout.bounds(); +        let translation = +            state.translation(self.direction, bounds, content_bounds); + +        operation.scrollable( +            state, +            self.id.as_ref().map(|id| &id.0), +            bounds, +            translation, +        );          operation.container(              self.id.as_ref().map(|id| &id.0), +            bounds,              &mut |operation| {                  self.content.as_widget().operate(                      &mut tree.children[0],  | 
