summaryrefslogtreecommitdiffstats
path: root/native/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-07-27 06:49:20 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-07-27 06:49:20 +0200
commitff2519b1d43d481987351a83b6dd7237524c21f0 (patch)
tree5731eeb7eb1247d4a8951de0d5bc5d8102640559 /native/src
parentc44267b85f7aaa2997e3caf1323b837d95818c22 (diff)
downloadiced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.gz
iced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.bz2
iced-ff2519b1d43d481987351a83b6dd7237524c21f0.zip
Replace stateful widgets with new `iced_pure` API
Diffstat (limited to 'native/src')
-rw-r--r--native/src/element.rs312
-rw-r--r--native/src/layout/flex.rs22
-rw-r--r--native/src/overlay.rs46
-rw-r--r--native/src/overlay/menu.rs54
-rw-r--r--native/src/program.rs2
-rw-r--r--native/src/renderer.rs2
-rw-r--r--native/src/user_interface.rs103
-rw-r--r--native/src/widget.rs53
-rw-r--r--native/src/widget/button.rs316
-rw-r--r--native/src/widget/checkbox.rs5
-rw-r--r--native/src/widget/column.rs74
-rw-r--r--native/src/widget/container.rs135
-rw-r--r--native/src/widget/helpers.rs283
-rw-r--r--native/src/widget/image.rs7
-rw-r--r--native/src/widget/image/viewer.rs163
-rw-r--r--native/src/widget/pane_grid.rs408
-rw-r--r--native/src/widget/pane_grid/content.rs84
-rw-r--r--native/src/widget/pane_grid/state.rs8
-rw-r--r--native/src/widget/pane_grid/title_bar.rs92
-rw-r--r--native/src/widget/pick_list.rs330
-rw-r--r--native/src/widget/progress_bar.rs2
-rw-r--r--native/src/widget/radio.rs5
-rw-r--r--native/src/widget/row.rs93
-rw-r--r--native/src/widget/rule.rs2
-rw-r--r--native/src/widget/scrollable.rs402
-rw-r--r--native/src/widget/slider.rs232
-rw-r--r--native/src/widget/space.rs2
-rw-r--r--native/src/widget/svg.rs6
-rw-r--r--native/src/widget/text.rs12
-rw-r--r--native/src/widget/text_input.rs233
-rw-r--r--native/src/widget/toggler.rs5
-rw-r--r--native/src/widget/tooltip.rs271
-rw-r--r--native/src/widget/tree.rs187
33 files changed, 2321 insertions, 1630 deletions
diff --git a/native/src/element.rs b/native/src/element.rs
index 425bddc2..cc74035e 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -3,9 +3,10 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
-use crate::{
- Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
-};
+use crate::widget::tree::{self, Tree};
+use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell, Widget};
+
+use std::borrow::Borrow;
/// A generic [`Widget`].
///
@@ -15,25 +16,33 @@ use crate::{
/// If you have a [built-in widget], you should be able to use `Into<Element>`
/// to turn it into an [`Element`].
///
-/// [built-in widget]: widget/index.html#built-in-widgets
+/// [built-in widget]: crate::widget
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
- pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
+ widget: Box<dyn Widget<Message, Renderer> + 'a>,
}
-impl<'a, Message, Renderer> Element<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
-{
+impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// Creates a new [`Element`] containing the given [`Widget`].
- pub fn new(
- widget: impl Widget<Message, Renderer> + 'a,
- ) -> Element<'a, Message, Renderer> {
- Element {
+ pub fn new(widget: impl Widget<Message, Renderer> + 'a) -> Self
+ where
+ Renderer: crate::Renderer,
+ {
+ Self {
widget: Box::new(widget),
}
}
+ /// Returns a reference to the [`Widget`] of the [`Element`],
+ pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> {
+ self.widget.as_ref()
+ }
+
+ /// Returns a mutable reference to the [`Widget`] of the [`Element`],
+ pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> {
+ self.widget.as_mut()
+ }
+
/// Applies a transformation to the produced message of the [`Element`].
///
/// This method is useful when you want to decouple different parts of your
@@ -168,127 +177,22 @@ where
/// }
/// }
/// ```
- pub fn map<F, B>(self, f: F) -> Element<'a, B, Renderer>
- where
- Message: 'static,
- Renderer: 'a,
- B: 'static,
- F: 'static + Fn(Message) -> B,
- {
- Element {
- widget: Box::new(Map::new(self.widget, f)),
- }
- }
-
- /// Marks the [`Element`] as _to-be-explained_.
- ///
- /// The [`Renderer`] will explain the layout of the [`Element`] graphically.
- /// This can be very useful for debugging your layout!
- ///
- /// [`Renderer`]: crate::Renderer
- pub fn explain<C: Into<Color>>(
+ pub fn map<B>(
self,
- color: C,
- ) -> Element<'a, Message, Renderer>
+ f: impl Fn(Message) -> B + 'a,
+ ) -> Element<'a, B, Renderer>
where
- Message: 'static,
- Renderer: 'a,
+ Message: 'a,
+ Renderer: crate::Renderer + 'a,
+ B: 'a,
{
- Element {
- widget: Box::new(Explain::new(self, color.into())),
- }
- }
-
- /// Returns the width of the [`Element`].
- pub fn width(&self) -> Length {
- self.widget.width()
- }
-
- /// Returns the height of the [`Element`].
- pub fn height(&self) -> Length {
- self.widget.height()
- }
-
- /// Computes the layout of the [`Element`] in the given [`Limits`].
- ///
- /// [`Limits`]: layout::Limits
- pub fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- self.widget.layout(renderer, limits)
- }
-
- /// Processes a runtime [`Event`].
- pub fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- self.widget.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
- }
-
- /// Draws the [`Element`] and its children using the given [`Layout`].
- pub fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) {
- self.widget.draw(
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- )
- }
-
- /// Returns the current [`mouse::Interaction`] of the [`Element`].
- pub fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- self.widget.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
- }
-
- /// Returns the overlay of the [`Element`], if there is any.
- pub fn overlay<'b>(
- &'b mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.widget.overlay(layout, renderer)
+ Element::new(Map::new(self.widget, f))
}
}
struct Map<'a, A, B, Renderer> {
widget: Box<dyn Widget<A, Renderer> + 'a>,
- mapper: Box<dyn Fn(A) -> B>,
+ mapper: Box<dyn Fn(A) -> B + 'a>,
}
impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
@@ -297,7 +201,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
mapper: F,
) -> Map<'a, A, B, Renderer>
where
- F: 'static + Fn(A) -> B,
+ F: 'a + Fn(A) -> B,
{
Map {
widget,
@@ -309,9 +213,25 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
Renderer: crate::Renderer + 'a,
- A: 'static,
- B: 'static,
+ A: 'a,
+ B: 'a,
{
+ fn tag(&self) -> tree::Tag {
+ self.widget.tag()
+ }
+
+ fn state(&self) -> tree::State {
+ self.widget.state()
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ self.widget.children()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ self.widget.diff(tree)
+ }
+
fn width(&self) -> Length {
self.widget.width()
}
@@ -330,6 +250,7 @@ where
fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -341,6 +262,7 @@ where
let mut local_shell = Shell::new(&mut local_messages);
let status = self.widget.on_event(
+ tree,
event,
layout,
cursor_position,
@@ -356,6 +278,7 @@ where
fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -364,6 +287,7 @@ where
viewport: &Rectangle,
) {
self.widget.draw(
+ tree,
renderer,
theme,
style,
@@ -375,12 +299,14 @@ where
fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.widget.mouse_interaction(
+ tree,
layout,
cursor_position,
viewport,
@@ -388,134 +314,32 @@ where
)
}
- fn overlay(
- &mut self,
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, B, Renderer>> {
+ ) -> Option<overlay::Element<'b, B, Renderer>> {
let mapper = &self.mapper;
self.widget
- .overlay(layout, renderer)
+ .overlay(tree, layout, renderer)
.map(move |overlay| overlay.map(mapper))
}
}
-struct Explain<'a, Message, Renderer: crate::Renderer> {
- element: Element<'a, Message, Renderer>,
- color: Color,
-}
-
-impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
+impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
+ for Element<'a, Message, Renderer>
{
- fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
- Explain { element, color }
+ fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
+ self.widget.borrow()
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Explain<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
+impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
+ for &Element<'a, Message, Renderer>
{
- fn width(&self) -> Length {
- self.element.widget.width()
- }
-
- fn height(&self) -> Length {
- self.element.widget.height()
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- self.element.widget.layout(renderer, limits)
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- self.element.widget.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) {
- fn explain_layout<Renderer: crate::Renderer>(
- renderer: &mut Renderer,
- color: Color,
- layout: Layout<'_>,
- ) {
- renderer.fill_quad(
- renderer::Quad {
- bounds: layout.bounds(),
- border_color: color,
- border_width: 1.0,
- border_radius: 0.0,
- },
- Color::TRANSPARENT,
- );
-
- for child in layout.children() {
- explain_layout(renderer, color, child);
- }
- }
-
- self.element.widget.draw(
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
-
- explain_layout(renderer, self.color, layout);
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- self.element.widget.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
- }
-
- fn overlay(
- &mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.element.overlay(layout, renderer)
+ fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
+ self.widget.borrow()
}
}
diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs
index 5fbcbca0..94121d76 100644
--- a/native/src/layout/flex.rs
+++ b/native/src/layout/flex.rs
@@ -16,8 +16,10 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::Element;
+
use crate::layout::{Limits, Node};
-use crate::{Alignment, Element, Padding, Point, Size};
+use crate::{Alignment, Padding, Point, Size};
/// The main axis of a flex layout.
#[derive(Debug)]
@@ -84,8 +86,8 @@ where
items.iter().for_each(|child| {
let cross_fill_factor = match axis {
- Axis::Horizontal => child.height(),
- Axis::Vertical => child.width(),
+ Axis::Horizontal => child.as_widget().height(),
+ Axis::Vertical => child.as_widget().width(),
}
.fill_factor();
@@ -95,7 +97,7 @@ where
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
- let layout = child.layout(renderer, &child_limits);
+ let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();
fill_cross = fill_cross.max(axis.cross(size));
@@ -107,8 +109,8 @@ where
for (i, child) in items.iter().enumerate() {
let fill_factor = match axis {
- Axis::Horizontal => child.width(),
- Axis::Vertical => child.height(),
+ Axis::Horizontal => child.as_widget().width(),
+ Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
@@ -130,7 +132,7 @@ where
Size::new(max_width, max_height),
);
- let layout = child.layout(renderer, &child_limits);
+ let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
@@ -149,8 +151,8 @@ where
for (i, child) in items.iter().enumerate() {
let fill_factor = match axis {
- Axis::Horizontal => child.width(),
- Axis::Vertical => child.height(),
+ Axis::Horizontal => child.as_widget().width(),
+ Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
@@ -179,7 +181,7 @@ where
Size::new(max_width, max_height),
);
- let layout = child.layout(renderer, &child_limits);
+ let layout = child.as_widget().layout(renderer, &child_limits);
if align_items != Alignment::Fill {
cross = cross.max(axis.cross(layout.size()));
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 792d2905..c2a98693 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -10,6 +10,7 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
+use crate::widget::tree::{self, Tree};
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
/// An interactive component that can be displayed on top of other widgets.
@@ -40,6 +41,28 @@ where
cursor_position: Point,
);
+ /// Returns the [`Tag`] of the [`Widget`].
+ ///
+ /// [`Tag`]: tree::Tag
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::stateless()
+ }
+
+ /// Returns the [`State`] of the [`Widget`].
+ ///
+ /// [`State`]: tree::State
+ fn state(&self) -> tree::State {
+ tree::State::None
+ }
+
+ /// Returns the state [`Tree`] of the children of the [`Widget`].
+ fn children(&self) -> Vec<Tree> {
+ Vec::new()
+ }
+
+ /// Reconciliates the [`Widget`] with the provided [`Tree`].
+ fn diff(&self, _tree: &mut Tree) {}
+
/// Processes a runtime [`Event`].
///
/// It receives:
@@ -77,3 +100,26 @@ where
mouse::Interaction::Idle
}
}
+
+/// Obtains the first overlay [`Element`] found in the given children.
+///
+/// This method will generally only be used by advanced users that are
+/// implementing the [`Widget`](crate::Widget) trait.
+pub fn from_children<'a, Message, Renderer>(
+ children: &'a [crate::Element<'_, Message, Renderer>],
+ tree: &'a mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+) -> Option<Element<'a, Message, Renderer>>
+where
+ Renderer: crate::Renderer,
+{
+ children
+ .iter()
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .filter_map(|((child, state), layout)| {
+ child.as_widget().overlay(state, layout, renderer)
+ })
+ .next()
+}
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index fc3f52b2..b4c77c25 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -9,6 +9,7 @@ use crate::text::{self, Text};
use crate::touch;
use crate::widget::container::{self, Container};
use crate::widget::scrollable::{self, Scrollable};
+use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@@ -114,15 +115,23 @@ where
}
/// The local state of a [`Menu`].
-#[derive(Debug, Clone, Default)]
+#[derive(Debug)]
pub struct State {
- scrollable: scrollable::State,
+ tree: Tree,
}
impl State {
/// Creates a new [`State`] for a [`Menu`].
pub fn new() -> Self {
- Self::default()
+ Self {
+ tree: Tree::empty(),
+ }
+ }
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::new()
}
}
@@ -131,6 +140,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
+ state: &'a mut Tree,
container: Container<'a, Message, Renderer>,
width: u16,
target_height: f32,
@@ -161,18 +171,18 @@ where
style,
} = menu;
- let container =
- Container::new(Scrollable::new(&mut state.scrollable).push(List {
- options,
- hovered_option,
- last_selection,
- font,
- text_size,
- padding,
- style,
- }));
+ let container = Container::new(Scrollable::new(List {
+ options,
+ hovered_option,
+ last_selection,
+ font,
+ text_size,
+ padding,
+ style,
+ }));
Self {
+ state: &mut state.tree,
container,
width,
target_height,
@@ -187,6 +197,18 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
+ fn tag(&self) -> tree::Tag {
+ self.container.tag()
+ }
+
+ fn state(&self) -> tree::State {
+ self.container.state()
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ self.container.children()
+ }
+
fn layout(
&self,
renderer: &Renderer,
@@ -230,6 +252,7 @@ where
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
+ &mut self.state,
event,
layout,
cursor_position,
@@ -247,6 +270,7 @@ where
renderer: &Renderer,
) -> mouse::Interaction {
self.container.mouse_interaction(
+ &self.state,
layout,
cursor_position,
viewport,
@@ -279,6 +303,7 @@ where
);
self.container.draw(
+ &self.state,
renderer,
theme,
style,
@@ -344,6 +369,7 @@ where
fn on_event(
&mut self,
+ _state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -407,6 +433,7 @@ where
fn mouse_interaction(
&self,
+ _state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@@ -423,6 +450,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/program.rs b/native/src/program.rs
index 9ee72703..c71c237f 100644
--- a/native/src/program.rs
+++ b/native/src/program.rs
@@ -26,5 +26,5 @@ pub trait Program: Sized {
/// Returns the widgets to display in the [`Program`].
///
/// These widgets can produce __messages__ based on user interaction.
- fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
+ fn view(&self) -> Element<'_, Self::Message, Self::Renderer>;
}
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index a7305a55..ef64ac36 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -21,7 +21,7 @@ pub trait Renderer: Sized {
element: &Element<'a, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
- element.layout(self, limits)
+ element.as_widget().layout(self, limits)
}
/// Draws the primitives recorded in the given closure in a new layer.
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index ef6f437e..9f3a8e21 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -4,6 +4,7 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
+use crate::widget;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// A set of interactive graphical elements with a specific [`Layout`].
@@ -22,6 +23,7 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
base: layout::Node,
+ state: widget::Tree,
overlay: Option<layout::Node>,
bounds: Size,
}
@@ -88,7 +90,7 @@ where
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
root: E,
bounds: Size,
- _cache: Cache,
+ cache: Cache,
renderer: &mut Renderer,
) -> Self {
let root = root.into();
@@ -96,9 +98,13 @@ where
let base =
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
+ let Cache { mut state } = cache;
+ state.diff(root.as_widget());
+
UserInterface {
root,
base,
+ state,
overlay: None,
bounds,
}
@@ -182,9 +188,12 @@ where
use std::mem::ManuallyDrop;
let mut state = State::Updated;
- let mut manual_overlay = ManuallyDrop::new(
- self.root.overlay(Layout::new(&self.base), renderer),
- );
+ let mut manual_overlay =
+ ManuallyDrop::new(self.root.as_widget().overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ ));
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
let bounds = self.bounds;
@@ -215,9 +224,12 @@ where
&layout::Limits::new(Size::ZERO, self.bounds),
);
- manual_overlay = ManuallyDrop::new(
- self.root.overlay(Layout::new(&self.base), renderer),
- );
+ manual_overlay =
+ ManuallyDrop::new(self.root.as_widget().overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ ));
if manual_overlay.is_none() {
break;
@@ -262,7 +274,8 @@ where
let mut shell = Shell::new(messages);
- let event_status = self.root.widget.on_event(
+ let event_status = self.root.as_widget_mut().on_event(
+ &mut self.state,
event,
Layout::new(&self.base),
base_cursor,
@@ -377,9 +390,11 @@ where
let viewport = Rectangle::with_size(self.bounds);
- let base_cursor = if let Some(overlay) =
- self.root.overlay(Layout::new(&self.base), renderer)
- {
+ let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ ) {
let overlay_layout = self
.overlay
.take()
@@ -399,7 +414,8 @@ where
cursor_position
};
- self.root.widget.draw(
+ self.root.as_widget().draw(
+ &self.state,
renderer,
theme,
style,
@@ -408,7 +424,8 @@ where
&viewport,
);
- let base_interaction = self.root.widget.mouse_interaction(
+ let base_interaction = self.root.as_widget().mouse_interaction(
+ &self.state,
Layout::new(&self.base),
cursor_position,
&viewport,
@@ -430,32 +447,34 @@ where
overlay
.as_ref()
.and_then(|layout| {
- root.overlay(Layout::new(base), renderer).map(|overlay| {
- let overlay_interaction = overlay.mouse_interaction(
- Layout::new(layout),
- cursor_position,
- &viewport,
- renderer,
- );
-
- let overlay_bounds = layout.bounds();
-
- renderer.with_layer(overlay_bounds, |renderer| {
- overlay.draw(
- renderer,
- theme,
- style,
+ root.as_widget()
+ .overlay(&mut self.state, Layout::new(base), renderer)
+ .map(|overlay| {
+ let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
cursor_position,
+ &viewport,
+ renderer,
);
- });
- if overlay_bounds.contains(cursor_position) {
- overlay_interaction
- } else {
- base_interaction
- }
- })
+ let overlay_bounds = layout.bounds();
+
+ renderer.with_layer(overlay_bounds, |renderer| {
+ overlay.draw(
+ renderer,
+ theme,
+ style,
+ Layout::new(layout),
+ cursor_position,
+ );
+ });
+
+ if overlay_bounds.contains(cursor_position) {
+ overlay_interaction
+ } else {
+ base_interaction
+ }
+ })
})
.unwrap_or(base_interaction)
}
@@ -463,19 +482,21 @@ where
/// Relayouts and returns a new [`UserInterface`] using the provided
/// bounds.
pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
- Self::build(self.root, bounds, Cache, renderer)
+ Self::build(self.root, bounds, Cache { state: self.state }, renderer)
}
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
pub fn into_cache(self) -> Cache {
- Cache
+ Cache { state: self.state }
}
}
/// Reusable data of a specific [`UserInterface`].
-#[derive(Debug, Clone)]
-pub struct Cache;
+#[derive(Debug)]
+pub struct Cache {
+ state: widget::Tree,
+}
impl Cache {
/// Creates an empty [`Cache`].
@@ -483,7 +504,9 @@ impl Cache {
/// You should use this to initialize a [`Cache`] before building your first
/// [`UserInterface`].
pub fn new() -> Cache {
- Cache
+ Cache {
+ state: widget::Tree::empty(),
+ }
}
}
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 9fe96e33..9a4f373a 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -15,6 +15,7 @@ pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
+pub mod helpers;
pub mod image;
pub mod pane_grid;
pub mod pick_list;
@@ -30,6 +31,7 @@ pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
+pub mod tree;
#[doc(no_inline)]
pub use button::Button;
@@ -40,6 +42,8 @@ pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
+pub use helpers::*;
+#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
@@ -69,6 +73,8 @@ pub use text_input::TextInput;
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
+#[doc(no_inline)]
+pub use tree::Tree;
use crate::event::{self, Event};
use crate::layout;
@@ -109,12 +115,10 @@ where
/// Returns the height of the [`Widget`].
fn height(&self) -> Length;
- /// Returns the [`Node`] of the [`Widget`].
+ /// Returns the [`layout::Node`] of the [`Widget`].
///
- /// This [`Node`] is used by the runtime to compute the [`Layout`] of the
+ /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
- ///
- /// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
@@ -124,6 +128,7 @@ where
/// Draws the [`Widget`] using the associated `Renderer`.
fn draw(
&self,
+ state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -132,20 +137,34 @@ where
viewport: &Rectangle,
);
- /// Processes a runtime [`Event`].
+ /// Returns the [`Tag`] of the [`Widget`].
+ ///
+ /// [`Tag`]: tree::Tag
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::stateless()
+ }
+
+ /// Returns the [`State`] of the [`Widget`].
///
- /// It receives:
- /// * an [`Event`] describing user interaction
- /// * the computed [`Layout`] of the [`Widget`]
- /// * the current cursor position
- /// * a mutable `Message` list, allowing the [`Widget`] to produce
- /// new messages based on user interaction.
- /// * the `Renderer`
- /// * a [`Clipboard`], if available
+ /// [`State`]: tree::State
+ fn state(&self) -> tree::State {
+ tree::State::None
+ }
+
+ /// Returns the state [`Tree`] of the children of the [`Widget`].
+ fn children(&self) -> Vec<Tree> {
+ Vec::new()
+ }
+
+ /// Reconciliates the [`Widget`] with the provided [`Tree`].
+ fn diff(&self, _tree: &mut Tree) {}
+
+ /// Processes a runtime [`Event`].
///
/// By default, it does nothing.
fn on_event(
&mut self,
+ _state: &mut Tree,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
@@ -161,6 +180,7 @@ where
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
+ _state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
@@ -170,11 +190,12 @@ where
}
/// Returns the overlay of the [`Widget`], if there is any.
- fn overlay(
- &mut self,
+ fn overlay<'a>(
+ &'a self,
+ _state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ ) -> Option<overlay::Element<'a, Message, Renderer>> {
None
}
}
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index a33ee7f7..6eac6c1b 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -7,6 +7,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
+use crate::widget::tree::{self, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Vector, Widget,
@@ -17,8 +18,6 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// A generic widget that produces a message when pressed.
///
/// ```
-/// # use iced_native::widget::{button, Text};
-/// #
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
@@ -27,17 +26,13 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// ButtonPressed,
/// }
///
-/// let mut state = button::State::new();
-/// let button = Button::new(&mut state, Text::new("Press me!"))
-/// .on_press(Message::ButtonPressed);
+/// let button = Button::new("Press me!").on_press(Message::ButtonPressed);
/// ```
///
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
/// be disabled:
///
/// ```
-/// # use iced_native::widget::{button, Text};
-/// #
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
@@ -46,12 +41,12 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// ButtonPressed,
/// }
///
-/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
-/// Button::new(state, Text::new("I'm disabled!"))
+/// fn disabled_button<'a>() -> Button<'a, Message> {
+/// Button::new("I'm disabled!")
/// }
///
-/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
-/// disabled_button(state).on_press(Message::ButtonPressed)
+/// fn enabled_button<'a>() -> Button<'a, Message> {
+/// disabled_button().on_press(Message::ButtonPressed)
/// }
/// ```
#[allow(missing_debug_implementations)]
@@ -60,7 +55,6 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
- state: &'a mut State,
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
width: Length,
@@ -71,24 +65,18 @@ where
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
- Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
- /// Creates a new [`Button`] with some local [`State`] and the given
- /// content.
- pub fn new<E>(state: &'a mut State, content: E) -> Self
- where
- E: Into<Element<'a, Message, Renderer>>,
- {
+ /// Creates a new [`Button`] with the given content.
+ pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Button {
- state,
content: content.into(),
on_press: None,
width: Length::Shrink,
height: Length::Shrink,
padding: Padding::new(5),
- style: Default::default(),
+ style: <Renderer::Theme as StyleSheet>::Style::default(),
}
}
@@ -111,13 +99,14 @@ where
}
/// Sets the message that will be produced when the [`Button`] is pressed.
- /// If on_press isn't set, button will be disabled.
+ ///
+ /// Unless `on_press` is called, the [`Button`] will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
- /// Sets the style of this [`Button`].
+ /// Sets the style variant of this [`Button`].
pub fn style(
mut self,
style: <Renderer::Theme as StyleSheet>::Style,
@@ -127,6 +116,159 @@ where
}
}
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Button<'a, Message, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content))
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.height,
+ self.padding,
+ |renderer, limits| {
+ self.content.as_widget().layout(renderer, limits)
+ },
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ if let event::Status::Captured = self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event.clone(),
+ layout.children().next().unwrap(),
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ ) {
+ return event::Status::Captured;
+ }
+
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ &self.on_press,
+ || tree.state.downcast_mut::<State>(),
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+
+ let styling = draw(
+ renderer,
+ bounds,
+ cursor_position,
+ self.on_press.is_some(),
+ theme,
+ self.style,
+ || tree.state.downcast_ref::<State>(),
+ );
+
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ &renderer::Style {
+ text_color: styling.text_color,
+ },
+ content_layout,
+ cursor_position,
+ &bounds,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ _tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(layout, cursor_position, self.on_press.is_some())
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content.as_widget().overlay(
+ &mut tree.children[0],
+ layout.children().next().unwrap(),
+ renderer,
+ )
+ }
+}
+
+impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: Clone + 'a,
+ Renderer: crate::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(button: Button<'a, Message, Renderer>) -> Self {
+ Self::new(button)
+ }
+}
+
/// The local state of a [`Button`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
@@ -292,131 +434,3 @@ pub fn mouse_interaction(
mouse::Interaction::default()
}
}
-
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Button<'a, Message, Renderer>
-where
- Message: Clone,
- Renderer: crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- self.height
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- layout(
- renderer,
- limits,
- self.width,
- self.height,
- self.padding,
- |renderer, limits| self.content.layout(renderer, limits),
- )
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- if let event::Status::Captured = self.content.on_event(
- event.clone(),
- layout.children().next().unwrap(),
- cursor_position,
- renderer,
- clipboard,
- shell,
- ) {
- return event::Status::Captured;
- }
-
- update(
- event,
- layout,
- cursor_position,
- shell,
- &self.on_press,
- || &mut self.state,
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- _renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position, self.on_press.is_some())
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- _style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- let bounds = layout.bounds();
- let content_layout = layout.children().next().unwrap();
-
- let styling = draw(
- renderer,
- bounds,
- cursor_position,
- self.on_press.is_some(),
- theme,
- self.style,
- || self.state,
- );
-
- self.content.draw(
- renderer,
- theme,
- &renderer::Style {
- text_color: styling.text_color,
- },
- content_layout,
- cursor_position,
- &bounds,
- );
- }
-
- fn overlay(
- &mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.content
- .overlay(layout.children().next().unwrap(), renderer)
- }
-}
-
-impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a + Clone,
- Renderer: 'a + crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn from(
- button: Button<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(button)
- }
-}
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index a49d2fa2..dc3c0bd0 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -6,7 +6,7 @@ use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
-use crate::widget::{self, Row, Text};
+use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Layout, Length, Point, Rectangle, Shell,
Widget,
@@ -168,6 +168,7 @@ where
fn on_event(
&mut self,
+ _tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -194,6 +195,7 @@ where
fn mouse_interaction(
&self,
+ _tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@@ -208,6 +210,7 @@ where
fn draw(
&self,
+ _tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 4eee7d3c..834f9858 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -4,6 +4,7 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
+use crate::widget::Tree;
use crate::{
Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Widget,
@@ -19,7 +20,6 @@ pub struct Column<'a, Message, Renderer> {
width: Length,
height: Length,
max_width: u32,
- max_height: u32,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
@@ -40,7 +40,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
- max_height: u32::MAX,
align_items: Alignment::Start,
children,
}
@@ -80,12 +79,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
- /// Sets the maximum height of the [`Column`] in pixels.
- pub fn max_height(mut self, max_height: u32) -> Self {
- self.max_height = max_height;
- self
- }
-
/// Sets the horizontal alignment of the contents of the [`Column`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
@@ -93,10 +86,10 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
}
/// Adds an element to the [`Column`].
- pub fn push<E>(mut self, child: E) -> Self
- where
- E: Into<Element<'a, Message, Renderer>>,
- {
+ pub fn push(
+ mut self,
+ child: impl Into<Element<'a, Message, Renderer>>,
+ ) -> Self {
self.children.push(child.into());
self
}
@@ -113,6 +106,14 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
where
Renderer: crate::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 width(&self) -> Length {
self.width
}
@@ -128,7 +129,6 @@ where
) -> layout::Node {
let limits = limits
.max_width(self.max_width)
- .max_height(self.max_height)
.width(self.width)
.height(self.height);
@@ -145,6 +145,7 @@ where
fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -154,9 +155,11 @@ where
) -> event::Status {
self.children
.iter_mut()
+ .zip(&mut tree.children)
.zip(layout.children())
- .map(|(child, layout)| {
- child.widget.on_event(
+ .map(|((child, state), layout)| {
+ child.as_widget_mut().on_event(
+ state,
event.clone(),
layout,
cursor_position,
@@ -170,6 +173,7 @@ where
fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@@ -177,9 +181,11 @@ where
) -> mouse::Interaction {
self.children
.iter()
+ .zip(&tree.children)
.zip(layout.children())
- .map(|(child, layout)| {
- child.widget.mouse_interaction(
+ .map(|((child, state), layout)| {
+ child.as_widget().mouse_interaction(
+ state,
layout,
cursor_position,
viewport,
@@ -192,6 +198,7 @@ where
fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -199,8 +206,14 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
- for (child, layout) in self.children.iter().zip(layout.children()) {
- child.draw(
+ for ((child, state), layout) in self
+ .children
+ .iter()
+ .zip(&tree.children)
+ .zip(layout.children())
+ {
+ child.as_widget().draw(
+ state,
renderer,
theme,
style,
@@ -211,30 +224,23 @@ where
}
}
- fn overlay(
- &mut self,
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.children
- .iter_mut()
- .zip(layout.children())
- .filter_map(|(child, layout)| {
- child.widget.overlay(layout, renderer)
- })
- .next()
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ overlay::from_children(&self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + crate::Renderer,
Message: 'a,
+ Renderer: crate::Renderer + 'a,
{
- fn from(
- column: Column<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(column)
+ fn from(column: Column<'a, Message, Renderer>) -> Self {
+ Self::new(column)
}
}
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 3d68a595..b0fa0315 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -5,6 +5,7 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
+use crate::widget::Tree;
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Widget,
@@ -121,46 +122,20 @@ where
}
}
-/// Computes the layout of a [`Container`].
-pub fn layout<Renderer>(
- renderer: &Renderer,
- limits: &layout::Limits,
- width: Length,
- height: Length,
- max_width: u32,
- max_height: u32,
- padding: Padding,
- horizontal_alignment: alignment::Horizontal,
- vertical_alignment: alignment::Vertical,
- layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
-) -> layout::Node {
- let limits = limits
- .loose()
- .max_width(max_width)
- .max_height(max_height)
- .width(width)
- .height(height)
- .pad(padding);
-
- let mut content = layout_content(renderer, &limits.loose());
- let size = limits.resolve(content.size());
-
- content.move_to(Point::new(padding.left.into(), padding.top.into()));
- content.align(
- Alignment::from(horizontal_alignment),
- Alignment::from(vertical_alignment),
- size,
- );
-
- layout::Node::with_children(size.pad(padding), vec![content])
-}
-
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content))
+ }
+
fn width(&self) -> Length {
self.width
}
@@ -184,12 +159,15 @@ where
self.padding,
self.horizontal_alignment,
self.vertical_alignment,
- |renderer, limits| self.content.layout(renderer, limits),
+ |renderer, limits| {
+ self.content.as_widget().layout(renderer, limits)
+ },
)
}
fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -197,7 +175,8 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- self.content.widget.on_event(
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
event,
layout.children().next().unwrap(),
cursor_position,
@@ -209,12 +188,14 @@ where
fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.content.widget.mouse_interaction(
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
layout.children().next().unwrap(),
cursor_position,
viewport,
@@ -224,6 +205,7 @@ where
fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
@@ -235,7 +217,8 @@ where
draw_background(renderer, &style, layout.bounds());
- self.content.draw(
+ self.content.as_widget().draw(
+ &tree.children[0],
renderer,
theme,
&renderer::Style {
@@ -249,16 +232,68 @@ where
);
}
- fn overlay(
- &mut self,
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.content
- .overlay(layout.children().next().unwrap(), renderer)
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content.as_widget().overlay(
+ &mut tree.children[0],
+ layout.children().next().unwrap(),
+ renderer,
+ )
}
}
+impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(
+ column: Container<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(column)
+ }
+}
+
+/// Computes the layout of a [`Container`].
+pub fn layout<Renderer>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ height: Length,
+ max_width: u32,
+ max_height: u32,
+ padding: Padding,
+ horizontal_alignment: alignment::Horizontal,
+ vertical_alignment: alignment::Vertical,
+ layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
+) -> layout::Node {
+ let limits = limits
+ .loose()
+ .max_width(max_width)
+ .max_height(max_height)
+ .width(width)
+ .height(height)
+ .pad(padding);
+
+ let mut content = layout_content(renderer, &limits.loose());
+ let size = limits.resolve(content.size());
+
+ content.move_to(Point::new(padding.left.into(), padding.top.into()));
+ content.align(
+ Alignment::from(horizontal_alignment),
+ Alignment::from(vertical_alignment),
+ size,
+ );
+
+ layout::Node::with_children(size.pad(padding), vec![content])
+}
+
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
@@ -281,17 +316,3 @@ pub fn draw_background<Renderer>(
);
}
}
-
-impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a + crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn from(
- column: Container<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(column)
- }
-}
diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs
new file mode 100644
index 00000000..648aab5f
--- /dev/null
+++ b/native/src/widget/helpers.rs
@@ -0,0 +1,283 @@
+//! Helper functions to create pure widgets.
+use crate::widget;
+use crate::{Element, Length};
+
+use std::borrow::Cow;
+use std::ops::RangeInclusive;
+
+/// Creates a [`Column`] with the given children.
+///
+/// [`Column`]: widget::Column
+#[macro_export]
+macro_rules! column {
+ () => (
+ $crate::widget::Column::new()
+ );
+ ($($x:expr),+ $(,)?) => (
+ $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+])
+ );
+}
+
+/// Creates a [Row`] with the given children.
+///
+/// [`Row`]: widget::Row
+#[macro_export]
+macro_rules! row {
+ () => (
+ $crate::widget::Row::new()
+ );
+ ($($x:expr),+ $(,)?) => (
+ $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+])
+ );
+}
+
+/// Creates a new [`Container`] with the provided content.
+///
+/// [`Container`]: widget::Container
+pub fn container<'a, Message, Renderer>(
+ content: impl Into<Element<'a, Message, Renderer>>,
+) -> widget::Container<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::container::StyleSheet,
+{
+ widget::Container::new(content)
+}
+
+/// Creates a new [`Column`] with the given children.
+///
+/// [`Column`]: widget::Column
+pub fn column<'a, Message, Renderer>(
+ children: Vec<Element<'a, Message, Renderer>>,
+) -> widget::Row<'a, Message, Renderer> {
+ widget::Row::with_children(children)
+}
+
+/// Creates a new [`Row`] with the given children.
+///
+/// [`Row`]: widget::Row
+pub fn row<'a, Message, Renderer>(
+ children: Vec<Element<'a, Message, Renderer>>,
+) -> widget::Row<'a, Message, Renderer> {
+ widget::Row::with_children(children)
+}
+
+/// Creates a new [`Scrollable`] with the provided content.
+///
+/// [`Scrollable`]: widget::Scrollable
+pub fn scrollable<'a, Message, Renderer>(
+ content: impl Into<Element<'a, Message, Renderer>>,
+) -> widget::Scrollable<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::scrollable::StyleSheet,
+{
+ widget::Scrollable::new(content)
+}
+
+/// Creates a new [`Button`] with the provided content.
+///
+/// [`Button`]: widget::Button
+pub fn button<'a, Message, Renderer>(
+ content: impl Into<Element<'a, Message, Renderer>>,
+) -> widget::Button<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::button::StyleSheet,
+{
+ widget::Button::new(content)
+}
+
+/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`].
+///
+/// [`Tooltip`]: widget::Tooltip
+/// [`tooltip::Position`]: widget::tooltip::Position
+pub fn tooltip<'a, Message, Renderer>(
+ content: impl Into<Element<'a, Message, Renderer>>,
+ tooltip: impl ToString,
+ position: widget::tooltip::Position,
+) -> widget::Tooltip<'a, Message, Renderer>
+where
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet,
+{
+ widget::Tooltip::new(content, tooltip, position)
+}
+
+/// Creates a new [`Text`] widget with the provided content.
+///
+/// [`Text`]: widget::Text
+pub fn text<Renderer>(text: impl Into<String>) -> widget::Text<Renderer>
+where
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::text::StyleSheet,
+{
+ widget::Text::new(text)
+}
+
+/// Creates a new [`Checkbox`].
+///
+/// [`Checkbox`]: widget::Checkbox
+pub fn checkbox<'a, Message, Renderer>(
+ label: impl Into<String>,
+ is_checked: bool,
+ f: impl Fn(bool) -> Message + 'a,
+) -> widget::Checkbox<'a, Message, Renderer>
+where
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet,
+{
+ widget::Checkbox::new(is_checked, label, f)
+}
+
+/// Creates a new [`Radio`].
+///
+/// [`Radio`]: widget::Radio
+pub fn radio<Message, Renderer, V>(
+ label: impl Into<String>,
+ value: V,
+ selected: Option<V>,
+ on_click: impl FnOnce(V) -> Message,
+) -> widget::Radio<Message, Renderer>
+where
+ Message: Clone,
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::radio::StyleSheet,
+ V: Copy + Eq,
+{
+ widget::Radio::new(value, label, selected, on_click)
+}
+
+/// Creates a new [`Toggler`].
+///
+/// [`Toggler`]: widget::Toggler
+pub fn toggler<'a, Message, Renderer>(
+ label: impl Into<Option<String>>,
+ is_checked: bool,
+ f: impl Fn(bool) -> Message + 'a,
+) -> widget::Toggler<'a, Message, Renderer>
+where
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::toggler::StyleSheet,
+{
+ widget::Toggler::new(is_checked, label, f)
+}
+
+/// Creates a new [`TextInput`].
+///
+/// [`TextInput`]: widget::TextInput
+pub fn text_input<'a, Message, Renderer>(
+ placeholder: &str,
+ value: &str,
+ on_change: impl Fn(String) -> Message + 'a,
+) -> widget::TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::text_input::StyleSheet,
+{
+ widget::TextInput::new(placeholder, value, on_change)
+}
+
+/// Creates a new [`Slider`].
+///
+/// [`Slider`]: widget::Slider
+pub fn slider<'a, T, Message, Renderer>(
+ range: std::ops::RangeInclusive<T>,
+ value: T,
+ on_change: impl Fn(T) -> Message + 'a,
+) -> widget::Slider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::slider::StyleSheet,
+{
+ widget::Slider::new(range, value, on_change)
+}
+
+/// Creates a new [`PickList`].
+///
+/// [`PickList`]: widget::PickList
+pub fn pick_list<'a, Message, Renderer, T>(
+ options: impl Into<Cow<'a, [T]>>,
+ selected: Option<T>,
+ on_selected: impl Fn(T) -> Message + 'a,
+) -> widget::PickList<'a, T, Message, Renderer>
+where
+ T: ToString + Eq + 'static,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Renderer: crate::text::Renderer,
+ Renderer::Theme: widget::pick_list::StyleSheet,
+{
+ widget::PickList::new(options, selected, on_selected)
+}
+
+/// Creates a new [`Image`].
+///
+/// [`Image`]: widget::Image
+pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> {
+ widget::Image::new(handle.into())
+}
+
+/// Creates a new horizontal [`Space`] with the given [`Length`].
+///
+/// [`Space`]: widget::Space
+pub fn horizontal_space(width: Length) -> widget::Space {
+ widget::Space::with_width(width)
+}
+
+/// Creates a new vertical [`Space`] with the given [`Length`].
+///
+/// [`Space`]: widget::Space
+pub fn vertical_space(height: Length) -> widget::Space {
+ widget::Space::with_height(height)
+}
+
+/// Creates a horizontal [`Rule`] with the given height.
+///
+/// [`Rule`]: widget::Rule
+pub fn horizontal_rule<Renderer>(height: u16) -> widget::Rule<Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::rule::StyleSheet,
+{
+ widget::Rule::horizontal(height)
+}
+
+/// Creates a vertical [`Rule`] with the given width.
+///
+/// [`Rule`]: widget::Rule
+pub fn vertical_rule<Renderer>(width: u16) -> widget::Rule<Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::rule::StyleSheet,
+{
+ widget::Rule::vertical(width)
+}
+
+/// Creates a new [`ProgressBar`].
+///
+/// It expects:
+/// * an inclusive range of possible values, and
+/// * the current value of the [`ProgressBar`].
+///
+/// [`ProgressBar`]: widget::ProgressBar
+pub fn progress_bar<Renderer>(
+ range: RangeInclusive<f32>,
+ value: f32,
+) -> widget::ProgressBar<Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: widget::progress_bar::StyleSheet,
+{
+ widget::ProgressBar::new(range, value)
+}
+
+/// Creates a new [`Svg`] widget from the given [`Handle`].
+///
+/// [`Svg`]: widget::Svg
+/// [`Handle`]: widget::svg::Handle
+pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg {
+ widget::Svg::new(handle)
+}
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 72075bd1..91d68e34 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -5,12 +5,18 @@ pub use viewer::Viewer;
use crate::image;
use crate::layout;
use crate::renderer;
+use crate::widget::Tree;
use crate::{
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use std::hash::Hash;
+/// Creates a new [`Viewer`] with the given image `Handle`.
+pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
+ Viewer::new(handle)
+}
+
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
@@ -135,6 +141,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index 1aa75aa0..b1fe596c 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -4,6 +4,7 @@ use crate::image;
use crate::layout;
use crate::mouse;
use crate::renderer;
+use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
@@ -13,8 +14,7 @@ use std::hash::Hash;
/// A frame that displays an image with the ability to zoom in/out and pan.
#[allow(missing_debug_implementations)]
-pub struct Viewer<'a, Handle> {
- state: &'a mut State,
+pub struct Viewer<Handle> {
padding: u16,
width: Length,
height: Length,
@@ -24,11 +24,10 @@ pub struct Viewer<'a, Handle> {
handle: Handle,
}
-impl<'a, Handle> Viewer<'a, Handle> {
+impl<Handle> Viewer<Handle> {
/// Creates a new [`Viewer`] with the given [`State`].
- pub fn new(state: &'a mut State, handle: Handle) -> Self {
+ pub fn new(handle: Handle) -> Self {
Viewer {
- state,
padding: 0,
width: Length::Shrink,
height: Length::Shrink,
@@ -81,43 +80,21 @@ impl<'a, Handle> Viewer<'a, Handle> {
self.scale_step = scale_step;
self
}
-
- /// Returns the bounds of the underlying image, given the bounds of
- /// the [`Viewer`]. Scaling will be applied and original aspect ratio
- /// will be respected.
- fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
- where
- Renderer: image::Renderer<Handle = Handle>,
- {
- let (width, height) = renderer.dimensions(&self.handle);
-
- let (width, height) = {
- let dimensions = (width as f32, height as f32);
-
- let width_ratio = bounds.width / dimensions.0;
- let height_ratio = bounds.height / dimensions.1;
-
- let ratio = width_ratio.min(height_ratio);
-
- let scale = self.state.scale;
-
- if ratio < 1.0 {
- (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
- } else {
- (dimensions.0 * scale, dimensions.1 * scale)
- }
- };
-
- Size::new(width, height)
- }
}
-impl<'a, Message, Renderer, Handle> Widget<Message, Renderer>
- for Viewer<'a, Handle>
+impl<Message, Renderer, Handle> Widget<Message, Renderer> for Viewer<Handle>
where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
fn width(&self) -> Length {
self.width
}
@@ -164,6 +141,7 @@ where
fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -181,39 +159,43 @@ where
match delta {
mouse::ScrollDelta::Lines { y, .. }
| mouse::ScrollDelta::Pixels { y, .. } => {
- let previous_scale = self.state.scale;
+ let state = tree.state.downcast_mut::<State>();
+ let previous_scale = state.scale;
if y < 0.0 && previous_scale > self.min_scale
|| y > 0.0 && previous_scale < self.max_scale
{
- self.state.scale = (if y > 0.0 {
- self.state.scale * (1.0 + self.scale_step)
+ state.scale = (if y > 0.0 {
+ state.scale * (1.0 + self.scale_step)
} else {
- self.state.scale / (1.0 + self.scale_step)
+ state.scale / (1.0 + self.scale_step)
})
.max(self.min_scale)
.min(self.max_scale);
- let image_size =
- self.image_size(renderer, bounds.size());
+ let image_size = image_size(
+ renderer,
+ &self.handle,
+ state,
+ bounds.size(),
+ );
- let factor =
- self.state.scale / previous_scale - 1.0;
+ let factor = state.scale / previous_scale - 1.0;
let cursor_to_center =
cursor_position - bounds.center();
let adjustment = cursor_to_center * factor
- + self.state.current_offset * factor;
+ + state.current_offset * factor;
- self.state.current_offset = Vector::new(
+ state.current_offset = Vector::new(
if image_size.width > bounds.width {
- self.state.current_offset.x + adjustment.x
+ state.current_offset.x + adjustment.x
} else {
0.0
},
if image_size.height > bounds.height {
- self.state.current_offset.y + adjustment.y
+ state.current_offset.y + adjustment.y
} else {
0.0
},
@@ -227,21 +209,34 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
if is_mouse_over =>
{
- self.state.cursor_grabbed_at = Some(cursor_position);
- self.state.starting_offset = self.state.current_offset;
+ let state = tree.state.downcast_mut::<State>();
+
+ state.cursor_grabbed_at = Some(cursor_position);
+ state.starting_offset = state.current_offset;
event::Status::Captured
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- if self.state.cursor_grabbed_at.is_some() =>
- {
- self.state.cursor_grabbed_at = None;
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ let state = tree.state.downcast_mut::<State>();
- event::Status::Captured
+ if state.cursor_grabbed_at.is_some() {
+ state.cursor_grabbed_at = None;
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
}
Event::Mouse(mouse::Event::CursorMoved { position }) => {
- if let Some(origin) = self.state.cursor_grabbed_at {
- let image_size = self.image_size(renderer, bounds.size());
+ let state = tree.state.downcast_mut::<State>();
+
+ if let Some(origin) = state.cursor_grabbed_at {
+ let image_size = image_size(
+ renderer,
+ &self.handle,
+ state,
+ bounds.size(),
+ );
let hidden_width = (image_size.width - bounds.width / 2.0)
.max(0.0)
@@ -255,7 +250,7 @@ where
let delta = position - origin;
let x = if bounds.width < image_size.width {
- (self.state.starting_offset.x - delta.x)
+ (state.starting_offset.x - delta.x)
.min(hidden_width)
.max(-hidden_width)
} else {
@@ -263,14 +258,14 @@ where
};
let y = if bounds.height < image_size.height {
- (self.state.starting_offset.y - delta.y)
+ (state.starting_offset.y - delta.y)
.min(hidden_height)
.max(-hidden_height)
} else {
0.0
};
- self.state.current_offset = Vector::new(x, y);
+ state.current_offset = Vector::new(x, y);
event::Status::Captured
} else {
@@ -283,15 +278,17 @@ where
fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
+ let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
- if self.state.is_cursor_grabbed() {
+ if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
@@ -302,6 +299,7 @@ where
fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
@@ -309,9 +307,11 @@ where
_cursor_position: Point,
_viewport: &Rectangle,
) {
+ let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
- let image_size = self.image_size(renderer, bounds.size());
+ let image_size =
+ image_size(renderer, &self.handle, state, bounds.size());
let translation = {
let image_top_left = Vector::new(
@@ -319,7 +319,7 @@ where
bounds.height / 2.0 - image_size.height / 2.0,
);
- image_top_left - self.state.offset(bounds, image_size)
+ image_top_left - state.offset(bounds, image_size)
};
renderer.with_layer(bounds, |renderer| {
@@ -385,14 +385,47 @@ impl State {
}
}
-impl<'a, Message, Renderer, Handle> From<Viewer<'a, Handle>>
+impl<'a, Message, Renderer, Handle> From<Viewer<Handle>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + image::Renderer<Handle = Handle>,
Message: 'a,
Handle: Clone + Hash + 'a,
{
- fn from(viewer: Viewer<'a, Handle>) -> Element<'a, Message, Renderer> {
+ fn from(viewer: Viewer<Handle>) -> Element<'a, Message, Renderer> {
Element::new(viewer)
}
}
+
+/// Returns the bounds of the underlying image, given the bounds of
+/// the [`Viewer`]. Scaling will be applied and original aspect ratio
+/// will be respected.
+pub fn image_size<Renderer>(
+ renderer: &Renderer,
+ handle: &<Renderer as image::Renderer>::Handle,
+ state: &State,
+ bounds: Size,
+) -> Size
+where
+ Renderer: image::Renderer,
+{
+ let (width, height) = renderer.dimensions(handle);
+
+ let (width, height) = {
+ let dimensions = (width as f32, height as f32);
+
+ let width_ratio = bounds.width / dimensions.0;
+ let height_ratio = bounds.height / dimensions.1;
+
+ let ratio = width_ratio.min(height_ratio);
+ let scale = state.scale;
+
+ if ratio < 1.0 {
+ (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
+ } else {
+ (dimensions.0 * scale, dimensions.1 * scale)
+ }
+ };
+
+ Size::new(width, height)
+}
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 70ca772c..d84fb7a0 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -30,6 +30,8 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
+pub use iced_style::pane_grid::{Line, StyleSheet};
+
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
@@ -37,13 +39,12 @@ use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::container;
+use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size,
Vector, Widget,
};
-pub use iced_style::pane_grid::{Line, StyleSheet};
-
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@@ -66,7 +67,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
/// ## Example
///
/// ```
-/// # use iced_native::widget::{pane_grid, Text};
+/// # use iced_native::widget::{pane_grid, text};
/// #
/// # type PaneGrid<'a, Message> =
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
@@ -84,10 +85,10 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&mut state, |pane, state| {
+/// PaneGrid::new(&state, |pane, state| {
/// pane_grid::Content::new(match state {
-/// PaneState::SomePane => Text::new("This is some pane"),
-/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
+/// PaneState::SomePane => text("This is some pane"),
+/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
/// })
/// })
/// .on_drag(Message::PaneDragged)
@@ -99,8 +100,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
- state: &'a mut state::Internal,
- action: &'a mut state::Action,
+ state: &'a state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
height: Length,
@@ -121,21 +121,20 @@ where
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
pub fn new<T>(
- state: &'a mut State<T>,
- view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
+ state: &'a State<T>,
+ view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
) -> Self {
let elements = {
state
.panes
- .iter_mut()
+ .iter()
.map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
.collect()
};
Self {
- state: &mut state.internal,
- action: &mut state.action,
elements,
+ state: &state.internal,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
@@ -211,6 +210,220 @@ where
}
}
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for PaneGrid<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet + container::StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<state::Action>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(state::Action::Idle)
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ self.elements
+ .iter()
+ .map(|(_, content)| content.state())
+ .collect()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children_custom(
+ &self.elements,
+ |state, (_, content)| content.diff(state),
+ |(_, content)| content.state(),
+ )
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ self.state,
+ self.width,
+ self.height,
+ self.spacing,
+ self.elements.iter().map(|(pane, content)| (*pane, content)),
+ |element, renderer, limits| element.layout(renderer, limits),
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ let action = tree.state.downcast_mut::<state::Action>();
+
+ let event_status = update(
+ action,
+ self.state,
+ &event,
+ layout,
+ cursor_position,
+ shell,
+ self.spacing,
+ self.elements.iter().map(|(pane, content)| (*pane, content)),
+ &self.on_click,
+ &self.on_drag,
+ &self.on_resize,
+ );
+
+ let picked_pane = action.picked_pane().map(|(pane, _)| pane);
+
+ self.elements
+ .iter_mut()
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .map(|(((pane, content), tree), layout)| {
+ let is_picked = picked_pane == Some(*pane);
+
+ content.on_event(
+ tree,
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ is_picked,
+ )
+ })
+ .fold(event_status, event::Status::merge)
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(
+ tree.state.downcast_ref(),
+ self.state,
+ layout,
+ cursor_position,
+ self.spacing,
+ self.on_resize.as_ref().map(|(leeway, _)| *leeway),
+ )
+ .unwrap_or_else(|| {
+ self.elements
+ .iter()
+ .zip(&tree.children)
+ .zip(layout.children())
+ .map(|(((_pane, content), tree), layout)| {
+ content.mouse_interaction(
+ tree,
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ })
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ draw(
+ tree.state.downcast_ref(),
+ self.state,
+ layout,
+ cursor_position,
+ renderer,
+ theme,
+ style,
+ viewport,
+ self.spacing,
+ self.on_resize.as_ref().map(|(leeway, _)| *leeway),
+ self.style,
+ self.elements
+ .iter()
+ .zip(&tree.children)
+ .map(|((pane, content), tree)| (*pane, (content, tree))),
+ |(content, tree),
+ renderer,
+ style,
+ layout,
+ cursor_position,
+ rectangle| {
+ content.draw(
+ tree,
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ rectangle,
+ );
+ },
+ )
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.elements
+ .iter()
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .filter_map(|(((_, pane), tree), layout)| {
+ pane.overlay(tree, layout, renderer)
+ })
+ .next()
+ }
+}
+
+impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet + container::StyleSheet,
+{
+ fn from(
+ pane_grid: PaneGrid<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(pane_grid)
+ }
+}
+
/// Calculates the [`Layout`] of a [`PaneGrid`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
@@ -656,175 +869,6 @@ pub struct ResizeEvent {
pub ratio: f32,
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for PaneGrid<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: StyleSheet + container::StyleSheet,
-{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- self.height
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- layout(
- renderer,
- limits,
- self.state,
- self.width,
- self.height,
- self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
- |element, renderer, limits| element.layout(renderer, limits),
- )
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- let event_status = update(
- self.action,
- self.state,
- &event,
- layout,
- cursor_position,
- shell,
- self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
- &self.on_click,
- &self.on_drag,
- &self.on_resize,
- );
-
- let picked_pane = self.action.picked_pane().map(|(pane, _)| pane);
-
- self.elements
- .iter_mut()
- .zip(layout.children())
- .map(|((pane, content), layout)| {
- let is_picked = picked_pane == Some(*pane);
-
- content.on_event(
- event.clone(),
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- is_picked,
- )
- })
- .fold(event_status, event::Status::merge)
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(
- self.action,
- self.state,
- layout,
- cursor_position,
- self.spacing,
- self.on_resize.as_ref().map(|(leeway, _)| *leeway),
- )
- .unwrap_or_else(|| {
- self.elements
- .iter()
- .zip(layout.children())
- .map(|((_pane, content), layout)| {
- content.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
- })
- .max()
- .unwrap_or_default()
- })
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) {
- draw(
- self.action,
- self.state,
- layout,
- cursor_position,
- renderer,
- theme,
- style,
- viewport,
- self.spacing,
- self.on_resize.as_ref().map(|(leeway, _)| *leeway),
- self.style,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
- |pane, renderer, style, layout, cursor_position, rectangle| {
- pane.draw(
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- rectangle,
- );
- },
- )
- }
-
- fn overlay(
- &mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.elements
- .iter_mut()
- .zip(layout.children())
- .filter_map(|((_, pane), layout)| pane.overlay(layout, renderer))
- .next()
- }
-}
-
-impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a + crate::Renderer,
- Renderer::Theme: StyleSheet + container::StyleSheet,
-{
- fn from(
- pane_grid: PaneGrid<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(pane_grid)
- }
-}
-
/*
* Helpers
*/
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index 4c9e65c9..98ce2c4b 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -5,6 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::pane_grid::{Draggable, TitleBar};
+use crate::widget::Tree;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// The content of a [`Pane`].
@@ -59,11 +60,37 @@ where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
+ pub(super) fn state(&self) -> Tree {
+ let children = if let Some(title_bar) = self.title_bar.as_ref() {
+ vec![Tree::new(&self.body), title_bar.state()]
+ } else {
+ vec![Tree::new(&self.body), Tree::empty()]
+ };
+
+ Tree {
+ children,
+ ..Tree::empty()
+ }
+ }
+
+ pub(super) fn diff(&self, tree: &mut Tree) {
+ if tree.children.len() == 2 {
+ if let Some(title_bar) = self.title_bar.as_ref() {
+ title_bar.diff(&mut tree.children[1]);
+ }
+
+ tree.children[0].diff(&self.body);
+ } else {
+ *tree = self.state();
+ }
+ }
+
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: crate::Renderer
+ /// [`Renderer`]: iced_native::Renderer
pub fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -89,6 +116,7 @@ where
let show_controls = bounds.contains(cursor_position);
title_bar.draw(
+ &tree.children[1],
renderer,
theme,
style,
@@ -98,7 +126,8 @@ where
show_controls,
);
- self.body.draw(
+ self.body.as_widget().draw(
+ &tree.children[0],
renderer,
theme,
style,
@@ -107,7 +136,8 @@ where
viewport,
);
} else {
- self.body.draw(
+ self.body.as_widget().draw(
+ &tree.children[0],
renderer,
theme,
style,
@@ -131,7 +161,7 @@ where
let title_bar_size = title_bar_layout.size();
- let mut body_layout = self.body.layout(
+ let mut body_layout = self.body.as_widget().layout(
renderer,
&layout::Limits::new(
Size::ZERO,
@@ -149,12 +179,13 @@ where
vec![title_bar_layout, body_layout],
)
} else {
- self.body.layout(renderer, limits)
+ self.body.as_widget().layout(renderer, limits)
}
}
pub(crate) fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -169,6 +200,7 @@ where
let mut children = layout.children();
event_status = title_bar.on_event(
+ &mut tree.children[1],
event.clone(),
children.next().unwrap(),
cursor_position,
@@ -185,7 +217,8 @@ where
let body_status = if is_picked {
event::Status::Ignored
} else {
- self.body.on_event(
+ self.body.as_widget_mut().on_event(
+ &mut tree.children[0],
event,
body_layout,
cursor_position,
@@ -200,6 +233,7 @@ where
pub(crate) fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@@ -218,6 +252,7 @@ where
}
let mouse_interaction = title_bar.mouse_interaction(
+ &tree.children[1],
title_bar_layout,
cursor_position,
viewport,
@@ -230,25 +265,46 @@ where
};
self.body
- .mouse_interaction(body_layout, cursor_position, viewport, renderer)
+ .as_widget()
+ .mouse_interaction(
+ &tree.children[0],
+ body_layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
.max(title_bar_interaction)
}
- pub(crate) fn overlay(
- &mut self,
+ pub(crate) fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- if let Some(title_bar) = self.title_bar.as_mut() {
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ if let Some(title_bar) = self.title_bar.as_ref() {
let mut children = layout.children();
let title_bar_layout = children.next()?;
- match title_bar.overlay(title_bar_layout, renderer) {
+ let mut states = tree.children.iter_mut();
+ let body_state = states.next().unwrap();
+ let title_bar_state = states.next().unwrap();
+
+ match title_bar.overlay(title_bar_state, title_bar_layout, renderer)
+ {
Some(overlay) => Some(overlay),
- None => self.body.overlay(children.next()?, renderer),
+ None => self.body.as_widget().overlay(
+ body_state,
+ children.next()?,
+ renderer,
+ ),
}
} else {
- self.body.overlay(layout, renderer)
+ self.body.as_widget().overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
}
}
}
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index 4e90f645..cdca6267 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -31,8 +31,6 @@ pub struct State<T> {
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub internal: Internal,
-
- pub(super) action: Action,
}
impl<T> State<T> {
@@ -54,11 +52,7 @@ impl<T> State<T> {
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
- State {
- panes,
- internal,
- action: Action::Idle,
- }
+ State { panes, internal }
}
/// Returns the total amount of panes in the [`State`].
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 14c3ab4e..f9050e3e 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -4,6 +4,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::container;
+use crate::widget::Tree;
use crate::{
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
};
@@ -86,11 +87,37 @@ where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
+ pub(super) fn state(&self) -> Tree {
+ let children = if let Some(controls) = self.controls.as_ref() {
+ vec![Tree::new(&self.content), Tree::new(controls)]
+ } else {
+ vec![Tree::new(&self.content), Tree::empty()]
+ };
+
+ Tree {
+ children,
+ ..Tree::empty()
+ }
+ }
+
+ pub(super) fn diff(&self, tree: &mut Tree) {
+ if tree.children.len() == 2 {
+ if let Some(controls) = self.controls.as_ref() {
+ tree.children[1].diff(controls);
+ }
+
+ tree.children[0].diff(&self.content);
+ } else {
+ *tree = self.state();
+ }
+ }
+
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: crate::Renderer
+ /// [`Renderer`]: iced_native::Renderer
pub fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
@@ -118,14 +145,15 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
+ if title_layout.bounds().width + controls_layout.bounds().width
+ > padded.bounds().width
+ {
+ show_title = false;
+ }
if show_controls || self.always_show_controls {
- if title_layout.bounds().width + controls_layout.bounds().width
- > padded.bounds().width
- {
- show_title = false;
- }
- controls.draw(
+ controls.as_widget().draw(
+ &tree.children[1],
renderer,
theme,
&inherited_style,
@@ -137,7 +165,8 @@ where
}
if show_title {
- self.content.draw(
+ self.content.as_widget().draw(
+ &tree.children[0],
renderer,
theme,
&inherited_style,
@@ -186,11 +215,14 @@ where
let title_layout = self
.content
+ .as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
+
let title_size = title_layout.size();
let mut node = if let Some(controls) = &self.controls {
let mut controls_layout = controls
+ .as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
let controls_size = controls_layout.size();
@@ -221,6 +253,7 @@ where
pub(crate) fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -243,7 +276,8 @@ where
show_title = false;
}
- controls.on_event(
+ controls.as_widget_mut().on_event(
+ &mut tree.children[1],
event.clone(),
controls_layout,
cursor_position,
@@ -256,7 +290,8 @@ where
};
let title_status = if show_title {
- self.content.on_event(
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
event,
title_layout,
cursor_position,
@@ -273,6 +308,7 @@ where
pub(crate) fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@@ -284,7 +320,8 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
- let title_interaction = self.content.mouse_interaction(
+ let title_interaction = self.content.as_widget().mouse_interaction(
+ &tree.children[0],
title_layout,
cursor_position,
viewport,
@@ -293,7 +330,8 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
- let controls_interaction = controls.mouse_interaction(
+ let controls_interaction = controls.as_widget().mouse_interaction(
+ &tree.children[1],
controls_layout,
cursor_position,
viewport,
@@ -312,11 +350,12 @@ where
}
}
- pub(crate) fn overlay(
- &mut self,
+ pub(crate) fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
let mut children = layout.children();
let padded = children.next()?;
@@ -327,12 +366,23 @@ where
content, controls, ..
} = self;
- content.overlay(title_layout, renderer).or_else(move || {
- controls.as_mut().and_then(|controls| {
- let controls_layout = children.next()?;
-
- controls.overlay(controls_layout, renderer)
+ let mut states = tree.children.iter_mut();
+ let title_state = states.next().unwrap();
+ let controls_state = states.next().unwrap();
+
+ content
+ .as_widget()
+ .overlay(title_state, title_layout, renderer)
+ .or_else(move || {
+ controls.as_ref().and_then(|controls| {
+ let controls_layout = children.next()?;
+
+ controls.as_widget().overlay(
+ controls_state,
+ controls_layout,
+ renderer,
+ )
+ })
})
- })
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index 61b0eb96..1232878b 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -9,8 +9,7 @@ use crate::overlay::menu::{self, Menu};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
-use crate::widget::container;
-use crate::widget::scrollable;
+use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size,
Widget,
@@ -27,8 +26,7 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
- state: &'a mut State<T>,
- on_selected: Box<dyn Fn(T) -> Message>,
+ on_selected: Box<dyn Fn(T) -> Message + 'a>,
options: Cow<'a, [T]>,
placeholder: Option<String>,
selected: Option<T>,
@@ -39,35 +37,6 @@ where
style: <Renderer::Theme as StyleSheet>::Style,
}
-/// The local state of a [`PickList`].
-#[derive(Debug, Clone)]
-pub struct State<T> {
- menu: menu::State,
- keyboard_modifiers: keyboard::Modifiers,
- is_open: bool,
- hovered_option: Option<usize>,
- last_selection: Option<T>,
-}
-
-impl<T> State<T> {
- /// Creates a new [`State`] for a [`PickList`].
- pub fn new() -> Self {
- Self {
- menu: menu::State::default(),
- keyboard_modifiers: keyboard::Modifiers::default(),
- is_open: bool::default(),
- hovered_option: Option::default(),
- last_selection: Option::default(),
- }
- }
-}
-
-impl<T> Default for State<T> {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
@@ -78,17 +47,14 @@ where
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5);
- /// Creates a new [`PickList`] with the given [`State`], a list of options,
- /// the current selected value, and the message to produce when an option is
- /// selected.
+ /// Creates a new [`PickList`] with the given list of options, the current
+ /// selected value, and the message to produce when an option is selected.
pub fn new(
- state: &'a mut State<T>,
options: impl Into<Cow<'a, [T]>>,
selected: Option<T>,
- on_selected: impl Fn(T) -> Message + 'static,
+ on_selected: impl Fn(T) -> Message + 'a,
) -> Self {
Self {
- state,
on_selected: Box::new(on_selected),
options: options.into(),
placeholder: None,
@@ -141,6 +107,168 @@ where
}
}
+impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
+ for PickList<'a, T, Message, Renderer>
+where
+ T: Clone + ToString + Eq + 'static,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Message: 'a,
+ Renderer: text::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State<T>>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::<T>::new())
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.padding,
+ self.text_size,
+ &self.font,
+ self.placeholder.as_deref(),
+ &self.options,
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ self.on_selected.as_ref(),
+ self.selected.as_ref(),
+ &self.options,
+ || tree.state.downcast_mut::<State<T>>(),
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ _tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(layout, cursor_position)
+ }
+
+ fn draw(
+ &self,
+ _tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ draw(
+ renderer,
+ theme,
+ layout,
+ cursor_position,
+ self.padding,
+ self.text_size,
+ &self.font,
+ self.placeholder.as_deref(),
+ self.selected.as_ref(),
+ self.style,
+ )
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ let state = tree.state.downcast_mut::<State<T>>();
+
+ overlay(
+ layout,
+ state,
+ self.padding,
+ self.text_size,
+ self.font.clone(),
+ &self.options,
+ self.style,
+ )
+ }
+}
+
+impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ T: Clone + ToString + Eq + 'static,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Message: 'a,
+ Renderer: text::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
+ Self::new(pick_list)
+ }
+}
+
+/// The local state of a [`PickList`].
+#[derive(Debug)]
+pub struct State<T> {
+ menu: menu::State,
+ keyboard_modifiers: keyboard::Modifiers,
+ is_open: bool,
+ hovered_option: Option<usize>,
+ last_selection: Option<T>,
+}
+
+impl<T> State<T> {
+ /// Creates a new [`State`] for a [`PickList`].
+ pub fn new() -> Self {
+ Self {
+ menu: menu::State::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
+ is_open: bool::default(),
+ hovered_option: Option::default(),
+ last_selection: Option::default(),
+ }
+ }
+}
+
+impl<T> Default for State<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
/// Computes the layout of a [`PickList`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
@@ -429,127 +557,3 @@ pub fn draw<T, Renderer>(
});
}
}
-
-impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
- for PickList<'a, T, Message, Renderer>
-where
- T: Clone + ToString + Eq,
- [T]: ToOwned<Owned = Vec<T>>,
- Message: 'static,
- Renderer: text::Renderer + 'a,
- Renderer::Theme: StyleSheet,
-{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- Length::Shrink
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- layout(
- renderer,
- limits,
- self.width,
- self.padding,
- self.text_size,
- &self.font,
- self.placeholder.as_deref(),
- &self.options,
- )
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- _renderer: &Renderer,
- _clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- update(
- event,
- layout,
- cursor_position,
- shell,
- self.on_selected.as_ref(),
- self.selected.as_ref(),
- &self.options,
- || &mut self.state,
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- _renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- _style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- draw(
- renderer,
- theme,
- layout,
- cursor_position,
- self.padding,
- self.text_size,
- &self.font,
- self.placeholder.as_deref(),
- self.selected.as_ref(),
- self.style,
- )
- }
-
- fn overlay(
- &mut self,
- layout: Layout<'_>,
- _renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- overlay(
- layout,
- self.state,
- self.padding,
- self.text_size,
- self.font.clone(),
- &self.options,
- self.style,
- )
- }
-}
-
-impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- T: Clone + ToString + Eq,
- [T]: ToOwned<Owned = Vec<T>>,
- Message: 'static,
- Renderer: text::Renderer + 'a,
- Renderer::Theme: StyleSheet
- + container::StyleSheet
- + scrollable::StyleSheet
- + menu::StyleSheet,
- <Renderer::Theme as StyleSheet>::Style:
- Into<<Renderer::Theme as menu::StyleSheet>::Style>,
-{
- fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
- Element::new(pick_list)
- }
-}
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index 50bdcda6..8a945433 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -1,6 +1,7 @@
//! Provide progress feedback to your users.
use crate::layout;
use crate::renderer;
+use crate::widget::Tree;
use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
use std::ops::RangeInclusive;
@@ -105,6 +106,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 647e3c5a..c9152d05 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -6,7 +6,7 @@ use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
-use crate::widget::{self, Row, Text};
+use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
Shell, Widget,
@@ -176,6 +176,7 @@ where
fn on_event(
&mut self,
+ _state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -200,6 +201,7 @@ where
fn mouse_interaction(
&self,
+ _state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@@ -214,6 +216,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 9d8cc715..c342c277 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,16 +1,15 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
-use crate::layout;
+use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
+use crate::widget::Tree;
use crate::{
- Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
- Shell, Widget,
+ Alignment, Clipboard, Element, Length, Padding, Point, Rectangle, Shell,
+ Widget,
};
-use std::u32;
-
/// A container that distributes its contents horizontally.
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
@@ -18,8 +17,6 @@ pub struct Row<'a, Message, Renderer> {
padding: Padding,
width: Length,
height: Length,
- max_width: u32,
- max_height: u32,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
@@ -39,8 +36,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
- max_width: u32::MAX,
- max_height: u32::MAX,
align_items: Alignment::Start,
children,
}
@@ -74,18 +69,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
- /// Sets the maximum width of the [`Row`].
- pub fn max_width(mut self, max_width: u32) -> Self {
- self.max_width = max_width;
- self
- }
-
- /// Sets the maximum height of the [`Row`].
- pub fn max_height(mut self, max_height: u32) -> Self {
- self.max_height = max_height;
- self
- }
-
/// Sets the vertical alignment of the contents of the [`Row`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
@@ -93,10 +76,10 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
}
/// Adds an [`Element`] to the [`Row`].
- pub fn push<E>(mut self, child: E) -> Self
- where
- E: Into<Element<'a, Message, Renderer>>,
- {
+ pub fn push(
+ mut self,
+ child: impl Into<Element<'a, Message, Renderer>>,
+ ) -> Self {
self.children.push(child.into());
self
}
@@ -113,6 +96,14 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
where
Renderer: crate::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 width(&self) -> Length {
self.width
}
@@ -126,11 +117,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits
- .max_width(self.max_width)
- .max_height(self.max_height)
- .width(self.width)
- .height(self.height);
+ let limits = limits.width(self.width).height(self.height);
layout::flex::resolve(
layout::flex::Axis::Horizontal,
@@ -145,6 +132,7 @@ where
fn on_event(
&mut self,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -154,9 +142,11 @@ where
) -> event::Status {
self.children
.iter_mut()
+ .zip(&mut tree.children)
.zip(layout.children())
- .map(|(child, layout)| {
- child.widget.on_event(
+ .map(|((child, state), layout)| {
+ child.as_widget_mut().on_event(
+ state,
event.clone(),
layout,
cursor_position,
@@ -170,6 +160,7 @@ where
fn mouse_interaction(
&self,
+ tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@@ -177,9 +168,11 @@ where
) -> mouse::Interaction {
self.children
.iter()
+ .zip(&tree.children)
.zip(layout.children())
- .map(|(child, layout)| {
- child.widget.mouse_interaction(
+ .map(|((child, state), layout)| {
+ child.as_widget().mouse_interaction(
+ state,
layout,
cursor_position,
viewport,
@@ -192,6 +185,7 @@ where
fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -199,8 +193,14 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
- for (child, layout) in self.children.iter().zip(layout.children()) {
- child.draw(
+ for ((child, state), layout) in self
+ .children
+ .iter()
+ .zip(&tree.children)
+ .zip(layout.children())
+ {
+ child.as_widget().draw(
+ state,
renderer,
theme,
style,
@@ -211,28 +211,23 @@ where
}
}
- fn overlay(
- &mut self,
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.children
- .iter_mut()
- .zip(layout.children())
- .filter_map(|(child, layout)| {
- child.widget.overlay(layout, renderer)
- })
- .next()
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ overlay::from_children(&self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + crate::Renderer,
Message: 'a,
+ Renderer: crate::Renderer + 'a,
{
- fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
- Element::new(row)
+ fn from(row: Row<'a, Message, Renderer>) -> Self {
+ Self::new(row)
}
}
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index f0fda8a9..56f8c80d 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -1,6 +1,7 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::layout;
use crate::renderer;
+use crate::widget::Tree;
use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
pub use iced_style::rule::{Appearance, FillMode, StyleSheet};
@@ -78,6 +79,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 77ed2066..b40c3743 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -5,10 +5,10 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
-use crate::widget::Column;
+use crate::widget::tree::{self, Tree};
use crate::{
- Alignment, Background, Clipboard, Color, Element, Layout, Length, Padding,
- Point, Rectangle, Shell, Size, Vector, Widget,
+ Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
+ Shell, Size, Vector, Widget,
};
use std::{f32, u32};
@@ -30,13 +30,11 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
- state: &'a mut State,
height: Length,
- max_height: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
- content: Column<'a, Message, Renderer>,
+ content: Element<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -46,67 +44,25 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
- /// Creates a new [`Scrollable`] with the given [`State`].
- pub fn new(state: &'a mut State) -> Self {
+ /// Creates a new [`Scrollable`].
+ pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Scrollable {
- state,
height: Length::Shrink,
- max_height: u32::MAX,
scrollbar_width: 10,
scrollbar_margin: 0,
scroller_width: 10,
- content: Column::new(),
+ content: content.into(),
on_scroll: None,
style: Default::default(),
}
}
- /// Sets the vertical spacing _between_ elements.
- ///
- /// Custom margins per element do not exist in Iced. You should use this
- /// method instead! While less flexible, it helps you keep spacing between
- /// elements consistent.
- pub fn spacing(mut self, units: u16) -> Self {
- self.content = self.content.spacing(units);
- self
- }
-
- /// Sets the [`Padding`] of the [`Scrollable`].
- pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
- self.content = self.content.padding(padding);
- self
- }
-
- /// Sets the width of the [`Scrollable`].
- pub fn width(mut self, width: Length) -> Self {
- self.content = self.content.width(width);
- self
- }
-
/// Sets the height of the [`Scrollable`].
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
- /// Sets the maximum width of the [`Scrollable`].
- pub fn max_width(mut self, max_width: u32) -> Self {
- self.content = self.content.max_width(max_width);
- self
- }
-
- /// Sets the maximum height of the [`Scrollable`] in pixels.
- pub fn max_height(mut self, max_height: u32) -> Self {
- self.max_height = max_height;
- self
- }
-
- /// Sets the horizontal alignment of the contents of the [`Scrollable`] .
- pub fn align_items(mut self, align_items: Alignment) -> Self {
- self.content = self.content.align_items(align_items);
- self
- }
-
/// Sets the scrollbar width of the [`Scrollable`] .
/// Silently enforces a minimum value of 1.
pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
@@ -132,7 +88,7 @@ where
///
/// The function takes the new relative offset of the [`Scrollable`]
/// (e.g. `0` means top, while `1` means bottom).
- pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
+ pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'a) -> Self {
self.on_scroll = Some(Box::new(f));
self
}
@@ -145,14 +101,189 @@ where
self.style = style.into();
self
}
+}
- /// Adds an element to the [`Scrollable`].
- pub fn push<E>(mut self, child: E) -> Self
- where
- E: Into<Element<'a, Message, Renderer>>,
- {
- self.content = self.content.push(child);
- self
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Scrollable<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content))
+ }
+
+ fn width(&self) -> Length {
+ self.content.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ Widget::<Message, Renderer>::width(self),
+ self.height,
+ u32::MAX,
+ |renderer, limits| {
+ self.content.as_widget().layout(renderer, limits)
+ },
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ tree.state.downcast_mut::<State>(),
+ event,
+ layout,
+ cursor_position,
+ clipboard,
+ shell,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ &self.on_scroll,
+ |event, layout, cursor_position, clipboard, shell| {
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ },
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ draw(
+ tree.state.downcast_ref::<State>(),
+ renderer,
+ theme,
+ layout,
+ cursor_position,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ self.style,
+ |renderer, layout, cursor_position, viewport| {
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ )
+ },
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(
+ tree.state.downcast_ref::<State>(),
+ layout,
+ cursor_position,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ |layout, cursor_position, viewport| {
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ },
+ )
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content
+ .as_widget()
+ .overlay(
+ &mut tree.children[0],
+ layout.children().next().unwrap(),
+ renderer,
+ )
+ .map(|overlay| {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let offset = tree
+ .state
+ .downcast_ref::<State>()
+ .offset(bounds, content_bounds);
+
+ overlay.translate(Vector::new(0.0, -(offset as f32)))
+ })
+ }
+}
+
+impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(
+ text_input: Scrollable<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(text_input)
}
}
@@ -625,145 +756,6 @@ fn notify_on_scroll<Message>(
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Scrollable<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn width(&self) -> Length {
- Widget::<Message, Renderer>::width(&self.content)
- }
-
- fn height(&self) -> Length {
- self.height
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- layout(
- renderer,
- limits,
- Widget::<Message, Renderer>::width(self),
- self.height,
- self.max_height,
- |renderer, limits| self.content.layout(renderer, limits),
- )
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- update(
- self.state,
- event,
- layout,
- cursor_position,
- clipboard,
- shell,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- &self.on_scroll,
- |event, layout, cursor_position, clipboard, shell| {
- self.content.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
- },
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(
- self.state,
- layout,
- cursor_position,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- |layout, cursor_position, viewport| {
- self.content.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
- },
- )
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- draw(
- self.state,
- renderer,
- theme,
- layout,
- cursor_position,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- self.style,
- |renderer, layout, cursor_position, viewport| {
- self.content.draw(
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- )
- },
- )
- }
-
- fn overlay(
- &mut self,
- layout: Layout<'_>,
- renderer: &Renderer,
- ) -> Option<overlay::Element<'_, Message, Renderer>> {
- let Self { content, state, .. } = self;
-
- content
- .overlay(layout.children().next().unwrap(), renderer)
- .map(|overlay| {
- let bounds = layout.bounds();
- let content_layout = layout.children().next().unwrap();
- let content_bounds = content_layout.bounds();
- let offset = state.offset(bounds, content_bounds);
-
- overlay.translate(Vector::new(0.0, -(offset as f32)))
- })
- }
-}
-
/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
pub struct State {
@@ -926,17 +918,3 @@ struct Scroller {
/// The bounds of the [`Scroller`].
bounds: Rectangle,
}
-
-impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a + crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn from(
- scrollable: Scrollable<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(scrollable)
- }
-}
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index a5ff611c..585d9c35 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -6,6 +6,7 @@ use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::touch;
+use crate::widget::tree::{self, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
Shell, Size, Widget,
@@ -29,15 +30,15 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
/// # use iced_native::renderer::Null;
/// #
/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>;
+/// #
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
-/// let state = &mut slider::State::new();
/// let value = 50.0;
///
-/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
+/// Slider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
@@ -47,11 +48,10 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
- state: &'a mut State,
range: RangeInclusive<T>,
step: T,
value: T,
- on_change: Box<dyn Fn(T) -> Message>,
+ on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
width: Length,
height: u16,
@@ -71,20 +71,14 @@ where
/// Creates a new [`Slider`].
///
/// It expects:
- /// * the local [`State`] of the [`Slider`]
/// * an inclusive range of possible values
/// * the current value of the [`Slider`]
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- pub fn new<F>(
- state: &'a mut State,
- range: RangeInclusive<T>,
- value: T,
- on_change: F,
- ) -> Self
+ pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
- F: 'static + Fn(T) -> Message,
+ F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
value
@@ -99,7 +93,6 @@ where
};
Slider {
- state,
value,
range,
step: T::from(1),
@@ -150,6 +143,120 @@ where
}
}
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
+ for Slider<'a, T, Message, Renderer>
+where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: Clone,
+ Renderer: crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits =
+ limits.width(self.width).height(Length::Units(self.height));
+
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ tree.state.downcast_mut::<State>(),
+ &mut self.value,
+ &self.range,
+ self.step,
+ self.on_change.as_ref(),
+ &self.on_release,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ draw(
+ renderer,
+ layout,
+ cursor_position,
+ tree.state.downcast_ref::<State>(),
+ self.value,
+ &self.range,
+ theme,
+ self.style,
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(
+ layout,
+ cursor_position,
+ tree.state.downcast_ref::<State>(),
+ )
+ }
+}
+
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: 'a + Clone,
+ Renderer: 'a + crate::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(
+ slider: Slider<'a, T, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(slider)
+ }
+}
+
/// Processes an [`Event`] and updates the [`State`] of a [`Slider`]
/// accordingly.
pub fn update<Message, T>(
@@ -366,102 +473,3 @@ impl State {
State::default()
}
}
-
-impl<'a, T, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, T, Message, Renderer>
-where
- T: Copy + Into<f64> + num_traits::FromPrimitive,
- Message: Clone,
- Renderer: crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- Length::Shrink
- }
-
- fn layout(
- &self,
- _renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- let limits =
- limits.width(self.width).height(Length::Units(self.height));
-
- let size = limits.resolve(Size::ZERO);
-
- layout::Node::new(size)
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- _renderer: &Renderer,
- _clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- update(
- event,
- layout,
- cursor_position,
- shell,
- self.state,
- &mut self.value,
- &self.range,
- self.step,
- self.on_change.as_ref(),
- &self.on_release,
- )
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- _style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- draw(
- renderer,
- layout,
- cursor_position,
- self.state,
- self.value,
- &self.range,
- theme,
- self.style,
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- _renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position, self.state)
- }
-}
-
-impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
- Message: 'a + Clone,
- Renderer: 'a + crate::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn from(
- slider: Slider<'a, T, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(slider)
- }
-}
diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs
index 81338306..9f835893 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -1,6 +1,7 @@
//! Distribute content vertically.
use crate::layout;
use crate::renderer;
+use crate::widget::Tree;
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
/// An amount of empty space.
@@ -59,6 +60,7 @@ where
fn draw(
&self,
+ _state: &Tree,
_renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 76b3eb8b..aa68bfb8 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,13 +1,16 @@
//! Display vector graphics in your application.
use crate::layout;
use crate::renderer;
-use crate::svg::{self, Handle};
+use crate::svg;
+use crate::widget::Tree;
use crate::{
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use std::path::PathBuf;
+pub use svg::Handle;
+
/// A vector graphics image.
///
/// An [`Svg`] image resizes smoothly without losing any quality.
@@ -109,6 +112,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 8d173b8a..26386505 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -3,6 +3,7 @@ use crate::alignment;
use crate::layout;
use crate::renderer;
use crate::text;
+use crate::widget::Tree;
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
pub use iced_style::text::{Appearance, StyleSheet};
@@ -145,6 +146,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -243,3 +245,13 @@ where
}
}
}
+
+impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
+where
+ Renderer: text::Renderer + 'a,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(contents: &'a str) -> Self {
+ Text::new(contents).into()
+ }
+}
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index fd360cd7..4ef9e11b 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -19,6 +19,7 @@ use crate::mouse::{self, click};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
+use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@@ -30,20 +31,15 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
///
/// # Example
/// ```
-/// # use iced_native::renderer::Null;
-/// # use iced_native::widget::text_input;
-/// #
-/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
+/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
/// }
///
-/// let mut state = text_input::State::new();
/// let value = "Some text";
///
/// let input = TextInput::new(
-/// &mut state,
/// "This is the placeholder...",
/// value,
/// Message::TextInputChanged,
@@ -57,7 +53,6 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
- state: &'a mut State,
placeholder: String,
value: Value,
is_secure: bool,
@@ -80,21 +75,14 @@ where
/// Creates a new [`TextInput`].
///
/// It expects:
- /// - some [`State`]
- /// - a placeholder
- /// - the current value
- /// - a function that produces a message when the [`TextInput`] changes
- pub fn new<F>(
- state: &'a mut State,
- placeholder: &str,
- value: &str,
- on_change: F,
- ) -> Self
+ /// - a placeholder,
+ /// - the current value, and
+ /// - a function that produces a message when the [`TextInput`] changes.
+ pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
where
F: 'a + Fn(String) -> Message,
{
TextInput {
- state,
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
@@ -127,7 +115,7 @@ where
/// Sets the [`Font`] of the [`TextInput`].
///
- /// [`Font`]: crate::text::Renderer::Font
+ /// [`Font`]: text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
@@ -166,17 +154,13 @@ where
self
}
- /// Returns the current [`State`] of the [`TextInput`].
- pub fn state(&self) -> &State {
- self.state
- }
-
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
- /// [`Value`] if provided.
+ /// [`text_input::Value`] if provided.
///
/// [`Renderer`]: text::Renderer
pub fn draw(
&self,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
@@ -188,7 +172,7 @@ where
theme,
layout,
cursor_position,
- self.state,
+ tree.state.downcast_ref::<State>(),
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
@@ -199,6 +183,116 @@ where
}
}
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: text::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::new())
+ }
+
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(renderer, limits, self.width, self.padding, self.size)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ &mut self.value,
+ self.size,
+ &self.font,
+ self.is_secure,
+ self.on_change.as_ref(),
+ self.on_paste.as_deref(),
+ &self.on_submit,
+ || tree.state.downcast_mut::<State>(),
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ draw(
+ renderer,
+ theme,
+ layout,
+ cursor_position,
+ tree.state.downcast_ref::<State>(),
+ &self.value,
+ &self.placeholder,
+ self.size,
+ &self.font,
+ self.is_secure,
+ self.style,
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ _state: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ mouse_interaction(layout, cursor_position)
+ }
+}
+
+impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + text::Renderer,
+ Renderer::Theme: StyleSheet,
+{
+ fn from(
+ text_input: TextInput<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(text_input)
+ }
+}
+
/// Computes the layout of a [`TextInput`].
pub fn layout<Renderer>(
renderer: &Renderer,
@@ -777,93 +871,6 @@ pub fn mouse_interaction(
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for TextInput<'a, Message, Renderer>
-where
- Message: Clone,
- Renderer: text::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- Length::Shrink
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- layout(renderer, limits, self.width, self.padding, self.size)
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- update(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- &mut self.value,
- self.size,
- &self.font,
- self.is_secure,
- self.on_change.as_ref(),
- self.on_paste.as_deref(),
- &self.on_submit,
- || &mut self.state,
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- _renderer: &Renderer,
- ) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- _style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- self.draw(renderer, theme, layout, cursor_position, None)
- }
-}
-
-impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a + Clone,
- Renderer: 'a + text::Renderer,
- Renderer::Theme: StyleSheet,
-{
- fn from(
- text_input: TextInput<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(text_input)
- }
-}
-
/// The state of a [`TextInput`].
#[derive(Debug, Default, Clone)]
pub struct State {
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index 3deaf287..7893f78c 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -5,7 +5,7 @@ use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
-use crate::widget::{self, Row, Text};
+use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Event, Layout, Length, Point, Rectangle,
Shell, Widget,
@@ -180,6 +180,7 @@ where
fn on_event(
&mut self,
+ _state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@@ -205,6 +206,7 @@ where
fn mouse_interaction(
&self,
+ _state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@@ -219,6 +221,7 @@ where
fn draw(
&self,
+ _state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
index 0548e9da..d8198004 100644
--- a/native/src/widget/tooltip.rs
+++ b/native/src/widget/tooltip.rs
@@ -6,7 +6,8 @@ use crate::renderer;
use crate::text;
use crate::widget;
use crate::widget::container;
-use crate::widget::text::Text;
+use crate::widget::overlay;
+use crate::widget::{Text, Tree};
use crate::{
Clipboard, Element, Event, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@@ -14,7 +15,7 @@ use crate::{
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
-pub struct Tooltip<'a, Message, Renderer>
+pub struct Tooltip<'a, Message, Renderer: text::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
@@ -89,6 +90,153 @@ where
}
}
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Tooltip<'a, Message, Renderer>
+where
+ Renderer: text::Renderer,
+ Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+{
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content))
+ }
+
+ fn width(&self) -> Length {
+ self.content.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.as_widget().height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget().layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout.children().next().unwrap(),
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ inherited_style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ inherited_style,
+ layout,
+ cursor_position,
+ viewport,
+ );
+
+ let tooltip = &self.tooltip;
+
+ draw(
+ renderer,
+ theme,
+ inherited_style,
+ layout,
+ cursor_position,
+ viewport,
+ self.position,
+ self.gap,
+ self.padding,
+ self.style,
+ |renderer, limits| {
+ Widget::<(), Renderer>::layout(tooltip, renderer, limits)
+ },
+ |renderer, defaults, layout, cursor_position, viewport| {
+ Widget::<(), Renderer>::draw(
+ tooltip,
+ &Tree::empty(),
+ renderer,
+ theme,
+ defaults,
+ layout,
+ cursor_position,
+ viewport,
+ );
+ },
+ );
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content.as_widget().overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ }
+}
+
+impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a,
+ Renderer: 'a + text::Renderer,
+ Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
+{
+ fn from(
+ tooltip: Tooltip<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(tooltip)
+ }
+}
+
/// The position of the tooltip. Defaults to following the cursor.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Position {
@@ -220,122 +368,3 @@ pub fn draw<Renderer>(
});
}
}
-
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Tooltip<'a, Message, Renderer>
-where
- Renderer: text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
-{
- fn width(&self) -> Length {
- self.content.width()
- }
-
- fn height(&self) -> Length {
- self.content.height()
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- self.content.layout(renderer, limits)
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- self.content.widget.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
- }
-
- fn mouse_interaction(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- renderer: &Renderer,
- ) -> mouse::Interaction {
- self.content.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- inherited_style: &renderer::Style,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) {
- self.content.draw(
- renderer,
- theme,
- inherited_style,
- layout,
- cursor_position,
- viewport,
- );
-
- let tooltip = &self.tooltip;
-
- draw(
- renderer,
- theme,
- inherited_style,
- layout,
- cursor_position,
- viewport,
- self.position,
- self.gap,
- self.padding,
- self.style,
- |renderer, limits| {
- Widget::<(), Renderer>::layout(tooltip, renderer, limits)
- },
- |renderer, defaults, layout, cursor_position, viewport| {
- Widget::<(), Renderer>::draw(
- tooltip,
- renderer,
- theme,
- defaults,
- layout,
- cursor_position,
- viewport,
- );
- },
- )
- }
-}
-
-impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
- for Element<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a + text::Renderer,
- Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
-{
- fn from(
- tooltip: Tooltip<'a, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
- Element::new(tooltip)
- }
-}
diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs
new file mode 100644
index 00000000..a8b1a185
--- /dev/null
+++ b/native/src/widget/tree.rs
@@ -0,0 +1,187 @@
+//! Store internal widget state in a state tree to ensure continuity.
+use crate::Widget;
+
+use std::any::{self, Any};
+use std::borrow::Borrow;
+use std::fmt;
+
+/// A persistent state widget tree.
+///
+/// A [`Tree`] is normally associated with a specific widget in the widget tree.
+#[derive(Debug)]
+pub struct Tree {
+ /// The tag of the [`Tree`].
+ pub tag: Tag,
+
+ /// The [`State`] of the [`Tree`].
+ pub state: State,
+
+ /// The children of the root widget of the [`Tree`].
+ pub children: Vec<Tree>,
+}
+
+impl Tree {
+ /// Creates an empty, stateless [`Tree`] with no children.
+ pub fn empty() -> Self {
+ Self {
+ tag: Tag::stateless(),
+ state: State::None,
+ children: Vec::new(),
+ }
+ }
+
+ /// Creates a new [`Tree`] for the provided [`Element`].
+ pub fn new<'a, Message, Renderer>(
+ widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
+ ) -> Self
+ where
+ Renderer: crate::Renderer,
+ {
+ let widget = widget.borrow();
+
+ Self {
+ tag: widget.tag(),
+ state: widget.state(),
+ children: widget.children(),
+ }
+ }
+
+ /// Reconciliates the current tree with the provided [`Element`].
+ ///
+ /// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
+ /// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
+ ///
+ /// Otherwise, the whole [`Tree`] is recreated.
+ ///
+ /// [`Widget::diff`]: crate::Widget::diff
+ pub fn diff<'a, Message, Renderer>(
+ &mut self,
+ new: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
+ ) where
+ Renderer: crate::Renderer,
+ {
+ if self.tag == new.borrow().tag() {
+ new.borrow().diff(self)
+ } else {
+ *self = Self::new(new);
+ }
+ }
+
+ /// Reconciliates the children of the tree with the provided list of [`Element`].
+ pub fn diff_children<'a, Message, Renderer>(
+ &mut self,
+ new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
+ ) where
+ Renderer: crate::Renderer,
+ {
+ self.diff_children_custom(
+ new_children,
+ |tree, widget| tree.diff(widget.borrow()),
+ |widget| Self::new(widget.borrow()),
+ )
+ }
+
+ /// Reconciliates the children of the tree with the provided list of [`Element`] using custom
+ /// logic both for diffing and creating new widget state.
+ pub fn diff_children_custom<T>(
+ &mut self,
+ new_children: &[T],
+ diff: impl Fn(&mut Tree, &T),
+ new_state: impl Fn(&T) -> Self,
+ ) {
+ if self.children.len() > new_children.len() {
+ self.children.truncate(new_children.len());
+ }
+
+ for (child_state, new) in
+ self.children.iter_mut().zip(new_children.iter())
+ {
+ diff(child_state, new);
+ }
+
+ if self.children.len() < new_children.len() {
+ self.children.extend(
+ new_children[self.children.len()..].iter().map(new_state),
+ );
+ }
+ }
+}
+
+/// The identifier of some widget state.
+#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct Tag(any::TypeId);
+
+impl Tag {
+ /// Creates a [`Tag`] for a state of type `T`.
+ pub fn of<T>() -> Self
+ where
+ T: 'static,
+ {
+ Self(any::TypeId::of::<T>())
+ }
+
+ /// Creates a [`Tag`] for a stateless widget.
+ pub fn stateless() -> Self {
+ Self::of::<()>()
+ }
+}
+
+/// The internal [`State`] of a widget.
+pub enum State {
+ /// No meaningful internal state.
+ None,
+
+ /// Some meaningful internal state.
+ Some(Box<dyn Any>),
+}
+
+impl State {
+ /// Creates a new [`State`].
+ pub fn new<T>(state: T) -> Self
+ where
+ T: 'static,
+ {
+ State::Some(Box::new(state))
+ }
+
+ /// Downcasts the [`State`] to `T` and returns a reference to it.
+ ///
+ /// # Panics
+ /// This method will panic if the downcast fails or the [`State`] is [`State::None`].
+ pub fn downcast_ref<T>(&self) -> &T
+ where
+ T: 'static,
+ {
+ match self {
+ State::None => panic!("Downcast on stateless state"),
+ State::Some(state) => {
+ state.downcast_ref().expect("Downcast widget state")
+ }
+ }
+ }
+
+ /// Downcasts the [`State`] to `T` and returns a mutable reference to it.
+ ///
+ /// # Panics
+ /// This method will panic if the downcast fails or the [`State`] is [`State::None`].
+ pub fn downcast_mut<T>(&mut self) -> &mut T
+ where
+ T: 'static,
+ {
+ match self {
+ State::None => panic!("Downcast on stateless state"),
+ State::Some(state) => {
+ state.downcast_mut().expect("Downcast widget state")
+ }
+ }
+ }
+}
+
+impl fmt::Debug for State {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::None => write!(f, "State::None"),
+ Self::Some(_) => write!(f, "State::Some"),
+ }
+ }
+}