summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--examples/component/Cargo.toml11
-rw-r--r--examples/component/src/main.rs180
-rw-r--r--graphics/src/widget/canvas.rs8
-rw-r--r--lazy/Cargo.toml11
-rw-r--r--lazy/src/component.rs186
-rw-r--r--lazy/src/lib.rs3
-rw-r--r--native/src/element.rs21
-rw-r--r--native/src/lib.rs2
-rw-r--r--native/src/overlay.rs4
-rw-r--r--native/src/overlay/element.rs22
-rw-r--r--native/src/overlay/menu.rs8
-rw-r--r--native/src/shell.rs54
-rw-r--r--native/src/user_interface.rs54
-rw-r--r--native/src/widget.rs4
-rw-r--r--native/src/widget/button.rs8
-rw-r--r--native/src/widget/checkbox.rs6
-rw-r--r--native/src/widget/column.rs6
-rw-r--r--native/src/widget/container.rs6
-rw-r--r--native/src/widget/image/viewer.rs6
-rw-r--r--native/src/widget/pane_grid.rs30
-rw-r--r--native/src/widget/pane_grid/content.rs10
-rw-r--r--native/src/widget/pane_grid/title_bar.rs8
-rw-r--r--native/src/widget/pick_list.rs8
-rw-r--r--native/src/widget/radio.rs6
-rw-r--r--native/src/widget/row.rs6
-rw-r--r--native/src/widget/scrollable.rs18
-rw-r--r--native/src/widget/slider.rs8
-rw-r--r--native/src/widget/text_input.rs16
-rw-r--r--native/src/widget/toggler.rs6
-rw-r--r--native/src/widget/tooltip.rs8
31 files changed, 605 insertions, 121 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b8d244fc..f91e4a84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,6 +57,7 @@ members = [
"graphics",
"glow",
"glutin",
+ "lazy",
"native",
"style",
"web",
@@ -65,6 +66,7 @@ members = [
"examples/bezier_tool",
"examples/clock",
"examples/color_palette",
+ "examples/component",
"examples/counter",
"examples/custom_widget",
"examples/download_progress",
diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml
new file mode 100644
index 00000000..5761db9f
--- /dev/null
+++ b/examples/component/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "component"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+iced_native = { path = "../../native" }
+iced_lazy = { path = "../../lazy" }
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
new file mode 100644
index 00000000..39335cf1
--- /dev/null
+++ b/examples/component/src/main.rs
@@ -0,0 +1,180 @@
+use iced::{Container, Element, Length, Sandbox, Settings};
+use numeric_input::NumericInput;
+
+pub fn main() -> iced::Result {
+ Component::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Component {
+ numeric_input: numeric_input::State,
+ value: Option<u32>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ NumericInputChanged(Option<u32>),
+}
+
+impl Sandbox for Component {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Component - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::NumericInputChanged(value) => {
+ self.value = value;
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ Container::new(NumericInput::new(
+ &mut self.numeric_input,
+ self.value,
+ Message::NumericInputChanged,
+ ))
+ .padding(20)
+ .height(Length::Fill)
+ .center_y()
+ .into()
+ }
+}
+
+mod numeric_input {
+ use iced_lazy::component::{self, Component};
+ use iced_native::alignment::{self, Alignment};
+ use iced_native::text;
+ use iced_native::widget::button::{self, Button};
+ use iced_native::widget::text_input::{self, TextInput};
+ use iced_native::widget::{Row, Text};
+ use iced_native::{Element, Length};
+
+ pub struct NumericInput<'a, Message> {
+ state: &'a mut State,
+ value: Option<u32>,
+ on_change: Box<dyn Fn(Option<u32>) -> Message>,
+ }
+
+ #[derive(Default)]
+ pub struct State {
+ input: text_input::State,
+ decrement_button: button::State,
+ increment_button: button::State,
+ }
+
+ #[derive(Debug, Clone)]
+ pub enum Event {
+ InputChanged(String),
+ IncrementPressed,
+ DecrementPressed,
+ }
+
+ impl<'a, Message> NumericInput<'a, Message> {
+ pub fn new(
+ state: &'a mut State,
+ value: Option<u32>,
+ on_change: impl Fn(Option<u32>) -> Message + 'static,
+ ) -> Self {
+ Self {
+ state,
+ value,
+ on_change: Box::new(on_change),
+ }
+ }
+ }
+
+ impl<'a, Message, Renderer> Component<Message, Renderer>
+ for NumericInput<'a, Message>
+ where
+ Renderer: 'a + text::Renderer,
+ {
+ type Event = Event;
+
+ fn update(&mut self, event: Event) -> Option<Message> {
+ match event {
+ Event::IncrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_add(1),
+ ))),
+ Event::DecrementPressed => Some((self.on_change)(Some(
+ self.value.unwrap_or_default().saturating_sub(1),
+ ))),
+ Event::InputChanged(value) => {
+ if value.is_empty() {
+ Some((self.on_change)(None))
+ } else {
+ value
+ .parse()
+ .ok()
+ .map(Some)
+ .map(self.on_change.as_ref())
+ }
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Event, Renderer> {
+ let button = |state, label, on_press| {
+ Button::new(
+ state,
+ Text::new(label)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .vertical_alignment(alignment::Vertical::Center),
+ )
+ .width(Length::Units(50))
+ .on_press(on_press)
+ };
+
+ Row::with_children(vec![
+ button(
+ &mut self.state.decrement_button,
+ "-",
+ Event::DecrementPressed,
+ )
+ .into(),
+ TextInput::new(
+ &mut self.state.input,
+ "Type a number",
+ self.value
+ .as_ref()
+ .map(u32::to_string)
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or(""),
+ Event::InputChanged,
+ )
+ .padding(10)
+ .into(),
+ button(
+ &mut self.state.increment_button,
+ "+",
+ Event::IncrementPressed,
+ )
+ .into(),
+ ])
+ .align_items(Alignment::Fill)
+ .spacing(10)
+ .into()
+ }
+ }
+
+ impl<'a, Message, Renderer> From<NumericInput<'a, Message>>
+ for Element<'a, Message, Renderer>
+ where
+ Message: 'a,
+ Renderer: text::Renderer + 'a,
+ {
+ fn from(numeric_input: NumericInput<'a, Message>) -> Self {
+ component::view(numeric_input)
+ }
+ }
+}
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index 639c2a9b..97846d65 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -9,8 +9,8 @@ use crate::{Backend, Primitive};
use iced_native::layout;
use iced_native::mouse;
use iced_native::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
- Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size,
+ Vector, Widget,
};
use std::hash::Hash;
use std::marker::PhantomData;
@@ -158,7 +158,7 @@ where
cursor_position: Point,
_renderer: &Renderer<B>,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
@@ -179,7 +179,7 @@ where
self.program.update(canvas_event, bounds, cursor);
if let Some(message) = message {
- messages.push(message);
+ shell.publish(message);
}
return event_status;
diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml
new file mode 100644
index 00000000..b840de50
--- /dev/null
+++ b/lazy/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "iced_lazy"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+ouroboros = "0.13"
+
+[dependencies.iced_native]
+version = "0.4"
+path = "../native"
diff --git a/lazy/src/component.rs b/lazy/src/component.rs
new file mode 100644
index 00000000..aa5b847e
--- /dev/null
+++ b/lazy/src/component.rs
@@ -0,0 +1,186 @@
+use iced_native::event;
+use iced_native::layout::{self, Layout};
+use iced_native::mouse;
+use iced_native::overlay;
+use iced_native::renderer;
+use iced_native::{
+ Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Widget,
+};
+
+use ouroboros::self_referencing;
+use std::marker::PhantomData;
+
+pub fn view<'a, C, Message, Renderer>(
+ component: C,
+) -> Element<'a, Message, Renderer>
+where
+ C: Component<Message, Renderer> + 'a,
+ Message: 'a,
+ Renderer: iced_native::Renderer + 'a,
+{
+ Element::new(Instance {
+ state: Some(
+ StateBuilder {
+ component: Box::new(component),
+ cache_builder: |state| Cache {
+ element: state.view(),
+ message: PhantomData,
+ },
+ }
+ .build(),
+ ),
+ })
+}
+
+pub trait Component<Message, Renderer> {
+ type Event;
+
+ fn update(&mut self, event: Self::Event) -> Option<Message>;
+
+ fn view(&mut self) -> Element<Self::Event, Renderer>;
+}
+
+struct Instance<'a, Message, Renderer, Event> {
+ state: Option<State<'a, Message, Renderer, Event>>,
+}
+
+#[self_referencing]
+struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> {
+ component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>,
+
+ #[borrows(mut component)]
+ #[covariant]
+ cache: Cache<'this, Message, Renderer, Event>,
+}
+
+struct Cache<'a, Message, Renderer, Event> {
+ element: Element<'a, Event, Renderer>,
+ message: PhantomData<Message>,
+}
+
+impl<'a, Message, Renderer, Event> Widget<Message, Renderer>
+ for Instance<'a, Message, Renderer, Event>
+where
+ Renderer: iced_native::Renderer,
+{
+ fn width(&self) -> Length {
+ self.state.as_ref().unwrap().borrow_cache().element.width()
+ }
+
+ fn height(&self) -> Length {
+ self.state.as_ref().unwrap().borrow_cache().element.width()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.state
+ .as_ref()
+ .unwrap()
+ .borrow_cache()
+ .element
+ .layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: iced_native::Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
+
+ let event_status =
+ self.state.as_mut().unwrap().with_cache_mut(|cache| {
+ cache.element.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ &mut local_shell,
+ )
+ });
+
+ if !local_messages.is_empty() {
+ let mut component =
+ self.state.take().unwrap().into_heads().component;
+
+ for message in local_messages
+ .into_iter()
+ .filter_map(|message| component.update(message))
+ {
+ shell.publish(message);
+ }
+
+ self.state = Some(
+ StateBuilder {
+ component,
+ cache_builder: |state| Cache {
+ element: state.view(),
+ message: PhantomData,
+ },
+ }
+ .build(),
+ );
+
+ shell.invalidate_layout();
+ }
+
+ event_status
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.state.as_ref().unwrap().borrow_cache().element.draw(
+ renderer,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ self.state
+ .as_ref()
+ .unwrap()
+ .borrow_cache()
+ .element
+ .hash_layout(state)
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.state
+ .as_ref()
+ .unwrap()
+ .borrow_cache()
+ .element
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
+ fn overlay(
+ &mut self,
+ _layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ // TODO: Rethink overlay composability
+ None
+ }
+}
diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs
new file mode 100644
index 00000000..42e5f587
--- /dev/null
+++ b/lazy/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod component;
+
+pub use component::Component;
diff --git a/native/src/element.rs b/native/src/element.rs
index ee404a1c..7e806b08 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -4,7 +4,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{
- Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Shell, Widget,
};
/// A generic [`Widget`].
@@ -228,7 +228,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.widget.on_event(
event,
@@ -236,7 +236,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}
@@ -327,9 +327,10 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<B>,
+ shell: &mut Shell<'_, B>,
) -> event::Status {
- let mut original_messages = Vec::new();
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
let status = self.widget.on_event(
event,
@@ -337,12 +338,10 @@ where
cursor_position,
renderer,
clipboard,
- &mut original_messages,
+ &mut local_shell,
);
- original_messages
- .drain(..)
- .for_each(|message| messages.push((self.mapper)(message)));
+ shell.merge(local_shell, &self.mapper);
status
}
@@ -427,7 +426,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.element.widget.on_event(
event,
@@ -435,7 +434,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 51b232e9..f340ec14 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -53,6 +53,7 @@ pub mod window;
mod element;
mod hasher;
mod runtime;
+mod shell;
mod user_interface;
// We disable debug capabilities on release builds unless the `debug` feature
@@ -85,6 +86,7 @@ pub use overlay::Overlay;
pub use program::Program;
pub use renderer::Renderer;
pub use runtime::Runtime;
+pub use shell::Shell;
pub use subscription::Subscription;
pub use user_interface::{Cache, UserInterface};
pub use widget::Widget;
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 1ac3cea5..e66d421a 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -10,7 +10,7 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
-use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@@ -71,7 +71,7 @@ where
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _messages: &mut Vec<Message>,
+ _shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index f418a518..70cb417e 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -4,7 +4,7 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
-use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector};
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size, Vector};
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
@@ -25,6 +25,11 @@ where
Self { position, overlay }
}
+ /// Returns the position of the [`Element`].
+ pub fn position(&self) -> Point {
+ self.position
+ }
+
/// Translates the [`Element`].
pub fn translate(mut self, translation: Vector) -> Self {
self.position = self.position + translation;
@@ -57,7 +62,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.overlay.on_event(
event,
@@ -65,7 +70,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}
@@ -131,9 +136,10 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<B>,
+ shell: &mut Shell<'_, B>,
) -> event::Status {
- let mut original_messages = Vec::new();
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
let event_status = self.content.on_event(
event,
@@ -141,12 +147,10 @@ where
cursor_position,
renderer,
clipboard,
- &mut original_messages,
+ &mut local_shell,
);
- original_messages
- .drain(..)
- .for_each(|message| messages.push((self.mapper)(message)));
+ shell.merge(local_shell, self.mapper);
event_status
}
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index ee3bee6e..6776a3ee 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -11,7 +11,7 @@ use crate::widget::scrollable::{self, Scrollable};
use crate::widget::Container;
use crate::{
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Size, Vector, Widget,
+ Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_style::menu::Style;
@@ -222,7 +222,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
event.clone(),
@@ -230,7 +230,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}
@@ -333,7 +333,7 @@ where
cursor_position: Point,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _messages: &mut Vec<Message>,
+ _shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
diff --git a/native/src/shell.rs b/native/src/shell.rs
new file mode 100644
index 00000000..e916f52d
--- /dev/null
+++ b/native/src/shell.rs
@@ -0,0 +1,54 @@
+/// A connection to the state of a shell.
+///
+/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
+/// like publishing messages or invalidating the current layout.
+///
+/// [`Widget`]: crate::Widget
+#[derive(Debug)]
+pub struct Shell<'a, Message> {
+ messages: &'a mut Vec<Message>,
+ is_layout_invalid: bool,
+}
+
+impl<'a, Message> Shell<'a, Message> {
+ /// Creates a new [`Shell`] with the provided buffer of messages.
+ pub fn new(messages: &'a mut Vec<Message>) -> Self {
+ Self {
+ messages,
+ is_layout_invalid: false,
+ }
+ }
+
+ /// Triggers the given function if the layout is invalid, cleaning it in the
+ /// process.
+ pub fn with_invalid_layout(&mut self, f: impl FnOnce()) {
+ if self.is_layout_invalid {
+ self.is_layout_invalid = false;
+
+ f()
+ }
+ }
+
+ /// Publish the given `Message` for an application to process it.
+ pub fn publish(&mut self, message: Message) {
+ self.messages.push(message);
+ }
+
+ /// Invalidates the current application layout.
+ ///
+ /// The shell will relayout the application widgets.
+ pub fn invalidate_layout(&mut self) {
+ self.is_layout_invalid = true;
+ }
+
+ /// Merges the current [`Shell`] with another one by applying the given
+ /// function to the messages of the latter.
+ ///
+ /// This method is useful for composition.
+ pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
+ self.messages.extend(other.messages.drain(..).map(f));
+
+ self.is_layout_invalid =
+ self.is_layout_invalid || other.is_layout_invalid;
+ }
+}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index f44ed1fb..5d24bf4f 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -3,7 +3,7 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
-use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
+use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
use std::hash::Hasher;
@@ -179,7 +179,7 @@ where
/// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
- /// &renderer,
+ /// &mut renderer,
/// &mut clipboard,
/// &mut messages
/// );
@@ -196,16 +196,18 @@ where
&mut self,
events: &[Event],
cursor_position: Point,
- renderer: &Renderer,
+ renderer: &mut Renderer,
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
) -> Vec<event::Status> {
let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
- let layer = Self::overlay_layer(
+ let bounds = self.bounds;
+
+ let mut layer = Self::overlay_layer(
self.overlay.take(),
- self.bounds,
+ bounds,
&mut overlay,
renderer,
);
@@ -214,14 +216,27 @@ where
.iter()
.cloned()
.map(|event| {
- overlay.on_event(
+ let mut shell = Shell::new(messages);
+
+ let event_status = overlay.on_event(
event,
Layout::new(&layer.layout),
cursor_position,
renderer,
clipboard,
- messages,
- )
+ &mut shell,
+ );
+
+ shell.with_invalid_layout(|| {
+ layer = Self::overlay_layer(
+ None,
+ bounds,
+ &mut overlay,
+ renderer,
+ );
+ });
+
+ event_status
})
.collect();
@@ -245,15 +260,34 @@ where
.cloned()
.zip(overlay_statuses.into_iter())
.map(|(event, overlay_status)| {
+ let mut shell = Shell::new(messages);
+
let event_status = self.root.widget.on_event(
event,
Layout::new(&self.base.layout),
base_cursor,
renderer,
clipboard,
- messages,
+ &mut shell,
);
+ shell.with_invalid_layout(|| {
+ let hash = {
+ let hasher = &mut crate::Hasher::default();
+ self.root.hash_layout(hasher);
+
+ hasher.finish()
+ };
+
+ let layout = renderer.layout(
+ &self.root,
+ &layout::Limits::new(Size::ZERO, self.bounds),
+ );
+
+ self.base = Layer { layout, hash };
+ self.overlay = None;
+ });
+
event_status.merge(overlay_status)
})
.collect()
@@ -313,7 +347,7 @@ where
/// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
- /// &renderer,
+ /// &mut renderer,
/// &mut clipboard,
/// &mut messages
/// );
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 07214b16..e2613e67 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -75,7 +75,7 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
-use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
+use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
@@ -163,7 +163,7 @@ where
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _messages: &mut Vec<Message>,
+ _shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
}
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 1d785f35..686289e4 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -9,7 +9,7 @@ use crate::renderer;
use crate::touch;
use crate::{
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
- Point, Rectangle, Vector, Widget,
+ Point, Rectangle, Shell, Vector, Widget,
};
use std::hash::Hash;
@@ -197,7 +197,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
if let event::Status::Captured = self.content.on_event(
event.clone(),
@@ -205,7 +205,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
) {
return event::Status::Captured;
}
@@ -232,7 +232,7 @@ where
self.state.is_pressed = false;
if bounds.contains(cursor_position) {
- messages.push(on_press);
+ shell.publish(on_press);
}
return event::Status::Captured;
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 81611426..2af2d60c 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -11,7 +11,7 @@ use crate::touch;
use crate::widget::{self, Row, Text};
use crate::{
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Rectangle, Shell, Widget,
};
pub use iced_style::checkbox::{Style, StyleSheet};
@@ -171,7 +171,7 @@ where
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
@@ -179,7 +179,7 @@ where
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
- messages.push((self.on_toggle)(!self.is_checked));
+ shell.publish((self.on_toggle)(!self.is_checked));
return event::Status::Captured;
}
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 0d4d6fa7..4c43c8c8 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -8,7 +8,7 @@ use crate::overlay;
use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Widget,
+ Rectangle, Shell, Widget,
};
use std::u32;
@@ -146,7 +146,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.children
.iter_mut()
@@ -158,7 +158,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 596af7fd..756d9d1b 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -9,7 +9,7 @@ use crate::overlay;
use crate::renderer;
use crate::{
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
- Point, Rectangle, Widget,
+ Point, Rectangle, Shell, Widget,
};
use std::u32;
@@ -167,7 +167,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.content.widget.on_event(
event,
@@ -175,7 +175,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index 95e5c6e4..0fc766c3 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -5,8 +5,8 @@ use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
- Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size,
+ Vector, Widget,
};
use std::hash::Hash;
@@ -169,7 +169,7 @@ where
cursor_position: Point,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _messages: &mut Vec<Message>,
+ _shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 3637822b..24b87eed 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -34,8 +34,8 @@ use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
- Vector, Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Shell,
+ Size, Vector, Widget,
};
pub use iced_style::pane_grid::{Line, StyleSheet};
@@ -205,7 +205,7 @@ where
&mut self,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) {
let mut clicked_region =
self.elements.iter().zip(layout.children()).filter(
@@ -214,7 +214,7 @@ where
if let Some(((pane, content), layout)) = clicked_region.next() {
if let Some(on_click) = &self.on_click {
- messages.push(on_click(*pane));
+ shell.publish(on_click(*pane));
}
if let Some(on_drag) = &self.on_drag {
@@ -226,7 +226,7 @@ where
self.state.pick_pane(pane, origin);
- messages.push(on_drag(DragEvent::Picked { pane: *pane }));
+ shell.publish(on_drag(DragEvent::Picked { pane: *pane }));
}
}
}
@@ -236,7 +236,7 @@ where
&mut self,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
@@ -263,7 +263,7 @@ where
}
};
- messages.push(on_resize(ResizeEvent { split, ratio }));
+ shell.publish(on_resize(ResizeEvent { split, ratio }));
return event::Status::Captured;
}
@@ -362,7 +362,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
let mut event_status = event::Status::Ignored;
@@ -395,15 +395,11 @@ where
if let Some((split, axis, _)) = clicked_split {
self.state.pick_split(&split, axis);
} else {
- self.click_pane(
- layout,
- cursor_position,
- messages,
- );
+ self.click_pane(layout, cursor_position, shell);
}
}
None => {
- self.click_pane(layout, cursor_position, messages);
+ self.click_pane(layout, cursor_position, shell);
}
}
}
@@ -430,7 +426,7 @@ where
_ => DragEvent::Canceled { pane },
};
- messages.push(on_drag(event));
+ shell.publish(on_drag(event));
}
self.state.idle();
@@ -445,7 +441,7 @@ where
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
event_status =
- self.trigger_resize(layout, cursor_position, messages);
+ self.trigger_resize(layout, cursor_position, shell);
}
_ => {}
}
@@ -464,7 +460,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
is_picked,
)
})
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index c44506dd..533827b7 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -5,7 +5,9 @@ use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::pane_grid::TitleBar;
-use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
+use crate::{
+ Clipboard, Element, Hasher, Layout, Point, Rectangle, Shell, Size,
+};
/// The content of a [`Pane`].
///
@@ -160,7 +162,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
is_picked: bool,
) -> event::Status {
let mut event_status = event::Status::Ignored;
@@ -174,7 +176,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
);
children.next().unwrap()
@@ -191,7 +193,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
};
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 070cf404..353e1ce9 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -5,7 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::{
- Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
+ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Shell, Size,
};
/// The title bar of a [`Pane`].
@@ -218,7 +218,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
let mut children = layout.children();
let padded = children.next().unwrap();
@@ -235,7 +235,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
} else {
event::Status::Ignored
@@ -247,7 +247,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
);
control_status.merge(title_status)
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index 9d1a86ec..cb781a89 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -11,7 +11,7 @@ use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Size, Widget,
+ Shell, Size, Widget,
};
use std::borrow::Cow;
@@ -245,7 +245,7 @@ where
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
@@ -271,7 +271,7 @@ where
};
if let Some(last_selection) = self.last_selection.take() {
- messages.push((self.on_selected)(last_selection));
+ shell.publish((self.on_selected)(last_selection));
*self.is_open = false;
@@ -312,7 +312,7 @@ where
};
if let Some(next_option) = next_option {
- messages.push((self.on_selected)(next_option.clone()));
+ shell.publish((self.on_selected)(next_option.clone()));
}
event::Status::Captured
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 86ad4c4e..523773bd 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -11,7 +11,7 @@ use crate::touch;
use crate::widget::{self, Row, Text};
use crate::{
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Rectangle, Shell, Widget,
};
pub use iced_style::radio::{Style, StyleSheet};
@@ -187,13 +187,13 @@ where
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
- messages.push(self.on_click.clone());
+ shell.publish(self.on_click.clone());
return event::Status::Captured;
}
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 6fe3284b..a0174f79 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -6,7 +6,7 @@ use crate::overlay;
use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Widget,
+ Rectangle, Shell, Widget,
};
use std::hash::Hash;
@@ -145,7 +145,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.children
.iter_mut()
@@ -157,7 +157,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 2bf2ea5e..fac8af9e 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -8,7 +8,7 @@ use crate::touch;
use crate::widget::Column;
use crate::{
Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
- Padding, Point, Rectangle, Size, Vector, Widget,
+ Padding, Point, Rectangle, Shell, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -144,14 +144,14 @@ impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
&self,
bounds: Rectangle,
content_bounds: Rectangle,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) {
if content_bounds.height <= bounds.height {
return;
}
if let Some(on_scroll) = &self.on_scroll {
- messages.push(on_scroll(
+ shell.publish(on_scroll(
self.state.offset.absolute(bounds, content_bounds)
/ (content_bounds.height - bounds.height),
));
@@ -251,7 +251,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
@@ -286,7 +286,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
};
@@ -307,7 +307,7 @@ where
}
}
- self.notify_on_scroll(bounds, content_bounds, messages);
+ self.notify_on_scroll(bounds, content_bounds, shell);
return event::Status::Captured;
}
@@ -336,7 +336,7 @@ where
self.notify_on_scroll(
bounds,
content_bounds,
- messages,
+ shell,
);
}
}
@@ -377,7 +377,7 @@ where
content_bounds,
);
- self.notify_on_scroll(bounds, content_bounds, messages);
+ self.notify_on_scroll(bounds, content_bounds, shell);
return event::Status::Captured;
}
@@ -409,7 +409,7 @@ where
self.notify_on_scroll(
bounds,
content_bounds,
- messages,
+ shell,
);
return event::Status::Captured;
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 3ce53f6c..65632a5c 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -8,7 +8,7 @@ use crate::renderer;
use crate::touch;
use crate::{
Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Rectangle, Shell, Size, Widget,
};
use std::hash::Hash;
@@ -191,7 +191,7 @@ where
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
let is_dragging = self.state.is_dragging;
@@ -220,7 +220,7 @@ where
};
if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
- messages.push((self.on_change)(new_value));
+ shell.publish((self.on_change)(new_value));
self.value = new_value;
}
@@ -241,7 +241,7 @@ where
| Event::Touch(touch::Event::FingerLost { .. }) => {
if is_dragging {
if let Some(on_release) = self.on_release.clone() {
- messages.push(on_release);
+ shell.publish(on_release);
}
self.state.is_dragging = false;
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 40c6c573..5e1726db 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -21,7 +21,7 @@ use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Size, Vector, Widget,
+ Rectangle, Shell, Size, Vector, Widget,
};
use std::u32;
@@ -384,7 +384,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
@@ -509,7 +509,7 @@ where
editor.insert(c);
let message = (self.on_change)(editor.contents());
- messages.push(message);
+ shell.publish(message);
return event::Status::Captured;
}
@@ -521,7 +521,7 @@ where
match key_code {
keyboard::KeyCode::Enter => {
if let Some(on_submit) = self.on_submit.clone() {
- messages.push(on_submit);
+ shell.publish(on_submit);
}
}
keyboard::KeyCode::Backspace => {
@@ -551,7 +551,7 @@ where
editor.backspace();
let message = (self.on_change)(editor.contents());
- messages.push(message);
+ shell.publish(message);
}
keyboard::KeyCode::Delete => {
if platform::is_jump_modifier_pressed(modifiers)
@@ -582,7 +582,7 @@ where
editor.delete();
let message = (self.on_change)(editor.contents());
- messages.push(message);
+ shell.publish(message);
}
keyboard::KeyCode::Left => {
if platform::is_jump_modifier_pressed(modifiers)
@@ -674,7 +674,7 @@ where
editor.delete();
let message = (self.on_change)(editor.contents());
- messages.push(message);
+ shell.publish(message);
}
keyboard::KeyCode::V => {
if self.state.keyboard_modifiers.command() {
@@ -700,7 +700,7 @@ where
editor.paste(content.clone());
let message = (self.on_change)(editor.contents());
- messages.push(message);
+ shell.publish(message);
self.state.is_pasting = Some(content);
} else {
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index 2dcc3ffe..6cecc7e1 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -10,7 +10,7 @@ use crate::text;
use crate::widget::{Row, Text};
use crate::{
Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Rectangle, Shell, Widget,
};
pub use iced_style::toggler::{Style, StyleSheet};
@@ -173,14 +173,14 @@ where
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
- messages.push((self.on_toggle)(!self.is_active));
+ shell.publish((self.on_toggle)(!self.is_active));
event::Status::Captured
} else {
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
index c35005e0..79a57824 100644
--- a/native/src/widget/tooltip.rs
+++ b/native/src/widget/tooltip.rs
@@ -11,8 +11,8 @@ use crate::text;
use crate::widget::container;
use crate::widget::text::Text;
use crate::{
- Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Size,
- Vector, Widget,
+ Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Shell,
+ Size, Vector, Widget,
};
/// An element to display a widget over another.
@@ -130,7 +130,7 @@ where
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
self.content.widget.on_event(
event,
@@ -138,7 +138,7 @@ where
cursor_position,
renderer,
clipboard,
- messages,
+ shell,
)
}