From ff2519b1d43d481987351a83b6dd7237524c21f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 06:49:20 +0200 Subject: [PATCH 01/19] Replace stateful widgets with new `iced_pure` API --- Cargo.toml | 14 - examples/{pure => }/arc/Cargo.toml | 2 +- examples/{pure => }/arc/README.md | 0 examples/{pure => }/arc/src/main.rs | 0 examples/bezier_tool/src/main.rs | 69 +- examples/clock/src/main.rs | 20 +- examples/color_palette/README.md | 2 +- examples/color_palette/src/main.rs | 79 +- examples/component/src/main.rs | 114 ++- examples/counter/src/main.rs | 32 +- examples/custom_widget/src/main.rs | 53 +- examples/download_progress/src/main.rs | 98 +-- examples/events/src/main.rs | 31 +- examples/exit/src/main.rs | 43 +- examples/game_of_life/src/main.rs | 270 +++--- examples/geometry/src/main.rs | 57 +- examples/integration_opengl/src/controls.rs | 13 +- examples/integration_wgpu/src/controls.rs | 21 +- examples/pane_grid/src/main.rs | 237 ++--- examples/pick_list/src/main.rs | 33 +- examples/pokedex/src/main.rs | 115 +-- examples/progress_bar/src/main.rs | 25 +- examples/pure/color_palette/Cargo.toml | 10 - examples/pure/color_palette/README.md | 15 - examples/pure/color_palette/screenshot.png | Bin 44798 -> 0 bytes examples/pure/color_palette/src/main.rs | 465 ---------- examples/pure/component/Cargo.toml | 12 - examples/pure/component/src/main.rs | 172 ---- examples/pure/counter/Cargo.toml | 9 - examples/pure/counter/src/main.rs | 49 -- examples/pure/game_of_life/Cargo.toml | 13 - examples/pure/game_of_life/README.md | 22 - examples/pure/game_of_life/src/main.rs | 903 -------------------- examples/pure/game_of_life/src/preset.rs | 142 --- examples/pure/pane_grid/Cargo.toml | 11 - examples/pure/pane_grid/src/main.rs | 369 -------- examples/pure/pick_list/Cargo.toml | 9 - examples/pure/pick_list/src/main.rs | 109 --- examples/pure/todos/Cargo.toml | 19 - examples/pure/todos/src/main.rs | 556 ------------ examples/pure/tooltip/Cargo.toml | 9 - examples/pure/tooltip/src/main.rs | 76 -- examples/pure/tour/Cargo.toml | 10 - examples/pure/tour/src/main.rs | 664 -------------- examples/qr_code/src/main.rs | 27 +- examples/scrollable/src/main.rs | 170 ++-- examples/solar_system/src/main.rs | 14 +- examples/stopwatch/src/main.rs | 38 +- examples/styling/src/main.rs | 109 +-- examples/svg/src/main.rs | 11 +- examples/system_information/src/main.rs | 76 +- examples/todos/src/main.rs | 285 +++--- examples/tooltip/src/main.rs | 134 +-- examples/tour/src/main.rs | 534 ++++-------- examples/url_handler/src/main.rs | 12 +- examples/websocket/src/main.rs | 59 +- graphics/Cargo.toml | 6 - graphics/src/lib.rs | 3 - graphics/src/renderer.rs | 2 +- graphics/src/widget/canvas.rs | 61 +- graphics/src/widget/canvas/cache.rs | 6 +- graphics/src/widget/canvas/frame.rs | 9 +- graphics/src/widget/canvas/path.rs | 2 +- graphics/src/widget/canvas/path/builder.rs | 2 +- graphics/src/widget/canvas/program.rs | 34 +- graphics/src/widget/pure/canvas/program.rs | 102 --- graphics/src/widget/qr_code.rs | 4 +- lazy/Cargo.toml | 8 - lazy/src/component.rs | 308 ++++--- lazy/src/lib.rs | 29 +- lazy/src/pure.rs | 31 - lazy/src/pure/component.rs | 479 ----------- lazy/src/pure/responsive.rs | 388 --------- lazy/src/responsive.rs | 433 +++++----- native/src/element.rs | 312 ++----- native/src/layout/flex.rs | 22 +- native/src/overlay.rs | 46 + native/src/overlay/menu.rs | 54 +- native/src/program.rs | 2 +- native/src/renderer.rs | 2 +- native/src/user_interface.rs | 103 ++- native/src/widget.rs | 53 +- native/src/widget/button.rs | 316 +++---- native/src/widget/checkbox.rs | 5 +- native/src/widget/column.rs | 74 +- native/src/widget/container.rs | 135 +-- {pure/src => native/src/widget}/helpers.rs | 82 +- native/src/widget/image.rs | 7 + native/src/widget/image/viewer.rs | 163 ++-- native/src/widget/pane_grid.rs | 408 +++++---- native/src/widget/pane_grid/content.rs | 84 +- native/src/widget/pane_grid/state.rs | 8 +- native/src/widget/pane_grid/title_bar.rs | 92 +- native/src/widget/pick_list.rs | 330 +++---- native/src/widget/progress_bar.rs | 2 + native/src/widget/radio.rs | 5 +- native/src/widget/row.rs | 93 +- native/src/widget/rule.rs | 2 + native/src/widget/scrollable.rs | 402 ++++----- native/src/widget/slider.rs | 232 ++--- native/src/widget/space.rs | 2 + native/src/widget/svg.rs | 6 +- native/src/widget/text.rs | 12 + native/src/widget/text_input.rs | 233 ++--- native/src/widget/toggler.rs | 5 +- native/src/widget/tooltip.rs | 271 +++--- {pure => native}/src/widget/tree.rs | 17 +- pure/Cargo.toml | 15 - pure/src/element.rs | 346 -------- pure/src/flex.rs | 232 ----- pure/src/lib.rs | 292 ------- pure/src/overlay.rs | 29 - pure/src/widget.rs | 149 ---- pure/src/widget/button.rs | 274 ------ pure/src/widget/checkbox.rs | 109 --- pure/src/widget/column.rs | 246 ------ pure/src/widget/container.rs | 263 ------ pure/src/widget/image.rs | 69 -- pure/src/widget/pane_grid.rs | 414 --------- pure/src/widget/pane_grid/content.rs | 345 -------- pure/src/widget/pane_grid/title_bar.rs | 388 --------- pure/src/widget/pick_list.rs | 240 ------ pure/src/widget/progress_bar.rs | 105 --- pure/src/widget/radio.rs | 109 --- pure/src/widget/row.rs | 233 ----- pure/src/widget/rule.rs | 105 --- pure/src/widget/scrollable.rs | 278 ------ pure/src/widget/slider.rs | 255 ------ pure/src/widget/space.rs | 101 --- pure/src/widget/svg.rs | 65 -- pure/src/widget/text.rs | 77 -- pure/src/widget/text_input.rs | 285 ------ pure/src/widget/toggler.rs | 109 --- pure/src/widget/tooltip.rs | 240 ------ src/application.rs | 13 +- src/lib.rs | 55 +- src/pure.rs | 112 --- src/pure/application.rs | 195 ----- src/pure/sandbox.rs | 123 --- src/pure/widget.rs | 174 ---- src/sandbox.rs | 10 +- src/widget.rs | 50 +- 142 files changed, 3585 insertions(+), 14448 deletions(-) rename examples/{pure => }/arc/Cargo.toml (63%) rename examples/{pure => }/arc/README.md (100%) rename examples/{pure => }/arc/src/main.rs (100%) delete mode 100644 examples/pure/color_palette/Cargo.toml delete mode 100644 examples/pure/color_palette/README.md delete mode 100644 examples/pure/color_palette/screenshot.png delete mode 100644 examples/pure/color_palette/src/main.rs delete mode 100644 examples/pure/component/Cargo.toml delete mode 100644 examples/pure/component/src/main.rs delete mode 100644 examples/pure/counter/Cargo.toml delete mode 100644 examples/pure/counter/src/main.rs delete mode 100644 examples/pure/game_of_life/Cargo.toml delete mode 100644 examples/pure/game_of_life/README.md delete mode 100644 examples/pure/game_of_life/src/main.rs delete mode 100644 examples/pure/game_of_life/src/preset.rs delete mode 100644 examples/pure/pane_grid/Cargo.toml delete mode 100644 examples/pure/pane_grid/src/main.rs delete mode 100644 examples/pure/pick_list/Cargo.toml delete mode 100644 examples/pure/pick_list/src/main.rs delete mode 100644 examples/pure/todos/Cargo.toml delete mode 100644 examples/pure/todos/src/main.rs delete mode 100644 examples/pure/tooltip/Cargo.toml delete mode 100644 examples/pure/tooltip/src/main.rs delete mode 100644 examples/pure/tour/Cargo.toml delete mode 100644 examples/pure/tour/src/main.rs delete mode 100644 graphics/src/widget/pure/canvas/program.rs delete mode 100644 lazy/src/pure.rs delete mode 100644 lazy/src/pure/component.rs delete mode 100644 lazy/src/pure/responsive.rs rename {pure/src => native/src/widget}/helpers.rs (76%) rename {pure => native}/src/widget/tree.rs (92%) delete mode 100644 pure/Cargo.toml delete mode 100644 pure/src/element.rs delete mode 100644 pure/src/flex.rs delete mode 100644 pure/src/lib.rs delete mode 100644 pure/src/overlay.rs delete mode 100644 pure/src/widget.rs delete mode 100644 pure/src/widget/button.rs delete mode 100644 pure/src/widget/checkbox.rs delete mode 100644 pure/src/widget/column.rs delete mode 100644 pure/src/widget/container.rs delete mode 100644 pure/src/widget/image.rs delete mode 100644 pure/src/widget/pane_grid.rs delete mode 100644 pure/src/widget/pane_grid/content.rs delete mode 100644 pure/src/widget/pane_grid/title_bar.rs delete mode 100644 pure/src/widget/pick_list.rs delete mode 100644 pure/src/widget/progress_bar.rs delete mode 100644 pure/src/widget/radio.rs delete mode 100644 pure/src/widget/row.rs delete mode 100644 pure/src/widget/rule.rs delete mode 100644 pure/src/widget/scrollable.rs delete mode 100644 pure/src/widget/slider.rs delete mode 100644 pure/src/widget/space.rs delete mode 100644 pure/src/widget/svg.rs delete mode 100644 pure/src/widget/text.rs delete mode 100644 pure/src/widget/text_input.rs delete mode 100644 pure/src/widget/toggler.rs delete mode 100644 pure/src/widget/tooltip.rs delete mode 100644 src/pure.rs delete mode 100644 src/pure/application.rs delete mode 100644 src/pure/sandbox.rs delete mode 100644 src/pure/widget.rs diff --git a/Cargo.toml b/Cargo.toml index 2f6727eb94..2482185eef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,6 @@ async-std = ["iced_futures/async-std"] smol = ["iced_futures/smol"] # Enables advanced color conversion via `palette` palette = ["iced_core/palette"] -# Enables pure, virtual widgets in the `pure` module -pure = ["iced_pure", "iced_graphics/pure"] # Enables querying system information system = ["iced_winit/system"] @@ -57,7 +55,6 @@ members = [ "glutin", "lazy", "native", - "pure", "style", "wgpu", "winit", @@ -90,16 +87,6 @@ members = [ "examples/tour", "examples/url_handler", "examples/websocket", - "examples/pure/arc", - "examples/pure/color_palette", - "examples/pure/component", - "examples/pure/counter", - "examples/pure/game_of_life", - "examples/pure/pane_grid", - "examples/pure/pick_list", - "examples/pure/todos", - "examples/pure/tooltip", - "examples/pure/tour", ] [dependencies] @@ -110,7 +97,6 @@ iced_graphics = { version = "0.3", path = "graphics" } iced_winit = { version = "0.4", path = "winit" } iced_glutin = { version = "0.3", path = "glutin", optional = true } iced_glow = { version = "0.3", path = "glow", optional = true } -iced_pure = { version = "0.2", path = "pure", optional = true } thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/pure/arc/Cargo.toml b/examples/arc/Cargo.toml similarity index 63% rename from examples/pure/arc/Cargo.toml rename to examples/arc/Cargo.toml index 22113cf1cb..299f5a3e7b 100644 --- a/examples/pure/arc/Cargo.toml +++ b/examples/arc/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../../..", features = ["pure", "canvas", "tokio", "debug"] } +iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } diff --git a/examples/pure/arc/README.md b/examples/arc/README.md similarity index 100% rename from examples/pure/arc/README.md rename to examples/arc/README.md diff --git a/examples/pure/arc/src/main.rs b/examples/arc/src/main.rs similarity index 100% rename from examples/pure/arc/src/main.rs rename to examples/arc/src/main.rs diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 11e4828e4e..7c3916d4a2 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -1,7 +1,6 @@ //! This example showcases an interactive `Canvas` for drawing Bézier curves. -use iced::{ - button, Alignment, Button, Column, Element, Length, Sandbox, Settings, Text, -}; +use iced::widget::{button, column, text}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Example::run(Settings { @@ -14,7 +13,6 @@ pub fn main() -> iced::Result { struct Example { bezier: bezier::State, curves: Vec, - button_state: button::State, } #[derive(Debug, Clone, Copy)] @@ -47,44 +45,34 @@ impl Sandbox for Example { } } - fn view(&mut self) -> Element { - Column::new() - .padding(20) - .spacing(20) - .align_items(Alignment::Center) - .push( - Text::new("Bezier tool example") - .width(Length::Shrink) - .size(50), - ) - .push(self.bezier.view(&self.curves).map(Message::AddCurve)) - .push( - Button::new(&mut self.button_state, Text::new("Clear")) - .padding(8) - .on_press(Message::Clear), - ) - .into() + fn view(&self) -> Element { + column![ + text("Bezier tool example").width(Length::Shrink).size(50), + self.bezier.view(&self.curves).map(Message::AddCurve), + button("Clear").padding(8).on_press(Message::Clear), + ] + .padding(20) + .spacing(20) + .align_items(Alignment::Center) + .into() } } mod bezier { - use iced::{ - canvas::event::{self, Event}, - canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke}, - mouse, Element, Length, Point, Rectangle, Theme, + use iced::mouse; + use iced::widget::canvas::event::{self, Event}; + use iced::widget::canvas::{ + self, Canvas, Cursor, Frame, Geometry, Path, Stroke, }; + use iced::{Element, Length, Point, Rectangle, Theme}; #[derive(Default)] pub struct State { - pending: Option, cache: canvas::Cache, } impl State { - pub fn view<'a>( - &'a mut self, - curves: &'a [Curve], - ) -> Element<'a, Curve> { + pub fn view<'a>(&'a self, curves: &'a [Curve]) -> Element<'a, Curve> { Canvas::new(Bezier { state: self, curves, @@ -100,13 +88,16 @@ mod bezier { } struct Bezier<'a> { - state: &'a mut State, + state: &'a State, curves: &'a [Curve], } impl<'a> canvas::Program for Bezier<'a> { + type State = Option; + fn update( - &mut self, + &self, + state: &mut Self::State, event: Event, bounds: Rectangle, cursor: Cursor, @@ -122,16 +113,16 @@ mod bezier { Event::Mouse(mouse_event) => { let message = match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { - match self.state.pending { + match *state { None => { - self.state.pending = Some(Pending::One { + *state = Some(Pending::One { from: cursor_position, }); None } Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { + *state = Some(Pending::Two { from, to: cursor_position, }); @@ -139,7 +130,7 @@ mod bezier { None } Some(Pending::Two { from, to }) => { - self.state.pending = None; + *state = None; Some(Curve { from, @@ -160,6 +151,7 @@ mod bezier { fn draw( &self, + state: &Self::State, _theme: &Theme, bounds: Rectangle, cursor: Cursor, @@ -170,11 +162,11 @@ mod bezier { frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), - Stroke::default(), + Stroke::default().with_width(2.0), ); }); - if let Some(pending) = &self.state.pending { + if let Some(pending) = state { let pending_curve = pending.draw(bounds, cursor); vec![content, pending_curve] @@ -185,6 +177,7 @@ mod bezier { fn mouse_interaction( &self, + _state: &Self::State, bounds: Rectangle, cursor: Cursor, ) -> mouse::Interaction { diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 48b4cd7bf4..8818fb54ed 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,10 +1,9 @@ -use iced::canvas::{ - self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke, -}; use iced::executor; +use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke}; +use iced::widget::{canvas, container}; use iced::{ - Application, Color, Command, Container, Element, Length, Point, Rectangle, - Settings, Subscription, Theme, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Settings, + Subscription, Theme, Vector, }; pub fn main() -> iced::Result { @@ -69,10 +68,12 @@ impl Application for Clock { }) } - fn view(&mut self) -> Element { - let canvas = Canvas::new(self).width(Length::Fill).height(Length::Fill); + fn view(&self) -> Element { + let canvas = canvas(self as &Self) + .width(Length::Fill) + .height(Length::Fill); - Container::new(canvas) + container(canvas) .width(Length::Fill) .height(Length::Fill) .padding(20) @@ -81,8 +82,11 @@ impl Application for Clock { } impl canvas::Program for Clock { + type State = (); + fn draw( &self, + _state: &Self::State, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md index 95a23f48d0..f90020b1d6 100644 --- a/examples/color_palette/README.md +++ b/examples/color_palette/README.md @@ -11,5 +11,5 @@ A color palette generator, based on a user-defined root color. You can run it with `cargo run`: ``` -cargo run --package color_palette +cargo run --package pure_color_palette ``` diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index cc1af750a3..421499655b 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,7 +1,8 @@ -use iced::canvas::{self, Cursor, Frame, Geometry, Path}; +use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; +use iced::widget::{column, row, text, Slider}; use iced::{ - alignment, slider, Alignment, Canvas, Color, Column, Element, Length, - Point, Rectangle, Row, Sandbox, Settings, Size, Slider, Text, Vector, + alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, + Settings, Size, Vector, }; use palette::{self, convert::FromColor, Hsl, Srgb}; use std::marker::PhantomData; @@ -59,7 +60,7 @@ impl Sandbox for ColorPalette { self.theme = Theme::new(srgb); } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let base = self.theme.base; let srgb = palette::Srgb::from(base); @@ -69,17 +70,18 @@ impl Sandbox for ColorPalette { let lab = palette::Lab::from_color(srgb); let lch = palette::Lch::from_color(srgb); - Column::new() - .padding(10) - .spacing(10) - .push(self.rgb.view(base).map(Message::RgbColorChanged)) - .push(self.hsl.view(hsl).map(Message::HslColorChanged)) - .push(self.hsv.view(hsv).map(Message::HsvColorChanged)) - .push(self.hwb.view(hwb).map(Message::HwbColorChanged)) - .push(self.lab.view(lab).map(Message::LabColorChanged)) - .push(self.lch.view(lch).map(Message::LchColorChanged)) - .push(self.theme.view()) - .into() + column![ + self.rgb.view(base).map(Message::RgbColorChanged), + self.hsl.view(hsl).map(Message::HslColorChanged), + self.hsv.view(hsv).map(Message::HsvColorChanged), + self.hwb.view(hwb).map(Message::HwbColorChanged), + self.lab.view(lab).map(Message::LabColorChanged), + self.lch.view(lch).map(Message::LchColorChanged), + self.theme.view(), + ] + .padding(10) + .spacing(10) + .into() } } @@ -139,7 +141,7 @@ impl Theme { .chain(self.higher.iter()) } - pub fn view(&mut self) -> Element { + pub fn view(&self) -> Element { Canvas::new(self) .width(Length::Fill) .height(Length::Fill) @@ -236,8 +238,11 @@ impl Theme { } impl canvas::Program for Theme { + type State = (); + fn draw( &self, + _state: &Self::State, _theme: &iced::Theme, bounds: Rectangle, _cursor: Cursor, @@ -267,7 +272,6 @@ fn color_hex_string(color: &Color) -> String { #[derive(Default)] struct ColorPicker { - sliders: [slider::State; 3], color_space: PhantomData, } @@ -282,37 +286,30 @@ trait ColorSpace: Sized { fn to_string(&self) -> String; } -impl ColorPicker { - fn view(&mut self, color: C) -> Element { +impl ColorPicker { + fn view(&self, color: C) -> Element { let [c1, c2, c3] = color.components(); - let [s1, s2, s3] = &mut self.sliders; let [cr1, cr2, cr3] = C::COMPONENT_RANGES; - fn slider( - state: &mut slider::State, + fn slider<'a, C: Clone>( range: RangeInclusive, component: f32, - update: impl Fn(f32) -> C + 'static, - ) -> Slider { - Slider::new(state, range, f64::from(component), move |v| { - update(v as f32) - }) - .step(0.01) + update: impl Fn(f32) -> C + 'a, + ) -> Slider<'a, f64, C, iced::Renderer> { + Slider::new(range, f64::from(component), move |v| update(v as f32)) + .step(0.01) } - Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new(C::LABEL).width(Length::Units(50))) - .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3))) - .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3))) - .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v))) - .push( - Text::new(color.to_string()) - .width(Length::Units(185)) - .size(14), - ) - .into() + row![ + text(C::LABEL).width(Length::Units(50)), + slider(cr1, c1, move |v| C::new(v, c2, c3)), + slider(cr2, c2, move |v| C::new(c1, v, c3)), + slider(cr3, c3, move |v| C::new(c1, c2, v)), + text(color.to_string()).width(Length::Units(185)).size(14), + ] + .spacing(10) + .align_items(Alignment::Center) + .into() } } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index 6a8f53e21a..06b1e53a41 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -1,5 +1,7 @@ -use iced::{Container, Element, Length, Sandbox, Settings}; -use numeric_input::NumericInput; +use iced::widget::container; +use iced::{Element, Length, Sandbox, Settings}; + +use numeric_input::numeric_input; pub fn main() -> iced::Result { Component::run(Settings::default()) @@ -7,7 +9,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Component { - numeric_input: numeric_input::State, value: Option, } @@ -35,39 +36,31 @@ impl Sandbox for Component { } } - fn view(&mut self) -> Element { - Container::new(NumericInput::new( - &mut self.numeric_input, - self.value, - Message::NumericInputChanged, - )) - .padding(20) - .height(Length::Fill) - .center_y() - .into() + fn view(&self) -> Element { + container(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::{self, Row, Text}; - use iced_native::{Element, Length}; - - pub struct NumericInput<'a, Message> { - state: &'a mut State, + use iced::alignment::{self, Alignment}; + use iced::widget::{self, button, row, text, text_input}; + use iced::{Element, Length}; + use iced_lazy::{self, Component}; + + pub struct NumericInput { value: Option, on_change: Box) -> Message>, } - #[derive(Default)] - pub struct State { - input: text_input::State, - decrement_button: button::State, - increment_button: button::State, + pub fn numeric_input( + value: Option, + on_change: impl Fn(Option) -> Message + 'static, + ) -> NumericInput { + NumericInput::new(value, on_change) } #[derive(Debug, Clone)] @@ -77,31 +70,33 @@ mod numeric_input { DecrementPressed, } - impl<'a, Message> NumericInput<'a, Message> { + impl NumericInput { pub fn new( - state: &'a mut State, value: Option, on_change: impl Fn(Option) -> Message + 'static, ) -> Self { Self { - state, value, on_change: Box::new(on_change), } } } - impl<'a, Message, Renderer> Component - for NumericInput<'a, Message> + impl Component for NumericInput where - Renderer: 'a + text::Renderer, - Renderer::Theme: button::StyleSheet - + text_input::StyleSheet + Renderer: iced_native::text::Renderer + 'static, + Renderer::Theme: widget::button::StyleSheet + + widget::text_input::StyleSheet + widget::text::StyleSheet, { + type State = (); type Event = Event; - fn update(&mut self, event: Event) -> Option { + fn update( + &mut self, + _state: &mut Self::State, + event: Event, + ) -> Option { match event { Event::IncrementPressed => Some((self.on_change)(Some( self.value.unwrap_or_default().saturating_add(1), @@ -123,11 +118,10 @@ mod numeric_input { } } - fn view(&mut self) -> Element { - let button = |state, label, on_press| { - Button::new( - state, - Text::new(label) + fn view(&self, _state: &Self::State) -> Element { + let button = |label, on_press| { + button( + text(label) .width(Length::Fill) .height(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center) @@ -137,15 +131,9 @@ mod numeric_input { .on_press(on_press) }; - Row::with_children(vec![ - button( - &mut self.state.decrement_button, - "-", - Event::DecrementPressed, - ) - .into(), - TextInput::new( - &mut self.state.input, + row![ + button("-", Event::DecrementPressed), + text_input( "Type a number", self.value .as_ref() @@ -154,32 +142,26 @@ mod numeric_input { .unwrap_or(""), Event::InputChanged, ) - .padding(10) - .into(), - button( - &mut self.state.increment_button, - "+", - Event::IncrementPressed, - ) - .into(), - ]) + .padding(10), + button("+", Event::IncrementPressed), + ] .align_items(Alignment::Fill) .spacing(10) .into() } } - impl<'a, Message, Renderer> From> + impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: button::StyleSheet - + text_input::StyleSheet + Renderer: 'static + iced_native::text::Renderer, + Renderer::Theme: widget::button::StyleSheet + + widget::text_input::StyleSheet + widget::text::StyleSheet, { - fn from(numeric_input: NumericInput<'a, Message>) -> Self { - component::view(numeric_input) + fn from(numeric_input: NumericInput) -> Self { + iced_lazy::component(numeric_input) } } } diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index e92f07f25d..f5ba7fae2a 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,15 +1,12 @@ -use iced::button::{self, Button}; -use iced::{Alignment, Column, Element, Sandbox, Settings, Text}; +use iced::widget::{button, column, text}; +use iced::{Alignment, Element, Sandbox, Settings}; pub fn main() -> iced::Result { Counter::run(Settings::default()) } -#[derive(Default)] struct Counter { value: i32, - increment_button: button::State, - decrement_button: button::State, } #[derive(Debug, Clone, Copy)] @@ -22,7 +19,7 @@ impl Sandbox for Counter { type Message = Message; fn new() -> Self { - Self::default() + Self { value: 0 } } fn title(&self) -> String { @@ -40,19 +37,14 @@ impl Sandbox for Counter { } } - fn view(&mut self) -> Element { - Column::new() - .padding(20) - .align_items(Alignment::Center) - .push( - Button::new(&mut self.increment_button, Text::new("Increment")) - .on_press(Message::IncrementPressed), - ) - .push(Text::new(self.value.to_string()).size(50)) - .push( - Button::new(&mut self.decrement_button, Text::new("Decrement")) - .on_press(Message::DecrementPressed), - ) - .into() + fn view(&self) -> Element { + column![ + button("Increment").on_press(Message::IncrementPressed), + text(self.value.to_string()).size(50), + button("Decrement").on_press(Message::DecrementPressed) + ] + .padding(20) + .align_items(Alignment::Center) + .into() } } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index d1a7bb065f..c37a1a1212 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -11,7 +11,8 @@ mod circle { // implemented by `iced_wgpu` and other renderers. use iced_native::layout::{self, Layout}; use iced_native::renderer; - use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget}; + use iced_native::widget::{self, Widget}; + use iced_native::{Color, Element, Length, Point, Rectangle, Size}; pub struct Circle { radius: f32, @@ -23,6 +24,10 @@ mod circle { } } + pub fn circle(radius: f32) -> Circle { + Circle::new(radius) + } + impl Widget for Circle where Renderer: renderer::Renderer, @@ -45,6 +50,7 @@ mod circle { fn draw( &self, + _state: &widget::Tree, renderer: &mut Renderer, _theme: &Renderer::Theme, _style: &renderer::Style, @@ -74,11 +80,9 @@ mod circle { } } -use circle::Circle; -use iced::{ - slider, Alignment, Column, Container, Element, Length, Sandbox, Settings, - Slider, Text, -}; +use circle::circle; +use iced::widget::{column, container, slider, text}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -86,7 +90,6 @@ pub fn main() -> iced::Result { struct Example { radius: f32, - slider: slider::State, } #[derive(Debug, Clone, Copy)] @@ -98,10 +101,7 @@ impl Sandbox for Example { type Message = Message; fn new() -> Self { - Example { - radius: 50.0, - slider: slider::State::new(), - } + Example { radius: 50.0 } } fn title(&self) -> String { @@ -116,25 +116,18 @@ impl Sandbox for Example { } } - fn view(&mut self) -> Element { - let content = Column::new() - .padding(20) - .spacing(20) - .max_width(500) - .align_items(Alignment::Center) - .push(Circle::new(self.radius)) - .push(Text::new(format!("Radius: {:.2}", self.radius))) - .push( - Slider::new( - &mut self.slider, - 1.0..=100.0, - self.radius, - Message::RadiusChanged, - ) - .step(0.01), - ); - - Container::new(content) + fn view(&self) -> Element { + let content = column![ + circle(self.radius), + text(format!("Radius: {:.2}", self.radius)), + slider(1.0..=100.0, self.radius, Message::RadiusChanged).step(0.01), + ] + .padding(20) + .spacing(20) + .max_width(500) + .align_items(Alignment::Center); + + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 2999bc7e80..3ef9ef7a30 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,8 +1,8 @@ -use iced::button; use iced::executor; +use iced::widget::{button, column, container, progress_bar, text, Column}; use iced::{ - Alignment, Application, Button, Column, Command, Container, Element, - Length, ProgressBar, Settings, Subscription, Text, Theme, + Alignment, Application, Command, Element, Length, Settings, Subscription, + Theme, }; mod download; @@ -15,7 +15,6 @@ pub fn main() -> iced::Result { struct Example { downloads: Vec, last_id: usize, - add: button::State, } #[derive(Debug, Clone)] @@ -36,7 +35,6 @@ impl Application for Example { Example { downloads: vec![Download::new(0)], last_id: 0, - add: button::State::new(), }, Command::none(), ) @@ -74,21 +72,19 @@ impl Application for Example { Subscription::batch(self.downloads.iter().map(Download::subscription)) } - fn view(&mut self) -> Element { - let downloads = self - .downloads - .iter_mut() - .fold(Column::new().spacing(20), |column, download| { - column.push(download.view()) - }) - .push( - Button::new(&mut self.add, Text::new("Add another download")) - .on_press(Message::Add) - .padding(10), - ) - .align_items(Alignment::End); - - Container::new(downloads) + fn view(&self) -> Element { + let downloads = Column::with_children( + self.downloads.iter().map(Download::view).collect(), + ) + .push( + button("Add another download") + .on_press(Message::Add) + .padding(10), + ) + .spacing(20) + .align_items(Alignment::End); + + container(downloads) .width(Length::Fill) .height(Length::Fill) .center_x() @@ -106,19 +102,17 @@ struct Download { #[derive(Debug)] enum State { - Idle { button: button::State }, + Idle, Downloading { progress: f32 }, - Finished { button: button::State }, - Errored { button: button::State }, + Finished, + Errored, } impl Download { pub fn new(id: usize) -> Self { Download { id, - state: State::Idle { - button: button::State::new(), - }, + state: State::Idle, } } @@ -143,14 +137,10 @@ impl Download { *progress = percentage; } download::Progress::Finished => { - self.state = State::Finished { - button: button::State::new(), - } + self.state = State::Finished; } download::Progress::Errored => { - self.state = State::Errored { - button: button::State::new(), - }; + self.state = State::Errored; } } } @@ -166,7 +156,7 @@ impl Download { } } - pub fn view(&mut self) -> Element { + pub fn view(&self) -> Element { let current_progress = match &self.state { State::Idle { .. } => 0.0, State::Downloading { progress } => *progress, @@ -174,36 +164,28 @@ impl Download { State::Errored { .. } => 0.0, }; - let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); + let progress_bar = progress_bar(0.0..=100.0, current_progress); - let control: Element<_> = match &mut self.state { - State::Idle { button } => { - Button::new(button, Text::new("Start the download!")) - .on_press(Message::Download(self.id)) + let control: Element<_> = match &self.state { + State::Idle => button("Start the download!") + .on_press(Message::Download(self.id)) + .into(), + State::Finished => { + column!["Download finished!", button("Start again")] + .spacing(10) + .align_items(Alignment::Center) .into() } - State::Finished { button } => Column::new() - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new("Download finished!")) - .push( - Button::new(button, Text::new("Start again")) - .on_press(Message::Download(self.id)), - ) - .into(), State::Downloading { .. } => { - Text::new(format!("Downloading... {:.2}%", current_progress)) - .into() + text(format!("Downloading... {:.2}%", current_progress)).into() } - State::Errored { button } => Column::new() - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new("Something went wrong :(")) - .push( - Button::new(button, Text::new("Try again")) - .on_press(Message::Download(self.id)), - ) - .into(), + State::Errored => column![ + "Something went wrong :(", + button("Try again").on_press(Message::Download(self.id)), + ] + .spacing(10) + .align_items(Alignment::Center) + .into(), }; Column::new() diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index c87fbc7258..234e14239d 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,9 +1,9 @@ use iced::alignment; -use iced::button; use iced::executor; +use iced::widget::{button, checkbox, container, text, Column}; use iced::{ - Alignment, Application, Button, Checkbox, Column, Command, Container, - Element, Length, Settings, Subscription, Text, Theme, + Alignment, Application, Command, Element, Length, Settings, Subscription, + Theme, }; use iced_native::{window, Event}; @@ -18,7 +18,6 @@ pub fn main() -> iced::Result { struct Events { last: Vec, enabled: bool, - exit: button::State, should_exit: bool, } @@ -76,23 +75,23 @@ impl Application for Events { self.should_exit } - fn view(&mut self) -> Element { - let events = self.last.iter().fold( - Column::new().spacing(10), - |column, event| { - column.push(Text::new(format!("{:?}", event)).size(40)) - }, + fn view(&self) -> Element { + let events = Column::with_children( + self.last + .iter() + .map(|event| text(format!("{:?}", event)).size(40)) + .map(Element::from) + .collect(), ); - let toggle = Checkbox::new( - self.enabled, + let toggle = checkbox( "Listen to runtime events", + self.enabled, Message::Toggled, ); - let exit = Button::new( - &mut self.exit, - Text::new("Exit") + let exit = button( + text("Exit") .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) @@ -107,7 +106,7 @@ impl Application for Events { .push(toggle) .push(exit); - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index c45a8205db..5d518d2fd0 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -1,7 +1,5 @@ -use iced::{ - button, Alignment, Button, Column, Container, Element, Length, Sandbox, - Settings, Text, -}; +use iced::widget::{button, column, container}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Exit::run(Settings::default()) @@ -11,8 +9,6 @@ pub fn main() -> iced::Result { struct Exit { show_confirm: bool, exit: bool, - confirm_button: button::State, - exit_button: button::State, } #[derive(Debug, Clone, Copy)] @@ -47,33 +43,24 @@ impl Sandbox for Exit { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let content = if self.show_confirm { - Column::new() - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new("Are you sure you want to exit?")) - .push( - Button::new( - &mut self.confirm_button, - Text::new("Yes, exit now"), - ) + column![ + "Are you sure you want to exit?", + button("Yes, exit now") .padding([10, 20]) .on_press(Message::Confirm), - ) + ] } else { - Column::new() - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new("Click the button to exit")) - .push( - Button::new(&mut self.exit_button, Text::new("Exit")) - .padding([10, 20]) - .on_press(Message::Exit), - ) - }; + column![ + "Click the button to exit", + button("Exit").padding([10, 20]).on_press(Message::Exit), + ] + } + .spacing(10) + .align_items(Alignment::Center); - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .padding(20) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 62ecc2d158..a2030275dd 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -3,18 +3,18 @@ mod preset; use grid::Grid; -use iced::button::{self, Button}; +use preset::Preset; + use iced::executor; -use iced::pick_list::{self, PickList}; -use iced::slider::{self, Slider}; use iced::theme::{self, Theme}; use iced::time; +use iced::widget::{ + button, checkbox, column, container, pick_list, row, slider, text, +}; use iced::window; use iced::{ - Alignment, Application, Checkbox, Column, Command, Element, Length, Row, - Settings, Subscription, Text, + Alignment, Application, Command, Element, Length, Settings, Subscription, }; -use preset::Preset; use std::time::{Duration, Instant}; pub fn main() -> iced::Result { @@ -33,7 +33,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct GameOfLife { grid: Grid, - controls: Controls, is_playing: bool, queued_ticks: usize, speed: usize, @@ -132,23 +131,26 @@ impl Application for GameOfLife { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let version = self.version; let selected_speed = self.next_speed.unwrap_or(self.speed); - let controls = self.controls.view( + let controls = view_controls( self.is_playing, self.grid.are_lines_visible(), selected_speed, self.grid.preset(), ); - Column::new() - .push( - self.grid - .view() - .map(move |message| Message::Grid(message, version)), - ) - .push(controls) + let content = column![ + self.grid + .view() + .map(move |message| Message::Grid(message, version)), + controls + ]; + + container(content) + .width(Length::Fill) + .height(Length::Fill) .into() } @@ -157,13 +159,59 @@ impl Application for GameOfLife { } } +fn view_controls<'a>( + is_playing: bool, + is_grid_enabled: bool, + speed: usize, + preset: Preset, +) -> Element<'a, Message> { + let playback_controls = row![ + button(if is_playing { "Pause" } else { "Play" }) + .on_press(Message::TogglePlayback), + button("Next") + .on_press(Message::Next) + .style(theme::Button::Secondary), + ] + .spacing(10); + + let speed_controls = row![ + slider(1.0..=1000.0, speed as f32, Message::SpeedChanged), + text(format!("x{}", speed)).size(16), + ] + .width(Length::Fill) + .align_items(Alignment::Center) + .spacing(10); + + row![ + playback_controls, + speed_controls, + checkbox("Grid", is_grid_enabled, Message::ToggleGrid) + .size(16) + .spacing(5) + .text_size(16), + pick_list(preset::ALL, Some(preset), Message::PresetPicked) + .padding(8) + .text_size(16), + button("Clear") + .on_press(Message::Clear) + .style(theme::Button::Destructive), + ] + .padding(10) + .spacing(20) + .align_items(Alignment::Center) + .into() +} + mod grid { use crate::Preset; + use iced::widget::canvas; + use iced::widget::canvas::event::{self, Event}; + use iced::widget::canvas::{ + Cache, Canvas, Cursor, Frame, Geometry, Path, Text, + }; use iced::{ - alignment, - canvas::event::{self, Event}, - canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text}, - mouse, Color, Element, Length, Point, Rectangle, Size, Theme, Vector, + alignment, mouse, Color, Element, Length, Point, Rectangle, Size, + Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -173,7 +221,6 @@ mod grid { pub struct Grid { state: State, preset: Preset, - interaction: Interaction, life_cache: Cache, grid_cache: Cache, translation: Vector, @@ -187,6 +234,8 @@ mod grid { pub enum Message { Populate(Cell), Unpopulate(Cell), + Translated(Vector), + Scaled(f32, Option), Ticked { result: Result, tick_duration: Duration, @@ -218,7 +267,6 @@ mod grid { .collect(), ), preset, - interaction: Interaction::None, life_cache: Cache::default(), grid_cache: Cache::default(), translation: Vector::default(), @@ -263,6 +311,22 @@ mod grid { self.preset = Preset::Custom; } + Message::Translated(translation) => { + self.translation = translation; + + self.life_cache.clear(); + self.grid_cache.clear(); + } + Message::Scaled(scaling, translation) => { + self.scaling = scaling; + + if let Some(translation) = translation { + self.translation = translation; + } + + self.life_cache.clear(); + self.grid_cache.clear(); + } Message::Ticked { result: Ok(life), tick_duration, @@ -280,7 +344,7 @@ mod grid { } } - pub fn view(&mut self) -> Element { + pub fn view(&self) -> Element { Canvas::new(self) .width(Length::Fill) .height(Length::Fill) @@ -329,14 +393,17 @@ mod grid { } impl canvas::Program for Grid { + type State = Interaction; + fn update( - &mut self, + &self, + interaction: &mut Interaction, event: Event, bounds: Rectangle, cursor: Cursor, ) -> (event::Status, Option) { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { - self.interaction = Interaction::None; + *interaction = Interaction::None; } let cursor_position = @@ -360,7 +427,7 @@ mod grid { mouse::Event::ButtonPressed(button) => { let message = match button { mouse::Button::Left => { - self.interaction = if is_populated { + *interaction = if is_populated { Interaction::Erasing } else { Interaction::Drawing @@ -369,7 +436,7 @@ mod grid { populate.or(unpopulate) } mouse::Button::Right => { - self.interaction = Interaction::Panning { + *interaction = Interaction::Panning { translation: self.translation, start: cursor_position, }; @@ -382,23 +449,20 @@ mod grid { (event::Status::Captured, message) } mouse::Event::CursorMoved { .. } => { - let message = match self.interaction { + let message = match *interaction { Interaction::Drawing => populate, Interaction::Erasing => unpopulate, Interaction::Panning { translation, start } => { - self.translation = translation - + (cursor_position - start) - * (1.0 / self.scaling); - - self.life_cache.clear(); - self.grid_cache.clear(); - - None + Some(Message::Translated( + translation + + (cursor_position - start) + * (1.0 / self.scaling), + )) } _ => None, }; - let event_status = match self.interaction { + let event_status = match interaction { Interaction::None => event::Status::Ignored, _ => event::Status::Captured, }; @@ -413,30 +477,38 @@ mod grid { { let old_scaling = self.scaling; - self.scaling = (self.scaling - * (1.0 + y / 30.0)) + let scaling = (self.scaling * (1.0 + y / 30.0)) .max(Self::MIN_SCALING) .min(Self::MAX_SCALING); - if let Some(cursor_to_center) = - cursor.position_from(bounds.center()) - { - let factor = self.scaling - old_scaling; - - self.translation = self.translation - - Vector::new( - cursor_to_center.x * factor - / (old_scaling * old_scaling), - cursor_to_center.y * factor - / (old_scaling * old_scaling), - ); - } - - self.life_cache.clear(); - self.grid_cache.clear(); + let translation = + if let Some(cursor_to_center) = + cursor.position_from(bounds.center()) + { + let factor = scaling - old_scaling; + + Some( + self.translation + - Vector::new( + cursor_to_center.x * factor + / (old_scaling + * old_scaling), + cursor_to_center.y * factor + / (old_scaling + * old_scaling), + ), + ) + } else { + None + }; + + ( + event::Status::Captured, + Some(Message::Scaled(scaling, translation)), + ) + } else { + (event::Status::Captured, None) } - - (event::Status::Captured, None) } }, _ => (event::Status::Ignored, None), @@ -447,6 +519,7 @@ mod grid { fn draw( &self, + _interaction: &Interaction, _theme: &Theme, bounds: Rectangle, cursor: Cursor, @@ -576,10 +649,11 @@ mod grid { fn mouse_interaction( &self, + interaction: &Interaction, bounds: Rectangle, cursor: Cursor, ) -> mouse::Interaction { - match self.interaction { + match interaction { Interaction::Drawing => mouse::Interaction::Crosshair, Interaction::Erasing => mouse::Interaction::Crosshair, Interaction::Panning { .. } => mouse::Interaction::Grabbing, @@ -808,86 +882,16 @@ mod grid { } } - enum Interaction { + pub enum Interaction { None, Drawing, Erasing, Panning { translation: Vector, start: Point }, } -} - -#[derive(Default)] -struct Controls { - toggle_button: button::State, - next_button: button::State, - clear_button: button::State, - speed_slider: slider::State, - preset_list: pick_list::State, -} -impl Controls { - fn view( - &mut self, - is_playing: bool, - is_grid_enabled: bool, - speed: usize, - preset: Preset, - ) -> Element { - let playback_controls = Row::new() - .spacing(10) - .push( - Button::new( - &mut self.toggle_button, - Text::new(if is_playing { "Pause" } else { "Play" }), - ) - .on_press(Message::TogglePlayback) - .style(theme::Button::Primary), - ) - .push( - Button::new(&mut self.next_button, Text::new("Next")) - .on_press(Message::Next) - .style(theme::Button::Secondary), - ); - - let speed_controls = Row::new() - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10) - .push(Slider::new( - &mut self.speed_slider, - 1.0..=1000.0, - speed as f32, - Message::SpeedChanged, - )) - .push(Text::new(format!("x{}", speed)).size(16)); - - Row::new() - .padding(10) - .spacing(20) - .align_items(Alignment::Center) - .push(playback_controls) - .push(speed_controls) - .push( - Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid) - .size(16) - .spacing(5) - .text_size(16), - ) - .push( - PickList::new( - &mut self.preset_list, - preset::ALL, - Some(preset), - Message::PresetPicked, - ) - .padding(8) - .text_size(16), - ) - .push( - Button::new(&mut self.clear_button, Text::new("Clear")) - .on_press(Message::Clear) - .style(theme::Button::Destructive), - ) - .into() + impl Default for Interaction { + fn default() -> Self { + Self::None + } } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 03eac69ebc..d8b99ab31e 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -13,8 +13,9 @@ mod rainbow { use iced_graphics::renderer::{self, Renderer}; use iced_graphics::{Backend, Primitive}; + use iced_native::widget::{self, Widget}; use iced_native::{ - layout, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, + layout, Element, Layout, Length, Point, Rectangle, Size, Vector, }; #[derive(Default)] @@ -26,6 +27,10 @@ mod rainbow { } } + pub fn rainbow() -> Rainbow { + Rainbow + } + impl Widget> for Rainbow where B: Backend, @@ -50,6 +55,7 @@ mod rainbow { fn draw( &self, + _tree: &widget::Tree, renderer: &mut Renderer, _theme: &T, _style: &renderer::Style, @@ -159,27 +165,21 @@ mod rainbow { } } -use iced::{ - scrollable, Alignment, Column, Container, Element, Length, Sandbox, - Scrollable, Settings, Text, -}; -use rainbow::Rainbow; +use iced::widget::{column, container, scrollable}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; +use rainbow::rainbow; pub fn main() -> iced::Result { Example::run(Settings::default()) } -struct Example { - scroll: scrollable::State, -} +struct Example; impl Sandbox for Example { type Message = (); fn new() -> Self { - Example { - scroll: scrollable::State::new(), - } + Example } fn title(&self) -> String { @@ -188,32 +188,27 @@ impl Sandbox for Example { fn update(&mut self, _: ()) {} - fn view(&mut self) -> Element<()> { - let content = Column::new() - .padding(20) - .spacing(20) - .max_width(500) - .align_items(Alignment::Start) - .push(Rainbow::new()) - .push(Text::new( - "In this example we draw a custom widget Rainbow, using \ + fn view(&self) -> Element<()> { + let content = column![ + rainbow(), + "In this example we draw a custom widget Rainbow, using \ the Mesh2D primitive. This primitive supplies a list of \ triangles, expressed as vertices and indices.", - )) - .push(Text::new( - "Move your cursor over it, and see the center vertex \ + "Move your cursor over it, and see the center vertex \ follow you!", - )) - .push(Text::new( - "Every Vertex2D defines its own color. You could use the \ + "Every Vertex2D defines its own color. You could use the \ Mesh2D primitive to render virtually any two-dimensional \ geometry for your widget.", - )); + ] + .padding(20) + .spacing(20) + .max_width(500) + .align_items(Alignment::Start); - let scrollable = Scrollable::new(&mut self.scroll) - .push(Container::new(content).width(Length::Fill).center_x()); + let scrollable = + scrollable(container(content).width(Length::Fill).center_x()); - Container::new(scrollable) + container(scrollable) .width(Length::Fill) .height(Length::Fill) .center_y() diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs index fdaa29d57e..076d37d3f6 100644 --- a/examples/integration_opengl/src/controls.rs +++ b/examples/integration_opengl/src/controls.rs @@ -1,11 +1,10 @@ use iced_glow::Renderer; -use iced_glutin::widget::slider::{self, Slider}; +use iced_glutin::widget::Slider; use iced_glutin::widget::{Column, Row, Text}; use iced_glutin::{Alignment, Color, Command, Element, Length, Program}; pub struct Controls { background_color: Color, - sliders: [slider::State; 3], } #[derive(Debug, Clone)] @@ -17,7 +16,6 @@ impl Controls { pub fn new() -> Controls { Controls { background_color: Color::BLACK, - sliders: Default::default(), } } @@ -40,15 +38,14 @@ impl Program for Controls { Command::none() } - fn view(&mut self) -> Element { - let [r, g, b] = &mut self.sliders; + fn view(&self) -> Element { let background_color = self.background_color; let sliders = Row::new() .width(Length::Units(500)) .spacing(20) .push( - Slider::new(r, 0.0..=1.0, background_color.r, move |r| { + Slider::new(0.0..=1.0, background_color.r, move |r| { Message::BackgroundColorChanged(Color { r, ..background_color @@ -57,7 +54,7 @@ impl Program for Controls { .step(0.01), ) .push( - Slider::new(g, 0.0..=1.0, background_color.g, move |g| { + Slider::new(0.0..=1.0, background_color.g, move |g| { Message::BackgroundColorChanged(Color { g, ..background_color @@ -66,7 +63,7 @@ impl Program for Controls { .step(0.01), ) .push( - Slider::new(b, 0.0..=1.0, background_color.b, move |b| { + Slider::new(0.0..=1.0, background_color.b, move |b| { Message::BackgroundColorChanged(Color { b, ..background_color diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 2f1daa91dd..6c41738cb5 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -1,14 +1,10 @@ use iced_wgpu::Renderer; -use iced_winit::widget::slider::{self, Slider}; -use iced_winit::widget::text_input::{self, TextInput}; -use iced_winit::widget::{Column, Row, Text}; +use iced_winit::widget::{slider, text_input, Column, Row, Text}; use iced_winit::{Alignment, Color, Command, Element, Length, Program}; pub struct Controls { background_color: Color, text: String, - sliders: [slider::State; 3], - text_input: text_input::State, } #[derive(Debug, Clone)] @@ -22,8 +18,6 @@ impl Controls { Controls { background_color: Color::BLACK, text: Default::default(), - sliders: Default::default(), - text_input: Default::default(), } } @@ -49,9 +43,7 @@ impl Program for Controls { Command::none() } - fn view(&mut self) -> Element { - let [r, g, b] = &mut self.sliders; - let t = &mut self.text_input; + fn view(&self) -> Element { let background_color = self.background_color; let text = &self.text; @@ -59,7 +51,7 @@ impl Program for Controls { .width(Length::Units(500)) .spacing(20) .push( - Slider::new(r, 0.0..=1.0, background_color.r, move |r| { + slider(0.0..=1.0, background_color.r, move |r| { Message::BackgroundColorChanged(Color { r, ..background_color @@ -68,7 +60,7 @@ impl Program for Controls { .step(0.01), ) .push( - Slider::new(g, 0.0..=1.0, background_color.g, move |g| { + slider(0.0..=1.0, background_color.g, move |g| { Message::BackgroundColorChanged(Color { g, ..background_color @@ -77,7 +69,7 @@ impl Program for Controls { .step(0.01), ) .push( - Slider::new(b, 0.0..=1.0, background_color.b, move |b| { + slider(0.0..=1.0, background_color.b, move |b| { Message::BackgroundColorChanged(Color { b, ..background_color @@ -108,8 +100,7 @@ impl Program for Controls { .size(14) .style(Color::WHITE), ) - .push(TextInput::new( - t, + .push(text_input( "Placeholder", text, Message::TextChanged, diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 5fbcea2c4e..ae8fa22b0c 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,15 +1,13 @@ use iced::alignment::{self, Alignment}; -use iced::button::{self, Button}; use iced::executor; use iced::keyboard; -use iced::pane_grid::{self, PaneGrid}; -use iced::scrollable::{self, Scrollable}; use iced::theme::{self, Theme}; +use iced::widget::pane_grid::{self, PaneGrid}; +use iced::widget::{button, column, container, row, scrollable, text}; use iced::{ - Application, Color, Column, Command, Container, Element, Length, Row, - Settings, Size, Subscription, Text, + Application, Color, Command, Element, Length, Settings, Size, Subscription, }; -use iced_lazy::responsive::{self, Responsive}; +use iced_lazy::responsive; use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { @@ -155,42 +153,32 @@ impl Application for Example { }) } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| { + let pane_grid = PaneGrid::new(&self.panes, |id, pane| { let is_focused = focus == Some(id); - let Pane { - responsive, + let pin_button = button( + text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), + ) + .on_press(Message::TogglePin(id)) + .padding(3); + + let title = row![ pin_button, - is_pinned, - content, - .. - } = pane; - - let text = if *is_pinned { "Unpin" } else { "Pin" }; - let pin_button = Button::new(pin_button, Text::new(text).size(14)) - .on_press(Message::TogglePin(id)) - .style(theme::Button::Secondary) - .padding(3); - - let title = Row::with_children(vec![ - pin_button.into(), - Text::new("Pane").into(), - Text::new(content.id.to_string()) - .style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - }) - .into(), - ]) + "Pane", + text(pane.id.to_string()).style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }), + ] .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(pane.controls.view(id, total_panes, *is_pinned)) + .controls(view_controls(id, total_panes, pane.is_pinned)) .padding(10) .style(if is_focused { style::title_bar_focused @@ -198,8 +186,8 @@ impl Application for Example { style::title_bar_active }); - pane_grid::Content::new(Responsive::new(responsive, move |size| { - content.view(id, total_panes, *is_pinned, size) + pane_grid::Content::new(responsive(move |size| { + view_content(id, total_panes, pane.is_pinned, size) })) .title_bar(title_bar) .style(if is_focused { @@ -215,7 +203,7 @@ impl Application for Example { .on_drag(Message::Dragged) .on_resize(10, Message::Resized); - Container::new(pane_grid) + container(pane_grid) .width(Length::Fill) .height(Length::Fill) .padding(10) @@ -255,139 +243,92 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { } struct Pane { - pub responsive: responsive::State, - pub is_pinned: bool, - pub pin_button: button::State, - pub content: Content, - pub controls: Controls, -} - -struct Content { id: usize, - scroll: scrollable::State, - split_horizontally: button::State, - split_vertically: button::State, - close: button::State, -} - -struct Controls { - close: button::State, + pub is_pinned: bool, } impl Pane { fn new(id: usize) -> Self { Self { - responsive: responsive::State::new(), + id, is_pinned: false, - pin_button: button::State::new(), - content: Content::new(id), - controls: Controls::new(), } } } -impl Content { - fn new(id: usize) -> Self { - Content { - id, - scroll: scrollable::State::new(), - split_horizontally: button::State::new(), - split_vertically: button::State::new(), - close: button::State::new(), - } +fn view_content<'a>( + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, + size: Size, +) -> Element<'a, Message> { + let button = |label, message| { + button( + text(label) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center) + .size(16), + ) + .width(Length::Fill) + .padding(8) + .on_press(message) + }; + + let mut controls = column![ + button( + "Split horizontally", + Message::Split(pane_grid::Axis::Horizontal, pane), + ), + button( + "Split vertically", + Message::Split(pane_grid::Axis::Vertical, pane), + ) + ] + .spacing(5) + .max_width(150); + + if total_panes > 1 && !is_pinned { + controls = controls.push( + button("Close", Message::Close(pane)) + .style(theme::Button::Destructive), + ); } - fn view( - &mut self, - pane: pane_grid::Pane, - total_panes: usize, - is_pinned: bool, - size: Size, - ) -> Element { - let Content { - scroll, - split_horizontally, - split_vertically, - close, - .. - } = self; - - let button = |state, label, message| { - Button::new( - state, - Text::new(label) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .size(16), - ) - .width(Length::Fill) - .padding(8) - .on_press(message) - }; - - let mut controls = Column::new() - .spacing(5) - .max_width(150) - .push(button( - split_horizontally, - "Split horizontally", - Message::Split(pane_grid::Axis::Horizontal, pane), - )) - .push(button( - split_vertically, - "Split vertically", - Message::Split(pane_grid::Axis::Vertical, pane), - )); - - if total_panes > 1 && !is_pinned { - controls = controls.push( - button(close, "Close", Message::Close(pane)) - .style(theme::Button::Destructive), - ); - } - let content = Scrollable::new(scroll) - .width(Length::Fill) - .spacing(10) - .align_items(Alignment::Center) - .push(Text::new(format!("{}x{}", size.width, size.height)).size(24)) - .push(controls); + let content = column![ + text(format!("{}x{}", size.width, size.height)).size(24), + controls, + ] + .width(Length::Fill) + .spacing(10) + .align_items(Alignment::Center); - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .padding(5) - .center_y() - .into() - } + container(scrollable(content)) + .width(Length::Fill) + .height(Length::Fill) + .padding(5) + .center_y() + .into() } -impl Controls { - fn new() -> Self { - Self { - close: button::State::new(), - } +fn view_controls<'a>( + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, +) -> Element<'a, Message> { + let mut button = button(text("Close").size(14)) + .style(theme::Button::Destructive) + .padding(3); + + if total_panes > 1 && !is_pinned { + button = button.on_press(Message::Close(pane)); } - pub fn view( - &mut self, - pane: pane_grid::Pane, - total_panes: usize, - is_pinned: bool, - ) -> Element { - let mut button = - Button::new(&mut self.close, Text::new("Close").size(14)) - .style(theme::Button::Destructive) - .padding(3); - - if total_panes > 1 && !is_pinned { - button = button.on_press(Message::Close(pane)); - } - button.into() - } + button.into() } mod style { - use iced::{container, Theme}; + use iced::widget::container; + use iced::Theme; pub fn title_bar_active(theme: &Theme) -> container::Appearance { let palette = theme.extended_palette(); diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 52303d705f..9df1f5c721 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -1,7 +1,5 @@ -use iced::{ - pick_list, scrollable, Alignment, Container, Element, Length, PickList, - Sandbox, Scrollable, Settings, Space, Text, -}; +use iced::widget::{column, container, pick_list, scrollable, vertical_space}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -9,8 +7,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Example { - scroll: scrollable::State, - pick_list: pick_list::State, selected_language: Option, } @@ -38,26 +34,25 @@ impl Sandbox for Example { } } - fn view(&mut self) -> Element { - let pick_list = PickList::new( - &mut self.pick_list, + fn view(&self) -> Element { + let pick_list = pick_list( &Language::ALL[..], self.selected_language, Message::LanguageSelected, ) .placeholder("Choose a language..."); - let mut content = Scrollable::new(&mut self.scroll) - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10) - .push(Space::with_height(Length::Units(600))) - .push(Text::new("Which is your favorite language?")) - .push(pick_list); - - content = content.push(Space::with_height(Length::Units(600))); + let content = column![ + vertical_space(Length::Units(600)), + "Which is your favorite language?", + pick_list, + vertical_space(Length::Units(600)), + ] + .width(Length::Fill) + .align_items(Alignment::Center) + .spacing(10); - Container::new(content) + container(scrollable(content)) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index a1cf68e82c..0744d9915e 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,9 +1,7 @@ -use iced::button; use iced::futures; -use iced::image; +use iced::widget::{self, column, container, image, row, text}; use iced::{ - Alignment, Application, Button, Color, Column, Command, Container, Element, - Length, Row, Settings, Text, Theme, + Alignment, Application, Color, Command, Element, Length, Settings, Theme, }; pub fn main() -> iced::Result { @@ -13,13 +11,8 @@ pub fn main() -> iced::Result { #[derive(Debug)] enum Pokedex { Loading, - Loaded { - pokemon: Pokemon, - search: button::State, - }, - Errored { - try_again: button::State, - }, + Loaded { pokemon: Pokemon }, + Errored, } #[derive(Debug, Clone)] @@ -54,17 +47,12 @@ impl Application for Pokedex { fn update(&mut self, message: Message) -> Command { match message { Message::PokemonFound(Ok(pokemon)) => { - *self = Pokedex::Loaded { - pokemon, - search: button::State::new(), - }; + *self = Pokedex::Loaded { pokemon }; Command::none() } Message::PokemonFound(Err(_error)) => { - *self = Pokedex::Errored { - try_again: button::State::new(), - }; + *self = Pokedex::Errored; Command::none() } @@ -79,27 +67,28 @@ impl Application for Pokedex { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let content = match self { - Pokedex::Loading => Column::new() - .width(Length::Shrink) - .push(Text::new("Searching for Pokémon...").size(40)), - Pokedex::Loaded { pokemon, search } => Column::new() - .max_width(500) - .spacing(20) - .align_items(Alignment::End) - .push(pokemon.view()) - .push( - button(search, "Keep searching!").on_press(Message::Search), - ), - Pokedex::Errored { try_again, .. } => Column::new() - .spacing(20) - .align_items(Alignment::End) - .push(Text::new("Whoops! Something went wrong...").size(40)) - .push(button(try_again, "Try again").on_press(Message::Search)), + Pokedex::Loading => { + column![text("Searching for Pokémon...").size(40),] + .width(Length::Shrink) + } + Pokedex::Loaded { pokemon } => column![ + pokemon.view(), + button("Keep searching!").on_press(Message::Search) + ] + .max_width(500) + .spacing(20) + .align_items(Alignment::End), + Pokedex::Errored => column![ + text("Whoops! Something went wrong...").size(40), + button("Try again").on_press(Message::Search) + ] + .spacing(20) + .align_items(Alignment::End), }; - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() @@ -114,41 +103,30 @@ struct Pokemon { name: String, description: String, image: image::Handle, - image_viewer: image::viewer::State, } impl Pokemon { const TOTAL: u16 = 807; - fn view(&mut self) -> Element { - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push(image::Viewer::new( - &mut self.image_viewer, - self.image.clone(), - )) - .push( - Column::new() - .spacing(20) - .push( - Row::new() - .align_items(Alignment::Center) - .spacing(20) - .push( - Text::new(&self.name) - .size(30) - .width(Length::Fill), - ) - .push( - Text::new(format!("#{}", self.number)) - .size(20) - .style(Color::from([0.5, 0.5, 0.5])), - ), - ) - .push(Text::new(&self.description)), - ) - .into() + fn view(&self) -> Element { + row![ + image::viewer(self.image.clone()), + column![ + row![ + text(&self.name).size(30).width(Length::Fill), + text(format!("#{}", self.number)) + .size(20) + .style(Color::from([0.5, 0.5, 0.5])), + ] + .align_items(Alignment::Center) + .spacing(20), + self.description.as_ref(), + ] + .spacing(20), + ] + .spacing(20) + .align_items(Alignment::Center) + .into() } async fn search() -> Result { @@ -204,7 +182,6 @@ impl Pokemon { .map(|c| if c.is_control() { ' ' } else { c }) .collect(), image, - image_viewer: image::viewer::State::new(), }) } @@ -240,6 +217,6 @@ impl From for Error { } } -fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> { - Button::new(state, Text::new(text)).padding(10) +fn button<'a>(text: &'a str) -> widget::Button<'a, Message> { + widget::button(text).padding(10) } diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs index c9a8e7986c..d4ebe4d31e 100644 --- a/examples/progress_bar/src/main.rs +++ b/examples/progress_bar/src/main.rs @@ -1,4 +1,5 @@ -use iced::{slider, Column, Element, ProgressBar, Sandbox, Settings, Slider}; +use iced::widget::{column, progress_bar, slider}; +use iced::{Element, Sandbox, Settings}; pub fn main() -> iced::Result { Progress::run(Settings::default()) @@ -7,7 +8,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Progress { value: f32, - progress_bar_slider: slider::State, } #[derive(Debug, Clone, Copy)] @@ -32,19 +32,12 @@ impl Sandbox for Progress { } } - fn view(&mut self) -> Element { - Column::new() - .padding(20) - .push(ProgressBar::new(0.0..=100.0, self.value)) - .push( - Slider::new( - &mut self.progress_bar_slider, - 0.0..=100.0, - self.value, - Message::SliderChanged, - ) - .step(0.01), - ) - .into() + fn view(&self) -> Element { + column![ + progress_bar(0.0..=100.0, self.value), + slider(0.0..=100.0, self.value, Message::SliderChanged).step(0.01) + ] + .padding(20) + .into() } } diff --git a/examples/pure/color_palette/Cargo.toml b/examples/pure/color_palette/Cargo.toml deleted file mode 100644 index d08309d59a..0000000000 --- a/examples/pure/color_palette/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "pure_color_palette" -version = "0.1.0" -authors = ["Clark Moody "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["pure", "canvas", "palette"] } -palette = "0.6.0" diff --git a/examples/pure/color_palette/README.md b/examples/pure/color_palette/README.md deleted file mode 100644 index f90020b1d6..0000000000 --- a/examples/pure/color_palette/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## Color palette - -A color palette generator, based on a user-defined root color. - - - -You can run it with `cargo run`: - -``` -cargo run --package pure_color_palette -``` diff --git a/examples/pure/color_palette/screenshot.png b/examples/pure/color_palette/screenshot.png deleted file mode 100644 index e8da35c4d52705b9e6cfc34b9e98101b231b4233..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44798 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 diff --git a/examples/pure/color_palette/src/main.rs b/examples/pure/color_palette/src/main.rs deleted file mode 100644 index 8a58afa764..0000000000 --- a/examples/pure/color_palette/src/main.rs +++ /dev/null @@ -1,465 +0,0 @@ -use iced::pure::{ - column, row, text, - widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}, - widget::Slider, - Element, Sandbox, -}; -use iced::{ - alignment, Alignment, Color, Length, Point, Rectangle, Settings, Size, - Vector, -}; -use palette::{self, convert::FromColor, Hsl, Srgb}; -use std::marker::PhantomData; -use std::ops::RangeInclusive; - -pub fn main() -> iced::Result { - ColorPalette::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} - -#[derive(Default)] -pub struct ColorPalette { - theme: Theme, - rgb: ColorPicker, - hsl: ColorPicker, - hsv: ColorPicker, - hwb: ColorPicker, - lab: ColorPicker, - lch: ColorPicker, -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - RgbColorChanged(Color), - HslColorChanged(palette::Hsl), - HsvColorChanged(palette::Hsv), - HwbColorChanged(palette::Hwb), - LabColorChanged(palette::Lab), - LchColorChanged(palette::Lch), -} - -impl Sandbox for ColorPalette { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Color palette - Iced") - } - - fn update(&mut self, message: Message) { - let srgb = match message { - Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb), - Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl), - Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv), - Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb), - Message::LabColorChanged(lab) => palette::Srgb::from_color(lab), - Message::LchColorChanged(lch) => palette::Srgb::from_color(lch), - }; - - self.theme = Theme::new(srgb); - } - - fn view(&self) -> Element { - let base = self.theme.base; - - let srgb = palette::Srgb::from(base); - let hsl = palette::Hsl::from_color(srgb); - let hsv = palette::Hsv::from_color(srgb); - let hwb = palette::Hwb::from_color(srgb); - let lab = palette::Lab::from_color(srgb); - let lch = palette::Lch::from_color(srgb); - - column() - .padding(10) - .spacing(10) - .push(self.rgb.view(base).map(Message::RgbColorChanged)) - .push(self.hsl.view(hsl).map(Message::HslColorChanged)) - .push(self.hsv.view(hsv).map(Message::HsvColorChanged)) - .push(self.hwb.view(hwb).map(Message::HwbColorChanged)) - .push(self.lab.view(lab).map(Message::LabColorChanged)) - .push(self.lch.view(lch).map(Message::LchColorChanged)) - .push(self.theme.view()) - .into() - } -} - -#[derive(Debug)] -struct Theme { - lower: Vec, - base: Color, - higher: Vec, - canvas_cache: canvas::Cache, -} - -impl Theme { - pub fn new(base: impl Into) -> Theme { - use palette::{Hue, Shade}; - - let base = base.into(); - - // Convert to HSL color for manipulation - let hsl = Hsl::from_color(Srgb::from(base)); - - let lower = [ - hsl.shift_hue(-135.0).lighten(0.075), - hsl.shift_hue(-120.0), - hsl.shift_hue(-105.0).darken(0.075), - hsl.darken(0.075), - ]; - - let higher = [ - hsl.lighten(0.075), - hsl.shift_hue(105.0).darken(0.075), - hsl.shift_hue(120.0), - hsl.shift_hue(135.0).lighten(0.075), - ]; - - Theme { - lower: lower - .iter() - .map(|&color| Srgb::from_color(color).into()) - .collect(), - base, - higher: higher - .iter() - .map(|&color| Srgb::from_color(color).into()) - .collect(), - canvas_cache: canvas::Cache::default(), - } - } - - pub fn len(&self) -> usize { - self.lower.len() + self.higher.len() + 1 - } - - pub fn colors(&self) -> impl Iterator { - self.lower - .iter() - .chain(std::iter::once(&self.base)) - .chain(self.higher.iter()) - } - - pub fn view(&self) -> Element { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() - } - - fn draw(&self, frame: &mut Frame) { - let pad = 20.0; - - let box_size = Size { - width: frame.width() / self.len() as f32, - height: frame.height() / 2.0 - pad, - }; - - let triangle = Path::new(|path| { - path.move_to(Point { x: 0.0, y: -0.5 }); - path.line_to(Point { x: -0.5, y: 0.0 }); - path.line_to(Point { x: 0.5, y: 0.0 }); - path.close(); - }); - - let mut text = canvas::Text { - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Top, - size: 15.0, - ..canvas::Text::default() - }; - - for (i, &color) in self.colors().enumerate() { - let anchor = Point { - x: (i as f32) * box_size.width, - y: 0.0, - }; - frame.fill_rectangle(anchor, box_size, color); - - // We show a little indicator for the base color - if color == self.base { - let triangle_x = anchor.x + box_size.width / 2.0; - - frame.with_save(|frame| { - frame.translate(Vector::new(triangle_x, 0.0)); - frame.scale(10.0); - frame.rotate(std::f32::consts::PI); - - frame.fill(&triangle, Color::WHITE); - }); - - frame.with_save(|frame| { - frame.translate(Vector::new(triangle_x, box_size.height)); - frame.scale(10.0); - - frame.fill(&triangle, Color::WHITE); - }); - } - - frame.fill_text(canvas::Text { - content: color_hex_string(&color), - position: Point { - x: anchor.x + box_size.width / 2.0, - y: box_size.height, - }, - ..text - }); - } - - text.vertical_alignment = alignment::Vertical::Bottom; - - let hsl = Hsl::from_color(Srgb::from(self.base)); - for i in 0..self.len() { - let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0); - let graded = Hsl { - lightness: 1.0 - pct, - ..hsl - }; - let color: Color = Srgb::from_color(graded).into(); - - let anchor = Point { - x: (i as f32) * box_size.width, - y: box_size.height + 2.0 * pad, - }; - - frame.fill_rectangle(anchor, box_size, color); - - frame.fill_text(canvas::Text { - content: color_hex_string(&color), - position: Point { - x: anchor.x + box_size.width / 2.0, - y: box_size.height + 2.0 * pad, - }, - ..text - }); - } - } -} - -impl canvas::Program for Theme { - type State = (); - - fn draw( - &self, - _state: &Self::State, - _theme: &iced::Theme, - bounds: Rectangle, - _cursor: Cursor, - ) -> Vec { - let theme = self.canvas_cache.draw(bounds.size(), |frame| { - self.draw(frame); - }); - - vec![theme] - } -} - -impl Default for Theme { - fn default() -> Self { - Theme::new(Color::from_rgb8(75, 128, 190)) - } -} - -fn color_hex_string(color: &Color) -> String { - format!( - "#{:x}{:x}{:x}", - (255.0 * color.r).round() as u8, - (255.0 * color.g).round() as u8, - (255.0 * color.b).round() as u8 - ) -} - -#[derive(Default)] -struct ColorPicker { - color_space: PhantomData, -} - -trait ColorSpace: Sized { - const LABEL: &'static str; - const COMPONENT_RANGES: [RangeInclusive; 3]; - - fn new(a: f32, b: f32, c: f32) -> Self; - - fn components(&self) -> [f32; 3]; - - fn to_string(&self) -> String; -} - -impl ColorPicker { - fn view(&self, color: C) -> Element { - let [c1, c2, c3] = color.components(); - let [cr1, cr2, cr3] = C::COMPONENT_RANGES; - - fn slider<'a, C: Clone>( - range: RangeInclusive, - component: f32, - update: impl Fn(f32) -> C + 'a, - ) -> Slider<'a, f64, C, iced::Renderer> { - Slider::new(range, f64::from(component), move |v| update(v as f32)) - .step(0.01) - } - - row() - .spacing(10) - .align_items(Alignment::Center) - .push(text(C::LABEL).width(Length::Units(50))) - .push(slider(cr1, c1, move |v| C::new(v, c2, c3))) - .push(slider(cr2, c2, move |v| C::new(c1, v, c3))) - .push(slider(cr3, c3, move |v| C::new(c1, c2, v))) - .push(text(color.to_string()).width(Length::Units(185)).size(14)) - .into() - } -} - -impl ColorSpace for Color { - const LABEL: &'static str = "RGB"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=1.0, 0.0..=1.0, 0.0..=1.0]; - - fn new(r: f32, g: f32, b: f32) -> Self { - Color::from_rgb(r, g, b) - } - - fn components(&self) -> [f32; 3] { - [self.r, self.g, self.b] - } - - fn to_string(&self) -> String { - format!( - "rgb({:.0}, {:.0}, {:.0})", - 255.0 * self.r, - 255.0 * self.g, - 255.0 * self.b - ) - } -} - -impl ColorSpace for palette::Hsl { - const LABEL: &'static str = "HSL"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; - - fn new(hue: f32, saturation: f32, lightness: f32) -> Self { - palette::Hsl::new( - palette::RgbHue::from_degrees(hue), - saturation, - lightness, - ) - } - - fn components(&self) -> [f32; 3] { - [ - self.hue.to_positive_degrees(), - self.saturation, - self.lightness, - ] - } - - fn to_string(&self) -> String { - format!( - "hsl({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), - 100.0 * self.saturation, - 100.0 * self.lightness - ) - } -} - -impl ColorSpace for palette::Hsv { - const LABEL: &'static str = "HSV"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; - - fn new(hue: f32, saturation: f32, value: f32) -> Self { - palette::Hsv::new(palette::RgbHue::from_degrees(hue), saturation, value) - } - - fn components(&self) -> [f32; 3] { - [self.hue.to_positive_degrees(), self.saturation, self.value] - } - - fn to_string(&self) -> String { - format!( - "hsv({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), - 100.0 * self.saturation, - 100.0 * self.value - ) - } -} - -impl ColorSpace for palette::Hwb { - const LABEL: &'static str = "HWB"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; - - fn new(hue: f32, whiteness: f32, blackness: f32) -> Self { - palette::Hwb::new( - palette::RgbHue::from_degrees(hue), - whiteness, - blackness, - ) - } - - fn components(&self) -> [f32; 3] { - [ - self.hue.to_positive_degrees(), - self.whiteness, - self.blackness, - ] - } - - fn to_string(&self) -> String { - format!( - "hwb({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), - 100.0 * self.whiteness, - 100.0 * self.blackness - ) - } -} - -impl ColorSpace for palette::Lab { - const LABEL: &'static str = "Lab"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=100.0, -128.0..=127.0, -128.0..=127.0]; - - fn new(l: f32, a: f32, b: f32) -> Self { - palette::Lab::new(l, a, b) - } - - fn components(&self) -> [f32; 3] { - [self.l, self.a, self.b] - } - - fn to_string(&self) -> String { - format!("Lab({:.1}, {:.1}, {:.1})", self.l, self.a, self.b) - } -} - -impl ColorSpace for palette::Lch { - const LABEL: &'static str = "Lch"; - const COMPONENT_RANGES: [RangeInclusive; 3] = - [0.0..=100.0, 0.0..=128.0, 0.0..=360.0]; - - fn new(l: f32, chroma: f32, hue: f32) -> Self { - palette::Lch::new(l, chroma, palette::LabHue::from_degrees(hue)) - } - - fn components(&self) -> [f32; 3] { - [self.l, self.chroma, self.hue.to_positive_degrees()] - } - - fn to_string(&self) -> String { - format!( - "Lch({:.1}, {:.1}, {:.1})", - self.l, - self.chroma, - self.hue.to_positive_degrees() - ) - } -} diff --git a/examples/pure/component/Cargo.toml b/examples/pure/component/Cargo.toml deleted file mode 100644 index b6c7a5136a..0000000000 --- a/examples/pure/component/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "pure_component" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["debug", "pure"] } -iced_native = { path = "../../../native" } -iced_lazy = { path = "../../../lazy", features = ["pure"] } -iced_pure = { path = "../../../pure" } diff --git a/examples/pure/component/src/main.rs b/examples/pure/component/src/main.rs deleted file mode 100644 index db22d019fb..0000000000 --- a/examples/pure/component/src/main.rs +++ /dev/null @@ -1,172 +0,0 @@ -use iced::pure::container; -use iced::pure::{Element, Sandbox}; -use iced::{Length, Settings}; - -use numeric_input::numeric_input; - -pub fn main() -> iced::Result { - Component::run(Settings::default()) -} - -#[derive(Default)] -struct Component { - value: Option, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - NumericInputChanged(Option), -} - -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(&self) -> Element { - container(numeric_input(self.value, Message::NumericInputChanged)) - .padding(20) - .height(Length::Fill) - .center_y() - .into() - } -} - -mod numeric_input { - use iced_lazy::pure::{self, Component}; - use iced_native::alignment::{self, Alignment}; - use iced_native::text; - use iced_native::widget; - use iced_native::Length; - use iced_pure::Element; - use iced_pure::{button, row, text, text_input}; - - pub struct NumericInput { - value: Option, - on_change: Box) -> Message>, - } - - pub fn numeric_input( - value: Option, - on_change: impl Fn(Option) -> Message + 'static, - ) -> NumericInput { - NumericInput::new(value, on_change) - } - - #[derive(Debug, Clone)] - pub enum Event { - InputChanged(String), - IncrementPressed, - DecrementPressed, - } - - impl NumericInput { - pub fn new( - value: Option, - on_change: impl Fn(Option) -> Message + 'static, - ) -> Self { - Self { - value, - on_change: Box::new(on_change), - } - } - } - - impl Component for NumericInput - where - Renderer: text::Renderer + 'static, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, - { - type State = (); - type Event = Event; - - fn update( - &mut self, - _state: &mut Self::State, - event: Event, - ) -> Option { - 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(&self, _state: &Self::State) -> Element { - let button = |label, on_press| { - button( - text(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() - .push(button("-", Event::DecrementPressed)) - .push( - text_input( - "Type a number", - self.value - .as_ref() - .map(u32::to_string) - .as_deref() - .unwrap_or(""), - Event::InputChanged, - ) - .padding(10), - ) - .push(button("+", Event::IncrementPressed)) - .align_items(Alignment::Fill) - .spacing(10) - .into() - } - } - - impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> - where - Message: 'a, - Renderer: 'static + text::Renderer, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, - { - fn from(numeric_input: NumericInput) -> Self { - pure::component(numeric_input) - } - } -} diff --git a/examples/pure/counter/Cargo.toml b/examples/pure/counter/Cargo.toml deleted file mode 100644 index 2fcd22d4fd..0000000000 --- a/examples/pure/counter/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "pure_counter" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["pure"] } diff --git a/examples/pure/counter/src/main.rs b/examples/pure/counter/src/main.rs deleted file mode 100644 index 726009dfe2..0000000000 --- a/examples/pure/counter/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -use iced::pure::{button, column, text, Element, Sandbox}; -use iced::{Alignment, Settings}; - -pub fn main() -> iced::Result { - Counter::run(Settings::default()) -} - -struct Counter { - value: i32, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - IncrementPressed, - DecrementPressed, -} - -impl Sandbox for Counter { - type Message = Message; - - fn new() -> Self { - Self { value: 0 } - } - - fn title(&self) -> String { - String::from("Counter - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::IncrementPressed => { - self.value += 1; - } - Message::DecrementPressed => { - self.value -= 1; - } - } - } - - fn view(&self) -> Element { - column() - .padding(20) - .align_items(Alignment::Center) - .push(button("Increment").on_press(Message::IncrementPressed)) - .push(text(self.value.to_string()).size(50)) - .push(button("Decrement").on_press(Message::DecrementPressed)) - .into() - } -} diff --git a/examples/pure/game_of_life/Cargo.toml b/examples/pure/game_of_life/Cargo.toml deleted file mode 100644 index 22e38f00c8..0000000000 --- a/examples/pure/game_of_life/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "pure_game_of_life" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["pure", "canvas", "tokio", "debug"] } -tokio = { version = "1.0", features = ["sync"] } -itertools = "0.9" -rustc-hash = "1.1" -env_logger = "0.9" diff --git a/examples/pure/game_of_life/README.md b/examples/pure/game_of_life/README.md deleted file mode 100644 index aa39201c86..0000000000 --- a/examples/pure/game_of_life/README.md +++ /dev/null @@ -1,22 +0,0 @@ -## Game of Life - -An interactive version of the [Game of Life], invented by [John Horton Conway]. - -It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support. - -The __[`main`]__ file contains the relevant code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package game_of_life -``` - -[`main`]: src/main.rs -[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life -[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway diff --git a/examples/pure/game_of_life/src/main.rs b/examples/pure/game_of_life/src/main.rs deleted file mode 100644 index cf6560d644..0000000000 --- a/examples/pure/game_of_life/src/main.rs +++ /dev/null @@ -1,903 +0,0 @@ -//! This example showcases an interactive version of the Game of Life, invented -//! by John Conway. It leverages a `Canvas` together with other widgets. -mod preset; - -use grid::Grid; -use preset::Preset; - -use iced::executor; -use iced::pure::{ - button, checkbox, column, container, pick_list, row, slider, text, -}; -use iced::pure::{Application, Element}; -use iced::theme::{self, Theme}; -use iced::time; -use iced::window; -use iced::{Alignment, Command, Length, Settings, Subscription}; -use std::time::{Duration, Instant}; - -pub fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); - - GameOfLife::run(Settings { - antialiasing: true, - window: window::Settings { - position: window::Position::Centered, - ..window::Settings::default() - }, - ..Settings::default() - }) -} - -#[derive(Default)] -struct GameOfLife { - grid: Grid, - is_playing: bool, - queued_ticks: usize, - speed: usize, - next_speed: Option, - version: usize, -} - -#[derive(Debug, Clone)] -enum Message { - Grid(grid::Message, usize), - Tick(Instant), - TogglePlayback, - ToggleGrid(bool), - Next, - Clear, - SpeedChanged(f32), - PresetPicked(Preset), -} - -impl Application for GameOfLife { - type Message = Message; - type Theme = Theme; - type Executor = executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (Self, Command) { - ( - Self { - speed: 5, - ..Self::default() - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Game of Life - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Grid(message, version) => { - if version == self.version { - self.grid.update(message); - } - } - Message::Tick(_) | Message::Next => { - self.queued_ticks = (self.queued_ticks + 1).min(self.speed); - - if let Some(task) = self.grid.tick(self.queued_ticks) { - if let Some(speed) = self.next_speed.take() { - self.speed = speed; - } - - self.queued_ticks = 0; - - let version = self.version; - - return Command::perform(task, move |message| { - Message::Grid(message, version) - }); - } - } - Message::TogglePlayback => { - self.is_playing = !self.is_playing; - } - Message::ToggleGrid(show_grid_lines) => { - self.grid.toggle_lines(show_grid_lines); - } - Message::Clear => { - self.grid.clear(); - self.version += 1; - } - Message::SpeedChanged(speed) => { - if self.is_playing { - self.next_speed = Some(speed.round() as usize); - } else { - self.speed = speed.round() as usize; - } - } - Message::PresetPicked(new_preset) => { - self.grid = Grid::from_preset(new_preset); - self.version += 1; - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - if self.is_playing { - time::every(Duration::from_millis(1000 / self.speed as u64)) - .map(Message::Tick) - } else { - Subscription::none() - } - } - - fn view(&self) -> Element { - let version = self.version; - let selected_speed = self.next_speed.unwrap_or(self.speed); - let controls = view_controls( - self.is_playing, - self.grid.are_lines_visible(), - selected_speed, - self.grid.preset(), - ); - - let content = column() - .push( - self.grid - .view() - .map(move |message| Message::Grid(message, version)), - ) - .push(controls); - - container(content) - .width(Length::Fill) - .height(Length::Fill) - .into() - } - - fn theme(&self) -> Theme { - Theme::Dark - } -} - -fn view_controls<'a>( - is_playing: bool, - is_grid_enabled: bool, - speed: usize, - preset: Preset, -) -> Element<'a, Message> { - let playback_controls = row() - .spacing(10) - .push( - button(if is_playing { "Pause" } else { "Play" }) - .on_press(Message::TogglePlayback), - ) - .push( - button("Next") - .on_press(Message::Next) - .style(theme::Button::Secondary), - ); - - let speed_controls = row() - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10) - .push(slider(1.0..=1000.0, speed as f32, Message::SpeedChanged)) - .push(text(format!("x{}", speed)).size(16)); - - row() - .padding(10) - .spacing(20) - .align_items(Alignment::Center) - .push(playback_controls) - .push(speed_controls) - .push( - checkbox("Grid", is_grid_enabled, Message::ToggleGrid) - .size(16) - .spacing(5) - .text_size(16), - ) - .push( - pick_list(preset::ALL, Some(preset), Message::PresetPicked) - .padding(8) - .text_size(16), - ) - .push( - button("Clear") - .on_press(Message::Clear) - .style(theme::Button::Destructive), - ) - .into() -} - -mod grid { - use crate::Preset; - use iced::pure::widget::canvas::event::{self, Event}; - use iced::pure::widget::canvas::{ - self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text, - }; - use iced::pure::Element; - use iced::{ - alignment, mouse, Color, Length, Point, Rectangle, Size, Theme, Vector, - }; - use rustc_hash::{FxHashMap, FxHashSet}; - use std::future::Future; - use std::ops::RangeInclusive; - use std::time::{Duration, Instant}; - - pub struct Grid { - state: State, - preset: Preset, - life_cache: Cache, - grid_cache: Cache, - translation: Vector, - scaling: f32, - show_lines: bool, - last_tick_duration: Duration, - last_queued_ticks: usize, - } - - #[derive(Debug, Clone)] - pub enum Message { - Populate(Cell), - Unpopulate(Cell), - Translated(Vector), - Scaled(f32, Option), - Ticked { - result: Result, - tick_duration: Duration, - }, - } - - #[derive(Debug, Clone)] - pub enum TickError { - JoinFailed, - } - - impl Default for Grid { - fn default() -> Self { - Self::from_preset(Preset::default()) - } - } - - impl Grid { - const MIN_SCALING: f32 = 0.1; - const MAX_SCALING: f32 = 2.0; - - pub fn from_preset(preset: Preset) -> Self { - Self { - state: State::with_life( - preset - .life() - .into_iter() - .map(|(i, j)| Cell { i, j }) - .collect(), - ), - preset, - life_cache: Cache::default(), - grid_cache: Cache::default(), - translation: Vector::default(), - scaling: 1.0, - show_lines: true, - last_tick_duration: Duration::default(), - last_queued_ticks: 0, - } - } - - pub fn tick( - &mut self, - amount: usize, - ) -> Option> { - let tick = self.state.tick(amount)?; - - self.last_queued_ticks = amount; - - Some(async move { - let start = Instant::now(); - let result = tick.await; - let tick_duration = start.elapsed() / amount as u32; - - Message::Ticked { - result, - tick_duration, - } - }) - } - - pub fn update(&mut self, message: Message) { - match message { - Message::Populate(cell) => { - self.state.populate(cell); - self.life_cache.clear(); - - self.preset = Preset::Custom; - } - Message::Unpopulate(cell) => { - self.state.unpopulate(&cell); - self.life_cache.clear(); - - self.preset = Preset::Custom; - } - Message::Translated(translation) => { - self.translation = translation; - - self.life_cache.clear(); - self.grid_cache.clear(); - } - Message::Scaled(scaling, translation) => { - self.scaling = scaling; - - if let Some(translation) = translation { - self.translation = translation; - } - - self.life_cache.clear(); - self.grid_cache.clear(); - } - Message::Ticked { - result: Ok(life), - tick_duration, - } => { - self.state.update(life); - self.life_cache.clear(); - - self.last_tick_duration = tick_duration; - } - Message::Ticked { - result: Err(error), .. - } => { - dbg!(error); - } - } - } - - pub fn view(&self) -> Element { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() - } - - pub fn clear(&mut self) { - self.state = State::default(); - self.preset = Preset::Custom; - - self.life_cache.clear(); - } - - pub fn preset(&self) -> Preset { - self.preset - } - - pub fn toggle_lines(&mut self, enabled: bool) { - self.show_lines = enabled; - } - - pub fn are_lines_visible(&self) -> bool { - self.show_lines - } - - fn visible_region(&self, size: Size) -> Region { - let width = size.width / self.scaling; - let height = size.height / self.scaling; - - Region { - x: -self.translation.x - width / 2.0, - y: -self.translation.y - height / 2.0, - width, - height, - } - } - - fn project(&self, position: Point, size: Size) -> Point { - let region = self.visible_region(size); - - Point::new( - position.x / self.scaling + region.x, - position.y / self.scaling + region.y, - ) - } - } - - impl canvas::Program for Grid { - type State = Interaction; - - fn update( - &self, - interaction: &mut Interaction, - event: Event, - bounds: Rectangle, - cursor: Cursor, - ) -> (event::Status, Option) { - if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { - *interaction = Interaction::None; - } - - let cursor_position = - if let Some(position) = cursor.position_in(&bounds) { - position - } else { - return (event::Status::Ignored, None); - }; - - let cell = Cell::at(self.project(cursor_position, bounds.size())); - let is_populated = self.state.contains(&cell); - - let (populate, unpopulate) = if is_populated { - (None, Some(Message::Unpopulate(cell))) - } else { - (Some(Message::Populate(cell)), None) - }; - - match event { - Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(button) => { - let message = match button { - mouse::Button::Left => { - *interaction = if is_populated { - Interaction::Erasing - } else { - Interaction::Drawing - }; - - populate.or(unpopulate) - } - mouse::Button::Right => { - *interaction = Interaction::Panning { - translation: self.translation, - start: cursor_position, - }; - - None - } - _ => None, - }; - - (event::Status::Captured, message) - } - mouse::Event::CursorMoved { .. } => { - let message = match *interaction { - Interaction::Drawing => populate, - Interaction::Erasing => unpopulate, - Interaction::Panning { translation, start } => { - Some(Message::Translated( - translation - + (cursor_position - start) - * (1.0 / self.scaling), - )) - } - _ => None, - }; - - let event_status = match interaction { - Interaction::None => event::Status::Ignored, - _ => event::Status::Captured, - }; - - (event_status, message) - } - mouse::Event::WheelScrolled { delta } => match delta { - mouse::ScrollDelta::Lines { y, .. } - | mouse::ScrollDelta::Pixels { y, .. } => { - if y < 0.0 && self.scaling > Self::MIN_SCALING - || y > 0.0 && self.scaling < Self::MAX_SCALING - { - let old_scaling = self.scaling; - - let scaling = (self.scaling * (1.0 + y / 30.0)) - .max(Self::MIN_SCALING) - .min(Self::MAX_SCALING); - - let translation = - if let Some(cursor_to_center) = - cursor.position_from(bounds.center()) - { - let factor = scaling - old_scaling; - - Some( - self.translation - - Vector::new( - cursor_to_center.x * factor - / (old_scaling - * old_scaling), - cursor_to_center.y * factor - / (old_scaling - * old_scaling), - ), - ) - } else { - None - }; - - ( - event::Status::Captured, - Some(Message::Scaled(scaling, translation)), - ) - } else { - (event::Status::Captured, None) - } - } - }, - _ => (event::Status::Ignored, None), - }, - _ => (event::Status::Ignored, None), - } - } - - fn draw( - &self, - _interaction: &Interaction, - _theme: &Theme, - bounds: Rectangle, - cursor: Cursor, - ) -> Vec { - let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); - - let life = self.life_cache.draw(bounds.size(), |frame| { - let background = Path::rectangle(Point::ORIGIN, frame.size()); - frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); - - frame.with_save(|frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - let region = self.visible_region(frame.size()); - - for cell in region.cull(self.state.cells()) { - frame.fill_rectangle( - Point::new(cell.j as f32, cell.i as f32), - Size::UNIT, - Color::WHITE, - ); - } - }); - }); - - let overlay = { - let mut frame = Frame::new(bounds.size()); - - let hovered_cell = - cursor.position_in(&bounds).map(|position| { - Cell::at(self.project(position, frame.size())) - }); - - if let Some(cell) = hovered_cell { - frame.with_save(|frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - frame.fill_rectangle( - Point::new(cell.j as f32, cell.i as f32), - Size::UNIT, - Color { - a: 0.5, - ..Color::BLACK - }, - ); - }); - } - - let text = Text { - color: Color::WHITE, - size: 14.0, - position: Point::new(frame.width(), frame.height()), - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Bottom, - ..Text::default() - }; - - if let Some(cell) = hovered_cell { - frame.fill_text(Text { - content: format!("({}, {})", cell.j, cell.i), - position: text.position - Vector::new(0.0, 16.0), - ..text - }); - } - - let cell_count = self.state.cell_count(); - - frame.fill_text(Text { - content: format!( - "{} cell{} @ {:?} ({})", - cell_count, - if cell_count == 1 { "" } else { "s" }, - self.last_tick_duration, - self.last_queued_ticks - ), - ..text - }); - - frame.into_geometry() - }; - - if self.scaling < 0.2 || !self.show_lines { - vec![life, overlay] - } else { - let grid = self.grid_cache.draw(bounds.size(), |frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - let region = self.visible_region(frame.size()); - let rows = region.rows(); - let columns = region.columns(); - let (total_rows, total_columns) = - (rows.clone().count(), columns.clone().count()); - let width = 2.0 / Cell::SIZE as f32; - let color = Color::from_rgb8(70, 74, 83); - - frame.translate(Vector::new(-width / 2.0, -width / 2.0)); - - for row in region.rows() { - frame.fill_rectangle( - Point::new(*columns.start() as f32, row as f32), - Size::new(total_columns as f32, width), - color, - ); - } - - for column in region.columns() { - frame.fill_rectangle( - Point::new(column as f32, *rows.start() as f32), - Size::new(width, total_rows as f32), - color, - ); - } - }); - - vec![life, grid, overlay] - } - } - - fn mouse_interaction( - &self, - interaction: &Interaction, - bounds: Rectangle, - cursor: Cursor, - ) -> mouse::Interaction { - match interaction { - Interaction::Drawing => mouse::Interaction::Crosshair, - Interaction::Erasing => mouse::Interaction::Crosshair, - Interaction::Panning { .. } => mouse::Interaction::Grabbing, - Interaction::None if cursor.is_over(&bounds) => { - mouse::Interaction::Crosshair - } - _ => mouse::Interaction::default(), - } - } - } - - #[derive(Default)] - struct State { - life: Life, - births: FxHashSet, - is_ticking: bool, - } - - impl State { - pub fn with_life(life: Life) -> Self { - Self { - life, - ..Self::default() - } - } - - fn cell_count(&self) -> usize { - self.life.len() + self.births.len() - } - - fn contains(&self, cell: &Cell) -> bool { - self.life.contains(cell) || self.births.contains(cell) - } - - fn cells(&self) -> impl Iterator { - self.life.iter().chain(self.births.iter()) - } - - fn populate(&mut self, cell: Cell) { - if self.is_ticking { - self.births.insert(cell); - } else { - self.life.populate(cell); - } - } - - fn unpopulate(&mut self, cell: &Cell) { - if self.is_ticking { - let _ = self.births.remove(cell); - } else { - self.life.unpopulate(cell); - } - } - - fn update(&mut self, mut life: Life) { - self.births.drain().for_each(|cell| life.populate(cell)); - - self.life = life; - self.is_ticking = false; - } - - fn tick( - &mut self, - amount: usize, - ) -> Option>> { - if self.is_ticking { - return None; - } - - self.is_ticking = true; - - let mut life = self.life.clone(); - - Some(async move { - tokio::task::spawn_blocking(move || { - for _ in 0..amount { - life.tick(); - } - - life - }) - .await - .map_err(|_| TickError::JoinFailed) - }) - } - } - - #[derive(Clone, Default)] - pub struct Life { - cells: FxHashSet, - } - - impl Life { - fn len(&self) -> usize { - self.cells.len() - } - - fn contains(&self, cell: &Cell) -> bool { - self.cells.contains(cell) - } - - fn populate(&mut self, cell: Cell) { - self.cells.insert(cell); - } - - fn unpopulate(&mut self, cell: &Cell) { - let _ = self.cells.remove(cell); - } - - fn tick(&mut self) { - let mut adjacent_life = FxHashMap::default(); - - for cell in &self.cells { - let _ = adjacent_life.entry(*cell).or_insert(0); - - for neighbor in Cell::neighbors(*cell) { - let amount = adjacent_life.entry(neighbor).or_insert(0); - - *amount += 1; - } - } - - for (cell, amount) in adjacent_life.iter() { - match amount { - 2 => {} - 3 => { - let _ = self.cells.insert(*cell); - } - _ => { - let _ = self.cells.remove(cell); - } - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.cells.iter() - } - } - - impl std::iter::FromIterator for Life { - fn from_iter>(iter: I) -> Self { - Life { - cells: iter.into_iter().collect(), - } - } - } - - impl std::fmt::Debug for Life { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Life") - .field("cells", &self.cells.len()) - .finish() - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub struct Cell { - i: isize, - j: isize, - } - - impl Cell { - const SIZE: usize = 20; - - fn at(position: Point) -> Cell { - let i = (position.y / Cell::SIZE as f32).ceil() as isize; - let j = (position.x / Cell::SIZE as f32).ceil() as isize; - - Cell { - i: i.saturating_sub(1), - j: j.saturating_sub(1), - } - } - - fn cluster(cell: Cell) -> impl Iterator { - use itertools::Itertools; - - let rows = cell.i.saturating_sub(1)..=cell.i.saturating_add(1); - let columns = cell.j.saturating_sub(1)..=cell.j.saturating_add(1); - - rows.cartesian_product(columns).map(|(i, j)| Cell { i, j }) - } - - fn neighbors(cell: Cell) -> impl Iterator { - Cell::cluster(cell).filter(move |candidate| *candidate != cell) - } - } - - pub struct Region { - x: f32, - y: f32, - width: f32, - height: f32, - } - - impl Region { - fn rows(&self) -> RangeInclusive { - let first_row = (self.y / Cell::SIZE as f32).floor() as isize; - - let visible_rows = - (self.height / Cell::SIZE as f32).ceil() as isize; - - first_row..=first_row + visible_rows - } - - fn columns(&self) -> RangeInclusive { - let first_column = (self.x / Cell::SIZE as f32).floor() as isize; - - let visible_columns = - (self.width / Cell::SIZE as f32).ceil() as isize; - - first_column..=first_column + visible_columns - } - - fn cull<'a>( - &self, - cells: impl Iterator, - ) -> impl Iterator { - let rows = self.rows(); - let columns = self.columns(); - - cells.filter(move |cell| { - rows.contains(&cell.i) && columns.contains(&cell.j) - }) - } - } - - pub enum Interaction { - None, - Drawing, - Erasing, - Panning { translation: Vector, start: Point }, - } - - impl Default for Interaction { - fn default() -> Self { - Self::None - } - } -} diff --git a/examples/pure/game_of_life/src/preset.rs b/examples/pure/game_of_life/src/preset.rs deleted file mode 100644 index 964b912036..0000000000 --- a/examples/pure/game_of_life/src/preset.rs +++ /dev/null @@ -1,142 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Preset { - Custom, - Xkcd, - Glider, - SmallExploder, - Exploder, - TenCellRow, - LightweightSpaceship, - Tumbler, - GliderGun, - Acorn, -} - -pub static ALL: &[Preset] = &[ - Preset::Custom, - Preset::Xkcd, - Preset::Glider, - Preset::SmallExploder, - Preset::Exploder, - Preset::TenCellRow, - Preset::LightweightSpaceship, - Preset::Tumbler, - Preset::GliderGun, - Preset::Acorn, -]; - -impl Preset { - pub fn life(self) -> Vec<(isize, isize)> { - #[rustfmt::skip] - let cells = match self { - Preset::Custom => vec![], - Preset::Xkcd => vec![ - " xxx ", - " x x ", - " x x ", - " x ", - "x xxx ", - " x x x ", - " x x", - " x x ", - " x x ", - ], - Preset::Glider => vec![ - " x ", - " x", - "xxx" - ], - Preset::SmallExploder => vec![ - " x ", - "xxx", - "x x", - " x ", - ], - Preset::Exploder => vec![ - "x x x", - "x x", - "x x", - "x x", - "x x x", - ], - Preset::TenCellRow => vec![ - "xxxxxxxxxx", - ], - Preset::LightweightSpaceship => vec![ - " xxxxx", - "x x", - " x", - "x x ", - ], - Preset::Tumbler => vec![ - " xx xx ", - " xx xx ", - " x x ", - "x x x x", - "x x x x", - "xx xx", - ], - Preset::GliderGun => vec![ - " x ", - " x x ", - " xx xx xx", - " x x xx xx", - "xx x x xx ", - "xx x x xx x x ", - " x x x ", - " x x ", - " xx ", - ], - Preset::Acorn => vec![ - " x ", - " x ", - "xx xxx", - ], - }; - - let start_row = -(cells.len() as isize / 2); - - cells - .into_iter() - .enumerate() - .flat_map(|(i, cells)| { - let start_column = -(cells.len() as isize / 2); - - cells - .chars() - .enumerate() - .filter(|(_, c)| !c.is_whitespace()) - .map(move |(j, _)| { - (start_row + i as isize, start_column + j as isize) - }) - }) - .collect() - } -} - -impl Default for Preset { - fn default() -> Preset { - Preset::Xkcd - } -} - -impl std::fmt::Display for Preset { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Preset::Custom => "Custom", - Preset::Xkcd => "xkcd #2293", - Preset::Glider => "Glider", - Preset::SmallExploder => "Small Exploder", - Preset::Exploder => "Exploder", - Preset::TenCellRow => "10 Cell Row", - Preset::LightweightSpaceship => "Lightweight spaceship", - Preset::Tumbler => "Tumbler", - Preset::GliderGun => "Gosper Glider Gun", - Preset::Acorn => "Acorn", - } - ) - } -} diff --git a/examples/pure/pane_grid/Cargo.toml b/examples/pure/pane_grid/Cargo.toml deleted file mode 100644 index a51cdaf0bb..0000000000 --- a/examples/pure/pane_grid/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "pure_pane_grid" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["pure", "debug"] } -iced_native = { path = "../../../native" } -iced_lazy = { path = "../../../lazy", features = ["pure"] } diff --git a/examples/pure/pane_grid/src/main.rs b/examples/pure/pane_grid/src/main.rs deleted file mode 100644 index e85ed78dcf..0000000000 --- a/examples/pure/pane_grid/src/main.rs +++ /dev/null @@ -1,369 +0,0 @@ -use iced::alignment::{self, Alignment}; -use iced::executor; -use iced::keyboard; -use iced::pure::widget::pane_grid::{self, PaneGrid}; -use iced::pure::{button, column, container, row, scrollable, text}; -use iced::pure::{Application, Element}; -use iced::theme::{self, Theme}; -use iced::{Color, Command, Length, Settings, Size, Subscription}; -use iced_lazy::pure::responsive; -use iced_native::{event, subscription, Event}; - -pub fn main() -> iced::Result { - Example::run(Settings::default()) -} - -struct Example { - panes: pane_grid::State, - panes_created: usize, - focus: Option, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Split(pane_grid::Axis, pane_grid::Pane), - SplitFocused(pane_grid::Axis), - FocusAdjacent(pane_grid::Direction), - Clicked(pane_grid::Pane), - Dragged(pane_grid::DragEvent), - Resized(pane_grid::ResizeEvent), - TogglePin(pane_grid::Pane), - Close(pane_grid::Pane), - CloseFocused, -} - -impl Application for Example { - type Message = Message; - type Theme = Theme; - type Executor = executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (Self, Command) { - let (panes, _) = pane_grid::State::new(Pane::new(0)); - - ( - Example { - panes, - panes_created: 1, - focus: None, - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Pane grid - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Split(axis, pane) => { - let result = self.panes.split( - axis, - &pane, - Pane::new(self.panes_created), - ); - - if let Some((pane, _)) = result { - self.focus = Some(pane); - } - - self.panes_created += 1; - } - Message::SplitFocused(axis) => { - if let Some(pane) = self.focus { - let result = self.panes.split( - axis, - &pane, - Pane::new(self.panes_created), - ); - - if let Some((pane, _)) = result { - self.focus = Some(pane); - } - - self.panes_created += 1; - } - } - Message::FocusAdjacent(direction) => { - if let Some(pane) = self.focus { - if let Some(adjacent) = - self.panes.adjacent(&pane, direction) - { - self.focus = Some(adjacent); - } - } - } - Message::Clicked(pane) => { - self.focus = Some(pane); - } - Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { - self.panes.resize(&split, ratio); - } - Message::Dragged(pane_grid::DragEvent::Dropped { - pane, - target, - }) => { - self.panes.swap(&pane, &target); - } - Message::Dragged(_) => {} - Message::TogglePin(pane) => { - if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane) - { - *is_pinned = !*is_pinned; - } - } - Message::Close(pane) => { - if let Some((_, sibling)) = self.panes.close(&pane) { - self.focus = Some(sibling); - } - } - Message::CloseFocused => { - if let Some(pane) = self.focus { - if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane) - { - if !is_pinned { - if let Some((_, sibling)) = self.panes.close(&pane) - { - self.focus = Some(sibling); - } - } - } - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| { - if let event::Status::Captured = status { - return None; - } - - match event { - Event::Keyboard(keyboard::Event::KeyPressed { - modifiers, - key_code, - }) if modifiers.command() => handle_hotkey(key_code), - _ => None, - } - }) - } - - fn view(&self) -> Element { - let focus = self.focus; - let total_panes = self.panes.len(); - - let pane_grid = PaneGrid::new(&self.panes, |id, pane| { - let is_focused = focus == Some(id); - - let pin_button = button( - text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), - ) - .on_press(Message::TogglePin(id)) - .padding(3); - - let title = row() - .push(pin_button) - .push("Pane") - .push(text(pane.id.to_string()).style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - })) - .spacing(5); - - let title_bar = pane_grid::TitleBar::new(title) - .controls(view_controls(id, total_panes, pane.is_pinned)) - .padding(10) - .style(if is_focused { - style::title_bar_focused - } else { - style::title_bar_active - }); - - pane_grid::Content::new(responsive(move |size| { - view_content(id, total_panes, pane.is_pinned, size) - })) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_click(Message::Clicked) - .on_drag(Message::Dragged) - .on_resize(10, Message::Resized); - - container(pane_grid) - .width(Length::Fill) - .height(Length::Fill) - .padding(10) - .into() - } -} - -const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( - 0xFF as f32 / 255.0, - 0xC7 as f32 / 255.0, - 0xC7 as f32 / 255.0, -); -const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( - 0xFF as f32 / 255.0, - 0x47 as f32 / 255.0, - 0x47 as f32 / 255.0, -); - -fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { - use keyboard::KeyCode; - use pane_grid::{Axis, Direction}; - - let direction = match key_code { - KeyCode::Up => Some(Direction::Up), - KeyCode::Down => Some(Direction::Down), - KeyCode::Left => Some(Direction::Left), - KeyCode::Right => Some(Direction::Right), - _ => None, - }; - - match key_code { - KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), - KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), - KeyCode::W => Some(Message::CloseFocused), - _ => direction.map(Message::FocusAdjacent), - } -} - -struct Pane { - id: usize, - pub is_pinned: bool, -} - -impl Pane { - fn new(id: usize) -> Self { - Self { - id, - is_pinned: false, - } - } -} - -fn view_content<'a>( - pane: pane_grid::Pane, - total_panes: usize, - is_pinned: bool, - size: Size, -) -> Element<'a, Message> { - let button = |label, message| { - button( - text(label) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .size(16), - ) - .width(Length::Fill) - .padding(8) - .on_press(message) - }; - - let mut controls = column() - .spacing(5) - .max_width(150) - .push(button( - "Split horizontally", - Message::Split(pane_grid::Axis::Horizontal, pane), - )) - .push(button( - "Split vertically", - Message::Split(pane_grid::Axis::Vertical, pane), - )); - - if total_panes > 1 && !is_pinned { - controls = controls.push( - button("Close", Message::Close(pane)) - .style(theme::Button::Destructive), - ); - } - - let content = column() - .width(Length::Fill) - .spacing(10) - .align_items(Alignment::Center) - .push(text(format!("{}x{}", size.width, size.height)).size(24)) - .push(controls); - - container(scrollable(content)) - .width(Length::Fill) - .height(Length::Fill) - .padding(5) - .center_y() - .into() -} - -fn view_controls<'a>( - pane: pane_grid::Pane, - total_panes: usize, - is_pinned: bool, -) -> Element<'a, Message> { - let mut button = button(text("Close").size(14)) - .style(theme::Button::Destructive) - .padding(3); - - if total_panes > 1 && !is_pinned { - button = button.on_press(Message::Close(pane)); - } - - button.into() -} - -mod style { - use iced::{container, Theme}; - - pub fn title_bar_active(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - text_color: Some(palette.background.strong.text), - background: Some(palette.background.strong.color.into()), - ..Default::default() - } - } - - pub fn title_bar_focused(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - text_color: Some(palette.primary.strong.text), - background: Some(palette.primary.strong.color.into()), - ..Default::default() - } - } - - pub fn pane_active(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - background: Some(palette.background.weak.color.into()), - border_width: 2.0, - border_color: palette.background.strong.color, - ..Default::default() - } - } - - pub fn pane_focused(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - background: Some(palette.background.weak.color.into()), - border_width: 2.0, - border_color: palette.primary.strong.color, - ..Default::default() - } - } -} diff --git a/examples/pure/pick_list/Cargo.toml b/examples/pure/pick_list/Cargo.toml deleted file mode 100644 index c0fcac3c4c..0000000000 --- a/examples/pure/pick_list/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "pure_pick_list" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["debug", "pure"] } diff --git a/examples/pure/pick_list/src/main.rs b/examples/pure/pick_list/src/main.rs deleted file mode 100644 index b9947107c1..0000000000 --- a/examples/pure/pick_list/src/main.rs +++ /dev/null @@ -1,109 +0,0 @@ -use iced::pure::{column, container, pick_list, scrollable, vertical_space}; -use iced::pure::{Element, Sandbox}; -use iced::{Alignment, Length, Settings}; - -pub fn main() -> iced::Result { - Example::run(Settings::default()) -} - -#[derive(Default)] -struct Example { - selected_language: Option, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - LanguageSelected(Language), -} - -impl Sandbox for Example { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Pick list - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::LanguageSelected(language) => { - self.selected_language = Some(language); - } - } - } - - fn view(&self) -> Element { - let pick_list = pick_list( - &Language::ALL[..], - self.selected_language, - Message::LanguageSelected, - ) - .placeholder("Choose a language..."); - - let content = column() - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10) - .push(vertical_space(Length::Units(600))) - .push("Which is your favorite language?") - .push(pick_list) - .push(vertical_space(Length::Units(600))); - - container(scrollable(content)) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Javascript, - Other, -} - -impl Language { - const ALL: [Language; 7] = [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Javascript, - Language::Other, - ]; -} - -impl Default for Language { - fn default() -> Language { - Language::Rust - } -} - -impl std::fmt::Display for Language { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Javascript => "Javascript", - Language::Other => "Some other language", - } - ) - } -} diff --git a/examples/pure/todos/Cargo.toml b/examples/pure/todos/Cargo.toml deleted file mode 100644 index 217179e83d..0000000000 --- a/examples/pure/todos/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "pure_todos" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["async-std", "debug", "default_system_font", "pure"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-std = "1.0" -directories-next = "2.0" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { version = "0.3", features = ["Window", "Storage"] } -wasm-timer = "0.2" diff --git a/examples/pure/todos/src/main.rs b/examples/pure/todos/src/main.rs deleted file mode 100644 index 9a74ea711e..0000000000 --- a/examples/pure/todos/src/main.rs +++ /dev/null @@ -1,556 +0,0 @@ -use iced::alignment::{self, Alignment}; -use iced::pure::widget::Text; -use iced::pure::{ - button, checkbox, column, container, row, scrollable, text, text_input, - Application, Element, -}; -use iced::theme::{self, Theme}; -use iced::window; -use iced::{Color, Command, Font, Length, Settings}; -use serde::{Deserialize, Serialize}; - -pub fn main() -> iced::Result { - Todos::run(Settings { - window: window::Settings { - size: (500, 800), - ..window::Settings::default() - }, - ..Settings::default() - }) -} - -#[derive(Debug)] -enum Todos { - Loading, - Loaded(State), -} - -#[derive(Debug, Default)] -struct State { - input_value: String, - filter: Filter, - tasks: Vec, - dirty: bool, - saving: bool, -} - -#[derive(Debug, Clone)] -enum Message { - Loaded(Result), - Saved(Result<(), SaveError>), - InputChanged(String), - CreateTask, - FilterChanged(Filter), - TaskMessage(usize, TaskMessage), -} - -impl Application for Todos { - type Message = Message; - type Theme = Theme; - type Executor = iced::executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (Todos, Command) { - ( - Todos::Loading, - Command::perform(SavedState::load(), Message::Loaded), - ) - } - - fn title(&self) -> String { - let dirty = match self { - Todos::Loading => false, - Todos::Loaded(state) => state.dirty, - }; - - format!("Todos{} - Iced", if dirty { "*" } else { "" }) - } - - fn update(&mut self, message: Message) -> Command { - match self { - Todos::Loading => { - match message { - Message::Loaded(Ok(state)) => { - *self = Todos::Loaded(State { - input_value: state.input_value, - filter: state.filter, - tasks: state.tasks, - ..State::default() - }); - } - Message::Loaded(Err(_)) => { - *self = Todos::Loaded(State::default()); - } - _ => {} - } - - Command::none() - } - Todos::Loaded(state) => { - let mut saved = false; - - match message { - Message::InputChanged(value) => { - state.input_value = value; - } - Message::CreateTask => { - if !state.input_value.is_empty() { - state - .tasks - .push(Task::new(state.input_value.clone())); - state.input_value.clear(); - } - } - Message::FilterChanged(filter) => { - state.filter = filter; - } - Message::TaskMessage(i, TaskMessage::Delete) => { - state.tasks.remove(i); - } - Message::TaskMessage(i, task_message) => { - if let Some(task) = state.tasks.get_mut(i) { - task.update(task_message); - } - } - Message::Saved(_) => { - state.saving = false; - saved = true; - } - _ => {} - } - - if !saved { - state.dirty = true; - } - - if state.dirty && !state.saving { - state.dirty = false; - state.saving = true; - - Command::perform( - SavedState { - input_value: state.input_value.clone(), - filter: state.filter, - tasks: state.tasks.clone(), - } - .save(), - Message::Saved, - ) - } else { - Command::none() - } - } - } - } - - fn view(&self) -> Element { - match self { - Todos::Loading => loading_message(), - Todos::Loaded(State { - input_value, - filter, - tasks, - .. - }) => { - let title = text("todos") - .width(Length::Fill) - .size(100) - .style(Color::from([0.5, 0.5, 0.5])) - .horizontal_alignment(alignment::Horizontal::Center); - - let input = text_input( - "What needs to be done?", - input_value, - Message::InputChanged, - ) - .padding(15) - .size(30) - .on_submit(Message::CreateTask); - - let controls = view_controls(tasks, *filter); - let filtered_tasks = - tasks.iter().filter(|task| filter.matches(task)); - - let tasks: Element<_> = if filtered_tasks.count() > 0 { - tasks - .iter() - .enumerate() - .filter(|(_, task)| filter.matches(task)) - .fold(column().spacing(20), |column, (i, task)| { - column.push(task.view().map(move |message| { - Message::TaskMessage(i, message) - })) - }) - .into() - } else { - empty_message(match filter { - Filter::All => "You have not created a task yet...", - Filter::Active => "All your tasks are done! :D", - Filter::Completed => { - "You have not completed a task yet..." - } - }) - }; - - let content = column() - .spacing(20) - .max_width(800) - .push(title) - .push(input) - .push(controls) - .push(tasks); - - scrollable( - container(content) - .width(Length::Fill) - .padding(40) - .center_x(), - ) - .into() - } - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct Task { - description: String, - completed: bool, - - #[serde(skip)] - state: TaskState, -} - -#[derive(Debug, Clone)] -pub enum TaskState { - Idle, - Editing, -} - -impl Default for TaskState { - fn default() -> Self { - Self::Idle - } -} - -#[derive(Debug, Clone)] -pub enum TaskMessage { - Completed(bool), - Edit, - DescriptionEdited(String), - FinishEdition, - Delete, -} - -impl Task { - fn new(description: String) -> Self { - Task { - description, - completed: false, - state: TaskState::Idle, - } - } - - fn update(&mut self, message: TaskMessage) { - match message { - TaskMessage::Completed(completed) => { - self.completed = completed; - } - TaskMessage::Edit => { - self.state = TaskState::Editing; - } - TaskMessage::DescriptionEdited(new_description) => { - self.description = new_description; - } - TaskMessage::FinishEdition => { - if !self.description.is_empty() { - self.state = TaskState::Idle; - } - } - TaskMessage::Delete => {} - } - } - - fn view(&self) -> Element { - match &self.state { - TaskState::Idle => { - let checkbox = checkbox( - &self.description, - self.completed, - TaskMessage::Completed, - ) - .width(Length::Fill); - - row() - .spacing(20) - .align_items(Alignment::Center) - .push(checkbox) - .push( - button(edit_icon()) - .on_press(TaskMessage::Edit) - .padding(10) - .style(theme::Button::Text), - ) - .into() - } - TaskState::Editing => { - let text_input = text_input( - "Describe your task...", - &self.description, - TaskMessage::DescriptionEdited, - ) - .on_submit(TaskMessage::FinishEdition) - .padding(10); - - row() - .spacing(20) - .align_items(Alignment::Center) - .push(text_input) - .push( - button( - row() - .spacing(10) - .push(delete_icon()) - .push("Delete"), - ) - .on_press(TaskMessage::Delete) - .padding(10) - .style(theme::Button::Destructive), - ) - .into() - } - } - } -} - -fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { - let tasks_left = tasks.iter().filter(|task| !task.completed).count(); - - let filter_button = |label, filter, current_filter| { - let label = text(label).size(16); - - let button = button(label).style(if filter == current_filter { - theme::Button::Primary - } else { - theme::Button::Text - }); - - button.on_press(Message::FilterChanged(filter)).padding(8) - }; - - row() - .spacing(20) - .align_items(Alignment::Center) - .push( - text(format!( - "{} {} left", - tasks_left, - if tasks_left == 1 { "task" } else { "tasks" } - )) - .width(Length::Fill) - .size(16), - ) - .push( - row() - .width(Length::Shrink) - .spacing(10) - .push(filter_button("All", Filter::All, current_filter)) - .push(filter_button("Active", Filter::Active, current_filter)) - .push(filter_button( - "Completed", - Filter::Completed, - current_filter, - )), - ) - .into() -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Filter { - All, - Active, - Completed, -} - -impl Default for Filter { - fn default() -> Self { - Filter::All - } -} - -impl Filter { - fn matches(&self, task: &Task) -> bool { - match self { - Filter::All => true, - Filter::Active => !task.completed, - Filter::Completed => task.completed, - } - } -} - -fn loading_message<'a>() -> Element<'a, Message> { - container( - text("Loading...") - .horizontal_alignment(alignment::Horizontal::Center) - .size(50), - ) - .width(Length::Fill) - .height(Length::Fill) - .center_y() - .into() -} - -fn empty_message(message: &str) -> Element<'_, Message> { - container( - text(message) - .width(Length::Fill) - .size(25) - .horizontal_alignment(alignment::Horizontal::Center) - .style(Color::from([0.7, 0.7, 0.7])), - ) - .width(Length::Fill) - .height(Length::Units(200)) - .center_y() - .into() -} - -// Fonts -const ICONS: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../../../todos/fonts/icons.ttf"), -}; - -fn icon(unicode: char) -> Text { - Text::new(unicode.to_string()) - .font(ICONS) - .width(Length::Units(20)) - .horizontal_alignment(alignment::Horizontal::Center) - .size(20) -} - -fn edit_icon() -> Text { - icon('\u{F303}') -} - -fn delete_icon() -> Text { - icon('\u{F1F8}') -} - -// Persistence -#[derive(Debug, Clone, Serialize, Deserialize)] -struct SavedState { - input_value: String, - filter: Filter, - tasks: Vec, -} - -#[derive(Debug, Clone)] -enum LoadError { - File, - Format, -} - -#[derive(Debug, Clone)] -enum SaveError { - File, - Write, - Format, -} - -#[cfg(not(target_arch = "wasm32"))] -impl SavedState { - fn path() -> std::path::PathBuf { - let mut path = if let Some(project_dirs) = - directories_next::ProjectDirs::from("rs", "Iced", "Todos") - { - project_dirs.data_dir().into() - } else { - std::env::current_dir().unwrap_or_default() - }; - - path.push("todos.json"); - - path - } - - async fn load() -> Result { - use async_std::prelude::*; - - let mut contents = String::new(); - - let mut file = async_std::fs::File::open(Self::path()) - .await - .map_err(|_| LoadError::File)?; - - file.read_to_string(&mut contents) - .await - .map_err(|_| LoadError::File)?; - - serde_json::from_str(&contents).map_err(|_| LoadError::Format) - } - - async fn save(self) -> Result<(), SaveError> { - use async_std::prelude::*; - - let json = serde_json::to_string_pretty(&self) - .map_err(|_| SaveError::Format)?; - - let path = Self::path(); - - if let Some(dir) = path.parent() { - async_std::fs::create_dir_all(dir) - .await - .map_err(|_| SaveError::File)?; - } - - { - let mut file = async_std::fs::File::create(path) - .await - .map_err(|_| SaveError::File)?; - - file.write_all(json.as_bytes()) - .await - .map_err(|_| SaveError::Write)?; - } - - // This is a simple way to save at most once every couple seconds - async_std::task::sleep(std::time::Duration::from_secs(2)).await; - - Ok(()) - } -} - -#[cfg(target_arch = "wasm32")] -impl SavedState { - fn storage() -> Option { - let window = web_sys::window()?; - - window.local_storage().ok()? - } - - async fn load() -> Result { - let storage = Self::storage().ok_or(LoadError::File)?; - - let contents = storage - .get_item("state") - .map_err(|_| LoadError::File)? - .ok_or(LoadError::File)?; - - serde_json::from_str(&contents).map_err(|_| LoadError::Format) - } - - async fn save(self) -> Result<(), SaveError> { - let storage = Self::storage().ok_or(SaveError::File)?; - - let json = serde_json::to_string_pretty(&self) - .map_err(|_| SaveError::Format)?; - - storage - .set_item("state", &json) - .map_err(|_| SaveError::Write)?; - - let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await; - - Ok(()) - } -} diff --git a/examples/pure/tooltip/Cargo.toml b/examples/pure/tooltip/Cargo.toml deleted file mode 100644 index d84dfb3736..0000000000 --- a/examples/pure/tooltip/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "pure_tooltip" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez ", "Casper Rogild Storm"] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["pure"] } diff --git a/examples/pure/tooltip/src/main.rs b/examples/pure/tooltip/src/main.rs deleted file mode 100644 index e9a6c11146..0000000000 --- a/examples/pure/tooltip/src/main.rs +++ /dev/null @@ -1,76 +0,0 @@ -use iced::pure::widget::tooltip::Position; -use iced::pure::{button, container, tooltip}; -use iced::pure::{Element, Sandbox}; -use iced::theme; -use iced::{Length, Settings}; - -pub fn main() -> iced::Result { - Example::run(Settings::default()) -} - -struct Example { - position: Position, -} - -#[derive(Debug, Clone)] -enum Message { - ChangePosition, -} - -impl Sandbox for Example { - type Message = Message; - - fn new() -> Self { - Self { - position: Position::Bottom, - } - } - - fn title(&self) -> String { - String::from("Tooltip - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::ChangePosition => { - let position = match &self.position { - Position::FollowCursor => Position::Top, - Position::Top => Position::Bottom, - Position::Bottom => Position::Left, - Position::Left => Position::Right, - Position::Right => Position::FollowCursor, - }; - - self.position = position - } - } - } - - fn view(&self) -> Element { - let tooltip = tooltip( - button("Press to change position") - .on_press(Message::ChangePosition), - position_to_text(self.position), - self.position, - ) - .gap(10) - .style(theme::Container::Box); - - container(tooltip) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -fn position_to_text<'a>(position: Position) -> &'a str { - match position { - Position::FollowCursor => "Follow Cursor", - Position::Top => "Top", - Position::Bottom => "Bottom", - Position::Left => "Left", - Position::Right => "Right", - } -} diff --git a/examples/pure/tour/Cargo.toml b/examples/pure/tour/Cargo.toml deleted file mode 100644 index 8ce5f198ad..0000000000 --- a/examples/pure/tour/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "pure_tour" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../../..", features = ["image", "debug", "pure"] } -env_logger = "0.8" diff --git a/examples/pure/tour/src/main.rs b/examples/pure/tour/src/main.rs deleted file mode 100644 index 05c269c37b..0000000000 --- a/examples/pure/tour/src/main.rs +++ /dev/null @@ -1,664 +0,0 @@ -use iced::alignment; -use iced::pure::widget::{Button, Column, Container, Slider}; -use iced::pure::{ - checkbox, column, container, horizontal_space, image, radio, row, - scrollable, slider, text, text_input, toggler, vertical_space, -}; -use iced::pure::{Element, Sandbox}; -use iced::theme; -use iced::{Color, Length, Renderer, Settings}; - -pub fn main() -> iced::Result { - env_logger::init(); - - Tour::run(Settings::default()) -} - -pub struct Tour { - steps: Steps, - debug: bool, -} - -impl Sandbox for Tour { - type Message = Message; - - fn new() -> Tour { - Tour { - steps: Steps::new(), - debug: false, - } - } - - fn title(&self) -> String { - format!("{} - Iced", self.steps.title()) - } - - fn update(&mut self, event: Message) { - match event { - Message::BackPressed => { - self.steps.go_back(); - } - Message::NextPressed => { - self.steps.advance(); - } - Message::StepMessage(step_msg) => { - self.steps.update(step_msg, &mut self.debug); - } - } - } - - fn view(&self) -> Element { - let Tour { steps, .. } = self; - - let mut controls = row(); - - if steps.has_previous() { - controls = controls.push( - button("Back") - .on_press(Message::BackPressed) - .style(theme::Button::Secondary), - ); - } - - controls = controls.push(horizontal_space(Length::Fill)); - - if steps.can_continue() { - controls = controls.push( - button("Next") - .on_press(Message::NextPressed) - .style(theme::Button::Primary), - ); - } - - let content: Element<_> = column() - .max_width(540) - .spacing(20) - .padding(20) - .push(steps.view(self.debug).map(Message::StepMessage)) - .push(controls) - .into(); - - let scrollable = - scrollable(container(content).width(Length::Fill).center_x()); - - container(scrollable).height(Length::Fill).center_y().into() - } -} - -#[derive(Debug, Clone)] -pub enum Message { - BackPressed, - NextPressed, - StepMessage(StepMessage), -} - -struct Steps { - steps: Vec, - current: usize, -} - -impl Steps { - fn new() -> Steps { - Steps { - steps: vec![ - Step::Welcome, - Step::Slider { value: 50 }, - Step::RowsAndColumns { - layout: Layout::Row, - spacing: 20, - }, - Step::Text { - size: 30, - color: Color::BLACK, - }, - Step::Radio { selection: None }, - Step::Toggler { - can_continue: false, - }, - Step::Image { width: 300 }, - Step::Scrollable, - Step::TextInput { - value: String::new(), - is_secure: false, - }, - Step::Debugger, - Step::End, - ], - current: 0, - } - } - - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - self.steps[self.current].update(msg, debug); - } - - fn view(&self, debug: bool) -> Element { - self.steps[self.current].view(debug) - } - - fn advance(&mut self) { - if self.can_continue() { - self.current += 1; - } - } - - fn go_back(&mut self) { - if self.has_previous() { - self.current -= 1; - } - } - - fn has_previous(&self) -> bool { - self.current > 0 - } - - fn can_continue(&self) -> bool { - self.current + 1 < self.steps.len() - && self.steps[self.current].can_continue() - } - - fn title(&self) -> &str { - self.steps[self.current].title() - } -} - -enum Step { - Welcome, - Slider { value: u8 }, - RowsAndColumns { layout: Layout, spacing: u16 }, - Text { size: u16, color: Color }, - Radio { selection: Option }, - Toggler { can_continue: bool }, - Image { width: u16 }, - Scrollable, - TextInput { value: String, is_secure: bool }, - Debugger, - End, -} - -#[derive(Debug, Clone)] -pub enum StepMessage { - SliderChanged(u8), - LayoutChanged(Layout), - SpacingChanged(u16), - TextSizeChanged(u16), - TextColorChanged(Color), - LanguageSelected(Language), - ImageWidthChanged(u16), - InputChanged(String), - ToggleSecureInput(bool), - DebugToggled(bool), - TogglerChanged(bool), -} - -impl<'a> Step { - fn update(&mut self, msg: StepMessage, debug: &mut bool) { - match msg { - StepMessage::DebugToggled(value) => { - if let Step::Debugger = self { - *debug = value; - } - } - StepMessage::LanguageSelected(language) => { - if let Step::Radio { selection } = self { - *selection = Some(language); - } - } - StepMessage::SliderChanged(new_value) => { - if let Step::Slider { value, .. } = self { - *value = new_value; - } - } - StepMessage::TextSizeChanged(new_size) => { - if let Step::Text { size, .. } = self { - *size = new_size; - } - } - StepMessage::TextColorChanged(new_color) => { - if let Step::Text { color, .. } = self { - *color = new_color; - } - } - StepMessage::LayoutChanged(new_layout) => { - if let Step::RowsAndColumns { layout, .. } = self { - *layout = new_layout; - } - } - StepMessage::SpacingChanged(new_spacing) => { - if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing; - } - } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width; - } - } - StepMessage::InputChanged(new_value) => { - if let Step::TextInput { value, .. } = self { - *value = new_value; - } - } - StepMessage::ToggleSecureInput(toggle) => { - if let Step::TextInput { is_secure, .. } = self { - *is_secure = toggle; - } - } - StepMessage::TogglerChanged(value) => { - if let Step::Toggler { can_continue, .. } = self { - *can_continue = value; - } - } - }; - } - - fn title(&self) -> &str { - match self { - Step::Welcome => "Welcome", - Step::Radio { .. } => "Radio button", - Step::Toggler { .. } => "Toggler", - Step::Slider { .. } => "Slider", - Step::Text { .. } => "Text", - Step::Image { .. } => "Image", - Step::RowsAndColumns { .. } => "Rows and columns", - Step::Scrollable => "Scrollable", - Step::TextInput { .. } => "Text input", - Step::Debugger => "Debugger", - Step::End => "End", - } - } - - fn can_continue(&self) -> bool { - match self { - Step::Welcome => true, - Step::Radio { selection } => *selection == Some(Language::Rust), - Step::Toggler { can_continue } => *can_continue, - Step::Slider { .. } => true, - Step::Text { .. } => true, - Step::Image { .. } => true, - Step::RowsAndColumns { .. } => true, - Step::Scrollable => true, - Step::TextInput { value, .. } => !value.is_empty(), - Step::Debugger => true, - Step::End => false, - } - } - - fn view(&self, debug: bool) -> Element { - match self { - Step::Welcome => Self::welcome(), - Step::Radio { selection } => Self::radio(*selection), - Step::Toggler { can_continue } => Self::toggler(*can_continue), - Step::Slider { value } => Self::slider(*value), - Step::Text { size, color } => Self::text(*size, *color), - Step::Image { width } => Self::image(*width), - Step::RowsAndColumns { layout, spacing } => { - Self::rows_and_columns(*layout, *spacing) - } - Step::Scrollable => Self::scrollable(), - Step::TextInput { value, is_secure } => { - Self::text_input(value, *is_secure) - } - Step::Debugger => Self::debugger(debug), - Step::End => Self::end(), - } - .into() - } - - fn container(title: &str) -> Column<'a, StepMessage> { - column().spacing(20).push(text(title).size(50)) - } - - fn welcome() -> Column<'a, StepMessage> { - Self::container("Welcome!") - .push( - "This is a simple tour meant to showcase a bunch of widgets \ - that can be easily implemented on top of Iced.", - ) - .push( - "Iced is a cross-platform GUI library for Rust focused on \ - simplicity and type-safety. It is heavily inspired by Elm.", - ) - .push( - "It was originally born as part of Coffee, an opinionated \ - 2D game engine for Rust.", - ) - .push( - "On native platforms, Iced provides by default a renderer \ - built on top of wgpu, a graphics library supporting Vulkan, \ - Metal, DX11, and DX12.", - ) - .push( - "Additionally, this tour can also run on WebAssembly thanks \ - to dodrio, an experimental VDOM library for Rust.", - ) - .push( - "You will need to interact with the UI in order to reach the \ - end!", - ) - } - - fn slider(value: u8) -> Column<'a, StepMessage> { - Self::container("Slider") - .push( - "A slider allows you to smoothly select a value from a range \ - of values.", - ) - .push( - "The following slider lets you choose an integer from \ - 0 to 100:", - ) - .push(slider(0..=100, value, StepMessage::SliderChanged)) - .push( - text(value.to_string()) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) - } - - fn rows_and_columns( - layout: Layout, - spacing: u16, - ) -> Column<'a, StepMessage> { - let row_radio = - radio("Row", Layout::Row, Some(layout), StepMessage::LayoutChanged); - - let column_radio = radio( - "Column", - Layout::Column, - Some(layout), - StepMessage::LayoutChanged, - ); - - let layout_section: Element<_> = match layout { - Layout::Row => row() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - Layout::Column => column() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - }; - - let spacing_section = column() - .spacing(10) - .push(slider(0..=80, spacing, StepMessage::SpacingChanged)) - .push( - text(format!("{} px", spacing)) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ); - - Self::container("Rows and columns") - .spacing(spacing) - .push( - "Iced uses a layout model based on flexbox to position UI \ - elements.", - ) - .push( - "Rows and columns can be used to distribute content \ - horizontally or vertically, respectively.", - ) - .push(layout_section) - .push("You can also easily change the spacing between elements:") - .push(spacing_section) - } - - fn text(size: u16, color: Color) -> Column<'a, StepMessage> { - let size_section = column() - .padding(20) - .spacing(20) - .push("You can change its size:") - .push(text(format!("This text is {} pixels", size)).size(size)) - .push(Slider::new(10..=70, size, StepMessage::TextSizeChanged)); - - let color_sliders = row() - .spacing(10) - .push(color_slider(color.r, move |r| Color { r, ..color })) - .push(color_slider(color.g, move |g| Color { g, ..color })) - .push(color_slider(color.b, move |b| Color { b, ..color })); - - let color_section = column() - .padding(20) - .spacing(20) - .push("And its color:") - .push(text(format!("{:?}", color)).style(color)) - .push(color_sliders); - - Self::container("Text") - .push( - "Text is probably the most essential widget for your UI. \ - It will try to adapt to the dimensions of its container.", - ) - .push(size_section) - .push(color_section) - } - - fn radio(selection: Option) -> Column<'a, StepMessage> { - let question = column() - .padding(20) - .spacing(10) - .push(text("Iced is written in...").size(24)) - .push(Language::all().iter().cloned().fold( - column().padding(10).spacing(20), - |choices, language| { - choices.push(radio( - language, - language, - selection, - StepMessage::LanguageSelected, - )) - }, - )); - - Self::container("Radio button") - .push( - "A radio button is normally used to represent a choice... \ - Surprise test!", - ) - .push(question) - .push( - "Iced works very well with iterators! The list above is \ - basically created by folding a column over the different \ - choices, creating a radio button for each one of them!", - ) - } - - fn toggler(can_continue: bool) -> Column<'a, StepMessage> { - Self::container("Toggler") - .push("A toggler is mostly used to enable or disable something.") - .push( - Container::new(toggler( - "Toggle me to continue...".to_owned(), - can_continue, - StepMessage::TogglerChanged, - )) - .padding([0, 40]), - ) - } - - fn image(width: u16) -> Column<'a, StepMessage> { - Self::container("Image") - .push("An image that tries to keep its aspect ratio.") - .push(ferris(width)) - .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) - .push( - text(format!("Width: {} px", width)) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) - } - - fn scrollable() -> Column<'a, StepMessage> { - Self::container("Scrollable") - .push( - "Iced supports scrollable content. Try it out! Find the \ - button further below.", - ) - .push( - text("Tip: You can use the scrollbar to scroll down faster!") - .size(16), - ) - .push(vertical_space(Length::Units(4096))) - .push( - text("You are halfway there!") - .width(Length::Fill) - .size(30) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .push(vertical_space(Length::Units(4096))) - .push(ferris(300)) - .push( - text("You made it!") - .width(Length::Fill) - .size(50) - .horizontal_alignment(alignment::Horizontal::Center), - ) - } - - fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> { - let text_input = text_input( - "Type something to continue...", - value, - StepMessage::InputChanged, - ) - .padding(10) - .size(30); - - Self::container("Text input") - .push("Use a text input to ask for different kinds of information.") - .push(if is_secure { - text_input.password() - } else { - text_input - }) - .push(checkbox( - "Enable password mode", - is_secure, - StepMessage::ToggleSecureInput, - )) - .push( - "A text input produces a message every time it changes. It is \ - very easy to keep track of its contents:", - ) - .push( - text(if value.is_empty() { - "You have not typed anything yet..." - } else { - value - }) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) - } - - fn debugger(debug: bool) -> Column<'a, StepMessage> { - Self::container("Debugger") - .push( - "You can ask Iced to visually explain the layouting of the \ - different elements comprising your UI!", - ) - .push( - "Give it a shot! Check the following checkbox to be able to \ - see element boundaries.", - ) - .push(if cfg!(target_arch = "wasm32") { - Element::new( - text("Not available on web yet!") - .style(Color::from([0.7, 0.7, 0.7])) - .horizontal_alignment(alignment::Horizontal::Center), - ) - } else { - checkbox("Explain layout", debug, StepMessage::DebugToggled) - .into() - }) - .push("Feel free to go back and take a look.") - } - - fn end() -> Column<'a, StepMessage> { - Self::container("You reached the end!") - .push("This tour will be updated as more features are added.") - .push("Make sure to keep an eye on it!") - } -} - -fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { - container( - // This should go away once we unify resource loading on native - // platforms - if cfg!(target_arch = "wasm32") { - image("tour/images/ferris.png") - } else { - image(format!( - "{}/../../tour/images/ferris.png", - env!("CARGO_MANIFEST_DIR") - )) - } - .width(Length::Units(width)), - ) - .width(Length::Fill) - .center_x() -} - -fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { - iced::pure::button( - text(label).horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(12) - .width(Length::Units(100)) -} - -fn color_slider<'a>( - component: f32, - update: impl Fn(f32) -> Color + 'a, -) -> Slider<'a, f64, StepMessage, Renderer> { - slider(0.0..=1.0, f64::from(component), move |c| { - StepMessage::TextColorChanged(update(c as f32)) - }) - .step(0.01) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Other, -} - -impl Language { - fn all() -> [Language; 6] { - [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Other, - ] - } -} - -impl From for String { - fn from(language: Language) -> String { - String::from(match language { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Other => "Other", - }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Layout { - Row, - Column, -} diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs index 3e9ba9213d..6f487e4cbd 100644 --- a/examples/qr_code/src/main.rs +++ b/examples/qr_code/src/main.rs @@ -1,9 +1,6 @@ -use iced::qr_code::{self, QRCode}; -use iced::text_input::{self, TextInput}; -use iced::{ - Alignment, Color, Column, Container, Element, Length, Sandbox, Settings, - Text, -}; +use iced::widget::qr_code::{self, QRCode}; +use iced::widget::{column, container, text, text_input}; +use iced::{Alignment, Color, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { QRGenerator::run(Settings::default()) @@ -12,7 +9,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct QRGenerator { data: String, - input: text_input::State, qr_code: Option, } @@ -46,13 +42,12 @@ impl Sandbox for QRGenerator { } } - fn view(&mut self) -> Element { - let title = Text::new("QR Code Generator") + fn view(&self) -> Element { + let title = text("QR Code Generator") .size(70) .style(Color::from([0.5, 0.5, 0.5])); - let input = TextInput::new( - &mut self.input, + let input = text_input( "Type the data of your QR code here...", &self.data, Message::DataChanged, @@ -60,18 +55,16 @@ impl Sandbox for QRGenerator { .size(30) .padding(15); - let mut content = Column::new() + let mut content = column![title, input] .width(Length::Units(700)) .spacing(20) - .align_items(Alignment::Center) - .push(title) - .push(input); + .align_items(Alignment::Center); - if let Some(qr_code) = self.qr_code.as_mut() { + if let Some(qr_code) = self.qr_code.as_ref() { content = content.push(QRCode::new(qr_code).cell_size(10)); } - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .padding(20) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index f66d21806f..405c8daf9c 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,9 +1,8 @@ -use iced::button; -use iced::scrollable; -use iced::{ - Button, Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, - Sandbox, Scrollable, Settings, Space, Text, Theme, +use iced::widget::{ + button, column, container, horizontal_rule, progress_bar, radio, + scrollable, text, vertical_space, Row, }; +use iced::{Element, Length, Sandbox, Settings, Theme}; pub fn main() -> iced::Result { ScrollableDemo::run(Settings::default()) @@ -41,14 +40,16 @@ impl Sandbox for ScrollableDemo { Message::ThemeChanged(theme) => self.theme = theme, Message::ScrollToTop(i) => { if let Some(variant) = self.variants.get_mut(i) { - variant.scrollable.snap_to(0.0); + // TODO + // variant.scrollable.snap_to(0.0); variant.latest_offset = 0.0; } } Message::ScrollToBottom(i) => { if let Some(variant) = self.variants.get_mut(i) { - variant.scrollable.snap_to(1.0); + // TODO + // variant.scrollable.snap_to(1.0); variant.latest_offset = 1.0; } @@ -61,17 +62,17 @@ impl Sandbox for ScrollableDemo { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let ScrollableDemo { theme, variants, .. } = self; let choose_theme = [Theme::Light, Theme::Dark].iter().fold( - Column::new().spacing(10).push(Text::new("Choose a theme:")), + column!["Choose a theme:"].spacing(10), |column, option| { - column.push(Radio::new( - *option, + column.push(radio( format!("{:?}", option), + *option, Some(*theme), Message::ThemeChanged, )) @@ -80,88 +81,86 @@ impl Sandbox for ScrollableDemo { let scrollable_row = Row::with_children( variants - .iter_mut() + .iter() .enumerate() .map(|(i, variant)| { - let mut scrollable = - Scrollable::new(&mut variant.scrollable) - .padding(10) - .spacing(10) + let mut contents = column![ + variant.title.as_ref(), + button("Scroll to bottom",) .width(Length::Fill) - .height(Length::Fill) - .on_scroll(move |offset| { - Message::Scrolled(i, offset) - }) - .push(Text::new(variant.title)) - .push( - Button::new( - &mut variant.scroll_to_bottom, - Text::new("Scroll to bottom"), - ) - .width(Length::Fill) - .padding(10) - .on_press(Message::ScrollToBottom(i)), - ); + .padding(10) + .on_press(Message::ScrollToBottom(i)), + ] + .padding(10) + .spacing(10) + .width(Length::Fill); if let Some(scrollbar_width) = variant.scrollbar_width { - scrollable = scrollable - .scrollbar_width(scrollbar_width) - .push(Text::new(format!( - "scrollbar_width: {:?}", - scrollbar_width - ))); + contents = contents.push(text(format!( + "scrollbar_width: {:?}", + scrollbar_width + ))); } if let Some(scrollbar_margin) = variant.scrollbar_margin { - scrollable = scrollable - .scrollbar_margin(scrollbar_margin) - .push(Text::new(format!( - "scrollbar_margin: {:?}", - scrollbar_margin - ))); + contents = contents.push(text(format!( + "scrollbar_margin: {:?}", + scrollbar_margin + ))); } if let Some(scroller_width) = variant.scroller_width { - scrollable = scrollable - .scroller_width(scroller_width) - .push(Text::new(format!( - "scroller_width: {:?}", - scroller_width - ))); + contents = contents.push(text(format!( + "scroller_width: {:?}", + scroller_width + ))); } - scrollable = scrollable - .push(Space::with_height(Length::Units(100))) - .push(Text::new( + contents = contents + .push(vertical_space(Length::Units(100))) + .push( "Some content that should wrap within the \ scrollable. Let's output a lot of short words, so \ that we'll make sure to see how wrapping works \ with these scrollbars.", - )) - .push(Space::with_height(Length::Units(1200))) - .push(Text::new("Middle")) - .push(Space::with_height(Length::Units(1200))) - .push(Text::new("The End.")) + ) + .push(vertical_space(Length::Units(1200))) + .push("Middle") + .push(vertical_space(Length::Units(1200))) + .push("The End.") .push( - Button::new( - &mut variant.scroll_to_top, - Text::new("Scroll to top"), - ) - .width(Length::Fill) - .padding(10) - .on_press(Message::ScrollToTop(i)), + button("Scroll to top") + .width(Length::Fill) + .padding(10) + .on_press(Message::ScrollToTop(i)), ); - Column::new() - .width(Length::Fill) + let mut scrollable = scrollable(contents) .height(Length::Fill) - .spacing(10) - .push(scrollable) - .push(ProgressBar::new( - 0.0..=1.0, - variant.latest_offset, - )) - .into() + .on_scroll(move |offset| Message::Scrolled(i, offset)); + + if let Some(scrollbar_width) = variant.scrollbar_width { + scrollable = + scrollable.scrollbar_width(scrollbar_width); + } + + if let Some(scrollbar_margin) = variant.scrollbar_margin { + scrollable = + scrollable.scrollbar_margin(scrollbar_margin); + } + + if let Some(scroller_width) = variant.scroller_width { + scrollable = scrollable.scroller_width(scroller_width); + } + + column![ + scrollable, + progress_bar(0.0..=1.0, variant.latest_offset,) + ] + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .into() }) .collect(), ) @@ -169,14 +168,12 @@ impl Sandbox for ScrollableDemo { .width(Length::Fill) .height(Length::Fill); - let content = Column::new() - .spacing(20) - .padding(20) - .push(choose_theme) - .push(Rule::horizontal(20)) - .push(scrollable_row); + let content = + column![choose_theme, horizontal_rule(20), scrollable_row] + .spacing(20) + .padding(20); - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() @@ -192,9 +189,6 @@ impl Sandbox for ScrollableDemo { /// A version of a scrollable struct Variant { title: &'static str, - scrollable: scrollable::State, - scroll_to_top: button::State, - scroll_to_bottom: button::State, scrollbar_width: Option, scrollbar_margin: Option, scroller_width: Option, @@ -206,9 +200,6 @@ impl Variant { vec![ Self { title: "Default Scrollbar", - scrollable: scrollable::State::new(), - scroll_to_top: button::State::new(), - scroll_to_bottom: button::State::new(), scrollbar_width: None, scrollbar_margin: None, scroller_width: None, @@ -216,9 +207,6 @@ impl Variant { }, Self { title: "Slimmed & Margin", - scrollable: scrollable::State::new(), - scroll_to_top: button::State::new(), - scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: Some(3), scroller_width: Some(4), @@ -226,9 +214,6 @@ impl Variant { }, Self { title: "Wide Scroller", - scrollable: scrollable::State::new(), - scroll_to_top: button::State::new(), - scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: None, scroller_width: Some(10), @@ -236,9 +221,6 @@ impl Variant { }, Self { title: "Narrow Scroller", - scrollable: scrollable::State::new(), - scroll_to_top: button::State::new(), - scroll_to_bottom: button::State::new(), scrollbar_width: Some(10), scrollbar_margin: None, scroller_width: Some(4), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index fc53d8f7c2..c59d73a833 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -7,14 +7,15 @@ //! //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::application; -use iced::canvas::{self, Cursor, Path, Stroke}; use iced::executor; use iced::theme::{self, Theme}; use iced::time; +use iced::widget::canvas; +use iced::widget::canvas::{Cursor, Path, Stroke}; use iced::window; use iced::{ - Application, Canvas, Color, Command, Element, Length, Point, Rectangle, - Settings, Size, Subscription, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Settings, + Size, Subscription, Vector, }; use std::time::Instant; @@ -68,8 +69,8 @@ impl Application for SolarSystem { time::every(std::time::Duration::from_millis(10)).map(Message::Tick) } - fn view(&mut self) -> Element { - Canvas::new(&mut self.state) + fn view(&self) -> Element { + canvas(&self.state) .width(Length::Fill) .height(Length::Fill) .into() @@ -145,8 +146,11 @@ impl State { } impl canvas::Program for State { + type State = (); + fn draw( &self, + _state: &Self::State, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index b7c816ff3e..b8cee807e0 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,11 +1,10 @@ use iced::alignment; -use iced::button; use iced::executor; use iced::theme::{self, Theme}; use iced::time; +use iced::widget::{button, column, container, row, text}; use iced::{ - Alignment, Application, Button, Column, Command, Container, Element, - Length, Row, Settings, Subscription, Text, + Alignment, Application, Command, Element, Length, Settings, Subscription, }; use std::time::{Duration, Instant}; @@ -17,8 +16,6 @@ pub fn main() -> iced::Result { struct Stopwatch { duration: Duration, state: State, - toggle: button::State, - reset: button::State, } enum State { @@ -44,8 +41,6 @@ impl Application for Stopwatch { Stopwatch { duration: Duration::default(), state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), }, Command::none(), ) @@ -90,13 +85,13 @@ impl Application for Stopwatch { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { const MINUTE: u64 = 60; const HOUR: u64 = 60 * MINUTE; let seconds = self.duration.as_secs(); - let duration = Text::new(format!( + let duration = text(format!( "{:0>2}:{:0>2}:{:0>2}.{:0>2}", seconds / HOUR, (seconds % HOUR) / MINUTE, @@ -105,11 +100,9 @@ impl Application for Stopwatch { )) .size(40); - let button = |state, label| { - Button::new( - state, - Text::new(label) - .horizontal_alignment(alignment::Horizontal::Center), + let button = |label| { + button( + text(label).horizontal_alignment(alignment::Horizontal::Center), ) .padding(10) .width(Length::Units(80)) @@ -121,25 +114,20 @@ impl Application for Stopwatch { State::Ticking { .. } => "Stop", }; - button(&mut self.toggle, label).on_press(Message::Toggle) + button(label).on_press(Message::Toggle) }; - let reset_button = button(&mut self.reset, "Reset") + let reset_button = button("Reset") .style(theme::Button::Destructive) .on_press(Message::Reset); - let controls = Row::new() - .spacing(20) - .push(toggle_button) - .push(reset_button); + let controls = row![toggle_button, reset_button].spacing(20); - let content = Column::new() + let content = column![duration, controls] .align_items(Alignment::Center) - .spacing(20) - .push(duration) - .push(controls); + .spacing(20); - Container::new(content) + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index aa90d17c3a..cda53e8796 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,12 +1,9 @@ -use iced::button; -use iced::scrollable; -use iced::slider; -use iced::text_input; -use iced::{ - Alignment, Button, Checkbox, Column, Container, Element, Length, - ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Slider, - Space, Text, TextInput, Theme, Toggler, +use iced::widget::{ + button, checkbox, column, container, horizontal_rule, progress_bar, radio, + row, scrollable, slider, text, text_input, toggler, vertical_rule, + vertical_space, }; +use iced::{Alignment, Element, Length, Sandbox, Settings, Theme}; pub fn main() -> iced::Result { Styling::run(Settings::default()) @@ -15,11 +12,7 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Styling { theme: Theme, - scroll: scrollable::State, - input: text_input::State, input_value: String, - button: button::State, - slider: slider::State, slider_value: f32, checkbox_value: bool, toggler_value: bool, @@ -57,21 +50,20 @@ impl Sandbox for Styling { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let choose_theme = [Theme::Light, Theme::Dark].iter().fold( - Column::new().spacing(10).push(Text::new("Choose a theme:")), + column![text("Choose a theme:")].spacing(10), |column, theme| { - column.push(Radio::new( - *theme, + column.push(radio( format!("{:?}", theme), + *theme, Some(self.theme), Message::ThemeChanged, )) }, ); - let text_input = TextInput::new( - &mut self.input, + let text_input = text_input( "Type something...", &self.input_value, Message::InputChanged, @@ -79,66 +71,59 @@ impl Sandbox for Styling { .padding(10) .size(20); - let button = Button::new(&mut self.button, Text::new("Submit")) + let button = button("Submit") .padding(10) .on_press(Message::ButtonPressed); - let slider = Slider::new( - &mut self.slider, - 0.0..=100.0, - self.slider_value, - Message::SliderChanged, - ); + let slider = + slider(0.0..=100.0, self.slider_value, Message::SliderChanged); - let progress_bar = ProgressBar::new(0.0..=100.0, self.slider_value); + let progress_bar = progress_bar(0.0..=100.0, self.slider_value); - let scrollable = Scrollable::new(&mut self.scroll) - .width(Length::Fill) - .height(Length::Units(100)) - .push(Text::new("Scroll me!")) - .push(Space::with_height(Length::Units(800))) - .push(Text::new("You did it!")); + let scrollable = scrollable( + column![ + "Scroll me!", + vertical_space(Length::Units(800)), + "You did it!" + ] + .width(Length::Fill), + ) + .height(Length::Units(100)); - let checkbox = Checkbox::new( - self.checkbox_value, + let checkbox = checkbox( "Check me!", + self.checkbox_value, Message::CheckboxToggled, ); - let toggler = Toggler::new( - self.toggler_value, + let toggler = toggler( String::from("Toggle me!"), + self.toggler_value, Message::TogglerToggled, ) .width(Length::Shrink) .spacing(10); - let content = Column::new() - .spacing(20) - .padding(20) - .max_width(600) - .push(choose_theme) - .push(Rule::horizontal(38)) - .push(Row::new().spacing(10).push(text_input).push(button)) - .push(slider) - .push(progress_bar) - .push( - Row::new() - .spacing(10) - .height(Length::Units(100)) - .align_items(Alignment::Center) - .push(scrollable) - .push(Rule::vertical(38)) - .push( - Column::new() - .width(Length::Shrink) - .spacing(20) - .push(checkbox) - .push(toggler), - ), - ); - - Container::new(content) + let content = column![ + choose_theme, + horizontal_rule(38), + row![text_input, button].spacing(10), + slider, + progress_bar, + row![ + scrollable, + vertical_rule(38), + column![checkbox, toggler].spacing(20) + ] + .spacing(10) + .height(Length::Units(100)) + .align_items(Alignment::Center), + ] + .spacing(20) + .padding(20) + .max_width(600); + + container(content) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 8707fa3b67..27d175da7d 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,4 +1,5 @@ -use iced::{Container, Element, Length, Sandbox, Settings, Svg}; +use iced::widget::{container, svg}; +use iced::{Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Tiger::run(Settings::default()) @@ -19,15 +20,15 @@ impl Sandbox for Tiger { fn update(&mut self, _message: ()) {} - fn view(&mut self) -> Element<()> { - let svg = Svg::from_path(format!( + fn view(&self) -> Element<()> { + let svg = svg(svg::Handle::from_path(format!( "{}/resources/tiger.svg", env!("CARGO_MANIFEST_DIR") - )) + ))) .width(Length::Fill) .height(Length::Fill); - Container::new(svg) + container(svg) .width(Length::Fill) .height(Length::Fill) .padding(20) diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 54fc635fa3..af67742f99 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -1,6 +1,6 @@ +use iced::widget::{button, column, container, text}; use iced::{ - button, executor, system, Application, Button, Column, Command, Container, - Element, Length, Settings, Text, Theme, + executor, system, Application, Command, Element, Length, Settings, Theme, }; use bytesize::ByteSize; @@ -11,10 +11,7 @@ pub fn main() -> iced::Result { enum Example { Loading, - Loaded { - information: system::Information, - refresh_button: button::State, - }, + Loaded { information: system::Information }, } #[derive(Clone, Debug)] @@ -48,25 +45,18 @@ impl Application for Example { return system::fetch_information(Message::InformationReceived); } Message::InformationReceived(information) => { - let refresh_button = button::State::new(); - *self = Self::Loaded { - information, - refresh_button, - }; + *self = Self::Loaded { information }; } } Command::none() } - fn view(&mut self) -> Element { - let content: Element = match self { - Example::Loading => Text::new("Loading...").size(40).into(), - Example::Loaded { - information, - refresh_button, - } => { - let system_name = Text::new(format!( + fn view(&self) -> Element { + let content: Element<_> = match self { + Example::Loading => text("Loading...").size(40).into(), + Example::Loaded { information } => { + let system_name = text(format!( "System name: {}", information .system_name @@ -74,7 +64,7 @@ impl Application for Example { .unwrap_or(&"unknown".to_string()) )); - let system_kernel = Text::new(format!( + let system_kernel = text(format!( "System kernel: {}", information .system_kernel @@ -82,7 +72,7 @@ impl Application for Example { .unwrap_or(&"unknown".to_string()) )); - let system_version = Text::new(format!( + let system_version = text(format!( "System version: {}", information .system_version @@ -90,12 +80,10 @@ impl Application for Example { .unwrap_or(&"unknown".to_string()) )); - let cpu_brand = Text::new(format!( - "Processor brand: {}", - information.cpu_brand - )); + let cpu_brand = + text(format!("Processor brand: {}", information.cpu_brand)); - let cpu_cores = Text::new(format!( + let cpu_cores = text(format!( "Processor cores: {}", information .cpu_cores @@ -106,7 +94,7 @@ impl Application for Example { let memory_readable = ByteSize::kb(information.memory_total).to_string(); - let memory_total = Text::new(format!( + let memory_total = text(format!( "Memory (total): {} kb ({})", information.memory_total, memory_readable )); @@ -122,38 +110,36 @@ impl Application for Example { }; let memory_used = - Text::new(format!("Memory (used): {}", memory_text)); + text(format!("Memory (used): {}", memory_text)); - let graphics_adapter = Text::new(format!( + let graphics_adapter = text(format!( "Graphics adapter: {}", information.graphics_adapter )); - let graphics_backend = Text::new(format!( + let graphics_backend = text(format!( "Graphics backend: {}", information.graphics_backend )); - Column::with_children(vec![ - system_name.size(30).into(), - system_kernel.size(30).into(), - system_version.size(30).into(), - cpu_brand.size(30).into(), - cpu_cores.size(30).into(), - memory_total.size(30).into(), - memory_used.size(30).into(), - graphics_adapter.size(30).into(), - graphics_backend.size(30).into(), - Button::new(refresh_button, Text::new("Refresh")) - .on_press(Message::Refresh) - .into(), - ]) + column![ + system_name.size(30), + system_kernel.size(30), + system_version.size(30), + cpu_brand.size(30), + cpu_cores.size(30), + memory_total.size(30), + memory_used.size(30), + graphics_adapter.size(30), + graphics_backend.size(30), + button("Refresh").on_press(Message::Refresh) + ] .spacing(10) .into() } }; - Container::new(content) + container(content) .center_x() .center_y() .width(Length::Fill) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 363e3fb802..7dde235a1f 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,16 +1,22 @@ use iced::alignment::{self, Alignment}; -use iced::button::{self, Button}; -use iced::scrollable::{self, Scrollable}; -use iced::text_input::{self, TextInput}; use iced::theme::{self, Theme}; -use iced::{ - Application, Checkbox, Color, Column, Command, Container, Element, Font, - Length, Row, Settings, Text, +use iced::widget::{ + button, checkbox, column, container, row, scrollable, text, text_input, + Text, }; +use iced::window; +use iced::{Application, Element}; +use iced::{Color, Command, Font, Length, Settings}; use serde::{Deserialize, Serialize}; pub fn main() -> iced::Result { - Todos::run(Settings::default()) + Todos::run(Settings { + window: window::Settings { + size: (500, 800), + ..window::Settings::default() + }, + ..Settings::default() + }) } #[derive(Debug)] @@ -21,12 +27,9 @@ enum Todos { #[derive(Debug, Default)] struct State { - scroll: scrollable::State, - input: text_input::State, input_value: String, filter: Filter, tasks: Vec, - controls: Controls, dirty: bool, saving: bool, } @@ -42,9 +45,9 @@ enum Message { } impl Application for Todos { - type Executor = iced::executor::Default; - type Theme = Theme; type Message = Message; + type Theme = Theme; + type Executor = iced::executor::Default; type Flags = (); fn new(_flags: ()) -> (Todos, Command) { @@ -140,26 +143,22 @@ impl Application for Todos { } } - fn view(&mut self) -> Element { + fn view(&self) -> Element { match self { Todos::Loading => loading_message(), Todos::Loaded(State { - scroll, - input, input_value, filter, tasks, - controls, .. }) => { - let title = Text::new("todos") + let title = text("todos") .width(Length::Fill) .size(100) .style(Color::from([0.5, 0.5, 0.5])) .horizontal_alignment(alignment::Horizontal::Center); - let input = TextInput::new( - input, + let input = text_input( "What needs to be done?", input_value, Message::InputChanged, @@ -168,21 +167,24 @@ impl Application for Todos { .size(30) .on_submit(Message::CreateTask); - let controls = controls.view(tasks, *filter); + let controls = view_controls(tasks, *filter); let filtered_tasks = tasks.iter().filter(|task| filter.matches(task)); let tasks: Element<_> = if filtered_tasks.count() > 0 { - tasks - .iter_mut() - .enumerate() - .filter(|(_, task)| filter.matches(task)) - .fold(Column::new().spacing(20), |column, (i, task)| { - column.push(task.view().map(move |message| { - Message::TaskMessage(i, message) - })) - }) - .into() + column( + tasks + .iter() + .enumerate() + .filter(|(_, task)| filter.matches(task)) + .map(|(i, task)| { + task.view().map(move |message| { + Message::TaskMessage(i, message) + }) + }) + .collect(), + ) + .into() } else { empty_message(match filter { Filter::All => "You have not created a task yet...", @@ -193,20 +195,17 @@ impl Application for Todos { }) }; - let content = Column::new() - .max_width(800) + let content = column![title, input, controls, tasks] .spacing(20) - .push(title) - .push(input) - .push(controls) - .push(tasks); - - Scrollable::new(scroll) - .padding(40) - .push( - Container::new(content).width(Length::Fill).center_x(), - ) - .into() + .max_width(800); + + scrollable( + container(content) + .width(Length::Fill) + .padding(40) + .center_x(), + ) + .into() } } } @@ -223,20 +222,13 @@ struct Task { #[derive(Debug, Clone)] pub enum TaskState { - Idle { - edit_button: button::State, - }, - Editing { - text_input: text_input::State, - delete_button: button::State, - }, + Idle, + Editing, } impl Default for TaskState { fn default() -> Self { - TaskState::Idle { - edit_button: button::State::new(), - } + Self::Idle } } @@ -254,9 +246,7 @@ impl Task { Task { description, completed: false, - state: TaskState::Idle { - edit_button: button::State::new(), - }, + state: TaskState::Idle, } } @@ -266,56 +256,43 @@ impl Task { self.completed = completed; } TaskMessage::Edit => { - let mut text_input = text_input::State::focused(); - text_input.select_all(); - - self.state = TaskState::Editing { - text_input, - delete_button: button::State::new(), - }; + self.state = TaskState::Editing; } TaskMessage::DescriptionEdited(new_description) => { self.description = new_description; } TaskMessage::FinishEdition => { if !self.description.is_empty() { - self.state = TaskState::Idle { - edit_button: button::State::new(), - } + self.state = TaskState::Idle; } } TaskMessage::Delete => {} } } - fn view(&mut self) -> Element { - match &mut self.state { - TaskState::Idle { edit_button } => { - let checkbox = Checkbox::new( - self.completed, + fn view(&self) -> Element { + match &self.state { + TaskState::Idle => { + let checkbox = checkbox( &self.description, + self.completed, TaskMessage::Completed, ) .width(Length::Fill); - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push(checkbox) - .push( - Button::new(edit_button, edit_icon()) - .on_press(TaskMessage::Edit) - .padding(10) - .style(theme::Button::Text), - ) - .into() + row![ + checkbox, + button(edit_icon()) + .on_press(TaskMessage::Edit) + .padding(10) + .style(theme::Button::Text), + ] + .spacing(20) + .align_items(Alignment::Center) + .into() } - TaskState::Editing { - text_input, - delete_button, - } => { - let text_input = TextInput::new( - text_input, + TaskState::Editing => { + let text_input = text_input( "Describe your task...", &self.description, TaskMessage::DescriptionEdited, @@ -323,93 +300,55 @@ impl Task { .on_submit(TaskMessage::FinishEdition) .padding(10); - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push(text_input) - .push( - Button::new( - delete_button, - Row::new() - .spacing(10) - .push(delete_icon()) - .push(Text::new("Delete")), - ) + row![ + text_input, + button(row![delete_icon(), "Delete"].spacing(10)) .on_press(TaskMessage::Delete) .padding(10) - .style(theme::Button::Destructive), - ) - .into() + .style(theme::Button::Destructive) + ] + .spacing(20) + .align_items(Alignment::Center) + .into() } } } } -#[derive(Debug, Default, Clone)] -pub struct Controls { - all_button: button::State, - active_button: button::State, - completed_button: button::State, -} - -impl Controls { - fn view(&mut self, tasks: &[Task], current_filter: Filter) -> Row { - let Controls { - all_button, - active_button, - completed_button, - } = self; - - let tasks_left = tasks.iter().filter(|task| !task.completed).count(); - - let filter_button = |state, label, filter, current_filter| { - let label = Text::new(label).size(16); - let button = - Button::new(state, label).style(if filter == current_filter { - theme::Button::Primary - } else { - theme::Button::Text - }); +fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { + let tasks_left = tasks.iter().filter(|task| !task.completed).count(); - button.on_press(Message::FilterChanged(filter)).padding(8) - }; + let filter_button = |label, filter, current_filter| { + let label = text(label).size(16); - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - Text::new(format!( - "{} {} left", - tasks_left, - if tasks_left == 1 { "task" } else { "tasks" } - )) - .width(Length::Fill) - .size(16), - ) - .push( - Row::new() - .width(Length::Shrink) - .spacing(10) - .push(filter_button( - all_button, - "All", - Filter::All, - current_filter, - )) - .push(filter_button( - active_button, - "Active", - Filter::Active, - current_filter, - )) - .push(filter_button( - completed_button, - "Completed", - Filter::Completed, - current_filter, - )), - ) - } + let button = button(label).style(if filter == current_filter { + theme::Button::Primary + } else { + theme::Button::Text + }); + + button.on_press(Message::FilterChanged(filter)).padding(8) + }; + + row![ + text(format!( + "{} {} left", + tasks_left, + if tasks_left == 1 { "task" } else { "tasks" } + )) + .width(Length::Fill) + .size(16), + row![ + filter_button("All", Filter::All, current_filter), + filter_button("Active", Filter::Active, current_filter), + filter_button("Completed", Filter::Completed, current_filter,), + ] + .width(Length::Shrink) + .spacing(10) + ] + .spacing(20) + .align_items(Alignment::Center) + .into() } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -436,8 +375,8 @@ impl Filter { } fn loading_message<'a>() -> Element<'a, Message> { - Container::new( - Text::new("Loading...") + container( + text("Loading...") .horizontal_alignment(alignment::Horizontal::Center) .size(50), ) @@ -447,9 +386,9 @@ fn loading_message<'a>() -> Element<'a, Message> { .into() } -fn empty_message<'a>(message: &str) -> Element<'a, Message> { - Container::new( - Text::new(message) +fn empty_message(message: &str) -> Element<'_, Message> { + container( + text(message) .width(Length::Fill) .size(25) .horizontal_alignment(alignment::Horizontal::Center) @@ -464,11 +403,11 @@ fn empty_message<'a>(message: &str) -> Element<'a, Message> { // Fonts const ICONS: Font = Font::External { name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), + bytes: include_bytes!("../../todos/fonts/icons.ttf"), }; fn icon(unicode: char) -> Text { - Text::new(unicode.to_string()) + text(unicode.to_string()) .font(ICONS) .width(Length::Units(20)) .horizontal_alignment(alignment::Horizontal::Center) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 7c8001128f..35b862a832 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -1,117 +1,75 @@ -use iced::alignment::{self, Alignment}; -use iced::button; use iced::theme; -use iced::tooltip::{self, Tooltip}; -use iced::{ - Button, Column, Container, Element, Length, Row, Sandbox, Settings, Text, -}; +use iced::widget::tooltip::Position; +use iced::widget::{button, container, tooltip}; +use iced::{Element, Length, Sandbox, Settings}; -pub fn main() { - Example::run(Settings::default()).unwrap() +pub fn main() -> iced::Result { + Example::run(Settings::default()) } -#[derive(Default)] struct Example { - top: button::State, - bottom: button::State, - right: button::State, - left: button::State, - follow_cursor: button::State, + position: Position, } -#[derive(Debug, Clone, Copy)] -struct Message; +#[derive(Debug, Clone)] +enum Message { + ChangePosition, +} impl Sandbox for Example { type Message = Message; fn new() -> Self { - Self::default() + Self { + position: Position::Bottom, + } } fn title(&self) -> String { String::from("Tooltip - Iced") } - fn update(&mut self, _message: Message) {} - - fn view(&mut self) -> Element { - let top = - tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top); - - let bottom = tooltip( - "Tooltip at bottom", - &mut self.bottom, - tooltip::Position::Bottom, - ); - - let left = - tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left); - - let right = tooltip( - "Tooltip at right", - &mut self.right, - tooltip::Position::Right, - ); - - let fixed_tooltips = Row::with_children(vec![top, bottom, left, right]) - .width(Length::Fill) - .height(Length::Fill) - .align_items(Alignment::Center) - .spacing(50); - - let follow_cursor = tooltip( - "Tooltip follows cursor", - &mut self.follow_cursor, - tooltip::Position::FollowCursor, - ); + fn update(&mut self, message: Message) { + match message { + Message::ChangePosition => { + let position = match &self.position { + Position::FollowCursor => Position::Top, + Position::Top => Position::Bottom, + Position::Bottom => Position::Left, + Position::Left => Position::Right, + Position::Right => Position::FollowCursor, + }; + + self.position = position + } + } + } - let content = Column::with_children(vec![ - Container::new(fixed_tooltips) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into(), - follow_cursor, - ]) - .width(Length::Fill) - .height(Length::Fill) - .spacing(50); + fn view(&self) -> Element { + let tooltip = tooltip( + button("Press to change position") + .on_press(Message::ChangePosition), + position_to_text(self.position), + self.position, + ) + .gap(10) + .style(theme::Container::Box); - Container::new(content) + container(tooltip) .width(Length::Fill) .height(Length::Fill) .center_x() .center_y() - .padding(50) .into() } } -fn tooltip<'a>( - label: &str, - button_state: &'a mut button::State, - position: tooltip::Position, -) -> Element<'a, Message> { - Tooltip::new( - Button::new( - button_state, - Text::new(label) - .size(40) - .width(Length::Fill) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center), - ) - .on_press(Message) - .width(Length::Fill) - .height(Length::Fill), - "Tooltip", - position, - ) - .gap(5) - .padding(10) - .style(theme::Container::Box) - .into() +fn position_to_text<'a>(position: Position) -> &'a str { + match position { + Position::FollowCursor => "Follow Cursor", + Position::Top => "Top", + Position::Bottom => "Bottom", + Position::Left => "Left", + Position::Right => "Right", + } } diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index d85f2916f4..75d2ce0883 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,14 +1,11 @@ use iced::alignment; -use iced::button; -use iced::scrollable; -use iced::slider; -use iced::text_input; use iced::theme; -use iced::{ - Button, Checkbox, Color, Column, Container, ContentFit, Element, Image, - Length, Radio, Row, Sandbox, Scrollable, Settings, Slider, Space, Text, - TextInput, Toggler, +use iced::widget::{ + checkbox, column, container, horizontal_space, image, radio, row, + scrollable, slider, text, text_input, toggler, vertical_space, }; +use iced::widget::{Button, Column, Container, Slider}; +use iced::{Color, Element, Length, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { env_logger::init(); @@ -18,9 +15,6 @@ pub fn main() -> iced::Result { pub struct Tour { steps: Steps, - scroll: scrollable::State, - back_button: button::State, - next_button: button::State, debug: bool, } @@ -30,9 +24,6 @@ impl Sandbox for Tour { fn new() -> Tour { Tour { steps: Steps::new(), - scroll: scrollable::State::new(), - back_button: button::State::new(), - next_button: button::State::new(), debug: false, } } @@ -55,56 +46,41 @@ impl Sandbox for Tour { } } - fn view(&mut self) -> Element { - let Tour { - steps, - scroll, - back_button, - next_button, - .. - } = self; + fn view(&self) -> Element { + let Tour { steps, .. } = self; - let mut controls = Row::new(); + let mut controls = row![]; if steps.has_previous() { controls = controls.push( - button(back_button, "Back") + button("Back") .on_press(Message::BackPressed) .style(theme::Button::Secondary), ); } - controls = controls.push(Space::with_width(Length::Fill)); + controls = controls.push(horizontal_space(Length::Fill)); if steps.can_continue() { controls = controls.push( - button(next_button, "Next") + button("Next") .on_press(Message::NextPressed) .style(theme::Button::Primary), ); } - let content: Element<_> = Column::new() - .max_width(540) - .spacing(20) - .padding(20) - .push(steps.view(self.debug).map(Message::StepMessage)) - .push(controls) - .into(); - - let content = if self.debug { - content.explain(Color::BLACK) - } else { - content - }; + let content = column![ + steps.view(self.debug).map(Message::StepMessage), + controls, + ] + .max_width(540) + .spacing(20) + .padding(20); - let scrollable = Scrollable::new(scroll) - .push(Container::new(content).width(Length::Fill).center_x()); + let scrollable = + scrollable(container(content).width(Length::Fill).center_x()); - Container::new(scrollable) - .height(Length::Fill) - .center_y() - .into() + container(scrollable).height(Length::Fill).center_y().into() } } @@ -125,35 +101,24 @@ impl Steps { Steps { steps: vec![ Step::Welcome, - Step::Slider { - state: slider::State::new(), - value: 50, - }, + Step::Slider { value: 50 }, Step::RowsAndColumns { layout: Layout::Row, - spacing_slider: slider::State::new(), spacing: 20, }, Step::Text { - size_slider: slider::State::new(), size: 30, - color_sliders: [slider::State::new(); 3], - color: Color::from_rgb(0.5, 0.5, 0.5), + color: Color::BLACK, }, Step::Radio { selection: None }, Step::Toggler { can_continue: false, }, - Step::Image { - height: 200, - current_fit: ContentFit::Contain, - slider: slider::State::new(), - }, + Step::Image { width: 300 }, Step::Scrollable, Step::TextInput { value: String::new(), is_secure: false, - state: text_input::State::new(), }, Step::Debugger, Step::End, @@ -166,7 +131,7 @@ impl Steps { self.steps[self.current].update(msg, debug); } - fn view(&mut self, debug: bool) -> Element { + fn view(&self, debug: bool) -> Element { self.steps[self.current].view(debug) } @@ -198,38 +163,14 @@ impl Steps { enum Step { Welcome, - Slider { - state: slider::State, - value: u8, - }, - RowsAndColumns { - layout: Layout, - spacing_slider: slider::State, - spacing: u16, - }, - Text { - size_slider: slider::State, - size: u16, - color_sliders: [slider::State; 3], - color: Color, - }, - Radio { - selection: Option, - }, - Toggler { - can_continue: bool, - }, - Image { - height: u16, - slider: slider::State, - current_fit: ContentFit, - }, + Slider { value: u8 }, + RowsAndColumns { layout: Layout, spacing: u16 }, + Text { size: u16, color: Color }, + Radio { selection: Option }, + Toggler { can_continue: bool }, + Image { width: u16 }, Scrollable, - TextInput { - value: String, - is_secure: bool, - state: text_input::State, - }, + TextInput { value: String, is_secure: bool }, Debugger, End, } @@ -242,8 +183,7 @@ pub enum StepMessage { TextSizeChanged(u16), TextColorChanged(Color), LanguageSelected(Language), - ImageHeightChanged(u16), - ImageFitSelected(ContentFit), + ImageWidthChanged(u16), InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), @@ -288,14 +228,9 @@ impl<'a> Step { *spacing = new_spacing; } } - StepMessage::ImageHeightChanged(new_height) => { - if let Step::Image { height, .. } = self { - *height = new_height; - } - } - StepMessage::ImageFitSelected(fit) => { - if let Step::Image { current_fit, .. } = self { - *current_fit = fit; + StepMessage::ImageWidthChanged(new_width) => { + if let Step::Image { width, .. } = self { + *width = new_width; } } StepMessage::InputChanged(new_value) => { @@ -348,34 +283,21 @@ impl<'a> Step { } } - fn view(&mut self, debug: bool) -> Element { + fn view(&self, debug: bool) -> Element { match self { Step::Welcome => Self::welcome(), Step::Radio { selection } => Self::radio(*selection), Step::Toggler { can_continue } => Self::toggler(*can_continue), - Step::Slider { state, value } => Self::slider(state, *value), - Step::Text { - size_slider, - size, - color_sliders, - color, - } => Self::text(size_slider, *size, color_sliders, *color), - Step::Image { - height, - slider, - current_fit, - } => Self::image(*height, slider, *current_fit), - Step::RowsAndColumns { - layout, - spacing_slider, - spacing, - } => Self::rows_and_columns(*layout, spacing_slider, *spacing), + Step::Slider { value } => Self::slider(*value), + Step::Text { size, color } => Self::text(*size, *color), + Step::Image { width } => Self::image(*width), + Step::RowsAndColumns { layout, spacing } => { + Self::rows_and_columns(*layout, *spacing) + } Step::Scrollable => Self::scrollable(), - Step::TextInput { - value, - is_secure, - state, - } => Self::text_input(value, *is_secure, state), + Step::TextInput { value, is_secure } => { + Self::text_input(value, *is_secure) + } Step::Debugger => Self::debugger(debug), Step::End => Self::end(), } @@ -383,59 +305,51 @@ impl<'a> Step { } fn container(title: &str) -> Column<'a, StepMessage> { - Column::new().spacing(20).push(Text::new(title).size(50)) + column![text(title).size(50)].spacing(20) } fn welcome() -> Column<'a, StepMessage> { Self::container("Welcome!") - .push(Text::new( + .push( "This is a simple tour meant to showcase a bunch of widgets \ that can be easily implemented on top of Iced.", - )) - .push(Text::new( + ) + .push( "Iced is a cross-platform GUI library for Rust focused on \ simplicity and type-safety. It is heavily inspired by Elm.", - )) - .push(Text::new( + ) + .push( "It was originally born as part of Coffee, an opinionated \ 2D game engine for Rust.", - )) - .push(Text::new( + ) + .push( "On native platforms, Iced provides by default a renderer \ built on top of wgpu, a graphics library supporting Vulkan, \ Metal, DX11, and DX12.", - )) - .push(Text::new( + ) + .push( "Additionally, this tour can also run on WebAssembly thanks \ to dodrio, an experimental VDOM library for Rust.", - )) - .push(Text::new( + ) + .push( "You will need to interact with the UI in order to reach the \ end!", - )) + ) } - fn slider( - state: &'a mut slider::State, - value: u8, - ) -> Column<'a, StepMessage> { + fn slider(value: u8) -> Column<'a, StepMessage> { Self::container("Slider") - .push(Text::new( + .push( "A slider allows you to smoothly select a value from a range \ of values.", - )) - .push(Text::new( + ) + .push( "The following slider lets you choose an integer from \ 0 to 100:", - )) - .push(Slider::new( - state, - 0..=100, - value, - StepMessage::SliderChanged, - )) + ) + .push(slider(0..=100, value, StepMessage::SliderChanged)) .push( - Text::new(value.to_string()) + text(value.to_string()) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) @@ -443,257 +357,197 @@ impl<'a> Step { fn rows_and_columns( layout: Layout, - spacing_slider: &'a mut slider::State, spacing: u16, ) -> Column<'a, StepMessage> { - let row_radio = Radio::new( - Layout::Row, - "Row", - Some(layout), - StepMessage::LayoutChanged, - ); + let row_radio = + radio("Row", Layout::Row, Some(layout), StepMessage::LayoutChanged); - let column_radio = Radio::new( - Layout::Column, + let column_radio = radio( "Column", + Layout::Column, Some(layout), StepMessage::LayoutChanged, ); let layout_section: Element<_> = match layout { - Layout::Row => Row::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), - Layout::Column => Column::new() - .spacing(spacing) - .push(row_radio) - .push(column_radio) - .into(), + Layout::Row => { + row![row_radio, column_radio].spacing(spacing).into() + } + Layout::Column => { + column![row_radio, column_radio].spacing(spacing).into() + } }; - let spacing_section = Column::new() - .spacing(10) - .push(Slider::new( - spacing_slider, - 0..=80, - spacing, - StepMessage::SpacingChanged, - )) - .push( - Text::new(format!("{} px", spacing)) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ); + let spacing_section = column![ + slider(0..=80, spacing, StepMessage::SpacingChanged), + text(format!("{} px", spacing)) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ] + .spacing(10); Self::container("Rows and columns") .spacing(spacing) - .push(Text::new( + .push( "Iced uses a layout model based on flexbox to position UI \ elements.", - )) - .push(Text::new( + ) + .push( "Rows and columns can be used to distribute content \ horizontally or vertically, respectively.", - )) + ) .push(layout_section) - .push(Text::new( - "You can also easily change the spacing between elements:", - )) + .push("You can also easily change the spacing between elements:") .push(spacing_section) } - fn text( - size_slider: &'a mut slider::State, - size: u16, - color_sliders: &'a mut [slider::State; 3], - color: Color, - ) -> Column<'a, StepMessage> { - let size_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("You can change its size:")) - .push(Text::new(format!("This text is {} pixels", size)).size(size)) - .push(Slider::new( - size_slider, - 10..=70, - size, - StepMessage::TextSizeChanged, - )); - - let [red, green, blue] = color_sliders; - - let color_sliders = Row::new() - .spacing(10) - .push(color_slider(red, color.r, move |r| Color { r, ..color })) - .push(color_slider(green, color.g, move |g| Color { g, ..color })) - .push(color_slider(blue, color.b, move |b| Color { b, ..color })); - - let color_section = Column::new() - .padding(20) - .spacing(20) - .push(Text::new("And its color:")) - .push(Text::new(format!("{:?}", color)).style(color)) - .push(color_sliders); + fn text(size: u16, color: Color) -> Column<'a, StepMessage> { + let size_section = column![ + "You can change its size:", + text(format!("This text is {} pixels", size)).size(size), + slider(10..=70, size, StepMessage::TextSizeChanged), + ] + .padding(20) + .spacing(20); + + let color_sliders = row![ + color_slider(color.r, move |r| Color { r, ..color }), + color_slider(color.g, move |g| Color { g, ..color }), + color_slider(color.b, move |b| Color { b, ..color }), + ] + .spacing(10); + + let color_section = column![ + "And its color:", + text(format!("{:?}", color)).style(color), + color_sliders, + ] + .padding(20) + .spacing(20); Self::container("Text") - .push(Text::new( + .push( "Text is probably the most essential widget for your UI. \ It will try to adapt to the dimensions of its container.", - )) + ) .push(size_section) .push(color_section) } fn radio(selection: Option) -> Column<'a, StepMessage> { - let question = Column::new() - .padding(20) - .spacing(10) - .push(Text::new("Iced is written in...").size(24)) - .push(Language::all().iter().cloned().fold( - Column::new().padding(10).spacing(20), - |choices, language| { - choices.push(Radio::new( - language, - language, - selection, - StepMessage::LanguageSelected, - )) - }, - )); + let question = column![ + text("Iced is written in...").size(24), + column( + Language::all() + .iter() + .cloned() + .map(|language| { + radio( + language, + language, + selection, + StepMessage::LanguageSelected, + ) + }) + .map(Element::from) + .collect() + ) + ] + .padding(20) + .spacing(10); Self::container("Radio button") - .push(Text::new( + .push( "A radio button is normally used to represent a choice... \ Surprise test!", - )) + ) .push(question) - .push(Text::new( + .push( "Iced works very well with iterators! The list above is \ basically created by folding a column over the different \ choices, creating a radio button for each one of them!", - )) + ) } fn toggler(can_continue: bool) -> Column<'a, StepMessage> { Self::container("Toggler") - .push(Text::new( - "A toggler is mostly used to enable or disable something.", - )) + .push("A toggler is mostly used to enable or disable something.") .push( - Container::new(Toggler::new( + Container::new(toggler( + "Toggle me to continue...".to_owned(), can_continue, - String::from("Toggle me to continue..."), StepMessage::TogglerChanged, )) .padding([0, 40]), ) } - fn image( - height: u16, - slider: &'a mut slider::State, - current_fit: ContentFit, - ) -> Column<'a, StepMessage> { - const FIT_MODES: [(ContentFit, &str); 3] = [ - (ContentFit::Contain, "Contain"), - (ContentFit::Cover, "Cover"), - (ContentFit::Fill, "Fill"), - ]; - - let mode_selector = FIT_MODES.iter().fold( - Column::new().padding(10).spacing(20), - |choices, (mode, name)| { - choices.push(Radio::new( - *mode, - *name, - Some(current_fit), - StepMessage::ImageFitSelected, - )) - }, - ); - + fn image(width: u16) -> Column<'a, StepMessage> { Self::container("Image") - .push(Text::new("Pictures of things in all shapes and sizes!")) - .push(ferris(height, current_fit)) - .push(Slider::new( - slider, - 50..=500, - height, - StepMessage::ImageHeightChanged, - )) + .push("An image that tries to keep its aspect ratio.") + .push(ferris(width)) + .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) .push( - Text::new(format!("Height: {} px", height)) + text(format!("Width: {} px", width)) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) - .push(Text::new("Pick a content fit strategy:")) - .push(mode_selector) } fn scrollable() -> Column<'a, StepMessage> { Self::container("Scrollable") - .push(Text::new( + .push( "Iced supports scrollable content. Try it out! Find the \ button further below.", - )) + ) .push( - Text::new( - "Tip: You can use the scrollbar to scroll down faster!", - ) - .size(16), + text("Tip: You can use the scrollbar to scroll down faster!") + .size(16), ) - .push(Column::new().height(Length::Units(4096))) + .push(vertical_space(Length::Units(4096))) .push( - Text::new("You are halfway there!") + text("You are halfway there!") .width(Length::Fill) .size(30) .horizontal_alignment(alignment::Horizontal::Center), ) - .push(Column::new().height(Length::Units(4096))) - .push(ferris(200, ContentFit::Contain)) + .push(vertical_space(Length::Units(4096))) + .push(ferris(300)) .push( - Text::new("You made it!") + text("You made it!") .width(Length::Fill) .size(50) .horizontal_alignment(alignment::Horizontal::Center), ) } - fn text_input( - value: &str, - is_secure: bool, - state: &'a mut text_input::State, - ) -> Column<'a, StepMessage> { - let text_input = TextInput::new( - state, + fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> { + let text_input = text_input( "Type something to continue...", value, StepMessage::InputChanged, ) .padding(10) .size(30); + Self::container("Text input") - .push(Text::new( - "Use a text input to ask for different kinds of information.", - )) + .push("Use a text input to ask for different kinds of information.") .push(if is_secure { text_input.password() } else { text_input }) - .push(Checkbox::new( - is_secure, + .push(checkbox( "Enable password mode", + is_secure, StepMessage::ToggleSecureInput, )) - .push(Text::new( + .push( "A text input produces a message every time it changes. It is \ very easy to keep track of its contents:", - )) + ) .push( - Text::new(if value.is_empty() { + text(if value.is_empty() { "You have not typed anything yet..." } else { value @@ -705,79 +559,65 @@ impl<'a> Step { fn debugger(debug: bool) -> Column<'a, StepMessage> { Self::container("Debugger") - .push(Text::new( + .push( "You can ask Iced to visually explain the layouting of the \ different elements comprising your UI!", - )) - .push(Text::new( + ) + .push( "Give it a shot! Check the following checkbox to be able to \ see element boundaries.", - )) + ) .push(if cfg!(target_arch = "wasm32") { Element::new( - Text::new("Not available on web yet!") + text("Not available on web yet!") .style(Color::from([0.7, 0.7, 0.7])) .horizontal_alignment(alignment::Horizontal::Center), ) } else { - Element::new(Checkbox::new( - debug, - "Explain layout", - StepMessage::DebugToggled, - )) + checkbox("Explain layout", debug, StepMessage::DebugToggled) + .into() }) - .push(Text::new("Feel free to go back and take a look.")) + .push("Feel free to go back and take a look.") } fn end() -> Column<'a, StepMessage> { Self::container("You reached the end!") - .push(Text::new( - "This tour will be updated as more features are added.", - )) - .push(Text::new("Make sure to keep an eye on it!")) + .push("This tour will be updated as more features are added.") + .push("Make sure to keep an eye on it!") } } -fn ferris<'a>( - height: u16, - content_fit: ContentFit, -) -> Container<'a, StepMessage> { - Container::new( +fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { + container( // This should go away once we unify resource loading on native // platforms if cfg!(target_arch = "wasm32") { - Image::new("tour/images/ferris.png") + image("tour/images/ferris.png") } else { - Image::new(format!( - "{}/images/ferris.png", - env!("CARGO_MANIFEST_DIR"), + image(format!( + "{}/../../tour/images/ferris.png", + env!("CARGO_MANIFEST_DIR") )) } - .height(Length::Units(height)) - .content_fit(content_fit), + .width(Length::Units(width)), ) .width(Length::Fill) .center_x() } -fn button<'a, Message: Clone>( - state: &'a mut button::State, - label: &str, -) -> Button<'a, Message> { - Button::new( - state, - Text::new(label).horizontal_alignment(alignment::Horizontal::Center), +fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { + iced::widget::button( + text(label).horizontal_alignment(alignment::Horizontal::Center), ) .padding(12) .width(Length::Units(100)) } -fn color_slider( - state: &mut slider::State, +fn color_slider<'a>( component: f32, - update: impl Fn(f32) -> Color + 'static, -) -> Slider { - Slider::new(state, 0.0..=1.0, f64::from(component), move |c| { + update: impl Fn(f32) -> Color + 'a, +) -> Slider<'a, f64, StepMessage, Renderer> { + slider(0.0..=1.0, f64::from(component), move |c| { StepMessage::TextColorChanged(update(c as f32)) }) .step(0.01) diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 3695b6b778..3257b5190a 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,7 +1,7 @@ use iced::executor; +use iced::widget::{container, text}; use iced::{ - Application, Command, Container, Element, Length, Settings, Subscription, - Text, Theme, + Application, Command, Element, Length, Settings, Subscription, Theme, }; use iced_native::{ event::{MacOS, PlatformSpecific}, @@ -55,13 +55,13 @@ impl Application for App { iced_native::subscription::events().map(Message::EventOccurred) } - fn view(&mut self) -> Element { + fn view(&self) -> Element { let content = match &self.url { - Some(url) => Text::new(url), - None => Text::new("No URL received yet!"), + Some(url) => text(url), + None => text("No URL received yet!"), }; - Container::new(content.size(48)) + container(content.size(48)) .width(Length::Fill) .height(Length::Fill) .center_x() diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 64addc8f83..28a9de3738 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -1,13 +1,12 @@ mod echo; use iced::alignment::{self, Alignment}; -use iced::button::{self, Button}; use iced::executor; -use iced::scrollable::{self, Scrollable}; -use iced::text_input::{self, TextInput}; +use iced::widget::{ + button, column, container, row, scrollable, text, text_input, Column, +}; use iced::{ - Application, Color, Column, Command, Container, Element, Length, Row, - Settings, Subscription, Text, Theme, + Application, Color, Command, Element, Length, Settings, Subscription, Theme, }; pub fn main() -> iced::Result { @@ -17,10 +16,7 @@ pub fn main() -> iced::Result { #[derive(Default)] struct WebSocket { messages: Vec, - message_log: scrollable::State, new_message: String, - new_message_state: text_input::State, - new_message_button: button::State, state: State, } @@ -75,7 +71,9 @@ impl Application for WebSocket { } echo::Event::MessageReceived(message) => { self.messages.push(message); - self.message_log.snap_to(1.0); + + // TODO + // self.message_log.snap_to(1.0); } }, Message::Server => {} @@ -88,10 +86,10 @@ impl Application for WebSocket { echo::connect().map(Message::Echo) } - fn view(&mut self) -> Element { - let message_log = if self.messages.is_empty() { - Container::new( - Text::new("Your messages will appear here...") + fn view(&self) -> Element { + let message_log: Element<_> = if self.messages.is_empty() { + container( + text("Your messages will appear here...") .style(Color::from_rgb8(0x88, 0x88, 0x88)), ) .width(Length::Fill) @@ -100,31 +98,32 @@ impl Application for WebSocket { .center_y() .into() } else { - self.messages - .iter() - .cloned() - .fold( - Scrollable::new(&mut self.message_log), - |scrollable, message| scrollable.push(Text::new(message)), + scrollable( + Column::with_children( + self.messages + .iter() + .cloned() + .map(text) + .map(Element::from) + .collect(), ) .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .into() + .spacing(10), + ) + .height(Length::Fill) + .into() }; let new_message_input = { - let mut input = TextInput::new( - &mut self.new_message_state, + let mut input = text_input( "Type a message...", &self.new_message, Message::NewMessageChanged, ) .padding(10); - let mut button = Button::new( - &mut self.new_message_button, - Text::new("Send") + let mut button = button( + text("Send") .height(Length::Fill) .vertical_alignment(alignment::Vertical::Center), ) @@ -137,12 +136,10 @@ impl Application for WebSocket { } } - Row::with_children(vec![input.into(), button.into()]) - .spacing(10) - .align_items(Alignment::Fill) + row![input, button].spacing(10).align_items(Alignment::Fill) }; - Column::with_children(vec![message_log, new_message_input.into()]) + column![message_log, new_message_input] .width(Length::Fill) .height(Length::Fill) .padding(20) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 9750011286..49d4d9c640 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -17,7 +17,6 @@ font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] -pure = ["iced_pure"] [dependencies] glam = "0.10" @@ -36,11 +35,6 @@ path = "../native" version = "0.4" path = "../style" -[dependencies.iced_pure] -version = "0.2" -path = "../pure" -optional = true - [dependencies.lyon] version = "1.0" optional = true diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a7a1cabba4..1108247208 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -36,9 +36,6 @@ pub mod triangle; pub mod widget; pub mod window; -#[doc(no_inline)] -pub use widget::*; - pub use antialiasing::Antialiasing; pub use backend::Backend; pub use error::Error; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 3c19fbfb3b..cdbc4f4062 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -58,7 +58,7 @@ where element: &Element<'a, Message, Self>, limits: &layout::Limits, ) -> layout::Node { - let layout = element.layout(self, limits); + let layout = element.as_widget().layout(self, limits); self.backend.trim_measurements(); diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index c3e28e8cb1..88403fd72f 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -3,8 +3,6 @@ //! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a //! [`Frame`]. It can be used for animation, data visualization, game graphics, //! and more! -use crate::renderer::{self, Renderer}; -use crate::{Backend, Primitive}; pub mod event; pub mod path; @@ -29,42 +27,31 @@ pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use text::Text; -use iced_native::layout; +use crate::{Backend, Primitive, Renderer}; + +use iced_native::layout::{self, Layout}; use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::tree::{self, Tree}; use iced_native::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, - Widget, + Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; use std::marker::PhantomData; /// A widget capable of drawing 2D graphics. /// -/// # Examples -/// The repository has a couple of [examples] showcasing how to use a -/// [`Canvas`]: -/// -/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock -/// and its hands to display the current time. -/// - [`game_of_life`], an interactive version of the Game of Life, invented by -/// John Conway. -/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget -/// and showcasing how to compose different transforms. -/// -/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.4/examples/clock -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.4/examples/game_of_life -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.4/examples/solar_system -/// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run /// # mod iced { -/// # pub use iced_graphics::canvas; +/// # pub mod widget { +/// # pub use iced_graphics::widget::canvas; +/// # } /// # pub use iced_native::{Color, Rectangle, Theme}; /// # } -/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; /// use iced::{Color, Rectangle, Theme}; /// /// // First, we define the data we need for drawing @@ -75,7 +62,9 @@ use std::marker::PhantomData; /// /// // Then, we implement the `Program` trait /// impl Program<()> for Circle { -/// fn draw(&self, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ +/// type State = (); +/// +/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ /// // We prepare a new `Frame` /// let mut frame = Frame::new(bounds.size()); /// @@ -140,6 +129,15 @@ where P: Program, B: Backend, { + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(P::State::default()) + } + fn width(&self) -> Length { self.width } @@ -161,6 +159,7 @@ where fn on_event( &mut self, + tree: &mut Tree, event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, @@ -183,8 +182,10 @@ where let cursor = Cursor::from_window_position(cursor_position); if let Some(canvas_event) = canvas_event { + let state = tree.state.downcast_mut::(); + let (event_status, message) = - self.program.update(canvas_event, bounds, cursor); + self.program.update(state, canvas_event, bounds, cursor); if let Some(message) = message { shell.publish(message); @@ -198,6 +199,7 @@ where fn mouse_interaction( &self, + tree: &Tree, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, @@ -205,12 +207,14 @@ where ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); + let state = tree.state.downcast_ref::(); - self.program.mouse_interaction(bounds, cursor) + self.program.mouse_interaction(state, bounds, cursor) } fn draw( &self, + tree: &Tree, renderer: &mut Renderer, theme: &T, _style: &renderer::Style, @@ -228,12 +232,13 @@ where let translation = Vector::new(bounds.x, bounds.y); let cursor = Cursor::from_window_position(cursor_position); + let state = tree.state.downcast_ref::(); renderer.with_translation(translation, |renderer| { renderer.draw_primitive(Primitive::Group { primitives: self .program - .draw(theme, bounds, cursor) + .draw(state, theme, bounds, cursor) .into_iter() .map(Geometry::into_primitive) .collect(), @@ -245,7 +250,7 @@ where impl<'a, Message, P, B, T> From> for Element<'a, Message, Renderer> where - Message: 'static, + Message: 'a, P: Program + 'a, B: Backend, T: 'a, diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs index a469417de3..5af694e9c2 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/graphics/src/widget/canvas/cache.rs @@ -1,7 +1,5 @@ -use crate::{ - canvas::{Frame, Geometry}, - Primitive, -}; +use crate::widget::canvas::{Frame, Geometry}; +use crate::Primitive; use iced_native::Size; use std::{cell::RefCell, sync::Arc}; diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index 2f46079c20..417412b2c5 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -2,11 +2,10 @@ use std::borrow::Cow; use iced_native::{Point, Rectangle, Size, Vector}; -use crate::{ - canvas::path, - canvas::{Fill, Geometry, Path, Stroke, Text}, - triangle, Primitive, -}; +use crate::triangle; +use crate::widget::canvas::path; +use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text}; +use crate::Primitive; use lyon::tessellation; diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs index 608507ad2c..aeb2589e02 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/graphics/src/widget/canvas/path.rs @@ -7,7 +7,7 @@ mod builder; pub use arc::Arc; pub use builder::Builder; -use crate::canvas::LineDash; +use crate::widget::canvas::LineDash; use iced_native::{Point, Size}; use lyon::algorithms::walk::{walk_along_path, RepeatedPattern, WalkerEvent}; diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs index c49ccdc337..5121aa6861 100644 --- a/graphics/src/widget/canvas/path/builder.rs +++ b/graphics/src/widget/canvas/path/builder.rs @@ -1,4 +1,4 @@ -use crate::canvas::path::{arc, Arc, Path}; +use crate::widget::canvas::path::{arc, Arc, Path}; use iced_native::{Point, Size}; use lyon::path::builder::SvgPathBuilder; diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index dddc387d7d..656dbfa627 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,8 +1,7 @@ -use crate::canvas::event::{self, Event}; -use crate::canvas::{Cursor, Geometry}; - -use iced_native::mouse; -use iced_native::Rectangle; +use crate::widget::canvas::event::{self, Event}; +use crate::widget::canvas::mouse; +use crate::widget::canvas::{Cursor, Geometry}; +use crate::Rectangle; /// The state and logic of a [`Canvas`]. /// @@ -11,7 +10,10 @@ use iced_native::Rectangle; /// /// [`Canvas`]: crate::widget::Canvas pub trait Program { - /// Updates the state of the [`Program`]. + /// The internal state mutated by the [`Program`]. + type State: Default + 'static; + + /// Updates the [`State`](Self::State) of the [`Program`]. /// /// When a [`Program`] is used in a [`Canvas`], the runtime will call this /// method for each [`Event`]. @@ -23,7 +25,8 @@ pub trait Program { /// /// [`Canvas`]: crate::widget::Canvas fn update( - &mut self, + &self, + _state: &mut Self::State, _event: Event, _bounds: Rectangle, _cursor: Cursor, @@ -40,6 +43,7 @@ pub trait Program { /// [`Cache`]: crate::widget::canvas::Cache fn draw( &self, + state: &Self::State, theme: &Theme, bounds: Rectangle, cursor: Cursor, @@ -53,6 +57,7 @@ pub trait Program { /// [`Canvas`]: crate::widget::Canvas fn mouse_interaction( &self, + _state: &Self::State, _bounds: Rectangle, _cursor: Cursor, ) -> mouse::Interaction { @@ -60,33 +65,38 @@ pub trait Program { } } -impl Program for &mut T +impl Program for &T where T: Program, { + type State = T::State; + fn update( - &mut self, + &self, + state: &mut Self::State, event: Event, bounds: Rectangle, cursor: Cursor, ) -> (event::Status, Option) { - T::update(self, event, bounds, cursor) + T::update(self, state, event, bounds, cursor) } fn draw( &self, + state: &Self::State, theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { - T::draw(self, theme, bounds, cursor) + T::draw(self, state, theme, bounds, cursor) } fn mouse_interaction( &self, + state: &Self::State, bounds: Rectangle, cursor: Cursor, ) -> mouse::Interaction { - T::mouse_interaction(self, bounds, cursor) + T::mouse_interaction(self, state, bounds, cursor) } } diff --git a/graphics/src/widget/pure/canvas/program.rs b/graphics/src/widget/pure/canvas/program.rs deleted file mode 100644 index 20c6406e96..0000000000 --- a/graphics/src/widget/pure/canvas/program.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::widget::pure::canvas::event::{self, Event}; -use crate::widget::pure::canvas::mouse; -use crate::widget::pure::canvas::{Cursor, Geometry}; -use crate::Rectangle; - -/// The state and logic of a [`Canvas`]. -/// -/// A [`Program`] can mutate internal state and produce messages for an -/// application. -/// -/// [`Canvas`]: crate::widget::Canvas -pub trait Program { - /// The internal state mutated by the [`Program`]. - type State: Default + 'static; - - /// Updates the [`State`](Self::State) of the [`Program`]. - /// - /// When a [`Program`] is used in a [`Canvas`], the runtime will call this - /// method for each [`Event`]. - /// - /// This method can optionally return a `Message` to notify an application - /// of any meaningful interactions. - /// - /// By default, this method does and returns nothing. - /// - /// [`Canvas`]: crate::widget::Canvas - fn update( - &self, - _state: &mut Self::State, - _event: Event, - _bounds: Rectangle, - _cursor: Cursor, - ) -> (event::Status, Option) { - (event::Status::Ignored, None) - } - - /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. - /// - /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a - /// [`Cache`]. - /// - /// [`Frame`]: crate::widget::canvas::Frame - /// [`Cache`]: crate::widget::canvas::Cache - fn draw( - &self, - state: &Self::State, - theme: &Theme, - bounds: Rectangle, - cursor: Cursor, - ) -> Vec; - - /// Returns the current mouse interaction of the [`Program`]. - /// - /// The interaction returned will be in effect even if the cursor position - /// is out of bounds of the program's [`Canvas`]. - /// - /// [`Canvas`]: crate::widget::Canvas - fn mouse_interaction( - &self, - _state: &Self::State, - _bounds: Rectangle, - _cursor: Cursor, - ) -> mouse::Interaction { - mouse::Interaction::default() - } -} - -impl Program for &T -where - T: Program, -{ - type State = T::State; - - fn update( - &self, - state: &mut Self::State, - event: Event, - bounds: Rectangle, - cursor: Cursor, - ) -> (event::Status, Option) { - T::update(self, state, event, bounds, cursor) - } - - fn draw( - &self, - state: &Self::State, - theme: &Theme, - bounds: Rectangle, - cursor: Cursor, - ) -> Vec { - T::draw(self, state, theme, bounds, cursor) - } - - fn mouse_interaction( - &self, - state: &Self::State, - bounds: Rectangle, - cursor: Cursor, - ) -> mouse::Interaction { - T::mouse_interaction(self, state, bounds, cursor) - } -} diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs index 1a5c0b0aa8..12ce5b1f90 100644 --- a/graphics/src/widget/qr_code.rs +++ b/graphics/src/widget/qr_code.rs @@ -1,9 +1,10 @@ //! Encode and display information in a QR code. -use crate::canvas; use crate::renderer::{self, Renderer}; +use crate::widget::canvas; use crate::Backend; use iced_native::layout; +use iced_native::widget::Tree; use iced_native::{ Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; @@ -72,6 +73,7 @@ where fn draw( &self, + _state: &Tree, renderer: &mut Renderer, _theme: &T, _style: &renderer::Style, diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml index 9a184dd1fe..12e4e313fb 100644 --- a/lazy/Cargo.toml +++ b/lazy/Cargo.toml @@ -10,17 +10,9 @@ documentation = "https://docs.rs/iced_lazy" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] -[features] -pure = ["iced_pure"] - [dependencies] ouroboros = "0.13" [dependencies.iced_native] version = "0.5" path = "../native" - -[dependencies.iced_pure] -version = "0.2" -path = "../pure" -optional = true diff --git a/lazy/src/component.rs b/lazy/src/component.rs index eac7e8ee35..ea4d731162 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -1,17 +1,16 @@ //! Build and reuse custom widgets using The Elm Architecture. -use crate::{Cache, CacheBuilder}; - 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::widget::tree::{self, Tree}; use iced_native::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; -use std::cell::RefCell; +use std::cell::{Ref, RefCell}; use std::marker::PhantomData; /// A reusable, custom widget that uses The Elm Architecture. @@ -28,17 +27,24 @@ use std::marker::PhantomData; /// Additionally, a [`Component`] is capable of producing a `Message` to notify /// the parent application of any relevant interactions. pub trait Component { + /// The internal state of this [`Component`]. + type State: Default; + /// The type of event this [`Component`] handles internally. type Event; /// Processes an [`Event`](Component::Event) and updates the [`Component`] state accordingly. /// /// It can produce a `Message` for the parent application. - fn update(&mut self, event: Self::Event) -> Option; + fn update( + &mut self, + state: &mut Self::State, + event: Self::Event, + ) -> Option; /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) /// on user interaction. - fn view(&mut self) -> Element<'_, Self::Event, Renderer>; + fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer>; } /// Turns an implementor of [`Component`] into an [`Element`] that can be @@ -48,6 +54,7 @@ pub fn view<'a, C, Message, Renderer>( ) -> Element<'a, Message, Renderer> where C: Component + 'a, + C::State: 'static, Message: 'a, Renderer: iced_native::Renderer + 'a, { @@ -56,36 +63,48 @@ where StateBuilder { component: Box::new(component), message: PhantomData, - cache_builder: |state| { - Some( - CacheBuilder { - element: state.view(), - overlay_builder: |_| None, - } - .build(), - ) - }, + state: PhantomData, + element_builder: |_| None, } .build(), )), }) } -struct Instance<'a, Message, Renderer, Event> { - state: RefCell>>, +struct Instance<'a, Message, Renderer, Event, S> { + state: RefCell>>, } #[self_referencing] -struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { - component: Box + 'a>, +struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> { + component: + Box + 'a>, message: PhantomData, + state: PhantomData, - #[borrows(mut component)] + #[borrows(component)] #[covariant] - cache: Option>, + element: Option>, } -impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { +impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S> +where + S: Default, +{ + fn rebuild_element(&self, state: &S) { + let heads = self.state.borrow_mut().take().unwrap().into_heads(); + + *self.state.borrow_mut() = Some( + StateBuilder { + component: heads.component, + message: PhantomData, + state: PhantomData, + element_builder: |component| Some(component.view(state)), + } + .build(), + ); + } + fn with_element( &self, f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, @@ -101,34 +120,43 @@ impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { .borrow_mut() .as_mut() .unwrap() - .with_cache_mut(|cache| { - let mut element = cache.take().unwrap().into_heads().element; - let result = f(&mut element); - - *cache = Some( - CacheBuilder { - element, - overlay_builder: |_| None, - } - .build(), - ); - - result - }) + .with_element_mut(|element| f(element.as_mut().unwrap())) } } -impl<'a, Message, Renderer, Event> Widget - for Instance<'a, Message, Renderer, Event> +impl<'a, Message, Renderer, Event, S> Widget + for Instance<'a, Message, Renderer, Event, S> where + S: 'static + Default, Renderer: iced_native::Renderer, { + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(S::default()) + } + + fn children(&self) -> Vec { + self.rebuild_element(&S::default()); + self.with_element(|element| vec![Tree::new(element)]) + } + + fn diff(&self, tree: &mut Tree) { + self.rebuild_element(tree.state.downcast_ref()); + self.with_element(|element| { + tree.diff_children(std::slice::from_ref(&element)) + }) + } + fn width(&self) -> Length { - self.with_element(|element| element.width()) + self.with_element(|element| element.as_widget().width()) } fn height(&self) -> Length { - self.with_element(|element| element.height()) + self.with_element(|element| element.as_widget().height()) } fn layout( @@ -136,11 +164,14 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.with_element(|element| element.layout(renderer, limits)) + self.with_element(|element| { + element.as_widget().layout(renderer, limits) + }) } fn on_event( &mut self, + tree: &mut Tree, event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, @@ -152,7 +183,8 @@ where let mut local_shell = Shell::new(&mut local_messages); let event_status = self.with_element_mut(|element| { - element.on_event( + element.as_widget_mut().on_event( + &mut tree.children[0], event, layout, cursor_position, @@ -165,37 +197,31 @@ where local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { - let mut component = self - .state - .borrow_mut() - .take() - .unwrap() - .into_heads() - .component; - - for message in local_messages - .into_iter() - .filter_map(|message| component.update(message)) - { + let mut heads = self.state.take().unwrap().into_heads(); + + for message in local_messages.into_iter().filter_map(|message| { + heads + .component + .update(tree.state.downcast_mut::(), message) + }) { shell.publish(message); } - *self.state.borrow_mut() = Some( + self.state = RefCell::new(Some( StateBuilder { - component, + component: heads.component, message: PhantomData, - cache_builder: |state| { - Some( - CacheBuilder { - element: state.view(), - overlay_builder: |_| None, - } - .build(), - ) + state: PhantomData, + element_builder: |state| { + Some(state.view(tree.state.downcast_ref::())) }, } .build(), - ); + )); + + self.with_element(|element| { + tree.diff_children(std::slice::from_ref(&element)) + }); shell.invalidate_layout(); } @@ -205,6 +231,7 @@ where fn draw( &self, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -213,7 +240,8 @@ where viewport: &Rectangle, ) { self.with_element(|element| { - element.draw( + element.as_widget().draw( + &tree.children[0], renderer, theme, style, @@ -226,13 +254,15 @@ where fn mouse_interaction( &self, + tree: &Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.with_element(|element| { - element.mouse_interaction( + element.as_widget().mouse_interaction( + &tree.children[0], layout, cursor_position, viewport, @@ -241,63 +271,72 @@ where }) } - fn overlay( - &mut self, + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, - ) -> Option> { - let has_overlay = self - .state - .borrow_mut() - .as_mut() - .unwrap() - .with_cache_mut(|cache| { - let element = cache.take().unwrap().into_heads().element; - - *cache = Some( - CacheBuilder { - element, - overlay_builder: |element| { - element.overlay(layout, renderer) - }, - } - .build(), - ); - - cache + ) -> Option> { + let overlay = OverlayBuilder { + instance: self, + instance_ref_builder: |instance| instance.state.borrow(), + tree, + types: PhantomData, + overlay_builder: |instance, tree| { + instance .as_ref() .unwrap() - .borrow_overlay() + .borrow_element() .as_ref() - .map(|overlay| overlay.position()) - }); + .unwrap() + .as_widget() + .overlay(&mut tree.children[0], layout, renderer) + }, + } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); has_overlay.map(|position| { overlay::Element::new( position, - Box::new(Overlay { instance: self }), + Box::new(OverlayInstance { + overlay: Some(overlay), + }), ) }) } } -struct Overlay<'a, 'b, Message, Renderer, Event> { - instance: &'b mut Instance<'a, Message, Renderer, Event>, +#[self_referencing] +struct Overlay<'a, 'b, Message, Renderer, Event, S> { + instance: &'a Instance<'b, Message, Renderer, Event, S>, + tree: &'a mut Tree, + types: PhantomData<(Message, Event, S)>, + + #[borrows(instance)] + #[covariant] + instance_ref: Ref<'this, Option>>, + + #[borrows(instance_ref, mut tree)] + #[covariant] + overlay: Option>, +} + +struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> { + overlay: Option>, } -impl<'a, 'b, Message, Renderer, Event> - Overlay<'a, 'b, Message, Renderer, Event> +impl<'a, 'b, Message, Renderer, Event, S> + OverlayInstance<'a, 'b, Message, Renderer, Event, S> { fn with_overlay_maybe( &self, f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, ) -> Option { - self.instance - .state - .borrow() - .as_ref() - .unwrap() - .borrow_cache() + self.overlay .as_ref() .unwrap() .borrow_overlay() @@ -306,27 +345,21 @@ impl<'a, 'b, Message, Renderer, Event> } fn with_overlay_mut_maybe( - &self, + &mut self, f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, ) -> Option { - self.instance - .state - .borrow_mut() + self.overlay .as_mut() .unwrap() - .with_cache_mut(|cache| { - cache - .as_mut() - .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) - }) + .with_overlay_mut(|overlay| overlay.as_mut().map(f)) } } -impl<'a, 'b, Message, Renderer, Event> overlay::Overlay - for Overlay<'a, 'b, Message, Renderer, Event> +impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay + for OverlayInstance<'a, 'b, Message, Renderer, Event, S> where Renderer: iced_native::Renderer, + S: 'static + Default, { fn layout( &self, @@ -401,32 +434,43 @@ where local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { - let mut component = - self.instance.state.take().unwrap().into_heads().component; - - for message in local_messages - .into_iter() - .filter_map(|message| component.update(message)) - { + let overlay = self.overlay.take().unwrap().into_heads(); + let mut heads = overlay.instance.state.take().unwrap().into_heads(); + + for message in local_messages.into_iter().filter_map(|message| { + heads + .component + .update(overlay.tree.state.downcast_mut::(), message) + }) { shell.publish(message); } - self.instance.state = RefCell::new(Some( + *overlay.instance.state.borrow_mut() = Some( StateBuilder { - component, + component: heads.component, message: PhantomData, - cache_builder: |state| { - Some( - CacheBuilder { - element: state.view(), - overlay_builder: |_| None, - } - .build(), - ) + state: PhantomData, + element_builder: |state| { + Some(state.view(overlay.tree.state.downcast_ref::())) }, } .build(), - )); + ); + + overlay.instance.with_element(|element| { + overlay.tree.diff_children(std::slice::from_ref(&element)) + }); + + self.overlay = Some( + OverlayBuilder { + instance: overlay.instance, + instance_ref_builder: |instance| instance.state.borrow(), + tree: overlay.tree, + types: PhantomData, + overlay_builder: |_, _| None, + } + .build(), + ); shell.invalidate_layout(); } diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index aed11e9f52..3827746c05 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -20,13 +20,32 @@ pub mod component; pub mod responsive; -#[cfg(feature = "pure")] -#[cfg_attr(docsrs, doc(cfg(feature = "pure")))] -pub mod pure; - pub use component::Component; pub use responsive::Responsive; mod cache; -use cache::{Cache, CacheBuilder}; +use iced_native::{Element, Size}; + +/// Turns an implementor of [`Component`] into an [`Element`] that can be +/// embedded in any application. +pub fn component<'a, C, Message, Renderer>( + component: C, +) -> Element<'a, Message, Renderer> +where + C: Component + 'a, + C::State: 'static, + Message: 'a, + Renderer: iced_native::Renderer + 'a, +{ + component::view(component) +} + +pub fn responsive<'a, Message, Renderer>( + f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, +) -> Responsive<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + Responsive::new(f) +} diff --git a/lazy/src/pure.rs b/lazy/src/pure.rs deleted file mode 100644 index dc500e5ee2..0000000000 --- a/lazy/src/pure.rs +++ /dev/null @@ -1,31 +0,0 @@ -mod component; -mod responsive; - -pub use component::Component; -pub use responsive::Responsive; - -use iced_native::Size; -use iced_pure::Element; - -/// Turns an implementor of [`Component`] into an [`Element`] that can be -/// embedded in any application. -pub fn component<'a, C, Message, Renderer>( - component: C, -) -> Element<'a, Message, Renderer> -where - C: Component + 'a, - C::State: 'static, - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - component::view(component) -} - -pub fn responsive<'a, Message, Renderer>( - f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, -) -> Responsive<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - Responsive::new(f) -} diff --git a/lazy/src/pure/component.rs b/lazy/src/pure/component.rs deleted file mode 100644 index b414a14992..0000000000 --- a/lazy/src/pure/component.rs +++ /dev/null @@ -1,479 +0,0 @@ -//! Build and reuse custom widgets using The Elm Architecture. -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, Length, Point, Rectangle, Shell, Size}; -use iced_pure::widget::tree::{self, Tree}; -use iced_pure::{Element, Widget}; - -use ouroboros::self_referencing; -use std::cell::{Ref, RefCell}; -use std::marker::PhantomData; - -/// A reusable, custom widget that uses The Elm Architecture. -/// -/// A [`Component`] allows you to implement custom widgets as if they were -/// `iced` applications with encapsulated state. -/// -/// In other words, a [`Component`] allows you to turn `iced` applications into -/// custom widgets and embed them without cumbersome wiring. -/// -/// A [`Component`] produces widgets that may fire an [`Event`](Component::Event) -/// and update the internal state of the [`Component`]. -/// -/// Additionally, a [`Component`] is capable of producing a `Message` to notify -/// the parent application of any relevant interactions. -pub trait Component { - /// The internal state of this [`Component`]. - type State: Default; - - /// The type of event this [`Component`] handles internally. - type Event; - - /// Processes an [`Event`](Component::Event) and updates the [`Component`] state accordingly. - /// - /// It can produce a `Message` for the parent application. - fn update( - &mut self, - state: &mut Self::State, - event: Self::Event, - ) -> Option; - - /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) - /// on user interaction. - fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer>; -} - -/// Turns an implementor of [`Component`] into an [`Element`] that can be -/// embedded in any application. -pub fn view<'a, C, Message, Renderer>( - component: C, -) -> Element<'a, Message, Renderer> -where - C: Component + 'a, - C::State: 'static, - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - Element::new(Instance { - state: RefCell::new(Some( - StateBuilder { - component: Box::new(component), - message: PhantomData, - state: PhantomData, - element_builder: |_| None, - } - .build(), - )), - }) -} - -struct Instance<'a, Message, Renderer, Event, S> { - state: RefCell>>, -} - -#[self_referencing] -struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> { - component: - Box + 'a>, - message: PhantomData, - state: PhantomData, - - #[borrows(component)] - #[covariant] - element: Option>, -} - -impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S> -where - S: Default, -{ - fn rebuild_element(&self, state: &S) { - let heads = self.state.borrow_mut().take().unwrap().into_heads(); - - *self.state.borrow_mut() = Some( - StateBuilder { - component: heads.component, - message: PhantomData, - state: PhantomData, - element_builder: |component| Some(component.view(state)), - } - .build(), - ); - } - - fn with_element( - &self, - f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, - ) -> T { - self.with_element_mut(|element| f(element)) - } - - fn with_element_mut( - &self, - f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T, - ) -> T { - self.state - .borrow_mut() - .as_mut() - .unwrap() - .with_element_mut(|element| f(element.as_mut().unwrap())) - } -} - -impl<'a, Message, Renderer, Event, S> Widget - for Instance<'a, Message, Renderer, Event, S> -where - S: 'static + Default, - Renderer: iced_native::Renderer, -{ - fn tag(&self) -> tree::Tag { - struct Tag(T); - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(S::default()) - } - - fn children(&self) -> Vec { - self.rebuild_element(&S::default()); - self.with_element(|element| vec![Tree::new(element)]) - } - - fn diff(&self, tree: &mut Tree) { - self.rebuild_element(tree.state.downcast_ref()); - self.with_element(|element| { - tree.diff_children(std::slice::from_ref(&element)) - }) - } - - fn width(&self) -> Length { - self.with_element(|element| element.as_widget().width()) - } - - fn height(&self) -> Length { - self.with_element(|element| element.as_widget().height()) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.with_element(|element| { - element.as_widget().layout(renderer, limits) - }) - } - - fn on_event( - &mut self, - tree: &mut Tree, - 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.with_element_mut(|element| { - element.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ) - }); - - local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if !local_messages.is_empty() { - let mut heads = self.state.take().unwrap().into_heads(); - - for message in local_messages.into_iter().filter_map(|message| { - heads - .component - .update(tree.state.downcast_mut::(), message) - }) { - shell.publish(message); - } - - self.state = RefCell::new(Some( - StateBuilder { - component: heads.component, - message: PhantomData, - state: PhantomData, - element_builder: |state| { - Some(state.view(tree.state.downcast_ref::())) - }, - } - .build(), - )); - - self.with_element(|element| { - tree.diff_children(std::slice::from_ref(&element)) - }); - - shell.invalidate_layout(); - } - - event_status - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.with_element(|element| { - element.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 { - self.with_element(|element| { - element.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> { - let overlay = OverlayBuilder { - instance: self, - instance_ref_builder: |instance| instance.state.borrow(), - tree, - types: PhantomData, - overlay_builder: |instance, tree| { - instance - .as_ref() - .unwrap() - .borrow_element() - .as_ref() - .unwrap() - .as_widget() - .overlay(&mut tree.children[0], layout, renderer) - }, - } - .build(); - - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); - - has_overlay.map(|position| { - overlay::Element::new( - position, - Box::new(OverlayInstance { - overlay: Some(overlay), - }), - ) - }) - } -} - -#[self_referencing] -struct Overlay<'a, 'b, Message, Renderer, Event, S> { - instance: &'a Instance<'b, Message, Renderer, Event, S>, - tree: &'a mut Tree, - types: PhantomData<(Message, Event, S)>, - - #[borrows(instance)] - #[covariant] - instance_ref: Ref<'this, Option>>, - - #[borrows(instance_ref, mut tree)] - #[covariant] - overlay: Option>, -} - -struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> { - overlay: Option>, -} - -impl<'a, 'b, Message, Renderer, Event, S> - OverlayInstance<'a, 'b, Message, Renderer, Event, S> -{ - fn with_overlay_maybe( - &self, - f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, - ) -> Option { - self.overlay - .as_ref() - .unwrap() - .borrow_overlay() - .as_ref() - .map(f) - } - - fn with_overlay_mut_maybe( - &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, - ) -> Option { - self.overlay - .as_mut() - .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) - } -} - -impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay - for OverlayInstance<'a, 'b, Message, Renderer, Event, S> -where - Renderer: iced_native::Renderer, - S: 'static + Default, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); - - overlay.layout(renderer, bounds).translate(vector) - }) - .unwrap_or_default() - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - let _ = self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, theme, style, layout, cursor_position); - }); - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.with_overlay_maybe(|overlay| { - overlay.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - }) - .unwrap_or_default() - } - - fn on_event( - &mut self, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> iced_native::event::Status { - let mut local_messages = Vec::new(); - let mut local_shell = Shell::new(&mut local_messages); - - let event_status = self - .with_overlay_mut_maybe(|overlay| { - overlay.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ) - }) - .unwrap_or(iced_native::event::Status::Ignored); - - local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if !local_messages.is_empty() { - let overlay = self.overlay.take().unwrap().into_heads(); - let mut heads = overlay.instance.state.take().unwrap().into_heads(); - - for message in local_messages.into_iter().filter_map(|message| { - heads - .component - .update(overlay.tree.state.downcast_mut::(), message) - }) { - shell.publish(message); - } - - *overlay.instance.state.borrow_mut() = Some( - StateBuilder { - component: heads.component, - message: PhantomData, - state: PhantomData, - element_builder: |state| { - Some(state.view(overlay.tree.state.downcast_ref::())) - }, - } - .build(), - ); - - overlay.instance.with_element(|element| { - overlay.tree.diff_children(std::slice::from_ref(&element)) - }); - - self.overlay = Some( - OverlayBuilder { - instance: overlay.instance, - instance_ref_builder: |instance| instance.state.borrow(), - tree: overlay.tree, - types: PhantomData, - overlay_builder: |_, _| None, - } - .build(), - ); - - shell.invalidate_layout(); - } - - event_status - } -} diff --git a/lazy/src/pure/responsive.rs b/lazy/src/pure/responsive.rs deleted file mode 100644 index 0964ebc83d..0000000000 --- a/lazy/src/pure/responsive.rs +++ /dev/null @@ -1,388 +0,0 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Size}; -use iced_pure::horizontal_space; -use iced_pure::overlay; -use iced_pure::widget::tree::{self, Tree}; -use iced_pure::{Element, Widget}; - -use ouroboros::self_referencing; -use std::cell::{RefCell, RefMut}; -use std::marker::PhantomData; -use std::ops::Deref; - -/// A widget that is aware of its dimensions. -/// -/// A [`Responsive`] widget will always try to fill all the available space of -/// its parent. -#[allow(missing_debug_implementations)] -pub struct Responsive<'a, Message, Renderer> { - view: Box Element<'a, Message, Renderer> + 'a>, - content: RefCell>, -} - -impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - /// Creates a new [`Responsive`] widget with a closure that produces its - /// contents. - /// - /// The `view` closure will be provided with the current [`Size`] of - /// the [`Responsive`] widget and, therefore, can be used to build the - /// contents of the widget in a responsive way. - pub fn new( - view: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, - ) -> Self { - Self { - view: Box::new(view), - content: RefCell::new(Content { - size: Size::ZERO, - layout: layout::Node::new(Size::ZERO), - element: Element::new(horizontal_space(Length::Units(0))), - }), - } - } -} - -struct Content<'a, Message, Renderer> { - size: Size, - layout: layout::Node, - element: Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn update( - &mut self, - tree: &mut Tree, - renderer: &Renderer, - new_size: Size, - view: &dyn Fn(Size) -> Element<'a, Message, Renderer>, - ) { - if self.size == new_size { - return; - } - - self.element = view(new_size); - self.size = new_size; - - tree.diff(&self.element); - - self.layout = self - .element - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, self.size)); - } - - fn resolve( - &mut self, - tree: &mut Tree, - renderer: R, - layout: Layout<'_>, - view: &dyn Fn(Size) -> Element<'a, Message, Renderer>, - f: impl FnOnce( - &mut Tree, - R, - Layout<'_>, - &mut Element<'a, Message, Renderer>, - ) -> T, - ) -> T - where - R: Deref, - { - self.update(tree, renderer.deref(), layout.bounds().size(), view); - - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &self.layout, - ); - - f(tree, renderer, content_layout, &mut self.element) - } -} - -struct State { - tree: RefCell, -} - -impl<'a, Message, Renderer> Widget - for Responsive<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State { - tree: RefCell::new(Tree::empty()), - }) - } - - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Fill - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout::Node::new(limits.max()) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let state = tree.state.downcast_mut::(); - let mut content = self.content.borrow_mut(); - - content.resolve( - &mut state.tree.borrow_mut(), - renderer, - layout, - &self.view, - |tree, renderer, layout, element| { - element.as_widget_mut().on_event( - tree, - 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, - ) { - let state = tree.state.downcast_ref::(); - let mut content = self.content.borrow_mut(); - - content.resolve( - &mut state.tree.borrow_mut(), - renderer, - layout, - &self.view, - |tree, renderer, layout, element| { - element.as_widget().draw( - tree, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - }, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let state = tree.state.downcast_ref::(); - let mut content = self.content.borrow_mut(); - - content.resolve( - &mut state.tree.borrow_mut(), - renderer, - layout, - &self.view, - |tree, renderer, layout, element| { - element.as_widget().mouse_interaction( - tree, - layout, - cursor_position, - viewport, - renderer, - ) - }, - ) - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - let state = tree.state.downcast_ref::(); - - let overlay = OverlayBuilder { - content: self.content.borrow_mut(), - tree: state.tree.borrow_mut(), - types: PhantomData, - overlay_builder: |content, tree| { - content.update( - tree, - renderer, - layout.bounds().size(), - &self.view, - ); - - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &content.layout, - ); - - content.element.as_widget().overlay( - tree, - content_layout, - renderer, - ) - }, - } - .build(); - - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); - - has_overlay - .map(|position| overlay::Element::new(position, Box::new(overlay))) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: iced_native::Renderer + 'a, - Message: 'a, -{ - fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { - Self::new(responsive) - } -} - -#[self_referencing] -struct Overlay<'a, 'b, Message, Renderer> { - content: RefMut<'a, Content<'b, Message, Renderer>>, - tree: RefMut<'a, Tree>, - types: PhantomData, - - #[borrows(mut content, mut tree)] - #[covariant] - overlay: Option>, -} - -impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { - fn with_overlay_maybe( - &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, - ) -> Option { - self.borrow_overlay().as_ref().map(f) - } - - fn with_overlay_mut_maybe( - &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, - ) -> Option { - self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) - } -} - -impl<'a, 'b, Message, Renderer> overlay::Overlay - for Overlay<'a, 'b, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); - - overlay.layout(renderer, bounds).translate(vector) - }) - .unwrap_or_default() - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - let _ = self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, theme, style, layout, cursor_position); - }); - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.with_overlay_maybe(|overlay| { - overlay.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - }) - .unwrap_or_default() - } - - 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 { - self.with_overlay_mut_maybe(|overlay| { - overlay.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .unwrap_or(iced_native::event::Status::Ignored) - } -} diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 4a3eb5c6ac..0b7ae6de10 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -1,71 +1,131 @@ -//! Build responsive widgets. -use crate::{Cache, CacheBuilder}; - -use iced_native::event::{self, Event}; +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::window; +use iced_native::widget::horizontal_space; +use iced_native::widget::tree::{self, Tree}; use iced_native::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; -use std::cell::RefCell; +use ouroboros::self_referencing; +use std::cell::{RefCell, RefMut}; +use std::marker::PhantomData; use std::ops::Deref; -/// The state of a [`Responsive`] widget. -#[derive(Debug, Clone, Default)] -pub struct State { - last_size: Option, - last_layout: layout::Node, -} - -impl State { - pub fn new() -> State { - State::default() - } - - fn layout(&self, parent: Layout<'_>) -> Layout<'_> { - Layout::with_offset( - parent.position() - Point::ORIGIN, - &self.last_layout, - ) - } -} - /// A widget that is aware of its dimensions. /// /// A [`Responsive`] widget will always try to fill all the available space of /// its parent. #[allow(missing_debug_implementations)] -pub struct Responsive<'a, Message, Renderer>( - RefCell>, -); +pub struct Responsive<'a, Message, Renderer> { + view: Box Element<'a, Message, Renderer> + 'a>, + content: RefCell>, +} -impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { - /// Creates a new [`Responsive`] widget with the given [`State`] and a - /// closure that produces its contents. +impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a new [`Responsive`] widget with a closure that produces its + /// contents. /// /// The `view` closure will be provided with the current [`Size`] of /// the [`Responsive`] widget and, therefore, can be used to build the /// contents of the widget in a responsive way. pub fn new( - state: &'a mut State, - view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a, + view: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, ) -> Self { - Self(RefCell::new(Internal { - state, - content: Content::Pending(Some(Box::new(view))), - })) + Self { + view: Box::new(view), + content: RefCell::new(Content { + size: Size::ZERO, + layout: layout::Node::new(Size::ZERO), + element: Element::new(horizontal_space(Length::Units(0))), + }), + } } } +struct Content<'a, Message, Renderer> { + size: Size, + layout: layout::Node, + element: Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn update( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + new_size: Size, + view: &dyn Fn(Size) -> Element<'a, Message, Renderer>, + ) { + if self.size == new_size { + return; + } + + self.element = view(new_size); + self.size = new_size; + + tree.diff(&self.element); + + self.layout = self + .element + .as_widget() + .layout(renderer, &layout::Limits::new(Size::ZERO, self.size)); + } + + fn resolve( + &mut self, + tree: &mut Tree, + renderer: R, + layout: Layout<'_>, + view: &dyn Fn(Size) -> Element<'a, Message, Renderer>, + f: impl FnOnce( + &mut Tree, + R, + Layout<'_>, + &mut Element<'a, Message, Renderer>, + ) -> T, + ) -> T + where + R: Deref, + { + self.update(tree, renderer.deref(), layout.bounds().size(), view); + + let content_layout = Layout::with_offset( + layout.position() - Point::ORIGIN, + &self.layout, + ); + + f(tree, renderer, content_layout, &mut self.element) + } +} + +struct State { + tree: RefCell, +} + impl<'a, Message, Renderer> Widget for Responsive<'a, Message, Renderer> where Renderer: iced_native::Renderer, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + tree: RefCell::new(Tree::empty()), + }) + } + fn width(&self) -> Length { Length::Fill } @@ -79,45 +139,44 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let size = limits.max(); - - self.0.borrow_mut().state.last_size = Some(size); - - layout::Node::new(size) + layout::Node::new(limits.max()) } fn on_event( &mut self, - event: Event, + tree: &mut Tree, + event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - let mut internal = self.0.borrow_mut(); - - if matches!(event, Event::Window(window::Event::Resized { .. })) - || internal.state.last_size - != Some(internal.state.last_layout.size()) - { - shell.invalidate_widgets(); - } - - internal.resolve(renderer, |state, renderer, content| { - content.on_event( - event, - state.layout(layout), - cursor_position, - renderer, - clipboard, - shell, - ) - }) + let state = tree.state.downcast_mut::(); + let mut content = self.content.borrow_mut(); + + content.resolve( + &mut state.tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, renderer, layout, element| { + element.as_widget_mut().on_event( + tree, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }, + ) } fn draw( &self, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -125,168 +184,96 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let mut internal = self.0.borrow_mut(); - - internal.resolve(renderer, |state, renderer, content| { - content.draw( - renderer, - theme, - style, - state.layout(layout), - cursor_position, - viewport, - ) - }) + let state = tree.state.downcast_ref::(); + let mut content = self.content.borrow_mut(); + + content.resolve( + &mut state.tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, renderer, layout, element| { + element.as_widget().draw( + tree, + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) + }, + ) } fn mouse_interaction( &self, + tree: &Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - let mut internal = self.0.borrow_mut(); - - internal.resolve(renderer, |state, renderer, content| { - content.mouse_interaction( - state.layout(layout), - cursor_position, - viewport, - renderer, - ) - }) + let state = tree.state.downcast_ref::(); + let mut content = self.content.borrow_mut(); + + content.resolve( + &mut state.tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, renderer, layout, element| { + element.as_widget().mouse_interaction( + tree, + layout, + cursor_position, + viewport, + renderer, + ) + }, + ) } - fn overlay( - &mut self, + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, - ) -> Option> { - let has_overlay = { - use std::ops::DerefMut; - - let mut internal = self.0.borrow_mut(); - - let _ = - internal.resolve(renderer, |_state, _renderer, _content| {}); - - let Internal { content, state } = internal.deref_mut(); - - let content_layout = state.layout(layout); - - match content { - Content::Pending(_) => None, - Content::Ready(cache) => { - *cache = Some( - CacheBuilder { - element: cache.take().unwrap().into_heads().element, - overlay_builder: |element| { - element.overlay(content_layout, renderer) - }, - } - .build(), - ); - - cache - .as_ref() - .unwrap() - .borrow_overlay() - .as_ref() - .map(|overlay| overlay.position()) - } - } - }; - - has_overlay.map(|position| { - overlay::Element::new( - position, - Box::new(Overlay { instance: self }), - ) - }) - } -} - -struct Internal<'a, Message, Renderer> { - state: &'a mut State, - content: Content<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Internal<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn resolve( - &mut self, - renderer: R, - f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, - ) -> T - where - R: Deref, - { - self.content.resolve(self.state, renderer, f) - } -} - -enum Content<'a, Message, Renderer> { - Pending( - Option Element<'a, Message, Renderer> + 'a>>, - ), - Ready(Option>), -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn resolve( - &mut self, - state: &mut State, - renderer: R, - f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, - ) -> T - where - R: Deref, - { - match self { - Content::Ready(cache) => { - let mut heads = cache.take().unwrap().into_heads(); - - let result = f(state, renderer, &mut heads.element); - - *cache = Some( - CacheBuilder { - element: heads.element, - overlay_builder: |_| None, - } - .build(), + ) -> Option> { + let state = tree.state.downcast_ref::(); + + let overlay = OverlayBuilder { + content: self.content.borrow_mut(), + tree: state.tree.borrow_mut(), + types: PhantomData, + overlay_builder: |content, tree| { + content.update( + tree, + renderer, + layout.bounds().size(), + &self.view, ); - result - } - Content::Pending(view) => { - let element = - view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); - - state.last_layout = element.layout( - renderer.deref(), - &layout::Limits::new( - Size::ZERO, - state.last_size.unwrap_or(Size::ZERO), - ), + let content_layout = Layout::with_offset( + layout.position() - Point::ORIGIN, + &content.layout, ); - *self = Content::Ready(Some( - CacheBuilder { - element, - overlay_builder: |_| None, - } - .build(), - )); - - self.resolve(state, renderer, f) - } + content.element.as_widget().overlay( + tree, + content_layout, + renderer, + ) + }, } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); + + has_overlay + .map(|position| overlay::Element::new(position, Box::new(overlay))) } } @@ -301,8 +288,15 @@ where } } +#[self_referencing] struct Overlay<'a, 'b, Message, Renderer> { - instance: &'b mut Responsive<'a, Message, Renderer>, + content: RefMut<'a, Content<'b, Message, Renderer>>, + tree: RefMut<'a, Tree>, + types: PhantomData, + + #[borrows(mut content, mut tree)] + #[covariant] + overlay: Option>, } impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { @@ -310,29 +304,14 @@ impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { &self, f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, ) -> Option { - let internal = self.instance.0.borrow(); - - match &internal.content { - Content::Pending(_) => None, - Content::Ready(cache) => { - cache.as_ref().unwrap().borrow_overlay().as_ref().map(f) - } - } + self.borrow_overlay().as_ref().map(f) } fn with_overlay_mut_maybe( - &self, + &mut self, f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, ) -> Option { - let mut internal = self.instance.0.borrow_mut(); - - match &mut internal.content { - Content::Pending(_) => None, - Content::Ready(cache) => cache - .as_mut() - .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)), - } + self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) } } @@ -394,7 +373,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> iced_native::event::Status { + ) -> event::Status { self.with_overlay_mut_maybe(|overlay| { overlay.on_event( event, diff --git a/native/src/element.rs b/native/src/element.rs index 425bddc2ef..cc74035e93 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` /// 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 + 'a>, + widget: Box + '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 + 'a, - ) -> Element<'a, Message, Renderer> { - Element { + pub fn new(widget: impl Widget + '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 { + self.widget.as_ref() + } + + /// Returns a mutable reference to the [`Widget`] of the [`Element`], + pub fn as_widget_mut(&mut self) -> &mut dyn Widget { + 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(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>( + pub fn map( 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> { - self.widget.overlay(layout, renderer) + Element::new(Map::new(self.widget, f)) } } struct Map<'a, A, B, Renderer> { widget: Box + 'a>, - mapper: Box B>, + mapper: Box 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 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 { + 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> { + ) -> Option> { 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 + 'a> + for Element<'a, Message, Renderer> { - fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self { - Explain { element, color } + fn borrow(&self) -> &(dyn Widget + 'a) { + self.widget.borrow() } } -impl<'a, Message, Renderer> Widget - for Explain<'a, Message, Renderer> -where - Renderer: crate::Renderer, +impl<'a, Message, Renderer> Borrow + '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: &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> { - self.element.overlay(layout, renderer) + fn borrow(&self) -> &(dyn Widget + 'a) { + self.widget.borrow() } } diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index 5fbcbca0ce..94121d7690 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 792d29058a..c2a9869324 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 { + 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> +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 fc3f52b2a2..b4c77c25f0 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 { + 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 9ee727032c..c71c237fc1 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 a7305a55cb..ef64ac36f4 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 ef6f437e4e..9f3a8e21a9 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, bounds: Size, } @@ -88,7 +90,7 @@ where pub fn build>>( 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 9fe96e337d..9a4f373acc 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 { + 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> { + ) -> Option> { None } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index a33ee7f75d..6eac6c1bca 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, 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(state: &'a mut State, content: E) -> Self - where - E: Into>, - { + /// Creates a new [`Button`] with the given content. + pub fn new(content: impl Into>) -> Self { Button { - state, content: content.into(), on_press: None, width: Length::Shrink, height: Length::Shrink, padding: Padding::new(5), - style: Default::default(), + style: ::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: ::Style, @@ -127,6 +116,159 @@ where } } +impl<'a, Message, Renderer> Widget + for Button<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn children(&self) -> Vec { + 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::(), + ) + } + + 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::(), + ); + + 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> { + self.content.as_widget().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } +} + +impl<'a, Message, Renderer> From> + 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 - 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> { - self.content - .overlay(layout.children().next().unwrap(), renderer) - } -} - -impl<'a, Message, Renderer> From> - 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 a49d2fa2b0..dc3c0bd061 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 4eee7d3c43..834f9858af 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>, } @@ -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(mut self, child: E) -> Self - where - E: Into>, - { + pub fn push( + mut self, + child: impl Into>, + ) -> Self { self.children.push(child.into()); self } @@ -113,6 +106,14 @@ impl<'a, Message, Renderer> Widget where Renderer: crate::Renderer, { + fn children(&self) -> Vec { + 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> { - self.children - .iter_mut() - .zip(layout.children()) - .filter_map(|(child, layout)| { - child.widget.overlay(layout, renderer) - }) - .next() + ) -> Option> { + overlay::from_children(&self.children, tree, layout, renderer) } } impl<'a, Message, Renderer> From> 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 3d68a5959c..b0fa0315dd 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, - 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 for Container<'a, Message, Renderer> where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { + fn children(&self) -> Vec { + 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> { - self.content - .overlay(layout.children().next().unwrap(), renderer) + ) -> Option> { + self.content.as_widget().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) } } +impl<'a, Message, Renderer> From> + 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, + 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: &mut Renderer, @@ -281,17 +316,3 @@ pub fn draw_background( ); } } - -impl<'a, Message, Renderer> From> - 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/pure/src/helpers.rs b/native/src/widget/helpers.rs similarity index 76% rename from pure/src/helpers.rs rename to native/src/widget/helpers.rs index 88598f9bac..648aab5f8f 100644 --- a/pure/src/helpers.rs +++ b/native/src/widget/helpers.rs @@ -1,11 +1,36 @@ //! Helper functions to create pure widgets. use crate::widget; -use crate::Element; +use crate::{Element, Length}; -use iced_native::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 @@ -13,25 +38,28 @@ pub fn container<'a, Message, Renderer>( content: impl Into>, ) -> widget::Container<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::container::StyleSheet, { widget::Container::new(content) } -/// Creates a new [`Column`]. +/// Creates a new [`Column`] with the given children. /// /// [`Column`]: widget::Column -pub fn column<'a, Message, Renderer>() -> widget::Column<'a, Message, Renderer> -{ - widget::Column::new() +pub fn column<'a, Message, Renderer>( + children: Vec>, +) -> widget::Row<'a, Message, Renderer> { + widget::Row::with_children(children) } -/// Creates a new [`Row`]. +/// Creates a new [`Row`] with the given children. /// /// [`Row`]: widget::Row -pub fn row<'a, Message, Renderer>() -> widget::Row<'a, Message, Renderer> { - widget::Row::new() +pub fn row<'a, Message, Renderer>( + children: Vec>, +) -> widget::Row<'a, Message, Renderer> { + widget::Row::with_children(children) } /// Creates a new [`Scrollable`] with the provided content. @@ -41,7 +69,7 @@ pub fn scrollable<'a, Message, Renderer>( content: impl Into>, ) -> widget::Scrollable<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::scrollable::StyleSheet, { widget::Scrollable::new(content) @@ -54,7 +82,7 @@ pub fn button<'a, Message, Renderer>( content: impl Into>, ) -> widget::Button<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::button::StyleSheet, { widget::Button::new(content) @@ -70,7 +98,7 @@ pub fn tooltip<'a, Message, Renderer>( position: widget::tooltip::Position, ) -> widget::Tooltip<'a, Message, Renderer> where - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet, { widget::Tooltip::new(content, tooltip, position) @@ -81,7 +109,7 @@ where /// [`Text`]: widget::Text pub fn text(text: impl Into) -> widget::Text where - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::text::StyleSheet, { widget::Text::new(text) @@ -96,7 +124,7 @@ pub fn checkbox<'a, Message, Renderer>( f: impl Fn(bool) -> Message + 'a, ) -> widget::Checkbox<'a, Message, Renderer> where - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, { widget::Checkbox::new(is_checked, label, f) @@ -113,7 +141,7 @@ pub fn radio( ) -> widget::Radio where Message: Clone, - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::radio::StyleSheet, V: Copy + Eq, { @@ -129,7 +157,7 @@ pub fn toggler<'a, Message, Renderer>( f: impl Fn(bool) -> Message + 'a, ) -> widget::Toggler<'a, Message, Renderer> where - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::toggler::StyleSheet, { widget::Toggler::new(is_checked, label, f) @@ -145,7 +173,7 @@ pub fn text_input<'a, Message, Renderer>( ) -> widget::TextInput<'a, Message, Renderer> where Message: Clone, - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::text_input::StyleSheet, { widget::TextInput::new(placeholder, value, on_change) @@ -162,7 +190,7 @@ pub fn slider<'a, T, Message, Renderer>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::slider::StyleSheet, { widget::Slider::new(range, value, on_change) @@ -179,7 +207,7 @@ pub fn pick_list<'a, Message, Renderer, T>( where T: ToString + Eq + 'static, [T]: ToOwned>, - Renderer: iced_native::text::Renderer, + Renderer: crate::text::Renderer, Renderer::Theme: widget::pick_list::StyleSheet, { widget::PickList::new(options, selected, on_selected) @@ -211,7 +239,7 @@ pub fn vertical_space(height: Length) -> widget::Space { /// [`Rule`]: widget::Rule pub fn horizontal_rule(height: u16) -> widget::Rule where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::rule::StyleSheet, { widget::Rule::horizontal(height) @@ -222,7 +250,7 @@ where /// [`Rule`]: widget::Rule pub fn vertical_rule(width: u16) -> widget::Rule where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, Renderer::Theme: widget::rule::StyleSheet, { widget::Rule::vertical(width) @@ -240,8 +268,16 @@ pub fn progress_bar( value: f32, ) -> widget::ProgressBar where - Renderer: iced_native::Renderer, + 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 { + widget::Svg::new(handle) +} diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 72075bd18d..91d68e3417 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) -> Viewer { + 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 1aa75aa064..b1fe596c17 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 { padding: u16, width: Length, height: Length, @@ -24,11 +24,10 @@ pub struct Viewer<'a, Handle> { handle: Handle, } -impl<'a, Handle> Viewer<'a, Handle> { +impl Viewer { /// 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(&self, renderer: &Renderer, bounds: Size) -> Size - where - Renderer: image::Renderer, - { - 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 - for Viewer<'a, Handle> +impl Widget for Viewer where Renderer: image::Renderer, Handle: Clone + Hash, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + 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::(); + 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.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::(); - 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::(); + + 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::(); 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::(); 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> +impl<'a, Message, Renderer, Handle> From> for Element<'a, Message, Renderer> where Renderer: 'a + image::Renderer, Message: 'a, Handle: Clone + Hash + 'a, { - fn from(viewer: Viewer<'a, Handle>) -> Element<'a, Message, Renderer> { + fn from(viewer: Viewer) -> 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, + handle: &::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 70ca772c74..d84fb7a0f9 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( - state: &'a mut State, - view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>, + state: &'a State, + 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 + for PaneGrid<'a, Message, Renderer> +where + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet + container::StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(state::Action::Idle) + } + + fn children(&self) -> Vec { + 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::(); + + 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> { + 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> + 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: &Renderer, @@ -656,175 +869,6 @@ pub struct ResizeEvent { pub ratio: f32, } -impl<'a, Message, Renderer> Widget - 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> { - self.elements - .iter_mut() - .zip(layout.children()) - .filter_map(|((_, pane), layout)| pane.overlay(layout, renderer)) - .next() - } -} - -impl<'a, Message, Renderer> From> - 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 4c9e65c94f..98ce2c4b96 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> { - if let Some(title_bar) = self.title_bar.as_mut() { + ) -> Option> { + 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 4e90f64543..cdca6267d9 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -31,8 +31,6 @@ pub struct State { /// /// [`PaneGrid`]: crate::widget::PaneGrid pub internal: Internal, - - pub(super) action: Action, } impl State { @@ -54,11 +52,7 @@ impl State { 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 14c3ab4e24..f9050e3e1b 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> { + ) -> Option> { 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 61b0eb9639..1232878bd4 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, - on_selected: Box Message>, + on_selected: Box Message + 'a>, options: Cow<'a, [T]>, placeholder: Option, selected: Option, @@ -39,35 +37,6 @@ where style: ::Style, } -/// The local state of a [`PickList`]. -#[derive(Debug, Clone)] -pub struct State { - menu: menu::State, - keyboard_modifiers: keyboard::Modifiers, - is_open: bool, - hovered_option: Option, - last_selection: Option, -} - -impl State { - /// 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 Default for State { - 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, options: impl Into>, selected: Option, - 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 + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + Message: 'a, + Renderer: text::Renderer + 'a, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + 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.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::>(), + ) + } + + 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> { + let state = tree.state.downcast_mut::>(); + + overlay( + layout, + state, + self.padding, + self.text_size, + self.font.clone(), + &self.options, + self.style, + ) + } +} + +impl<'a, T: 'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + 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 { + menu: menu::State, + keyboard_modifiers: keyboard::Modifiers, + is_open: bool, + hovered_option: Option, + last_selection: Option, +} + +impl State { + /// 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 Default for State { + fn default() -> Self { + Self::new() + } +} + /// Computes the layout of a [`PickList`]. pub fn layout( renderer: &Renderer, @@ -429,127 +557,3 @@ pub fn draw( }); } } - -impl<'a, T: 'a, Message, Renderer> Widget - for PickList<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - 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( - layout, - self.state, - self.padding, - self.text_size, - self.font.clone(), - &self.options, - self.style, - ) - } -} - -impl<'a, T: 'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - Message: 'static, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet - + container::StyleSheet - + scrollable::StyleSheet - + menu::StyleSheet, - ::Style: - Into<::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 50bdcda60f..8a9454332b 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 647e3c5ae2..c9152d05cd 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 9d8cc71512..c342c27711 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>, } @@ -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(mut self, child: E) -> Self - where - E: Into>, - { + pub fn push( + mut self, + child: impl Into>, + ) -> Self { self.children.push(child.into()); self } @@ -113,6 +96,14 @@ impl<'a, Message, Renderer> Widget where Renderer: crate::Renderer, { + fn children(&self) -> Vec { + 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> { - self.children - .iter_mut() - .zip(layout.children()) - .filter_map(|(child, layout)| { - child.widget.overlay(layout, renderer) - }) - .next() + ) -> Option> { + overlay::from_children(&self.children, tree, layout, renderer) } } impl<'a, Message, Renderer> From> 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 f0fda8a9cc..56f8c80d36 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 77ed206671..b40c374367 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 Message + 'a>>, style: ::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>) -> 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>(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(mut self, child: E) -> Self - where - E: Into>, - { - self.content = self.content.push(child); - self +impl<'a, Message, Renderer> Widget + for Scrollable<'a, Message, Renderer> +where + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn children(&self) -> Vec { + 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::::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::(), + 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::(), + 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::(), + 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> { + 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::() + .offset(bounds, content_bounds); + + overlay.translate(Vector::new(0.0, -(offset as f32))) + }) + } +} + +impl<'a, Message, Renderer> From> + 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( } } -impl<'a, Message, Renderer> Widget - for Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - Widget::::width(&self.content) - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout( - renderer, - limits, - Widget::::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> { - 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> - 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 a5ff611c83..585d9c35c9 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, step: T, value: T, - on_change: Box Message>, + on_change: Box Message + 'a>, on_release: Option, 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( - state: &'a mut State, - range: RangeInclusive, - value: T, - on_change: F, - ) -> Self + pub fn new(range: RangeInclusive, 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 + for Slider<'a, T, Message, Renderer> +where + T: Copy + Into + num_traits::FromPrimitive, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + 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::(), + &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::(), + 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::(), + ) + } +} + +impl<'a, T, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into + 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( @@ -366,102 +473,3 @@ impl State { State::default() } } - -impl<'a, T, Message, Renderer> Widget - for Slider<'a, T, Message, Renderer> -where - T: Copy + Into + 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> - for Element<'a, Message, Renderer> -where - T: 'a + Copy + Into + 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 81338306b6..9f83589386 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 76b3eb8bcf..aa68bfb851 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 8d173b8a84..2638650527 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 fd360cd727..4ef9e11bdf 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( - 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(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::(), value.unwrap_or(&self.value), &self.placeholder, self.size, @@ -199,6 +183,116 @@ where } } +impl<'a, Message, Renderer> Widget + for TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + 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::(), + ) + } + + 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::(), + &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> + 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, @@ -777,93 +871,6 @@ pub fn mouse_interaction( } } -impl<'a, Message, Renderer> Widget - 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> - 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 3deaf28756..7893f78c79 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 0548e9da45..d8198004db 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 + for Tooltip<'a, Message, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, +{ + fn children(&self) -> Vec { + 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> { + self.content.as_widget().overlay( + &mut tree.children[0], + layout, + renderer, + ) + } +} + +impl<'a, Message, Renderer> From> + 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( }); } } - -impl<'a, Message, Renderer> Widget - 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> - 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/pure/src/widget/tree.rs b/native/src/widget/tree.rs similarity index 92% rename from pure/src/widget/tree.rs rename to native/src/widget/tree.rs index 2f876523a4..a8b1a18539 100644 --- a/pure/src/widget/tree.rs +++ b/native/src/widget/tree.rs @@ -3,10 +3,12 @@ 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, @@ -33,7 +35,7 @@ impl Tree { widget: impl Borrow + 'a>, ) -> Self where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, { let widget = widget.borrow(); @@ -56,7 +58,7 @@ impl Tree { &mut self, new: impl Borrow + 'a>, ) where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, { if self.tag == new.borrow().tag() { new.borrow().diff(self) @@ -70,7 +72,7 @@ impl Tree { &mut self, new_children: &[impl Borrow + 'a>], ) where - Renderer: iced_native::Renderer, + Renderer: crate::Renderer, { self.diff_children_custom( new_children, @@ -174,3 +176,12 @@ impl 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"), + } + } +} diff --git a/pure/Cargo.toml b/pure/Cargo.toml deleted file mode 100644 index b57e4c5a49..0000000000 --- a/pure/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "iced_pure" -version = "0.2.2" -edition = "2021" -description = "Pure widgets for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_pure" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] - -[dependencies] -iced_native = { version = "0.5", path = "../native" } -iced_style = { version = "0.4", path = "../style" } -num-traits = "0.2" diff --git a/pure/src/element.rs b/pure/src/element.rs deleted file mode 100644 index 35c68716e8..0000000000 --- a/pure/src/element.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::overlay; -use crate::widget::tree::{self, Tree}; -use crate::widget::Widget; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -use std::borrow::Borrow; - -/// A generic [`Widget`]. -/// -/// It is useful to build composable user interfaces that do not leak -/// implementation details in their __view logic__. -/// -/// If you have a [built-in widget], you should be able to use `Into` -/// to turn it into an [`Element`]. -/// -/// [built-in widget]: crate::widget -pub struct Element<'a, Message, Renderer> { - widget: Box + 'a>, -} - -impl<'a, Message, Renderer> Element<'a, Message, Renderer> { - /// Creates a new [`Element`] containing the given [`Widget`]. - pub fn new(widget: impl Widget + 'a) -> Self - where - Renderer: iced_native::Renderer, - { - Self { - widget: Box::new(widget), - } - } - - /// Returns a reference to the [`Widget`] of the [`Element`], - pub fn as_widget(&self) -> &dyn Widget { - self.widget.as_ref() - } - - /// Returns a mutable reference to the [`Widget`] of the [`Element`], - pub fn as_widget_mut(&mut self) -> &mut dyn Widget { - 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 - /// UI and make them __composable__. - /// - /// # Example - /// Imagine we want to use [our counter](index.html#usage). But instead of - /// showing a single counter, we want to display many of them. We can reuse - /// the `Counter` type as it is! - /// - /// We use composition to model the __state__ of our new application: - /// - /// ``` - /// # mod counter { - /// # pub struct Counter; - /// # } - /// use counter::Counter; - /// - /// struct ManyCounters { - /// counters: Vec, - /// } - /// ``` - /// - /// We can store the state of multiple counters now. However, the - /// __messages__ we implemented before describe the user interactions - /// of a __single__ counter. Right now, we need to also identify which - /// counter is receiving user interactions. Can we use composition again? - /// Yes. - /// - /// ``` - /// # mod counter { - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # } - /// #[derive(Debug, Clone, Copy)] - /// pub enum Message { - /// Counter(usize, counter::Message) - /// } - /// ``` - /// - /// We compose the previous __messages__ with the index of the counter - /// producing them. Let's implement our __view logic__ now: - /// - /// ``` - /// # mod counter { - /// # type Text = iced_pure::widget::Text; - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn view(&mut self) -> Text { - /// # Text::new("") - /// # } - /// # } - /// # } - /// # - /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; - /// # } - /// # - /// # use counter::Counter; - /// # - /// # struct ManyCounters { - /// # counters: Vec, - /// # } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) - /// # } - /// use iced_pure::Element; - /// use iced_pure::widget::Row; - /// use iced_wgpu::Renderer; - /// - /// impl ManyCounters { - /// pub fn view(&mut self) -> Row { - /// // We can quickly populate a `Row` by folding over our counters - /// self.counters.iter_mut().enumerate().fold( - /// Row::new().spacing(20), - /// |row, (index, counter)| { - /// // We display the counter - /// let element: Element = - /// counter.view().into(); - /// - /// row.push( - /// // Here we turn our `Element` into - /// // an `Element` by combining the `index` and the - /// // message of the `element`. - /// element.map(move |message| Message::Counter(index, message)) - /// ) - /// } - /// ) - /// } - /// } - /// ``` - /// - /// Finally, our __update logic__ is pretty straightforward: simple - /// delegation. - /// - /// ``` - /// # mod counter { - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message {} - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn update(&mut self, _message: Message) {} - /// # } - /// # } - /// # - /// # use counter::Counter; - /// # - /// # struct ManyCounters { - /// # counters: Vec, - /// # } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) - /// # } - /// impl ManyCounters { - /// pub fn update(&mut self, message: Message) { - /// match message { - /// Message::Counter(index, counter_msg) => { - /// if let Some(counter) = self.counters.get_mut(index) { - /// counter.update(counter_msg); - /// } - /// } - /// } - /// } - /// } - /// ``` - pub fn map( - self, - f: impl Fn(Message) -> B + 'a, - ) -> Element<'a, B, Renderer> - where - Message: 'a, - Renderer: iced_native::Renderer + 'a, - B: 'a, - { - Element::new(Map::new(self.widget, f)) - } -} - -struct Map<'a, A, B, Renderer> { - widget: Box + 'a>, - mapper: Box B + 'a>, -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new( - widget: Box + 'a>, - mapper: F, - ) -> Map<'a, A, B, Renderer> - where - F: 'a + Fn(A) -> B, - { - Map { - widget, - mapper: Box::new(mapper), - } - } -} - -impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> -where - Renderer: iced_native::Renderer + 'a, - A: 'a, - B: 'a, -{ - fn tag(&self) -> tree::Tag { - self.widget.tag() - } - - fn state(&self) -> tree::State { - self.widget.state() - } - - fn children(&self) -> Vec { - self.widget.children() - } - - fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree) - } - - fn width(&self) -> Length { - self.widget.width() - } - - fn height(&self) -> Length { - self.widget.height() - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.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<'_, B>, - ) -> event::Status { - let mut local_messages = Vec::new(); - let mut local_shell = Shell::new(&mut local_messages); - - let status = self.widget.on_event( - tree, - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ); - - shell.merge(local_shell, &self.mapper); - - status - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.widget.draw( - tree, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - 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, - renderer, - ) - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - let mapper = &self.mapper; - - self.widget - .overlay(tree, layout, renderer) - .map(move |overlay| overlay.map(mapper)) - } -} - -impl<'a, Message, Renderer> Borrow + 'a> - for Element<'a, Message, Renderer> -{ - fn borrow(&self) -> &(dyn Widget + 'a) { - self.widget.borrow() - } -} - -impl<'a, Message, Renderer> Borrow + 'a> - for &Element<'a, Message, Renderer> -{ - fn borrow(&self) -> &(dyn Widget + 'a) { - self.widget.borrow() - } -} diff --git a/pure/src/flex.rs b/pure/src/flex.rs deleted file mode 100644 index 3f65a28b62..0000000000 --- a/pure/src/flex.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Distribute elements using a flex-based layout. -// This code is heavily inspired by the [`druid`] codebase. -// -// [`druid`]: https://github.com/xi-editor/druid -// -// Copyright 2018 The xi-editor Authors, Héctor Ramón -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// 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 iced_native::layout::{Limits, Node}; -use iced_native::{Alignment, Padding, Point, Size}; - -/// The main axis of a flex layout. -#[derive(Debug)] -pub enum Axis { - /// The horizontal axis - Horizontal, - - /// The vertical axis - Vertical, -} - -impl Axis { - fn main(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.width, - Axis::Vertical => size.height, - } - } - - fn cross(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.height, - Axis::Vertical => size.width, - } - } - - fn pack(&self, main: f32, cross: f32) -> (f32, f32) { - match self { - Axis::Horizontal => (main, cross), - Axis::Vertical => (cross, main), - } - } -} - -/// Computes the flex layout with the given axis and limits, applying spacing, -/// padding and alignment to the items as needed. -/// -/// It returns a new layout [`Node`]. -pub fn resolve( - axis: Axis, - renderer: &Renderer, - limits: &Limits, - padding: Padding, - spacing: f32, - align_items: Alignment, - items: &[Element<'_, Message, Renderer>], -) -> Node -where - Renderer: iced_native::Renderer, -{ - let limits = limits.pad(padding); - let total_spacing = spacing * items.len().saturating_sub(1) as f32; - let max_cross = axis.cross(limits.max()); - - let mut fill_sum = 0; - let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill())); - let mut available = axis.main(limits.max()) - total_spacing; - - let mut nodes: Vec = Vec::with_capacity(items.len()); - nodes.resize(items.len(), Node::default()); - - if align_items == Alignment::Fill { - let mut fill_cross = axis.cross(limits.min()); - - items.iter().for_each(|child| { - let cross_fill_factor = match axis { - Axis::Horizontal => child.as_widget().height(), - Axis::Vertical => child.as_widget().width(), - } - .fill_factor(); - - if cross_fill_factor == 0 { - let (max_width, max_height) = axis.pack(available, max_cross); - - let child_limits = - Limits::new(Size::ZERO, Size::new(max_width, max_height)); - - let layout = child.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - fill_cross = fill_cross.max(axis.cross(size)); - } - }); - - cross = fill_cross; - } - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.as_widget().width(), - Axis::Vertical => child.as_widget().height(), - } - .fill_factor(); - - if fill_factor == 0 { - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(0.0, cross) - } else { - axis.pack(0.0, 0.0) - }; - - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(available, cross) - } else { - axis.pack(available, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); - - let layout = child.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - available -= axis.main(size); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(size)); - } - - nodes[i] = layout; - } else { - fill_sum += fill_factor; - } - } - - let remaining = available.max(0.0); - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.as_widget().width(), - Axis::Vertical => child.as_widget().height(), - } - .fill_factor(); - - if fill_factor != 0 { - let max_main = remaining * fill_factor as f32 / fill_sum as f32; - let min_main = if max_main.is_infinite() { - 0.0 - } else { - max_main - }; - - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(min_main, cross) - } else { - axis.pack(min_main, axis.cross(limits.min())) - }; - - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(max_main, cross) - } else { - axis.pack(max_main, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); - - let layout = child.as_widget().layout(renderer, &child_limits); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(layout.size())); - } - - nodes[i] = layout; - } - } - - let pad = axis.pack(padding.left as f32, padding.top as f32); - let mut main = pad.0; - - for (i, node) in nodes.iter_mut().enumerate() { - if i > 0 { - main += spacing; - } - - let (x, y) = axis.pack(main, pad.1); - - node.move_to(Point::new(x, y)); - - match axis { - Axis::Horizontal => { - node.align( - Alignment::Start, - align_items, - Size::new(0.0, cross), - ); - } - Axis::Vertical => { - node.align( - align_items, - Alignment::Start, - Size::new(cross, 0.0), - ); - } - } - - let size = node.size(); - - main += axis.main(size); - } - - let (width, height) = axis.pack(main - pad.0, cross); - let size = limits.resolve(Size::new(width, height)); - - Node::with_children(size.pad(padding), nodes) -} diff --git a/pure/src/lib.rs b/pure/src/lib.rs deleted file mode 100644 index 9c5f3bc863..0000000000 --- a/pure/src/lib.rs +++ /dev/null @@ -1,292 +0,0 @@ -//! Stateless, pure widgets for iced. -//! -//! # The Elm Architecture, purity, and continuity -//! As you may know, applications made with `iced` use [The Elm Architecture]. -//! -//! In a nutshell, this architecture defines the initial state of the application, a way to `view` it, and a way to `update` it after a user interaction. The `update` logic is called after a meaningful user interaction, which in turn updates the state of the application. Then, the `view` logic is executed to redisplay the application. -//! -//! Since `view` logic is only run after an `update`, all of the mutations to the application state must only happen in the `update` logic. If the application state changes anywhere else, the `view` logic will not be rerun and, therefore, the previously generated `view` may stay outdated. -//! -//! However, the `Application` trait in `iced` defines `view` as: -//! -//! ```ignore -//! pub trait Application { -//! fn view(&mut self) -> Element; -//! } -//! ``` -//! -//! As a consequence, the application state can be mutated in `view` logic. The `view` logic in `iced` is __impure__. -//! -//! This impurity is necessary because `iced` puts the burden of widget __continuity__ on its users. In other words, it's up to you to provide `iced` with the internal state of each widget every time `view` is called. -//! -//! If we take a look at the classic `counter` example: -//! -//! ```ignore -//! struct Counter { -//! value: i32, -//! increment_button: button::State, -//! decrement_button: button::State, -//! } -//! -//! // ... -//! -//! impl Counter { -//! pub fn view(&mut self) -> Column { -//! Column::new() -//! .push( -//! Button::new(&mut self.increment_button, Text::new("+")) -//! .on_press(Message::IncrementPressed), -//! ) -//! .push(Text::new(self.value.to_string()).size(50)) -//! .push( -//! Button::new(&mut self.decrement_button, Text::new("-")) -//! .on_press(Message::DecrementPressed), -//! ) -//! } -//! } -//! ``` -//! -//! We can see how we need to keep track of the `button::State` of each `Button` in our `Counter` state and provide a mutable reference to the widgets in our `view` logic. The widgets produced by `view` are __stateful__. -//! -//! While this approach forces users to keep track of widget state and causes impurity, I originally chose it because it allows `iced` to directly consume the widget tree produced by `view`. Since there is no internal state decoupled from `view` maintained by the runtime, `iced` does not need to compare (e.g. reconciliate) widget trees in order to ensure continuity. -//! -//! # Stateless widgets -//! As the library matures, the need for some kind of persistent widget data (see #553) between `view` calls becomes more apparent (e.g. incremental rendering, animations, accessibility, etc.). -//! -//! If we are going to end up having persistent widget data anyways... There is no reason to have impure, stateful widgets anymore! -//! -//! And so I started exploring and ended up creating a new subcrate called `iced_pure`, which introduces a completely stateless implementation for every widget in `iced`. -//! -//! With the help of this crate, we can now write a pure `counter` example: -//! -//! ```ignore -//! struct Counter { -//! value: i32, -//! } -//! -//! // ... -//! -//! impl Counter { -//! fn view(&self) -> Column { -//! Column::new() -//! .push(Button::new("Increment").on_press(Message::IncrementPressed)) -//! .push(Text::new(self.value.to_string()).size(50)) -//! .push(Button::new("Decrement").on_press(Message::DecrementPressed)) -//! } -//! } -//! ``` -//! -//! Notice how we no longer need to keep track of the `button::State`! The widgets in `iced_pure` do not take any mutable application state in `view`. They are __stateless__ widgets. As a consequence, we do not need mutable access to `self` in `view` anymore. `view` becomes __pure__. -//! -//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_docs, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(rust_2018_idioms, unsafe_code)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -pub mod flex; -pub mod helpers; -pub mod overlay; -pub mod widget; - -mod element; - -pub use element::Element; -pub use helpers::*; -pub use widget::Widget; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -/// A bridge between impure and pure widgets. -/// -/// If you already have an existing `iced` application, you do not need to switch completely to the new traits in order to benefit from the `pure` module. Instead, you can leverage the new `Pure` widget to include `pure` widgets in your impure `Application`. -/// -/// For instance, let's say we want to use our pure `Counter` in an impure application: -/// -/// ```ignore -/// use iced_pure::{self, Pure}; -/// -/// struct Impure { -/// state: pure::State, -/// counter: Counter, -/// } -/// -/// impl Sandbox for Impure { -/// // ... -/// -/// pub fn view(&mut self) -> Element { -/// Pure::new(&mut self.state, self.counter.view()).into() -/// } -/// } -/// ``` -/// -/// [`Pure`] acts as a bridge between pure and impure widgets. It is completely opt-in and can be used to slowly migrate your application to the new architecture. -/// -/// The purification of your application may trigger a bunch of important refactors, since it's far easier to keep your data decoupled from the GUI state with stateless widgets. For this reason, I recommend starting small in the most nested views of your application and slowly expand the purity upwards. -pub struct Pure<'a, Message, Renderer> { - state: &'a mut State, - element: Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Pure<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - /// Creates a new [`Pure`] widget with the given [`State`] and impure [`Element`]. - pub fn new( - state: &'a mut State, - content: impl Into>, - ) -> Self { - let element = content.into(); - state.diff(&element); - - Self { state, element } - } -} - -/// The internal state of a [`Pure`] widget. -pub struct State { - state_tree: widget::Tree, -} - -impl Default for State { - fn default() -> Self { - Self::new() - } -} - -impl State { - /// Creates a new [`State`] for a [`Pure`] widget. - pub fn new() -> Self { - Self { - state_tree: widget::Tree::empty(), - } - } - - fn diff( - &mut self, - new_element: &Element<'_, Message, Renderer>, - ) where - Renderer: iced_native::Renderer, - { - self.state_tree.diff(new_element); - } -} - -impl<'a, Message, Renderer> iced_native::Widget - for Pure<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - fn width(&self) -> Length { - self.element.as_widget().width() - } - - fn height(&self) -> Length { - self.element.as_widget().height() - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.element.as_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.as_widget_mut().on_event( - &mut self.state.state_tree, - 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, - ) { - self.element.as_widget().draw( - &self.state.state_tree, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.element.as_widget().mouse_interaction( - &self.state.state_tree, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn overlay( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - self.element.as_widget_mut().overlay( - &mut self.state.state_tree, - layout, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for iced_native::Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - fn from(pure: Pure<'a, Message, Renderer>) -> Self { - Self::new(pure) - } -} diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs deleted file mode 100644 index b82d8a67de..0000000000 --- a/pure/src/overlay.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Display interactive elements on top of other widgets. -use crate::widget::Tree; - -use iced_native::Layout; - -pub use iced_native::overlay::*; - -/// 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> -where - Renderer: iced_native::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/pure/src/widget.rs b/pure/src/widget.rs deleted file mode 100644 index cd825ad877..0000000000 --- a/pure/src/widget.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Use the built-in widgets or create your own. -pub mod button; -pub mod checkbox; -pub mod container; -pub mod image; -pub mod pane_grid; -pub mod pick_list; -pub mod progress_bar; -pub mod radio; -pub mod rule; -pub mod scrollable; -pub mod slider; -pub mod svg; -pub mod text; -pub mod text_input; -pub mod toggler; -pub mod tooltip; -pub mod tree; - -mod column; -mod row; -mod space; - -pub use button::Button; -pub use checkbox::Checkbox; -pub use column::Column; -pub use container::Container; -pub use image::Image; -pub use pane_grid::PaneGrid; -pub use pick_list::PickList; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use row::Row; -pub use rule::Rule; -pub use scrollable::Scrollable; -pub use slider::Slider; -pub use space::Space; -pub use svg::Svg; -pub use text::Text; -pub use text_input::TextInput; -pub use toggler::Toggler; -pub use tooltip::{Position, Tooltip}; -pub use tree::Tree; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -/// A component that displays information and allows interaction. -/// -/// If you want to build your own widgets, you will need to implement this -/// trait. -pub trait Widget -where - Renderer: iced_native::Renderer, -{ - /// Returns the width of the [`Widget`]. - fn width(&self) -> Length; - - /// Returns the height of the [`Widget`]. - fn height(&self) -> Length; - - /// Returns the [`layout::Node`] of the [`Widget`]. - /// - /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the - /// user interface. - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node; - - /// Draws the [`Widget`] using the associated `Renderer`. - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ); - - /// 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 { - 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, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored - } - - /// Returns the current [`mouse::Interaction`] of the [`Widget`]. - /// - /// By default, it returns [`mouse::Interaction::Idle`]. - fn mouse_interaction( - &self, - _state: &Tree, - _layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse::Interaction::Idle - } - - /// Returns the overlay of the [`Widget`], if there is any. - fn overlay<'a>( - &'a self, - _state: &'a mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - ) -> Option> { - None - } -} diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs deleted file mode 100644 index eb174e5731..0000000000 --- a/pure/src/widget/button.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -use crate::overlay; -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::button; -use iced_native::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, -}; - -pub use iced_style::button::{Appearance, StyleSheet}; - -use button::State; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # type Button<'a, Message> = -/// # iced_pure::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum 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: -/// -/// ``` -/// # type Button<'a, Message> = -/// # iced_pure::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// fn disabled_button<'a>() -> Button<'a, Message> { -/// Button::new("I'm disabled!") -/// } -/// -/// fn enabled_button<'a>() -> Button<'a, Message> { -/// disabled_button().on_press(Message::ButtonPressed) -/// } -/// ``` -pub struct Button<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - content: Element<'a, Message, Renderer>, - on_press: Option, - width: Length, - height: Length, - padding: Padding, - style: ::Style, -} - -impl<'a, Message, Renderer> Button<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`Button`] with the given content. - pub fn new(content: impl Into>) -> Self { - Button { - content: content.into(), - on_press: None, - width: Length::Shrink, - height: Length::Shrink, - padding: Padding::new(5), - style: ::Style::default(), - } - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// - /// 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 variant of this [`Button`]. - pub fn style( - mut self, - style: ::Style, - ) -> Self { - self.style = style; - self - } -} - -impl<'a, Message, Renderer> Widget - for Button<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn children(&self) -> Vec { - 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 { - button::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; - } - - button::update( - event, - layout, - cursor_position, - shell, - &self.on_press, - || tree.state.downcast_mut::(), - ) - } - - 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 = button::draw( - renderer, - bounds, - cursor_position, - self.on_press.is_some(), - theme, - self.style, - || tree.state.downcast_ref::(), - ); - - 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 { - button::mouse_interaction( - layout, - cursor_position, - self.on_press.is_some(), - ) - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - self.content.as_widget().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: Clone + 'a, - Renderer: iced_native::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(button: Button<'a, Message, Renderer>) -> Self { - Self::new(button) - } -} diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs deleted file mode 100644 index e0f9b76432..0000000000 --- a/pure/src/widget/checkbox.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::checkbox::{Appearance, Checkbox, StyleSheet}; - -impl<'a, Message, Renderer> Widget - for Checkbox<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - 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( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn from(checkbox: Checkbox<'a, Message, Renderer>) -> Self { - Self::new(checkbox) - } -} diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs deleted file mode 100644 index 027eff0a09..0000000000 --- a/pure/src/widget/column.rs +++ /dev/null @@ -1,246 +0,0 @@ -use crate::flex; -use crate::overlay; -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{ - Alignment, Clipboard, Length, Padding, Point, Rectangle, Shell, -}; - -use std::u32; - -/// A container that distributes its contents vertically. -pub struct Column<'a, Message, Renderer> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - align_items: Alignment, - children: Vec>, -} - -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { - /// Creates an empty [`Column`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children( - children: Vec>, - ) -> Self { - Column { - spacing: 0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// 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.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Column`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Column`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Column`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Column`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - pub fn push( - mut self, - child: impl Into>, - ) -> Self { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { - fn default() -> Self { - Self::new() - } -} - -impl<'a, Message, Renderer> Widget - for Column<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn children(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children); - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits - .max_width(self.max_width) - .width(self.width) - .height(self.height); - - flex::resolve( - flex::Axis::Vertical, - renderer, - &limits, - self.padding, - self.spacing as f32, - self.align_items, - &self.children, - ) - } - - 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.children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget().mouse_interaction( - state, - 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, - ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child.as_widget().draw( - state, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - overlay::from_children(&self.children, tree, layout, renderer) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - fn from(column: Column<'a, Message, Renderer>) -> Self { - Self::new(column) - } -} diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs deleted file mode 100644 index 44ffff8c2d..0000000000 --- a/pure/src/widget/container.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Decorate content and apply alignment. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::alignment; -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::container; -use iced_native::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, -}; - -use std::u32; - -pub use iced_style::container::{Appearance, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - style: ::Style, - content: Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Container<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - /// Creates an empty [`Container`]. - pub fn new(content: T) -> Self - where - T: Into>, - { - Container { - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style: Default::default(), - content: content.into(), - } - } - - /// Sets the [`Padding`] of the [`Container`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Container`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Container`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Container`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Container`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the content alignment for the horizontal axis of the [`Container`]. - pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the content alignment for the vertical axis of the [`Container`]. - pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { - self.vertical_alignment = alignment; - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - self - } - - /// Sets the style of the [`Container`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget - for Container<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn children(&self) -> Vec { - 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 { - container::layout( - renderer, - limits, - self.width, - self.height, - self.max_width, - self.max_height, - self.padding, - self.horizontal_alignment, - self.vertical_alignment, - |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 { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout.children().next().unwrap(), - 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, - renderer_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - let style = theme.appearance(self.style); - - container::draw_background(renderer, &style, layout.bounds()); - - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &renderer::Style { - text_color: style - .text_color - .unwrap_or(renderer_style.text_color), - }, - layout.children().next().unwrap(), - cursor_position, - viewport, - ); - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - self.content.as_widget().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - column: Container<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(column) - } -} diff --git a/pure/src/widget/image.rs b/pure/src/widget/image.rs deleted file mode 100644 index 58f81a6f9c..0000000000 --- a/pure/src/widget/image.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Display images in your user interface. -use crate::widget::{Tree, Widget}; -use crate::Element; - -use iced_native::layout::{self, Layout}; -use iced_native::renderer; -use iced_native::widget::image; -use iced_native::{Length, Point, Rectangle}; - -use std::hash::Hash; - -pub use image::Image; - -impl Widget for Image -where - Handle: Clone + Hash, - Renderer: iced_native::image::Renderer, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - >::draw( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } -} - -impl<'a, Message, Renderer, Handle> From> - for Element<'a, Message, Renderer> -where - Message: Clone + 'a, - Renderer: iced_native::image::Renderer + 'a, - Handle: Clone + Hash + 'a, -{ - fn from(image: Image) -> Self { - Self::new(image) - } -} diff --git a/pure/src/widget/pane_grid.rs b/pure/src/widget/pane_grid.rs deleted file mode 100644 index 69150aa87b..0000000000 --- a/pure/src/widget/pane_grid.rs +++ /dev/null @@ -1,414 +0,0 @@ -//! Let your users split regions of your application and organize layout dynamically. -//! -//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) -//! -//! # Example -//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, -//! drag and drop, and hotkey support. -//! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid -mod content; -mod title_bar; - -pub use content::Content; -pub use title_bar::TitleBar; - -pub use iced_native::widget::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, - State, -}; - -use crate::overlay; -use crate::widget::container; -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::pane_grid; -use iced_native::widget::pane_grid::state; -use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell}; - -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. -/// -/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier) -/// -/// This distribution of space is common in tiling window managers (like -/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even -/// [`tmux`](https://github.com/tmux/tmux)). -/// -/// A [`PaneGrid`] supports: -/// -/// * Vertical and horizontal splits -/// * Tracking of the last active pane -/// * Mouse-based resizing -/// * Drag and drop to reorganize panes -/// * Hotkey support -/// * Configurable modifier keys -/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.) -/// -/// ## Example -/// -/// ``` -/// # use iced_pure::widget::pane_grid; -/// # use iced_pure::text; -/// # -/// # type PaneGrid<'a, Message> = -/// # iced_pure::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; -/// # -/// enum PaneState { -/// SomePane, -/// AnotherKindOfPane, -/// } -/// -/// enum Message { -/// PaneDragged(pane_grid::DragEvent), -/// PaneResized(pane_grid::ResizeEvent), -/// } -/// -/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); -/// -/// let pane_grid = -/// PaneGrid::new(&state, |pane, state| { -/// pane_grid::Content::new(match state { -/// PaneState::SomePane => text("This is some pane"), -/// PaneState::AnotherKindOfPane => text("This is another kind of pane"), -/// }) -/// }) -/// .on_drag(Message::PaneDragged) -/// .on_resize(10, Message::PaneResized); -/// ``` -#[allow(missing_debug_implementations)] -pub struct PaneGrid<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - state: &'a state::Internal, - elements: Vec<(Pane, Content<'a, Message, Renderer>)>, - width: Length, - height: Length, - spacing: u16, - on_click: Option Message + 'a>>, - on_drag: Option Message + 'a>>, - on_resize: Option<(u16, Box Message + 'a>)>, - style: ::Style, -} - -impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - /// Creates a [`PaneGrid`] with the given [`State`] and view function. - /// - /// The view function will be called to display each [`Pane`] present in the - /// [`State`]. - pub fn new( - state: &'a State, - view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, - ) -> Self { - let elements = { - state - .panes - .iter() - .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) - .collect() - }; - - Self { - elements, - state: &state.internal, - width: Length::Fill, - height: Length::Fill, - spacing: 0, - on_click: None, - on_drag: None, - on_resize: None, - style: Default::default(), - } - } - - /// Sets the width of the [`PaneGrid`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`PaneGrid`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the spacing _between_ the panes of the [`PaneGrid`]. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the message that will be produced when a [`Pane`] of the - /// [`PaneGrid`] is clicked. - pub fn on_click(mut self, f: F) -> Self - where - F: 'a + Fn(Pane) -> Message, - { - self.on_click = Some(Box::new(f)); - self - } - - /// Enables the drag and drop interactions of the [`PaneGrid`], which will - /// use the provided function to produce messages. - pub fn on_drag(mut self, f: F) -> Self - where - F: 'a + Fn(DragEvent) -> Message, - { - self.on_drag = Some(Box::new(f)); - self - } - - /// Enables the resize interactions of the [`PaneGrid`], which will - /// use the provided function to produce messages. - /// - /// The `leeway` describes the amount of space around a split that can be - /// used to grab it. - /// - /// The grabbable area of a split will have a length of `spacing + leeway`, - /// properly centered. In other words, a length of - /// `(spacing + leeway) / 2.0` on either side of the split line. - pub fn on_resize(mut self, leeway: u16, f: F) -> Self - where - F: 'a + Fn(ResizeEvent) -> Message, - { - self.on_resize = Some((leeway, Box::new(f))); - self - } - - /// Sets the style of the [`PaneGrid`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget - for PaneGrid<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(state::Action::Idle) - } - - fn children(&self) -> Vec { - 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 { - pane_grid::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::(); - - let event_status = pane_grid::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 { - pane_grid::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, - ) { - pane_grid::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> { - 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> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a + iced_native::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn from( - pane_grid: PaneGrid<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(pane_grid) - } -} diff --git a/pure/src/widget/pane_grid/content.rs b/pure/src/widget/pane_grid/content.rs deleted file mode 100644 index 9c2a0f4ac4..0000000000 --- a/pure/src/widget/pane_grid/content.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::widget::pane_grid::TitleBar; -use crate::widget::tree::Tree; -use crate::Element; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::container; -use iced_native::widget::pane_grid::Draggable; -use iced_native::{Clipboard, Layout, Point, Rectangle, Shell, Size}; - -/// The content of a [`Pane`]. -/// -/// [`Pane`]: crate::widget::pane_grid::Pane -#[allow(missing_debug_implementations)] -pub struct Content<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - title_bar: Option>, - body: Element<'a, Message, Renderer>, - style: ::Style, -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - /// Creates a new [`Content`] with the provided body. - pub fn new(body: impl Into>) -> Self { - Self { - title_bar: None, - body: body.into(), - style: Default::default(), - } - } - - /// Sets the [`TitleBar`] of this [`Content`]. - pub fn title_bar( - mut self, - title_bar: TitleBar<'a, Message, Renderer>, - ) -> Self { - self.title_bar = Some(title_bar); - self - } - - /// Sets the style of the [`Content`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: iced_native::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`]: iced_native::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - use container::StyleSheet; - - let bounds = layout.bounds(); - - { - let style = theme.appearance(self.style); - - container::draw_background(renderer, &style, bounds); - } - - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - let body_layout = children.next().unwrap(); - - let show_controls = bounds.contains(cursor_position); - - title_bar.draw( - &tree.children[1], - renderer, - theme, - style, - title_bar_layout, - cursor_position, - viewport, - show_controls, - ); - - self.body.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - body_layout, - cursor_position, - viewport, - ); - } else { - self.body.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - pub(crate) fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - if let Some(title_bar) = &self.title_bar { - let max_size = limits.max(); - - let title_bar_layout = title_bar - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); - - let title_bar_size = title_bar_layout.size(); - - let mut body_layout = self.body.as_widget().layout( - renderer, - &layout::Limits::new( - Size::ZERO, - Size::new( - max_size.width, - max_size.height - title_bar_size.height, - ), - ), - ); - - body_layout.move_to(Point::new(0.0, title_bar_size.height)); - - layout::Node::with_children( - max_size, - vec![title_bar_layout, body_layout], - ) - } else { - self.body.as_widget().layout(renderer, limits) - } - } - - pub(crate) 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>, - is_picked: bool, - ) -> event::Status { - let mut event_status = event::Status::Ignored; - - let body_layout = if let Some(title_bar) = &mut self.title_bar { - let mut children = layout.children(); - - event_status = title_bar.on_event( - &mut tree.children[1], - event.clone(), - children.next().unwrap(), - cursor_position, - renderer, - clipboard, - shell, - ); - - children.next().unwrap() - } else { - layout - }; - - let body_status = if is_picked { - event::Status::Ignored - } else { - self.body.as_widget_mut().on_event( - &mut tree.children[0], - event, - body_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }; - - event_status.merge(body_status) - } - - pub(crate) fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let (body_layout, title_bar_interaction) = - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - - let is_over_pick_area = title_bar - .is_over_pick_area(title_bar_layout, cursor_position); - - if is_over_pick_area { - return mouse::Interaction::Grab; - } - - let mouse_interaction = title_bar.mouse_interaction( - &tree.children[1], - title_bar_layout, - cursor_position, - viewport, - renderer, - ); - - (children.next().unwrap(), mouse_interaction) - } else { - (layout, mouse::Interaction::default()) - }; - - self.body - .as_widget() - .mouse_interaction( - &tree.children[0], - body_layout, - cursor_position, - viewport, - renderer, - ) - .max(title_bar_interaction) - } - - pub(crate) fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - if let Some(title_bar) = self.title_bar.as_ref() { - let mut children = layout.children(); - let title_bar_layout = children.next()?; - - 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.as_widget().overlay( - body_state, - children.next()?, - renderer, - ), - } - } else { - self.body.as_widget().overlay( - &mut tree.children[0], - layout, - renderer, - ) - } - } -} - -impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - fn can_be_dragged_at( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - if let Some(title_bar) = &self.title_bar { - let mut children = layout.children(); - let title_bar_layout = children.next().unwrap(); - - title_bar.is_over_pick_area(title_bar_layout, cursor_position) - } else { - false - } - } -} - -impl<'a, T, Message, Renderer> From for Content<'a, Message, Renderer> -where - T: Into>, - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - fn from(element: T) -> Self { - Self::new(element) - } -} diff --git a/pure/src/widget/pane_grid/title_bar.rs b/pure/src/widget/pane_grid/title_bar.rs deleted file mode 100644 index de9591a202..0000000000 --- a/pure/src/widget/pane_grid/title_bar.rs +++ /dev/null @@ -1,388 +0,0 @@ -use crate::widget::Tree; -use crate::Element; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::container; -use iced_native::{Clipboard, Layout, Padding, Point, Rectangle, Shell, Size}; - -/// The title bar of a [`Pane`]. -/// -/// [`Pane`]: crate::widget::pane_grid::Pane -#[allow(missing_debug_implementations)] -pub struct TitleBar<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - content: Element<'a, Message, Renderer>, - controls: Option>, - padding: Padding, - always_show_controls: bool, - style: ::Style, -} - -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: container::StyleSheet, -{ - /// Creates a new [`TitleBar`] with the given content. - pub fn new(content: E) -> Self - where - E: Into>, - { - Self { - content: content.into(), - controls: None, - padding: Padding::ZERO, - always_show_controls: false, - style: Default::default(), - } - } - - /// Sets the controls of the [`TitleBar`]. - pub fn controls( - mut self, - controls: impl Into>, - ) -> Self { - self.controls = Some(controls.into()); - self - } - - /// Sets the [`Padding`] of the [`TitleBar`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the style of the [`TitleBar`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are - /// always visible. - /// - /// By default, the controls are only visible when the [`Pane`] of this - /// [`TitleBar`] is hovered. - /// - /// [`controls`]: Self::controls - /// [`Pane`]: crate::widget::pane_grid::Pane - pub fn always_show_controls(mut self) -> Self { - self.always_show_controls = true; - self - } -} - -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> -where - Renderer: iced_native::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`]: iced_native::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - show_controls: bool, - ) { - use container::StyleSheet; - - let bounds = layout.bounds(); - let style = theme.appearance(self.style); - let inherited_style = renderer::Style { - text_color: style.text_color.unwrap_or(inherited_style.text_color), - }; - - container::draw_background(renderer, &style, bounds); - - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - let mut show_title = true; - - 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 { - controls.as_widget().draw( - &tree.children[1], - renderer, - theme, - &inherited_style, - controls_layout, - cursor_position, - viewport, - ); - } - } - - if show_title { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - &inherited_style, - title_layout, - cursor_position, - viewport, - ); - } - } - - /// Returns whether the mouse cursor is over the pick area of the - /// [`TitleBar`] or not. - /// - /// The whole [`TitleBar`] is a pick area, except its controls. - pub fn is_over_pick_area( - &self, - layout: Layout<'_>, - cursor_position: Point, - ) -> bool { - if layout.bounds().contains(cursor_position) { - let mut children = layout.children(); - let padded = children.next().unwrap(); - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - - if self.controls.is_some() { - let controls_layout = children.next().unwrap(); - - !controls_layout.bounds().contains(cursor_position) - && !title_layout.bounds().contains(cursor_position) - } else { - !title_layout.bounds().contains(cursor_position) - } - } else { - false - } - } - - pub(crate) fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.pad(self.padding); - let max_size = limits.max(); - - 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(); - let space_before_controls = max_size.width - controls_size.width; - - let height = title_size.height.max(controls_size.height); - - controls_layout.move_to(Point::new(space_before_controls, 0.0)); - - layout::Node::with_children( - Size::new(max_size.width, height), - vec![title_layout, controls_layout], - ) - } else { - layout::Node::with_children( - Size::new(max_size.width, title_size.height), - vec![title_layout], - ) - }; - - node.move_to(Point::new( - self.padding.left.into(), - self.padding.top.into(), - )); - - layout::Node::with_children(node.size().pad(self.padding), vec![node]) - } - - pub(crate) 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 mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - let mut show_title = true; - - let control_status = if let Some(controls) = &mut self.controls { - let controls_layout = children.next().unwrap(); - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - show_title = false; - } - - controls.as_widget_mut().on_event( - &mut tree.children[1], - event.clone(), - controls_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } else { - event::Status::Ignored - }; - - let title_status = if show_title { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - title_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } else { - event::Status::Ignored - }; - - control_status.merge(title_status) - } - - pub(crate) fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let mut children = layout.children(); - let padded = children.next().unwrap(); - - let mut children = padded.children(); - let title_layout = children.next().unwrap(); - - let title_interaction = self.content.as_widget().mouse_interaction( - &tree.children[0], - title_layout, - cursor_position, - viewport, - renderer, - ); - - if let Some(controls) = &self.controls { - let controls_layout = children.next().unwrap(); - let controls_interaction = controls.as_widget().mouse_interaction( - &tree.children[1], - controls_layout, - cursor_position, - viewport, - renderer, - ); - - if title_layout.bounds().width + controls_layout.bounds().width - > padded.bounds().width - { - controls_interaction - } else { - controls_interaction.max(title_interaction) - } - } else { - title_interaction - } - } - - pub(crate) fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - let mut children = layout.children(); - let padded = children.next()?; - - let mut children = padded.children(); - let title_layout = children.next()?; - - let Self { - content, controls, .. - } = self; - - 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/pure/src/widget/pick_list.rs b/pure/src/widget/pick_list.rs deleted file mode 100644 index 9264544ac4..0000000000 --- a/pure/src/widget/pick_list.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget::pick_list; -use iced_native::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, -}; - -use std::borrow::Cow; - -pub use iced_style::pick_list::{Appearance, StyleSheet}; - -/// A widget for selecting a single value from a list of options. -#[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer> -where - [T]: ToOwned>, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - on_selected: Box Message + 'a>, - options: Cow<'a, [T]>, - placeholder: Option, - selected: Option, - width: Length, - padding: Padding, - text_size: Option, - font: Renderer::Font, - style: ::Style, -} - -impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer> -where - T: ToString + Eq, - [T]: ToOwned>, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default padding of a [`PickList`]. - pub const DEFAULT_PADDING: Padding = Padding::new(5); - - /// 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( - options: impl Into>, - selected: Option, - on_selected: impl Fn(T) -> Message + 'a, - ) -> Self { - Self { - on_selected: Box::new(on_selected), - options: options.into(), - placeholder: None, - selected, - width: Length::Shrink, - text_size: None, - padding: Self::DEFAULT_PADDING, - font: Default::default(), - style: Default::default(), - } - } - - /// Sets the placeholder of the [`PickList`]. - pub fn placeholder(mut self, placeholder: impl Into) -> Self { - self.placeholder = Some(placeholder.into()); - self - } - - /// Sets the width of the [`PickList`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the [`Padding`] of the [`PickList`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`PickList`]. - pub fn text_size(mut self, size: u16) -> Self { - self.text_size = Some(size); - self - } - - /// Sets the font of the [`PickList`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; - self - } - - /// Sets the style of the [`PickList`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, T: 'a, Message, Renderer> Widget - for PickList<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(pick_list::State::::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - pick_list::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 { - pick_list::update( - event, - layout, - cursor_position, - shell, - self.on_selected.as_ref(), - self.selected.as_ref(), - &self.options, - || tree.state.downcast_mut::>(), - ) - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - pick_list::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, - ) { - pick_list::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> { - let state = tree.state.downcast_mut::>(); - - pick_list::overlay( - layout, - state, - self.padding, - self.text_size, - self.font.clone(), - &self.options, - self.style, - ) - } -} - -impl<'a, T: 'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { - Self::new(pick_list) - } -} diff --git a/pure/src/widget/progress_bar.rs b/pure/src/widget/progress_bar.rs deleted file mode 100644 index c96448531e..0000000000 --- a/pure/src/widget/progress_bar.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Provide progress feedback to your users. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::progress_bar::*; - -impl Widget for ProgressBar -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - 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( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: iced_native::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(progress_bar: ProgressBar) -> Self { - Self::new(progress_bar) - } -} diff --git a/pure/src/widget/radio.rs b/pure/src/widget/radio.rs deleted file mode 100644 index 604c2785f9..0000000000 --- a/pure/src/widget/radio.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Create choices using radio buttons. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::radio::{Appearance, Radio, StyleSheet}; - -impl Widget for Radio -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - 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( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn from(radio: Radio) -> Self { - Self::new(radio) - } -} diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs deleted file mode 100644 index a288a68df6..0000000000 --- a/pure/src/widget/row.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::flex; -use crate::overlay; -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{ - Alignment, Clipboard, Length, Padding, Point, Rectangle, Shell, -}; - -/// A container that distributes its contents horizontally. -pub struct Row<'a, Message, Renderer> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - align_items: Alignment, - children: Vec>, -} - -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { - /// Creates an empty [`Row`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children( - children: Vec>, - ) -> Self { - Row { - spacing: 0, - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - align_items: Alignment::Start, - children, - } - } - - /// Sets the horizontal 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.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Row`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Row`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Row`]. - pub fn height(mut self, height: Length) -> Self { - self.height = 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; - self - } - - /// Adds an [`Element`] to the [`Row`]. - pub fn push( - mut self, - child: impl Into>, - ) -> Self { - self.children.push(child.into()); - self - } -} - -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { - fn default() -> Self { - Self::new() - } -} - -impl<'a, Message, Renderer> Widget - for Row<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - fn children(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - - flex::resolve( - flex::Axis::Horizontal, - renderer, - &limits, - self.padding, - self.spacing as f32, - self.align_items, - &self.children, - ) - } - - 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.children - .iter_mut() - .zip(&mut tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.children - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|((child, state), layout)| { - child.as_widget().mouse_interaction( - state, - 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, - ) { - for ((child, state), layout) in self - .children - .iter() - .zip(&tree.children) - .zip(layout.children()) - { - child.as_widget().draw( - state, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ); - } - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - overlay::from_children(&self.children, tree, layout, renderer) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: iced_native::Renderer + 'a, -{ - fn from(row: Row<'a, Message, Renderer>) -> Self { - Self::new(row) - } -} diff --git a/pure/src/widget/rule.rs b/pure/src/widget/rule.rs deleted file mode 100644 index 0fb4ebab7f..0000000000 --- a/pure/src/widget/rule.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Display a horizontal or vertical rule for dividing content. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::rule::*; - -impl Widget for Rule -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - 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( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: iced_native::Renderer + 'a, - Renderer::Theme: StyleSheet, -{ - fn from(rule: Rule) -> Self { - Self::new(rule) - } -} diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs deleted file mode 100644 index 4118b67eff..0000000000 --- a/pure/src/widget/scrollable.rs +++ /dev/null @@ -1,278 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::overlay; -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::scrollable; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Vector}; - -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - height: Length, - scrollbar_width: u16, - scrollbar_margin: u16, - scroller_width: u16, - content: Element<'a, Message, Renderer>, - on_scroll: Option Message + 'a>>, - style: ::Style, -} - -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`Scrollable`]. - pub fn new(content: impl Into>) -> Self { - Scrollable { - height: Length::Shrink, - scrollbar_width: 10, - scrollbar_margin: 0, - scroller_width: 10, - content: content.into(), - on_scroll: None, - style: Default::default(), - } - } - - /// Sets the height of the [`Scrollable`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - 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 { - self.scrollbar_width = scrollbar_width.max(1); - self - } - - /// Sets the scrollbar margin of the [`Scrollable`] . - pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self { - self.scrollbar_margin = scrollbar_margin; - self - } - - /// Sets the scroller width of the [`Scrollable`] . - /// - /// It silently enforces a minimum value of 1. - pub fn scroller_width(mut self, scroller_width: u16) -> Self { - self.scroller_width = scroller_width.max(1); - self - } - - /// Sets a function to call when the [`Scrollable`] is scrolled. - /// - /// 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 + 'a) -> Self { - self.on_scroll = Some(Box::new(f)); - self - } - - /// Sets the style of the [`Scrollable`] . - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget - for Scrollable<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(scrollable::State::new()) - } - - fn children(&self) -> Vec { - 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 { - scrollable::layout( - renderer, - limits, - Widget::::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 { - scrollable::update( - tree.state.downcast_mut::(), - 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, - ) { - scrollable::draw( - tree.state.downcast_ref::(), - 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 { - scrollable::mouse_interaction( - tree.state.downcast_ref::(), - 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> { - 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::() - .offset(bounds, content_bounds); - - overlay.translate(Vector::new(0.0, -(offset as f32))) - }) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - text_input: Scrollable<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(text_input) - } -} diff --git a/pure/src/widget/slider.rs b/pure/src/widget/slider.rs deleted file mode 100644 index fed979e5b9..0000000000 --- a/pure/src/widget/slider.rs +++ /dev/null @@ -1,255 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::slider; -use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell, Size}; - -use std::ops::RangeInclusive; - -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_pure::widget::slider; -/// # use iced_native::renderer::Null; -/// # -/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; -/// # -/// #[derive(Clone)] -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let value = 50.0; -/// -/// 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) -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Renderer> -where - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - range: RangeInclusive, - step: T, - value: T, - on_change: Box Message + 'a>, - on_release: Option, - width: Length, - height: u16, - style: ::Style, -} - -impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> -where - T: Copy + From + std::cmp::PartialOrd, - Message: Clone, - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - /// The default height of a [`Slider`]. - pub const DEFAULT_HEIGHT: u16 = 22; - - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * 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(range: RangeInclusive, value: T, on_change: F) -> Self - where - F: 'a + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - Slider { - value, - range, - step: T::from(1), - on_change: Box::new(on_change), - on_release: None, - width: Length::Fill, - height: Self::DEFAULT_HEIGHT, - style: Default::default(), - } - } - - /// Sets the release message of the [`Slider`]. - /// This is called when the mouse is released from the slider. - /// - /// Typically, the user's interaction with the slider is finished when this message is produced. - /// This is useful if you need to spawn a long-running task from the slider's result, where - /// the default on_change message could create too many events. - pub fn on_release(mut self, on_release: Message) -> Self { - self.on_release = Some(on_release); - self - } - - /// Sets the width of the [`Slider`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Slider`]. - pub fn height(mut self, height: u16) -> Self { - self.height = height; - self - } - - /// Sets the style of the [`Slider`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message, Renderer> Widget - for Slider<'a, T, Message, Renderer> -where - T: Copy + Into + num_traits::FromPrimitive, - Message: Clone, - Renderer: iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(slider::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 { - slider::update( - event, - layout, - cursor_position, - shell, - tree.state.downcast_mut::(), - &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, - ) { - slider::draw( - renderer, - layout, - cursor_position, - tree.state.downcast_ref::(), - self.value, - &self.range, - theme, - self.style, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - slider::mouse_interaction( - layout, - cursor_position, - tree.state.downcast_ref::(), - ) - } -} - -impl<'a, T, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - T: 'a + Copy + Into + num_traits::FromPrimitive, - Message: 'a + Clone, - Renderer: 'a + iced_native::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from( - slider: Slider<'a, T, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(slider) - } -} diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs deleted file mode 100644 index 408cb6470f..0000000000 --- a/pure/src/widget/space.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::Space; - -impl Widget for Space -where - Renderer: iced_native::Renderer, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - 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( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> -where - Renderer: iced_native::Renderer + 'a, -{ - fn from(space: Space) -> Self { - Self::new(space) - } -} diff --git a/pure/src/widget/svg.rs b/pure/src/widget/svg.rs deleted file mode 100644 index ae4e8cffce..0000000000 --- a/pure/src/widget/svg.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Display vector graphics in your application. -use crate::widget::{Tree, Widget}; -use crate::Element; - -use iced_native::layout::{self, Layout}; -use iced_native::renderer; -use iced_native::widget::svg; -use iced_native::{Length, Point, Rectangle}; - -pub use iced_native::svg::Handle; -pub use svg::Svg; - -impl Widget for Svg -where - Renderer: iced_native::svg::Renderer, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - >::draw( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } -} - -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> -where - Message: Clone + 'a, - Renderer: iced_native::svg::Renderer + 'a, -{ - fn from(svg: Svg) -> Self { - Self::new(svg) - } -} diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs deleted file mode 100644 index 7c6f6ce977..0000000000 --- a/pure/src/widget/text.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Write some text for your users to read. -use crate::widget::Tree; -use crate::{Element, Widget}; - -use iced_native::layout::{self, Layout}; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget; -use iced_native::{Length, Point, Rectangle}; - -pub use iced_native::widget::text::{Appearance, StyleSheet, Text}; - -impl Widget for Text -where - Renderer: text::Renderer, - Renderer::Theme: widget::text::StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - >::draw( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: text::Renderer + 'a, - Renderer::Theme: widget::text::StyleSheet, -{ - fn from(text: Text) -> Self { - Self::new(text) - } -} - -impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> -where - Renderer: text::Renderer + 'a, - Renderer::Theme: widget::text::StyleSheet, -{ - fn from(contents: &'a str) -> Self { - Text::new(contents).into() - } -} diff --git a/pure/src/widget/text_input.rs b/pure/src/widget/text_input.rs deleted file mode 100644 index 514a6795c7..0000000000 --- a/pure/src/widget/text_input.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! Display fields that can be filled with text. -use crate::widget::tree::{self, Tree}; -use crate::{Element, Widget}; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget::text_input; -use iced_native::{Clipboard, Length, Padding, Point, Rectangle, Shell}; - -pub use iced_style::text_input::{Appearance, StyleSheet}; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # pub type TextInput<'a, Message> = iced_pure::widget::TextInput<'a, Message, iced_native::renderer::Null>; -/// #[derive(Debug, Clone)] -/// enum Message { -/// TextInputChanged(String), -/// } -/// -/// let value = "Some text"; -/// -/// let input = TextInput::new( -/// "This is the placeholder...", -/// value, -/// Message::TextInputChanged, -/// ) -/// .padding(10); -/// ``` -/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true) -#[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - placeholder: String, - value: text_input::Value, - is_secure: bool, - font: Renderer::Font, - width: Length, - padding: Padding, - size: Option, - on_change: Box Message + 'a>, - on_paste: Option Message + 'a>>, - on_submit: Option, - style: ::Style, -} - -impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - /// Creates a new [`TextInput`]. - /// - /// It expects: - /// - a placeholder, - /// - the current value, and - /// - a function that produces a message when the [`TextInput`] changes. - pub fn new(placeholder: &str, value: &str, on_change: F) -> Self - where - F: 'a + Fn(String) -> Message, - { - TextInput { - placeholder: String::from(placeholder), - value: text_input::Value::new(value), - is_secure: false, - font: Default::default(), - width: Length::Fill, - padding: Padding::ZERO, - size: None, - on_change: Box::new(on_change), - on_paste: None, - on_submit: None, - style: Default::default(), - } - } - - /// Converts the [`TextInput`] into a secure password input. - pub fn password(mut self) -> Self { - self.is_secure = true; - self - } - - /// Sets the message that should be produced when some text is pasted into - /// the [`TextInput`]. - pub fn on_paste( - mut self, - on_paste: impl Fn(String) -> Message + 'a, - ) -> Self { - self.on_paste = Some(Box::new(on_paste)); - self - } - - /// Sets the [`Font`] of the [`TextInput`]. - /// - /// [`Font`]: text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; - self - } - /// Sets the width of the [`TextInput`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`TextInput`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } - - /// Sets the style of the [`TextInput`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Draws the [`TextInput`] with the given [`Renderer`], overriding its - /// [`text_input::Value`] if provided. - /// - /// [`Renderer`]: text::Renderer - pub fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: Point, - value: Option<&text_input::Value>, - ) { - text_input::draw( - renderer, - theme, - layout, - cursor_position, - tree.state.downcast_ref::(), - value.unwrap_or(&self.value), - &self.placeholder, - self.size, - &self.font, - self.is_secure, - self.style, - ) - } -} - -impl<'a, Message, Renderer> Widget - for TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(text_input::State::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - text_input::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 { - text_input::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::(), - ) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - text_input::draw( - renderer, - theme, - layout, - cursor_position, - tree.state.downcast_ref::(), - &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 { - text_input::mouse_interaction(layout, cursor_position) - } -} - -impl<'a, Message, Renderer> From> - 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) - } -} diff --git a/pure/src/widget/toggler.rs b/pure/src/widget/toggler.rs deleted file mode 100644 index 8d0044d296..0000000000 --- a/pure/src/widget/toggler.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Show toggle controls using togglers. -use crate::widget::{Tree, Widget}; -use crate::Element; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub use iced_native::widget::toggler::{Appearance, StyleSheet, Toggler}; - -impl<'a, Message, Renderer> Widget - for Toggler<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn width(&self) -> Length { - >::width(self) - } - - fn height(&self) -> Length { - >::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >::layout( - self, renderer, limits, - ) - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - >::draw( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - >::mouse_interaction( - self, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - >::on_event( - self, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ - fn from(toggler: Toggler<'a, Message, Renderer>) -> Self { - Self::new(toggler) - } -} diff --git a/pure/src/widget/tooltip.rs b/pure/src/widget/tooltip.rs deleted file mode 100644 index cbc3472272..0000000000 --- a/pure/src/widget/tooltip.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Display a widget over another. -use crate::widget::Tree; -use crate::{Element, Widget}; -use iced_native::event::{self, Event}; -use iced_native::layout; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::text; -use iced_native::widget::container; -use iced_native::widget::tooltip; -use iced_native::widget::{self, Text}; -use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell}; - -pub use iced_style::container::{Appearance, StyleSheet}; -pub use tooltip::Position; - -/// An element to display a widget over another. -#[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: text::Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - content: Element<'a, Message, Renderer>, - tooltip: Text, - position: Position, - gap: u16, - padding: u16, - style: ::Style, -} - -impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - /// The default padding of a [`Tooltip`] drawn by this renderer. - const DEFAULT_PADDING: u16 = 5; - - /// Creates a new [`Tooltip`]. - /// - /// [`Tooltip`]: struct.Tooltip.html - pub fn new( - content: impl Into>, - tooltip: impl ToString, - position: Position, - ) -> Self { - Tooltip { - content: content.into(), - tooltip: Text::new(tooltip.to_string()), - position, - gap: 0, - padding: Self::DEFAULT_PADDING, - style: Default::default(), - } - } - - /// Sets the size of the text of the [`Tooltip`]. - pub fn size(mut self, size: u16) -> Self { - self.tooltip = self.tooltip.size(size); - self - } - - /// Sets the font of the [`Tooltip`]. - /// - /// [`Font`]: Renderer::Font - pub fn font(mut self, font: impl Into) -> Self { - self.tooltip = self.tooltip.font(font); - self - } - - /// Sets the gap between the content and its [`Tooltip`]. - pub fn gap(mut self, gap: u16) -> Self { - self.gap = gap; - self - } - - /// Sets the padding of the [`Tooltip`]. - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; - self - } - - /// Sets the style of the [`Tooltip`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message, Renderer> Widget - for Tooltip<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ - fn children(&self) -> Vec { - 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; - - 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> { - self.content.as_widget().overlay( - &mut tree.children[0], - layout, - renderer, - ) - } -} - -impl<'a, Message, Renderer> From> - 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/src/application.rs b/src/application.rs index aca97367be..58d4a57770 100644 --- a/src/application.rs +++ b/src/application.rs @@ -60,7 +60,8 @@ pub use iced_native::application::{Appearance, StyleSheet}; /// says "Hello, world!": /// /// ```no_run -/// use iced::{executor, Application, Command, Element, Settings, Text, Theme}; +/// use iced::executor; +/// use iced::{Application, Command, Element, Settings, Theme}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -86,8 +87,8 @@ pub use iced_native::application::{Appearance, StyleSheet}; /// Command::none() /// } /// -/// fn view(&mut self) -> Element { -/// Text::new("Hello, world!").into() +/// fn view(&self) -> Element { +/// "Hello, world!".into() /// } /// } /// ``` @@ -139,9 +140,7 @@ pub trait Application: Sized { /// Returns the widgets to display in the [`Application`]. /// /// These widgets can produce __messages__ based on user interaction. - fn view( - &mut self, - ) -> Element<'_, Self::Message, crate::Renderer>; + fn view(&self) -> Element<'_, Self::Message, crate::Renderer>; /// Returns the current [`Theme`] of the [`Application`]. /// @@ -249,7 +248,7 @@ where self.0.update(message) } - fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer> { + fn view(&self) -> Element<'_, Self::Message, Self::Renderer> { self.0.view() } } diff --git a/src/lib.rs b/src/lib.rs index 4e8d67879e..96ee4eb5a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,15 +51,9 @@ //! We start by modelling the __state__ of our application: //! //! ``` -//! use iced::button; -//! //! struct Counter { //! // The counter value //! value: i32, -//! -//! // The local state of the two buttons -//! increment_button: button::State, -//! decrement_button: button::State, //! } //! ``` //! @@ -78,15 +72,9 @@ //! __view logic__: //! //! ``` -//! # use iced::button; -//! # //! # struct Counter { //! # // The counter value //! # value: i32, -//! # -//! # // The local state of the two buttons -//! # increment_button: button::State, -//! # decrement_button: button::State, //! # } //! # //! # #[derive(Debug, Clone, Copy)] @@ -95,28 +83,22 @@ //! # DecrementPressed, //! # } //! # -//! use iced::{Button, Column, Text}; +//! use iced::widget::{button, column, text, Column}; //! //! impl Counter { //! pub fn view(&mut self) -> Column { //! // We use a column: a simple vertical layout -//! Column::new() -//! .push( -//! // The increment button. We tell it to produce an -//! // `IncrementPressed` message when pressed -//! Button::new(&mut self.increment_button, Text::new("+")) -//! .on_press(Message::IncrementPressed), -//! ) -//! .push( -//! // We show the value of the counter here -//! Text::new(self.value.to_string()).size(50), -//! ) -//! .push( -//! // The decrement button. We tell it to produce a -//! // `DecrementPressed` message when pressed -//! Button::new(&mut self.decrement_button, Text::new("-")) -//! .on_press(Message::DecrementPressed), -//! ) +//! column![ +//! // The increment button. We tell it to produce an +//! // `IncrementPressed` message when pressed +//! button("+").on_press(Message::IncrementPressed), +//! +//! // We show the value of the counter here +//! text(self.value.to_string()).size(50), +//! +//! // The decrement button. We tell it to produce a +//! button("-").on_press(Message::DecrementPressed), +//! ] //! } //! } //! ``` @@ -125,15 +107,9 @@ //! our __state__ accordingly in our __update logic__: //! //! ``` -//! # use iced::button; -//! # //! # struct Counter { //! # // The counter value //! # value: i32, -//! # -//! # // The local state of the two buttons -//! # increment_button: button::State, -//! # decrement_button: button::State, //! # } //! # //! # #[derive(Debug, Clone, Copy)] @@ -203,10 +179,6 @@ pub mod time; pub mod widget; pub mod window; -#[cfg(feature = "pure")] -#[cfg_attr(docsrs, doc(cfg(feature = "pure")))] -pub mod pure; - #[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_winit as runtime; @@ -221,9 +193,6 @@ use iced_glow as renderer; pub use iced_native::theme; -#[doc(no_inline)] -pub use widget::*; - pub use application::Application; pub use element::Element; pub use error::Error; diff --git a/src/pure.rs b/src/pure.rs deleted file mode 100644 index 23f56570a9..0000000000 --- a/src/pure.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Leverage pure, virtual widgets in your application. -//! -//! The widgets found in this module are completely stateless versions of -//! [the original widgets]. -//! -//! Effectively, this means that, as a user of the library, you do not need to -//! keep track of the local state of each widget (e.g. [`button::State`]). -//! Instead, the runtime will keep track of everything for you! -//! -//! You can embed pure widgets anywhere in your [impure `Application`] using the -//! [`Pure`] widget and some [`State`]. -//! -//! In case you want to only use pure widgets in your application, this module -//! offers an alternate [`Application`] trait with a completely pure `view` -//! method. -//! -//! # The Elm Architecture, purity, and continuity -//! As you may know, applications made with `iced` use [The Elm Architecture]. -//! -//! In a nutshell, this architecture defines the initial state of the application, a way to `view` it, and a way to `update` it after a user interaction. The `update` logic is called after a meaningful user interaction, which in turn updates the state of the application. Then, the `view` logic is executed to redisplay the application. -//! -//! Since `view` logic is only run after an `update`, all of the mutations to the application state must only happen in the `update` logic. If the application state changes anywhere else, the `view` logic will not be rerun and, therefore, the previously generated `view` may stay outdated. -//! -//! However, the `Application` trait in `iced` defines `view` as: -//! -//! ```ignore -//! pub trait Application { -//! fn view(&mut self) -> Element; -//! } -//! ``` -//! -//! As a consequence, the application state can be mutated in `view` logic. The `view` logic in `iced` is __impure__. -//! -//! This impurity is necessary because `iced` puts the burden of widget __continuity__ on its users. In other words, it's up to you to provide `iced` with the internal state of each widget every time `view` is called. -//! -//! If we take a look at the classic `counter` example: -//! -//! ```ignore -//! struct Counter { -//! value: i32, -//! increment_button: button::State, -//! decrement_button: button::State, -//! } -//! -//! // ... -//! -//! impl Counter { -//! pub fn view(&mut self) -> Column { -//! Column::new() -//! .push( -//! Button::new(&mut self.increment_button, Text::new("+")) -//! .on_press(Message::IncrementPressed), -//! ) -//! .push(Text::new(self.value.to_string()).size(50)) -//! .push( -//! Button::new(&mut self.decrement_button, Text::new("-")) -//! .on_press(Message::DecrementPressed), -//! ) -//! } -//! } -//! ``` -//! -//! We can see how we need to keep track of the `button::State` of each `Button` in our `Counter` state and provide a mutable reference to the widgets in our `view` logic. The widgets produced by `view` are __stateful__. -//! -//! While this approach forces users to keep track of widget state and causes impurity, I originally chose it because it allows `iced` to directly consume the widget tree produced by `view`. Since there is no internal state decoupled from `view` maintained by the runtime, `iced` does not need to compare (e.g. reconciliate) widget trees in order to ensure continuity. -//! -//! # Stateless widgets -//! As the library matures, the need for some kind of persistent widget data (see #553) between `view` calls becomes more apparent (e.g. incremental rendering, animations, accessibility, etc.). -//! -//! If we are going to end up having persistent widget data anyways... There is no reason to have impure, stateful widgets anymore! -//! -//! With the help of this module, we can now write a pure `counter` example: -//! -//! ```ignore -//! struct Counter { -//! value: i32, -//! } -//! -//! // ... -//! -//! impl Counter { -//! fn view(&self) -> Column { -//! Column::new() -//! .push(Button::new("Increment").on_press(Message::IncrementPressed)) -//! .push(Text::new(self.value.to_string()).size(50)) -//! .push(Button::new("Decrement").on_press(Message::DecrementPressed)) -//! } -//! } -//! ``` -//! -//! Notice how we no longer need to keep track of the `button::State`! The widgets in `iced_pure` do not take any mutable application state in `view`. They are __stateless__ widgets. As a consequence, we do not need mutable access to `self` in `view` anymore. `view` becomes __pure__. -//! -//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ -//! -//! [the original widgets]: crate::widget -//! [`button::State`]: crate::widget::button::State -//! [impure `Application`]: crate::Application -pub mod application; -pub mod widget; - -mod sandbox; - -pub use application::Application; -pub use sandbox::Sandbox; - -pub use iced_pure::helpers::*; -pub use iced_pure::Widget; -pub use iced_pure::{Pure, State}; - -/// A generic, pure [`Widget`]. -pub type Element<'a, Message, Renderer = crate::Renderer> = - iced_pure::Element<'a, Message, Renderer>; diff --git a/src/pure/application.rs b/src/pure/application.rs deleted file mode 100644 index 396854ad23..0000000000 --- a/src/pure/application.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Build interactive cross-platform applications. -use crate::pure::{self, Pure}; -use crate::window; -use crate::{Command, Executor, Settings, Subscription}; - -pub use iced_native::application::StyleSheet; - -/// A pure version of [`Application`]. -/// -/// Unlike the impure version, the `view` method of this trait takes an -/// immutable reference to `self` and returns a pure [`Element`]. -/// -/// [`Application`]: crate::Application -/// [`Element`]: pure::Element -pub trait Application: Sized { - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [default executor] can be a good starting point! - /// - /// [`Executor`]: Self::Executor - /// [default executor]: crate::executor::Default - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; - - /// The theme of your [`Application`]. - type Theme: Default + StyleSheet; - - /// The data needed to initialize your [`Application`]. - type Flags; - - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`] if you need to perform some - /// async action in the background on startup. This is useful if you want to - /// load state from a file, perform an initial HTTP request, etc. - /// - /// [`run`]: Self::run - fn new(flags: Self::Flags) -> (Self, Command); - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command; - - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view( - &self, - ) -> pure::Element<'_, Self::Message, crate::Renderer>; - - /// Returns the current [`Theme`] of the [`Application`]. - fn theme(&self) -> Self::Theme { - Self::Theme::default() - } - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - fn subscription(&self) -> Subscription { - Subscription::none() - } - - /// Returns the current [`Application`] mode. - /// - /// The runtime will automatically transition your application if a new mode - /// is returned. - /// - /// Currently, the mode only has an effect in native platforms. - /// - /// By default, an application will run in windowed mode. - fn mode(&self) -> window::Mode { - window::Mode::Windowed - } - - /// Returns the scale factor of the [`Application`]. - /// - /// It can be used to dynamically control the size of the UI at runtime - /// (i.e. zooming). - /// - /// For instance, a scale factor of `2.0` will make widgets twice as big, - /// while a scale factor of `0.5` will shrink them to half their size. - /// - /// By default, it returns `1.0`. - fn scale_factor(&self) -> f64 { - 1.0 - } - - /// Returns whether the [`Application`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - - /// Runs the [`Application`]. - /// - /// On native platforms, this method will take control of the current thread - /// until the [`Application`] exits. - /// - /// On the web platform, this method __will NOT return__ unless there is an - /// [`Error`] during startup. - /// - /// [`Error`]: crate::Error - fn run(settings: Settings) -> crate::Result - where - Self: 'static, - { - as crate::Application>::run(settings) - } -} - -struct Instance { - application: A, - state: pure::State, -} - -impl crate::Application for Instance -where - A: Application, - A::Message: 'static, -{ - type Executor = A::Executor; - type Message = A::Message; - type Flags = A::Flags; - type Theme = A::Theme; - - fn new(flags: Self::Flags) -> (Self, Command) { - let (application, command) = A::new(flags); - - ( - Instance { - application, - state: pure::State::new(), - }, - command, - ) - } - - fn title(&self) -> String { - A::title(&self.application) - } - - fn update(&mut self, message: Self::Message) -> Command { - A::update(&mut self.application, message) - } - - fn subscription(&self) -> Subscription { - A::subscription(&self.application) - } - - fn view( - &mut self, - ) -> crate::Element<'_, Self::Message, crate::Renderer> { - let content = A::view(&self.application); - - Pure::new(&mut self.state, content).into() - } - - fn theme(&self) -> Self::Theme { - A::theme(&self.application) - } - - fn mode(&self) -> window::Mode { - A::mode(&self.application) - } - - fn scale_factor(&self) -> f64 { - A::scale_factor(&self.application) - } - - fn should_exit(&self) -> bool { - A::should_exit(&self.application) - } -} diff --git a/src/pure/sandbox.rs b/src/pure/sandbox.rs deleted file mode 100644 index a58cace768..0000000000 --- a/src/pure/sandbox.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::pure; -use crate::{Command, Error, Settings, Subscription, Theme}; - -/// A pure version of [`Sandbox`]. -/// -/// Unlike the impure version, the `view` method of this trait takes an -/// immutable reference to `self` and returns a pure [`Element`]. -/// -/// [`Sandbox`]: crate::Sandbox -/// [`Element`]: pure::Element -pub trait Sandbox { - /// The type of __messages__ your [`Sandbox`] will produce. - type Message: std::fmt::Debug + Send; - - /// Initializes the [`Sandbox`]. - /// - /// Here is where you should return the initial state of your app. - fn new() -> Self; - - /// Returns the current title of the [`Sandbox`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Sandbox`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by user interactions, will be handled by this method. - fn update(&mut self, message: Self::Message); - - /// Returns the widgets to display in the [`Sandbox`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> pure::Element<'_, Self::Message>; - - /// Returns the current [`Theme`] of the [`Sandbox`]. - /// - /// If you want to use your own custom theme type, you will have to use an - /// [`Application`]. - /// - /// By default, it returns [`Theme::default`]. - fn theme(&self) -> Theme { - Theme::default() - } - - /// Returns the scale factor of the [`Sandbox`]. - /// - /// It can be used to dynamically control the size of the UI at runtime - /// (i.e. zooming). - /// - /// For instance, a scale factor of `2.0` will make widgets twice as big, - /// while a scale factor of `0.5` will shrink them to half their size. - /// - /// By default, it returns `1.0`. - fn scale_factor(&self) -> f64 { - 1.0 - } - - /// Returns whether the [`Sandbox`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - - /// Runs the [`Sandbox`]. - /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. - /// - /// It should probably be that last thing you call in your `main` function. - fn run(settings: Settings<()>) -> Result<(), Error> - where - Self: 'static + Sized, - { - ::run(settings) - } -} - -impl pure::Application for T -where - T: Sandbox, -{ - type Executor = iced_futures::backend::null::Executor; - type Flags = (); - type Message = T::Message; - type Theme = Theme; - - fn new(_flags: ()) -> (Self, Command) { - (T::new(), Command::none()) - } - - fn title(&self) -> String { - T::title(self) - } - - fn update(&mut self, message: T::Message) -> Command { - T::update(self, message); - - Command::none() - } - - fn view(&self) -> pure::Element<'_, T::Message> { - T::view(self) - } - - fn theme(&self) -> Self::Theme { - T::theme(self) - } - - fn subscription(&self) -> Subscription { - Subscription::none() - } - - fn scale_factor(&self) -> f64 { - T::scale_factor(self) - } - - fn should_exit(&self) -> bool { - T::should_exit(self) - } -} diff --git a/src/pure/widget.rs b/src/pure/widget.rs deleted file mode 100644 index 336f498f58..0000000000 --- a/src/pure/widget.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! Pure versions of the widgets. - -/// A container that distributes its contents vertically. -pub type Column<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Column<'a, Message, Renderer>; - -/// A container that distributes its contents horizontally. -pub type Row<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Row<'a, Message, Renderer>; - -/// A paragraph of text. -pub type Text = iced_pure::widget::Text; - -pub mod button { - //! Allow your users to perform actions by pressing a button. - pub use iced_pure::widget::button::{Appearance, StyleSheet}; - - /// A widget that produces a message when clicked. - pub type Button<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Button<'a, Message, Renderer>; -} - -pub mod checkbox { - //! Show toggle controls using checkboxes. - pub use iced_pure::widget::checkbox::{Appearance, StyleSheet}; - - /// A box that can be checked. - pub type Checkbox<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Checkbox<'a, Message, Renderer>; -} - -pub mod container { - //! Decorate content and apply alignment. - pub use iced_pure::widget::container::{Appearance, StyleSheet}; - - /// An element decorating some content. - pub type Container<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Container<'a, Message, Renderer>; -} - -pub mod pane_grid { - //! Let your users split regions of your application and organize layout dynamically. - //! - //! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - //! - //! # Example - //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, - //! drag and drop, and hotkey support. - //! - //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid - pub use iced_pure::widget::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Line, Node, Pane, - ResizeEvent, Split, State, StyleSheet, - }; - - /// A collection of panes distributed using either vertical or horizontal splits - /// to completely fill the space available. - /// - /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::PaneGrid<'a, Message, Renderer>; - - /// The content of a [`Pane`]. - pub type Content<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::pane_grid::Content<'a, Message, Renderer>; - - /// The title bar of a [`Pane`]. - pub type TitleBar<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::pane_grid::TitleBar<'a, Message, Renderer>; -} - -pub mod pick_list { - //! Display a dropdown list of selectable values. - pub use iced_pure::widget::pick_list::{Appearance, StyleSheet}; - - /// A widget allowing the selection of a single value from a list of options. - pub type PickList<'a, T, Message, Renderer = crate::Renderer> = - iced_pure::widget::PickList<'a, T, Message, Renderer>; -} - -pub mod radio { - //! Create choices using radio buttons. - pub use iced_pure::widget::radio::{Appearance, StyleSheet}; - - /// A circular button representing a choice. - pub type Radio = - iced_pure::widget::Radio; -} - -pub mod scrollable { - //! Navigate an endless amount of content with a scrollbar. - pub use iced_pure::widget::scrollable::{Scrollbar, Scroller, StyleSheet}; - - /// A widget that can vertically display an infinite amount of content - /// with a scrollbar. - pub type Scrollable<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Scrollable<'a, Message, Renderer>; -} - -pub mod toggler { - //! Show toggle controls using togglers. - pub use iced_pure::widget::toggler::{Appearance, StyleSheet}; - - /// A toggler widget. - pub type Toggler<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Toggler<'a, Message, Renderer>; -} - -pub mod text_input { - //! Display fields that can be filled with text. - pub use iced_pure::widget::text_input::{Appearance, StyleSheet}; - - /// A field that can be filled with text. - pub type TextInput<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::TextInput<'a, Message, Renderer>; -} - -pub mod tooltip { - //! Display a widget over another. - pub use iced_pure::widget::tooltip::Position; - - /// A widget allowing the selection of a single value from a list of options. - pub type Tooltip<'a, Message, Renderer = crate::Renderer> = - iced_pure::widget::Tooltip<'a, Message, Renderer>; -} - -pub use iced_pure::widget::progress_bar; -pub use iced_pure::widget::rule; -pub use iced_pure::widget::slider; -pub use iced_pure::widget::Space; - -pub use button::Button; -pub use checkbox::Checkbox; -pub use container::Container; -pub use pane_grid::PaneGrid; -pub use pick_list::PickList; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use rule::Rule; -pub use scrollable::Scrollable; -pub use slider::Slider; -pub use text_input::TextInput; -pub use toggler::Toggler; -pub use tooltip::Tooltip; - -#[cfg(feature = "canvas")] -pub use iced_graphics::widget::pure::canvas; - -#[cfg(feature = "qr_code")] -pub use iced_graphics::widget::pure::qr_code; - -#[cfg(feature = "image")] -pub mod image { - //! Display images in your user interface. - pub use iced_native::image::Handle; - - /// A frame that displays an image. - pub type Image = iced_pure::widget::Image; -} - -#[cfg(feature = "svg")] -pub use iced_pure::widget::svg; - -#[cfg(feature = "canvas")] -pub use canvas::Canvas; - -#[cfg(feature = "qr_code")] -pub use qr_code::QRCode; - -#[cfg(feature = "image")] -pub use image::Image; - -#[cfg(feature = "svg")] -pub use svg::Svg; diff --git a/src/sandbox.rs b/src/sandbox.rs index 3ca3fe8f83..bdb6ad5a4a 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -56,7 +56,7 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// says "Hello, world!": /// /// ```no_run -/// use iced::{Element, Sandbox, Settings, Text}; +/// use iced::{Element, Sandbox, Settings}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -79,8 +79,8 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// // This application has no interactions /// } /// -/// fn view(&mut self) -> Element { -/// Text::new("Hello, world!").into() +/// fn view(&self) -> Element { +/// "Hello, world!".into() /// } /// } /// ``` @@ -108,7 +108,7 @@ pub trait Sandbox { /// Returns the widgets to display in the [`Sandbox`]. /// /// These widgets can produce __messages__ based on user interaction. - fn view(&mut self) -> Element<'_, Self::Message>; + fn view(&self) -> Element<'_, Self::Message>; /// Returns the current [`Theme`] of the [`Sandbox`]. /// @@ -184,7 +184,7 @@ where Command::none() } - fn view(&mut self) -> Element<'_, T::Message> { + fn view(&self) -> Element<'_, T::Message> { T::view(self) } diff --git a/src/widget.rs b/src/widget.rs index b8b5c4937b..4ddf056666 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,18 +1,7 @@ //! Display information and interactive controls in your application. -//! -//! # Re-exports -//! For convenience, the contents of this module are available at the root -//! module. Therefore, you can directly type: -//! -//! ``` -//! use iced::{button, Button}; -//! ``` -//! -//! # Stateful widgets -//! Some widgets need to keep track of __local state__. -//! -//! These widgets have their own module with a `State` type. For instance, a -//! [`TextInput`] has some [`text_input::State`]. +pub use iced_native::widget::helpers::*; + +pub use iced_native::{column, row}; /// A container that distributes its contents vertically. pub type Column<'a, Message, Renderer = crate::Renderer> = @@ -22,14 +11,18 @@ pub type Column<'a, Message, Renderer = crate::Renderer> = pub type Row<'a, Message, Renderer = crate::Renderer> = iced_native::widget::Row<'a, Message, Renderer>; -/// A paragraph of text. -pub type Text = iced_native::widget::Text; +pub mod text { + //! Write some text for your users to read. + pub use iced_native::widget::text::{Appearance, StyleSheet}; + + /// A paragraph of text. + pub type Text = + iced_native::widget::Text; +} pub mod button { //! Allow your users to perform actions by pressing a button. - //! - //! A [`Button`] has some local [`State`]. - pub use iced_native::widget::button::{Appearance, State, StyleSheet}; + pub use iced_native::widget::button::{Appearance, StyleSheet}; /// A widget that produces a message when clicked. pub type Button<'a, Message, Renderer = crate::Renderer> = @@ -87,7 +80,7 @@ pub mod pane_grid { pub mod pick_list { //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{Appearance, State, StyleSheet}; + pub use iced_native::widget::pick_list::{Appearance, StyleSheet}; /// A widget allowing the selection of a single value from a list of options. pub type PickList<'a, T, Message, Renderer = crate::Renderer> = @@ -106,7 +99,7 @@ pub mod radio { pub mod scrollable { //! Navigate an endless amount of content with a scrollbar. pub use iced_native::widget::scrollable::{ - style::Scrollbar, style::Scroller, State, StyleSheet, + style::Scrollbar, style::Scroller, StyleSheet, }; /// A widget that can vertically display an infinite amount of content @@ -126,9 +119,7 @@ pub mod toggler { pub mod text_input { //! Display fields that can be filled with text. - //! - //! A [`TextInput`] has some local [`State`]. - pub use iced_native::widget::text_input::{Appearance, State, StyleSheet}; + pub use iced_native::widget::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. pub type TextInput<'a, Message, Renderer = crate::Renderer> = @@ -159,6 +150,7 @@ pub use radio::Radio; pub use rule::Rule; pub use scrollable::Scrollable; pub use slider::Slider; +pub use text::Text; pub use text_input::TextInput; pub use toggler::Toggler; pub use tooltip::Tooltip; @@ -167,6 +159,16 @@ pub use tooltip::Tooltip; #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] pub use iced_graphics::widget::canvas; +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +/// Creates a new [`Canvas`]. +pub fn canvas(program: P) -> Canvas +where + P: canvas::Program, +{ + Canvas::new(program) +} + #[cfg(feature = "image")] #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { From a1c5f8839dd972a016e4fd6af3a898c1d8f2684f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 06:56:09 +0200 Subject: [PATCH 02/19] Use `ToString` for `Text::new` instead of `Into` --- examples/counter/src/main.rs | 2 +- examples/websocket/src/echo.rs | 15 ++++++++------- native/src/widget/helpers.rs | 2 +- native/src/widget/text.rs | 4 ++-- src/lib.rs | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index f5ba7fae2a..13dcbf8615 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -40,7 +40,7 @@ impl Sandbox for Counter { fn view(&self) -> Element { column![ button("Increment").on_press(Message::IncrementPressed), - text(self.value.to_string()).size(50), + text(self.value).size(50), button("Decrement").on_press(Message::DecrementPressed) ] .padding(20) diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index 8832188053..ae65e064a7 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -8,6 +8,7 @@ use futures::sink::SinkExt; use futures::stream::StreamExt; use async_tungstenite::tungstenite; +use std::fmt; pub fn connect() -> Subscription { struct Connect; @@ -63,7 +64,7 @@ pub fn connect() -> Subscription { } message = input.select_next_some() => { - let result = websocket.send(tungstenite::Message::Text(String::from(message))).await; + let result = websocket.send(tungstenite::Message::Text(message.to_string())).await; if result.is_ok() { (None, State::Connected(websocket, input)) @@ -133,14 +134,14 @@ impl Message { } } -impl From for String { - fn from(message: Message) -> Self { - match message { - Message::Connected => String::from("Connected successfully!"), +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Message::Connected => write!(f, "Connected successfully!"), Message::Disconnected => { - String::from("Connection lost... Retrying...") + write!(f, "Connection lost... Retrying...") } - Message::User(message) => message, + Message::User(message) => write!(f, "{}", message), } } } diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 648aab5f8f..a0d3994325 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -107,7 +107,7 @@ where /// Creates a new [`Text`] widget with the provided content. /// /// [`Text`]: widget::Text -pub fn text(text: impl Into) -> widget::Text +pub fn text(text: impl ToString) -> widget::Text where Renderer: crate::text::Renderer, Renderer::Theme: widget::text::StyleSheet, diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 2638650527..b30f4518e7 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -45,9 +45,9 @@ where Renderer::Theme: StyleSheet, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new>(label: T) -> Self { + pub fn new(label: T) -> Self { Text { - content: label.into(), + content: label.to_string(), size: None, font: Default::default(), width: Length::Shrink, diff --git a/src/lib.rs b/src/lib.rs index 96ee4eb5a2..38ba48be71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ //! button("+").on_press(Message::IncrementPressed), //! //! // We show the value of the counter here -//! text(self.value.to_string()).size(50), +//! text(self.value).size(50), //! //! // The decrement button. We tell it to produce a //! button("-").on_press(Message::DecrementPressed), From 2dbcdba209b4c815fb5aa68584bfdf052c983e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 06:56:26 +0200 Subject: [PATCH 03/19] Update `counter` example in `README` :tada: --- README.md | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index abb5b0375f..5e52234e6c 100644 --- a/README.md +++ b/README.md @@ -98,15 +98,9 @@ that can be incremented and decremented using two buttons. We start by modelling the __state__ of our application: ```rust -use iced::button; - struct Counter { // The counter value value: i32, - - // The local state of the two buttons - increment_button: button::State, - decrement_button: button::State, } ``` @@ -125,28 +119,23 @@ Now, let's show the actual counter by putting it all together in our __view logic__: ```rust -use iced::{Button, Column, Text}; +use iced::widget::{button, column, text, Column}; impl Counter { - pub fn view(&mut self) -> Column { + pub fn view(&self) -> Column { // We use a column: a simple vertical layout - Column::new() - .push( - // The increment button. We tell it to produce an - // `IncrementPressed` message when pressed - Button::new(&mut self.increment_button, Text::new("+")) - .on_press(Message::IncrementPressed), - ) - .push( - // We show the value of the counter here - Text::new(self.value.to_string()).size(50), - ) - .push( - // The decrement button. We tell it to produce a - // `DecrementPressed` message when pressed - Button::new(&mut self.decrement_button, Text::new("-")) - .on_press(Message::DecrementPressed), - ) + column![ + // The increment button. We tell it to produce an + // `IncrementPressed` message when pressed + button("+").on_press(Message::IncrementPressed), + + // We show the value of the counter here + text(self.value).size(50), + + // The decrement button. We tell it to produce a + // `DecrementPressed` message when pressed + button("-").on_press(Message::DecrementPressed), + ] } } ``` From effa6881f725fdfd2087205737484726f3580a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 06:57:49 +0200 Subject: [PATCH 04/19] Enable `arc` example --- Cargo.toml | 1 + examples/arc/Cargo.toml | 2 +- examples/arc/src/main.rs | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2482185eef..bff0eb7bf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "style", "wgpu", "winit", + "examples/arc", "examples/bezier_tool", "examples/clock", "examples/color_palette", diff --git a/examples/arc/Cargo.toml b/examples/arc/Cargo.toml index 299f5a3e7b..e6e7436321 100644 --- a/examples/arc/Cargo.toml +++ b/examples/arc/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index df0e1e8a2c..0c619dc9ba 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -1,11 +1,13 @@ use std::{f32::consts::PI, time::Instant}; use iced::executor; -use iced::pure::widget::canvas::{ +use iced::widget::canvas::{ self, Cache, Canvas, Cursor, Geometry, Path, Stroke, }; -use iced::pure::{Application, Element}; -use iced::{Command, Length, Point, Rectangle, Settings, Subscription, Theme}; +use iced::{ + Application, Command, Element, Length, Point, Rectangle, Settings, + Subscription, Theme, +}; pub fn main() -> iced::Result { Arc::run(Settings { From c512d50e1987c0e098b64e0de9d1c6e7aa2eb0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 06:59:54 +0200 Subject: [PATCH 05/19] Fix `clippy` lints --- examples/pokedex/src/main.rs | 2 +- examples/scrollable/src/main.rs | 2 +- native/src/overlay/menu.rs | 6 +++--- native/src/widget/helpers.rs | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 0744d9915e..4fe2d07cc2 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -217,6 +217,6 @@ impl From for Error { } } -fn button<'a>(text: &'a str) -> widget::Button<'a, Message> { +fn button(text: &str) -> widget::Button<'_, Message> { widget::button(text).padding(10) } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 405c8daf9c..c9c1be7cd3 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -85,7 +85,7 @@ impl Sandbox for ScrollableDemo { .enumerate() .map(|(i, variant)| { let mut contents = column![ - variant.title.as_ref(), + variant.title, button("Scroll to bottom",) .width(Length::Fill) .padding(10) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index b4c77c25f0..fecf9f3c98 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -252,7 +252,7 @@ where shell: &mut Shell<'_, Message>, ) -> event::Status { self.container.on_event( - &mut self.state, + self.state, event, layout, cursor_position, @@ -270,7 +270,7 @@ where renderer: &Renderer, ) -> mouse::Interaction { self.container.mouse_interaction( - &self.state, + self.state, layout, cursor_position, viewport, @@ -303,7 +303,7 @@ where ); self.container.draw( - &self.state, + self.state, renderer, theme, style, diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index a0d3994325..518ac23bbc 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -47,18 +47,18 @@ where /// Creates a new [`Column`] with the given children. /// /// [`Column`]: widget::Column -pub fn column<'a, Message, Renderer>( - children: Vec>, -) -> widget::Row<'a, Message, Renderer> { +pub fn column( + children: Vec>, +) -> widget::Row<'_, 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>, -) -> widget::Row<'a, Message, Renderer> { +pub fn row( + children: Vec>, +) -> widget::Row<'_, Message, Renderer> { widget::Row::with_children(children) } From 375386faa9b9c85ebe8ec8dc6042ea5a6c555d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 Jul 2022 07:21:15 +0200 Subject: [PATCH 06/19] Remove `pure` leftovers in `iced_graphics` --- graphics/src/widget.rs | 3 - graphics/src/widget/pure.rs | 12 -- graphics/src/widget/pure/canvas.rs | 246 ---------------------------- graphics/src/widget/pure/qr_code.rs | 64 -------- 4 files changed, 325 deletions(-) delete mode 100644 graphics/src/widget/pure.rs delete mode 100644 graphics/src/widget/pure/canvas.rs delete mode 100644 graphics/src/widget/pure/qr_code.rs diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index cf500a69b9..e7fab97c17 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -14,6 +14,3 @@ pub mod qr_code; #[cfg(feature = "qr_code")] #[doc(no_inline)] pub use qr_code::QRCode; - -#[cfg(feature = "pure")] -pub mod pure; diff --git a/graphics/src/widget/pure.rs b/graphics/src/widget/pure.rs deleted file mode 100644 index ee530379db..0000000000 --- a/graphics/src/widget/pure.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Leverage pure, virtual widgets in your application. -#[cfg(feature = "canvas")] -pub mod canvas; - -#[cfg(feature = "canvas")] -pub use canvas::Canvas; - -#[cfg(feature = "qr_code")] -pub mod qr_code; - -#[cfg(feature = "qr_code")] -pub use qr_code::QRCode; diff --git a/graphics/src/widget/pure/canvas.rs b/graphics/src/widget/pure/canvas.rs deleted file mode 100644 index 19bcb0de2f..0000000000 --- a/graphics/src/widget/pure/canvas.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Draw 2D graphics for your users. -//! -//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a -//! [`Frame`]. It can be used for animation, data visualization, game graphics, -//! and more! -mod program; - -pub use crate::widget::canvas::{Canvas as _, Program as _, *}; - -pub use program::Program; - -use crate::{Backend, Primitive, Renderer}; - -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Size, Vector}; -use iced_pure::widget::tree::{self, Tree}; -use iced_pure::{Element, Widget}; - -use std::marker::PhantomData; - -/// A widget capable of drawing 2D graphics. -/// -/// ## Drawing a simple circle -/// If you want to get a quick overview, here's how we can draw a simple circle: -/// -/// ```no_run -/// # mod iced { -/// # pub mod pure { -/// # pub mod widget { -/// # pub use iced_graphics::pure::canvas; -/// # } -/// # } -/// # pub use iced_native::{Color, Rectangle, Theme}; -/// # } -/// use iced::pure::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle, Theme}; -/// -/// // First, we define the data we need for drawing -/// #[derive(Debug)] -/// struct Circle { -/// radius: f32, -/// } -/// -/// // Then, we implement the `Program` trait -/// impl Program<()> for Circle { -/// type State = (); -/// -/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ -/// // We prepare a new `Frame` -/// let mut frame = Frame::new(bounds.size()); -/// -/// // We create a `Path` representing a simple circle -/// let circle = Path::circle(frame.center(), self.radius); -/// -/// // And fill it with some color -/// frame.fill(&circle, Color::BLACK); -/// -/// // Finally, we produce the geometry -/// vec![frame.into_geometry()] -/// } -/// } -/// -/// // Finally, we simply use our `Circle` to create the `Canvas`! -/// let canvas = Canvas::new(Circle { radius: 50.0 }); -/// ``` -#[derive(Debug)] -pub struct Canvas -where - P: Program, -{ - width: Length, - height: Length, - program: P, - message_: PhantomData, - theme_: PhantomData, -} - -impl Canvas -where - P: Program, -{ - const DEFAULT_SIZE: u16 = 100; - - /// Creates a new [`Canvas`]. - pub fn new(program: P) -> Self { - Canvas { - width: Length::Units(Self::DEFAULT_SIZE), - height: Length::Units(Self::DEFAULT_SIZE), - program, - message_: PhantomData, - theme_: PhantomData, - } - } - - /// Sets the width of the [`Canvas`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Canvas`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } -} - -impl Widget> for Canvas -where - P: Program, - B: Backend, -{ - fn tag(&self) -> tree::Tag { - struct Tag(T); - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(P::State::default()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let bounds = layout.bounds(); - - let canvas_event = match event { - iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(mouse_event)) - } - iced_native::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - _ => None, - }; - - let cursor = Cursor::from_window_position(cursor_position); - - if let Some(canvas_event) = canvas_event { - let state = tree.state.downcast_mut::(); - - let (event_status, message) = - self.program.update(state, canvas_event, bounds, cursor); - - if let Some(message) = message { - shell.publish(message); - } - - return event_status; - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let bounds = layout.bounds(); - let cursor = Cursor::from_window_position(cursor_position); - let state = tree.state.downcast_ref::(); - - self.program.mouse_interaction(state, bounds, cursor) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &T, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - use iced_native::Renderer as _; - - let bounds = layout.bounds(); - - if bounds.width < 1.0 || bounds.height < 1.0 { - return; - } - - let translation = Vector::new(bounds.x, bounds.y); - let cursor = Cursor::from_window_position(cursor_position); - let state = tree.state.downcast_ref::(); - - renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(Primitive::Group { - primitives: self - .program - .draw(state, theme, bounds, cursor) - .into_iter() - .map(Geometry::into_primitive) - .collect(), - }); - }); - } -} - -impl<'a, Message, P, B, T> From> - for Element<'a, Message, Renderer> -where - Message: 'a, - P: Program + 'a, - B: Backend, - T: 'a, -{ - fn from( - canvas: Canvas, - ) -> Element<'a, Message, Renderer> { - Element::new(canvas) - } -} diff --git a/graphics/src/widget/pure/qr_code.rs b/graphics/src/widget/pure/qr_code.rs deleted file mode 100644 index bff391feb9..0000000000 --- a/graphics/src/widget/pure/qr_code.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Encode and display information in a QR code. -pub use crate::qr_code::*; - -use crate::{Backend, Renderer}; - -use iced_native::layout::{self, Layout}; -use iced_native::renderer; -use iced_native::{Length, Point, Rectangle}; -use iced_pure::widget::tree::Tree; -use iced_pure::{Element, Widget}; - -impl<'a, Message, B, T> Widget> for QRCode<'a> -where - B: Backend, -{ - fn width(&self) -> Length { - >>::width(self) - } - - fn height(&self) -> Length { - >>::height(self) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - >>::layout( - self, renderer, limits, - ) - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &T, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - >>::draw( - self, - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - } -} - -impl<'a, Message, B, T> From> - for Element<'a, Message, Renderer> -where - B: Backend, -{ - fn from(qr_code: QRCode<'a>) -> Self { - Self::new(qr_code) - } -} From a003e797e8a1bb5d365c1db5de6af88e61a47329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 Jul 2022 00:50:03 +0200 Subject: [PATCH 07/19] Fix uninitialized `Tree` in `overlay::Menu` --- native/src/overlay/menu.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index fecf9f3c98..e1c79a5e84 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -181,6 +181,8 @@ where style, })); + state.tree.diff(&container as &dyn Widget<_, _>); + Self { state: &mut state.tree, container, From 80688689aa4b15bc23824df899974a9094a77b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 Jul 2022 02:46:51 +0200 Subject: [PATCH 08/19] Draft widget operations --- examples/tour/src/main.rs | 6 +- glutin/src/application.rs | 46 +++++++------ native/src/command.rs | 10 +++ native/src/command/action.rs | 7 ++ native/src/lib.rs | 4 +- native/src/overlay.rs | 9 +++ native/src/overlay/element.rs | 10 +++ native/src/user_interface.rs | 21 ++++++ native/src/widget.rs | 17 +++++ native/src/widget/action.rs | 78 +++++++++++++++++++++ native/src/widget/helpers.rs | 4 +- native/src/widget/id.rs | 38 ++++++++++ native/src/widget/operation.rs | 62 +++++++++++++++++ native/src/widget/state.rs | 5 ++ native/src/widget/text_input.rs | 15 ++++ winit/src/application.rs | 118 ++++++++++++++++++++++++-------- 16 files changed, 395 insertions(+), 55 deletions(-) create mode 100644 native/src/widget/action.rs create mode 100644 native/src/widget/id.rs create mode 100644 native/src/widget/operation.rs create mode 100644 native/src/widget/state.rs diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 75d2ce0883..82dac0cd97 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -452,6 +452,7 @@ impl<'a> Step { .map(Element::from) .collect() ) + .spacing(10) ] .padding(20) .spacing(10); @@ -594,10 +595,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { if cfg!(target_arch = "wasm32") { image("tour/images/ferris.png") } else { - image(format!( - "{}/../../tour/images/ferris.png", - env!("CARGO_MANIFEST_DIR") - )) + image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR"))) } .width(Length::Units(width)), ) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index dddf006748..96b982ac5b 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -12,7 +12,7 @@ use iced_winit::futures; use iced_winit::futures::channel::mpsc; use iced_winit::renderer; use iced_winit::user_interface; -use iced_winit::{Clipboard, Debug, Proxy, Settings}; +use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; use glutin::window::Window; use std::mem::ManuallyDrop; @@ -39,9 +39,9 @@ where debug.startup_started(); let mut event_loop = EventLoop::with_user_event(); - let mut proxy = event_loop.create_proxy(); + let proxy = event_loop.create_proxy(); - let mut runtime = { + let runtime = { let executor = E::new().map_err(Error::ExecutorCreationFailed)?; let proxy = Proxy::new(event_loop.create_proxy()); @@ -54,8 +54,6 @@ where runtime.enter(|| A::new(flags)) }; - let subscription = application.subscription(); - let context = { let builder = settings.window.into_builder( &application.title(), @@ -125,18 +123,6 @@ where })? }; - let mut clipboard = Clipboard::connect(context.window()); - - application::run_command( - init_command, - &mut runtime, - &mut clipboard, - &mut proxy, - context.window(), - || compositor.fetch_information(), - ); - runtime.track(subscription); - let (mut sender, receiver) = mpsc::unbounded(); let mut instance = Box::pin(run_instance::( @@ -144,11 +130,11 @@ where compositor, renderer, runtime, - clipboard, proxy, debug, receiver, context, + init_command, settings.exit_on_close_request, )); @@ -196,11 +182,11 @@ async fn run_instance( mut compositor: C, mut renderer: A::Renderer, mut runtime: Runtime, A::Message>, - mut clipboard: Clipboard, mut proxy: glutin::event_loop::EventLoopProxy, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, mut context: glutin::ContextWrapper, + init_command: Command, exit_on_close_request: bool, ) where A: Application + 'static, @@ -211,9 +197,26 @@ async fn run_instance( use glutin::event; use iced_winit::futures::stream::StreamExt; + let mut clipboard = Clipboard::connect(&context.window()); + let mut cache = user_interface::Cache::default(); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); + application::run_command( + &application, + &mut cache, + &state, + &mut renderer, + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + context.window(), + || compositor.fetch_information(), + ); + runtime.track(application.subscription()); + let mut user_interface = ManuallyDrop::new(application::build_user_interface( &mut application, @@ -258,12 +261,15 @@ async fn run_instance( user_interface::State::Outdated ) { - let cache = + let mut cache = ManuallyDrop::into_inner(user_interface).into_cache(); // Update application application::update( &mut application, + &mut cache, + &state, + &mut renderer, &mut runtime, &mut clipboard, &mut proxy, diff --git a/native/src/command.rs b/native/src/command.rs index 89d0f04576..b0b12805c7 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -3,6 +3,8 @@ mod action; pub use action::Action; +use crate::widget; + use iced_futures::MaybeSend; use std::fmt; @@ -24,6 +26,13 @@ impl Command { Self(iced_futures::Command::single(action)) } + /// Creates a [`Command`] that performs a [`widget::Operation`]. + pub fn widget(operation: impl widget::Operation + 'static) -> Self { + Self(iced_futures::Command::single(Action::Widget( + widget::Action::new(operation), + ))) + } + /// Creates a [`Command`] that performs the action of the given future. pub fn perform( future: impl Future + 'static + MaybeSend, @@ -51,6 +60,7 @@ impl Command { ) -> Command where T: 'static, + A: 'static, { let Command(command) = self; diff --git a/native/src/command/action.rs b/native/src/command/action.rs index 1bb03cef51..3fb02899b7 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,5 +1,6 @@ use crate::clipboard; use crate::system; +use crate::widget; use crate::window; use iced_futures::MaybeSend; @@ -23,6 +24,9 @@ pub enum Action { /// Run a system action. System(system::Action), + + /// Run a widget action. + Widget(widget::Action), } impl Action { @@ -34,6 +38,7 @@ impl Action { f: impl Fn(T) -> A + 'static + MaybeSend + Sync, ) -> Action where + A: 'static, T: 'static, { use iced_futures::futures::FutureExt; @@ -43,6 +48,7 @@ impl Action { Self::Clipboard(action) => Action::Clipboard(action.map(f)), Self::Window(window) => Action::Window(window), Self::System(system) => Action::System(system.map(f)), + Self::Widget(widget) => Action::Widget(widget.map(f)), } } } @@ -56,6 +62,7 @@ impl fmt::Debug for Action { } Self::Window(action) => write!(f, "Action::Window({:?})", action), Self::System(action) => write!(f, "Action::System({:?})", action), + Self::Widget(_action) => write!(f, "Action::Widget"), } } } diff --git a/native/src/lib.rs b/native/src/lib.rs index 131739011c..73a6c62484 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -32,8 +32,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( - missing_debug_implementations, - missing_docs, +// missing_debug_implementations, +// missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, diff --git a/native/src/overlay.rs b/native/src/overlay.rs index c2a9869324..905d3389d9 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; use crate::widget::tree::{self, Tree}; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; @@ -63,6 +64,14 @@ where /// Reconciliates the [`Widget`] with the provided [`Tree`]. fn diff(&self, _tree: &mut Tree) {} + /// Applies an [`Operation`] to the [`Widget`]. + fn operate( + &self, + _layout: Layout<'_>, + _operation: &mut dyn widget::Operation, + ) { + } + /// Processes a runtime [`Event`]. /// /// It receives: diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index de2e1f3718..b919c221ee 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.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, Layout, Point, Rectangle, Shell, Size, Vector}; /// A generic [`Overlay`]. @@ -102,6 +103,15 @@ where self.overlay .draw(renderer, theme, style, layout, cursor_position) } + + /// Applies an [`Operation`] to the [`Element`]. + pub fn operate( + &self, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.overlay.operate(layout, operation); + } } struct Map<'a, A, B, Renderer> { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 9f3a8e21a9..25557240b6 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -479,6 +479,27 @@ where .unwrap_or(base_interaction) } + /// Applies a [`widget::Operation`] to the [`UserInterface`]. + pub fn operate( + &mut self, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.root + .as_widget() + .operate(Layout::new(&self.base), operation); + + if let Some(layout) = self.overlay.as_ref() { + if let Some(overlay) = self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + overlay.operate(Layout::new(layout), operation); + } + } + } + /// Relayouts and returns a new [`UserInterface`] using the provided /// bounds. pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { diff --git a/native/src/widget.rs b/native/src/widget.rs index 9a4f373acc..79f6ae3af8 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -17,6 +17,7 @@ pub mod column; pub mod container; pub mod helpers; pub mod image; +pub mod operation; pub mod pane_grid; pub mod pick_list; pub mod progress_bar; @@ -26,6 +27,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod space; +pub mod state; pub mod svg; pub mod text; pub mod text_input; @@ -33,6 +35,9 @@ pub mod toggler; pub mod tooltip; pub mod tree; +mod action; +mod id; + #[doc(no_inline)] pub use button::Button; #[doc(no_inline)] @@ -76,6 +81,10 @@ pub use tooltip::Tooltip; #[doc(no_inline)] pub use tree::Tree; +pub use action::Action; +pub use id::Id; +pub use operation::Operation; + use crate::event::{self, Event}; use crate::layout; use crate::mouse; @@ -159,6 +168,14 @@ where /// Reconciliates the [`Widget`] with the provided [`Tree`]. fn diff(&self, _tree: &mut Tree) {} + /// Applies an [`Operation`] to the [`Widget`]. + fn operate( + &self, + _layout: Layout<'_>, + _operation: &mut dyn Operation, + ) { + } + /// Processes a runtime [`Event`]. /// /// By default, it does nothing. diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs new file mode 100644 index 0000000000..23ea426954 --- /dev/null +++ b/native/src/widget/action.rs @@ -0,0 +1,78 @@ +use crate::widget::state; +use crate::widget::{Id, Operation}; + +use iced_futures::MaybeSend; + +pub struct Action(Box>); + +impl Action { + pub fn new(operation: impl Operation + 'static) -> Self { + Self(Box::new(operation)) + } + + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + A: 'static, + { + Action(Box::new(Map { + operation: self.0, + f: Box::new(f), + })) + } + + pub fn into_operation(self) -> Box> { + self.0 + } +} + +struct Map { + operation: Box>, + f: Box B>, +} + +impl Operation for Map +where + A: 'static, + B: 'static, +{ + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + struct MapRef<'a, A, B> { + operation: &'a mut dyn Operation, + f: &'a dyn Fn(A) -> B, + } + + impl<'a, A, B> Operation for MapRef<'a, A, B> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + let Self { operation, f } = self; + + operation.container(id, &|operation| { + operate_on_children(&mut MapRef { operation, f }); + }); + } + } + + let Self { operation, f } = self; + + MapRef { + operation: operation.as_mut(), + f, + } + .container(id, operate_on_children); + } + + fn focusable(&mut self, state: &mut dyn state::Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } +} diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 518ac23bbc..a62448e94c 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -49,8 +49,8 @@ where /// [`Column`]: widget::Column pub fn column( children: Vec>, -) -> widget::Row<'_, Message, Renderer> { - widget::Row::with_children(children) +) -> widget::Column<'_, Message, Renderer> { + widget::Column::with_children(children) } /// Creates a new [`Row`] with the given children. diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs new file mode 100644 index 0000000000..4c0ab999e9 --- /dev/null +++ b/native/src/widget/id.rs @@ -0,0 +1,38 @@ +use std::borrow; +use std::sync::atomic::{self, AtomicUsize}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(Internal); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(Internal::Custom(id.into())) + } + + pub fn unique() -> Self { + let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); + + Self(Internal::Unique(id)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Internal { + Unique(usize), + Custom(borrow::Cow<'static, str>), +} + +#[cfg(test)] +mod tests { + use super::Id; + + #[test] + fn unique_generates_different_ids() { + let a = Id::unique(); + let b = Id::unique(); + + assert_ne!(a, b); + } +} diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs new file mode 100644 index 0000000000..b6c108e0df --- /dev/null +++ b/native/src/widget/operation.rs @@ -0,0 +1,62 @@ +use crate::widget::state; +use crate::widget::Id; + +pub trait Operation { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ); + + fn focusable( + &mut self, + _state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + } + + fn finish(&self) -> Outcome { + Outcome::None + } +} + +pub enum Outcome { + None, + Some(T), + Chain(Box>), +} + +pub fn focus(target: Id) -> impl Operation { + struct Focus { + target: Id, + } + + impl Operation for Focus { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + id: Option<&Id>, + ) { + if state.is_focused() { + match id { + Some(id) if id == &self.target => { + state.focus(); + } + _ => { + state.unfocus(); + } + } + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Focus { target } +} diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs new file mode 100644 index 0000000000..d1984a71d9 --- /dev/null +++ b/native/src/widget/state.rs @@ -0,0 +1,5 @@ +pub trait Focusable { + fn is_focused(&self) -> bool; + fn focus(&mut self); + fn unfocus(&mut self); +} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 4ef9e11bdf..1dbb8d6b45 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::state; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, @@ -942,6 +943,20 @@ impl State { } } +impl state::Focusable for State { + fn is_focused(&self) -> bool { + State::is_focused(self) + } + + fn focus(&mut self) { + State::focus(self) + } + + fn unfocus(&mut self) { + State::unfocus(self) + } +} + mod platform { use crate::keyboard; diff --git a/winit/src/application.rs b/winit/src/application.rs index 99402cf564..a27adf3a51 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -7,6 +7,7 @@ use crate::clipboard::{self, Clipboard}; use crate::conversion; use crate::mouse; use crate::renderer; +use crate::widget::operation; use crate::{ Command, Debug, Error, Executor, Mode, Proxy, Runtime, Settings, Size, Subscription, @@ -131,9 +132,9 @@ where debug.startup_started(); let event_loop = EventLoop::with_user_event(); - let mut proxy = event_loop.create_proxy(); + let proxy = event_loop.create_proxy(); - let mut runtime = { + let runtime = { let proxy = Proxy::new(event_loop.create_proxy()); let executor = E::new().map_err(Error::ExecutorCreationFailed)?; @@ -146,8 +147,6 @@ where runtime.enter(|| A::new(flags)) }; - let subscription = application.subscription(); - let builder = settings.window.into_builder( &application.title(), application.mode(), @@ -176,20 +175,8 @@ where .expect("Append canvas to HTML body"); } - let mut clipboard = Clipboard::connect(&window); - let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; - run_command( - init_command, - &mut runtime, - &mut clipboard, - &mut proxy, - &window, - || compositor.fetch_information(), - ); - runtime.track(subscription); - let (mut sender, receiver) = mpsc::unbounded(); let mut instance = Box::pin(run_instance::( @@ -197,10 +184,10 @@ where compositor, renderer, runtime, - clipboard, proxy, debug, receiver, + init_command, window, settings.exit_on_close_request, )); @@ -247,10 +234,10 @@ async fn run_instance( mut compositor: C, mut renderer: A::Renderer, mut runtime: Runtime, A::Message>, - mut clipboard: Clipboard, mut proxy: winit::event_loop::EventLoopProxy, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, + init_command: Command, window: winit::window::Window, exit_on_close_request: bool, ) where @@ -262,6 +249,8 @@ async fn run_instance( use iced_futures::futures::stream::StreamExt; use winit::event; + let mut clipboard = Clipboard::connect(&window); + let mut cache = user_interface::Cache::default(); let mut surface = compositor.create_surface(&window); let mut state = State::new(&application, &window); @@ -275,9 +264,24 @@ async fn run_instance( physical_size.height, ); + run_command( + &application, + &mut cache, + &state, + &mut renderer, + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &window, + || compositor.fetch_information(), + ); + runtime.track(application.subscription()); + let mut user_interface = ManuallyDrop::new(build_user_interface( &mut application, - user_interface::Cache::default(), + cache, &mut renderer, state.logical_size(), &mut debug, @@ -318,12 +322,15 @@ async fn run_instance( user_interface::State::Outdated, ) { - let cache = + let mut cache = ManuallyDrop::into_inner(user_interface).into_cache(); // Update application update( &mut application, + &mut cache, + &state, + &mut renderer, &mut runtime, &mut clipboard, &mut proxy, @@ -515,7 +522,7 @@ pub fn requests_exit( /// Builds a [`UserInterface`] for the provided [`Application`], logging /// [`struct@Debug`] information accordingly. pub fn build_user_interface<'a, A: Application>( - application: &'a mut A, + application: &'a A, cache: user_interface::Cache, renderer: &mut A::Renderer, size: Size, @@ -539,6 +546,9 @@ where /// resulting [`Command`], and tracking its [`Subscription`]. pub fn update( application: &mut A, + cache: &mut user_interface::Cache, + state: &State, + renderer: &mut A::Renderer, runtime: &mut Runtime, A::Message>, clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy, @@ -556,7 +566,19 @@ pub fn update( let command = runtime.enter(|| application.update(message)); debug.update_finished(); - run_command(command, runtime, clipboard, proxy, window, graphics_info); + run_command( + application, + cache, + state, + renderer, + command, + runtime, + clipboard, + proxy, + debug, + window, + graphics_info, + ); } let subscription = application.subscription(); @@ -564,14 +586,23 @@ pub fn update( } /// Runs the actions of a [`Command`]. -pub fn run_command( - command: Command, - runtime: &mut Runtime, Message>, +pub fn run_command( + application: &A, + cache: &mut user_interface::Cache, + state: &State, + renderer: &mut A::Renderer, + command: Command, + runtime: &mut Runtime, A::Message>, clipboard: &mut Clipboard, - proxy: &mut winit::event_loop::EventLoopProxy, + proxy: &mut winit::event_loop::EventLoopProxy, + debug: &mut Debug, window: &winit::window::Window, _graphics_info: impl FnOnce() -> compositor::Information + Copy, -) { +) where + A: Application, + E: Executor, + ::Theme: StyleSheet, +{ use iced_native::command; use iced_native::system; use iced_native::window; @@ -627,6 +658,39 @@ pub fn run_command( } } }, + command::Action::Widget(action) => { + let mut current_cache = + std::mem::replace(cache, user_interface::Cache::default()); + + let mut current_operation = Some(action.into_operation()); + + while let Some(mut operation) = current_operation.take() { + let mut user_interface = build_user_interface( + application, + current_cache, + renderer, + state.logical_size(), + debug, + ); + + user_interface.operate(renderer, operation.as_mut()); + current_cache = user_interface.into_cache(); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(message) => { + proxy + .send_event(message) + .expect("Send message to event loop"); + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + + *cache = current_cache; + } } } } From 52f84e51e90db1c324310565f2aff8b7e6987cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 Jul 2022 03:53:47 +0200 Subject: [PATCH 09/19] Implement `Widget::operate` for `TextInput` --- native/src/element.rs | 37 +++++++++++++++++++++++++++++++ native/src/user_interface.rs | 8 ++++--- native/src/widget.rs | 1 + native/src/widget/action.rs | 6 ++--- native/src/widget/button.rs | 16 ++++++++++++++ native/src/widget/column.rs | 19 +++++++++++++++- native/src/widget/container.rs | 17 +++++++++++++- native/src/widget/operation.rs | 18 +++++++-------- native/src/widget/row.rs | 19 +++++++++++++++- native/src/widget/scrollable.rs | 16 ++++++++++++++ native/src/widget/text_input.rs | 39 +++++++++++++++++++++++++++++++-- src/widget.rs | 4 +++- 12 files changed, 178 insertions(+), 22 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index cc74035e93..01b71aa4a2 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -3,6 +3,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; +use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell, Widget}; @@ -248,6 +249,42 @@ where self.widget.layout(renderer, limits) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + struct MapOperation<'a, B> { + operation: &'a mut dyn widget::Operation, + } + + impl<'a, T, B> widget::Operation for MapOperation<'a, B> { + fn container( + &mut self, + id: Option<&widget::Id>, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation, + ), + ) { + self.operation.container(id, &mut |operation| { + operate_on_children(&mut MapOperation { operation }); + }); + } + + fn focusable( + &mut self, + state: &mut dyn widget::state::Focusable, + id: Option<&widget::Id>, + ) { + self.operation.focusable(state, id); + } + } + + self.widget + .operate(tree, layout, &mut MapOperation { operation }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 25557240b6..42669f95e6 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -485,9 +485,11 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - self.root - .as_widget() - .operate(Layout::new(&self.base), operation); + self.root.as_widget().operate( + &mut self.state, + Layout::new(&self.base), + operation, + ); if let Some(layout) = self.overlay.as_ref() { if let Some(overlay) = self.root.as_widget().overlay( diff --git a/native/src/widget.rs b/native/src/widget.rs index 79f6ae3af8..56ba28c868 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -171,6 +171,7 @@ where /// Applies an [`Operation`] to the [`Widget`]. fn operate( &self, + _state: &mut Tree, _layout: Layout<'_>, _operation: &mut dyn Operation, ) { diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 23ea426954..697233586e 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -42,7 +42,7 @@ where fn container( &mut self, id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { struct MapRef<'a, A, B> { operation: &'a mut dyn Operation, @@ -53,11 +53,11 @@ where fn container( &mut self, id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { let Self { operation, f } = self; - operation.container(id, &|operation| { + operation.container(id, &mut |operation| { operate_on_children(&mut MapRef { operation, f }); }); } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 6eac6c1bca..6c0b8f6e79 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -8,6 +8,7 @@ use crate::overlay; use crate::renderer; use crate::touch; use crate::widget::tree::{self, Tree}; +use crate::widget::Operation; use crate::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, @@ -164,6 +165,21 @@ where ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + operation, + ); + }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 834f9858af..a8b0f18307 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -4,7 +4,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::widget::Tree; +use crate::widget::{Operation, Tree}; use crate::{ Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Widget, @@ -143,6 +143,23 @@ where ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child.as_widget().operate(state, layout, operation); + }) + }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index b0fa0315dd..2afad3f2d3 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -5,7 +5,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::widget::Tree; +use crate::widget::{Operation, Tree}; use crate::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Widget, @@ -165,6 +165,21 @@ where ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + operation, + ); + }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index b6c108e0df..2cfba005c5 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -5,7 +5,7 @@ pub trait Operation { fn container( &mut self, id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); fn focusable( @@ -37,14 +37,12 @@ pub fn focus(target: Id) -> impl Operation { state: &mut dyn state::Focusable, id: Option<&Id>, ) { - if state.is_focused() { - match id { - Some(id) if id == &self.target => { - state.focus(); - } - _ => { - state.unfocus(); - } + match id { + Some(id) if id == &self.target => { + state.focus(); + } + _ => { + state.unfocus(); } } } @@ -52,7 +50,7 @@ pub fn focus(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c342c27711..eda7c2d355 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -4,7 +4,7 @@ use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::widget::Tree; +use crate::widget::{Operation, Tree}; use crate::{ Alignment, Clipboard, Element, Length, Padding, Point, Rectangle, Shell, Widget, @@ -130,6 +130,23 @@ where ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child.as_widget().operate(state, layout, operation); + }) + }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b40c374367..91c13eb575 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -6,6 +6,7 @@ use crate::overlay; use crate::renderer; use crate::touch; use crate::widget::tree::{self, Tree}; +use crate::widget::Operation; use crate::{ Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, @@ -150,6 +151,21 @@ where ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + operation, + ); + }); + } + fn on_event( &mut self, tree: &mut Tree, diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 1dbb8d6b45..1ca5ccf27c 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -19,11 +19,13 @@ use crate::mouse::{self, click}; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget; +use crate::widget::operation::{self, Operation}; use crate::widget::state; use crate::widget::tree::{self, Tree}; use crate::{ - Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, - Shell, Size, Vector, Widget, + Clipboard, Color, Command, Element, Layout, Length, Padding, Point, + Rectangle, Shell, Size, Vector, Widget, }; pub use iced_style::text_input::{Appearance, StyleSheet}; @@ -54,6 +56,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { + id: Option, placeholder: String, value: Value, is_secure: bool, @@ -84,6 +87,7 @@ where F: 'a + Fn(String) -> Message, { TextInput { + id: None, placeholder: String::from(placeholder), value: Value::new(value), is_secure: false, @@ -98,6 +102,12 @@ where } } + /// Sets the [`Id`] of the [`TextInput`]. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + /// Converts the [`TextInput`] into a secure password input. pub fn password(mut self) -> Self { self.is_secure = true; @@ -215,6 +225,17 @@ where layout(renderer, limits, self.width, self.padding, self.size) } + fn operate( + &self, + tree: &mut Tree, + _layout: Layout<'_>, + operation: &mut dyn Operation, + ) { + let state = tree.state.downcast_mut::(); + + operation.focusable(state, self.id.as_ref().map(|id| &id.0)); + } + fn on_event( &mut self, tree: &mut Tree, @@ -294,6 +315,19 @@ where } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(widget::Id::new(id)) + } +} + +pub fn focus(id: Id) -> Command { + Command::widget(operation::focus(id.0)) +} + /// Computes the layout of a [`TextInput`]. pub fn layout( renderer: &Renderer, @@ -915,6 +949,7 @@ impl State { /// Focuses the [`TextInput`]. pub fn focus(&mut self) { self.is_focused = true; + self.move_cursor_to_end(); } /// Unfocuses the [`TextInput`]. diff --git a/src/widget.rs b/src/widget.rs index 4ddf056666..abffadd505 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -119,7 +119,9 @@ pub mod toggler { pub mod text_input { //! Display fields that can be filled with text. - pub use iced_native::widget::text_input::{Appearance, StyleSheet}; + pub use iced_native::widget::text_input::{ + focus, Appearance, Id, StyleSheet, + }; /// A field that can be filled with text. pub type TextInput<'a, Message, Renderer = crate::Renderer> = From 744edbd6c17c3c9d872992c84e074f14c8e75a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 Jul 2022 03:54:02 +0200 Subject: [PATCH 10/19] Focus text inputs in `todos` example --- examples/todos/Cargo.toml | 1 + examples/todos/src/main.rs | 31 ++++++++++++++++++++++++++----- native/src/widget/text_input.rs | 4 ++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 5b068c789f..2326ffc6e1 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -9,6 +9,7 @@ publish = false iced = { path = "../..", features = ["async-std", "debug"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +lazy_static = "1.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 7dde235a1f..5cbb6228fc 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -7,8 +7,14 @@ use iced::widget::{ use iced::window; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings}; + +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +lazy_static! { + static ref INPUT_ID: text_input::Id = text_input::Id::unique(); +} + pub fn main() -> iced::Result { Todos::run(Settings { window: window::Settings { @@ -84,10 +90,11 @@ impl Application for Todos { _ => {} } - Command::none() + text_input::focus(INPUT_ID.clone()) } Todos::Loaded(state) => { let mut saved = false; + let mut task_command = Command::none(); match message { Message::InputChanged(value) => { @@ -109,6 +116,11 @@ impl Application for Todos { } Message::TaskMessage(i, task_message) => { if let Some(task) = state.tasks.get_mut(i) { + if matches!(task_message, TaskMessage::Edit) { + task_command = + text_input::focus(Task::text_input_id(i)); + } + task.update(task_message); } } @@ -123,7 +135,7 @@ impl Application for Todos { state.dirty = true; } - if state.dirty && !state.saving { + let save = if state.dirty && !state.saving { state.dirty = false; state.saving = true; @@ -138,7 +150,9 @@ impl Application for Todos { ) } else { Command::none() - } + }; + + Command::batch(vec![task_command, save]) } } } @@ -163,6 +177,7 @@ impl Application for Todos { input_value, Message::InputChanged, ) + .id(INPUT_ID.clone()) .padding(15) .size(30) .on_submit(Message::CreateTask); @@ -178,12 +193,13 @@ impl Application for Todos { .enumerate() .filter(|(_, task)| filter.matches(task)) .map(|(i, task)| { - task.view().map(move |message| { + task.view(i).map(move |message| { Message::TaskMessage(i, message) }) }) .collect(), ) + .spacing(10) .into() } else { empty_message(match filter { @@ -242,6 +258,10 @@ pub enum TaskMessage { } impl Task { + fn text_input_id(i: usize) -> text_input::Id { + text_input::Id::new(format!("task-{}", i)) + } + fn new(description: String) -> Self { Task { description, @@ -270,7 +290,7 @@ impl Task { } } - fn view(&self) -> Element { + fn view(&self, i: usize) -> Element { match &self.state { TaskState::Idle => { let checkbox = checkbox( @@ -297,6 +317,7 @@ impl Task { &self.description, TaskMessage::DescriptionEdited, ) + .id(Self::text_input_id(i)) .on_submit(TaskMessage::FinishEdition) .padding(10); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 1ca5ccf27c..a81cfaed3b 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -322,6 +322,10 @@ impl Id { pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + + pub fn unique() -> Self { + Self(widget::Id::unique()) + } } pub fn focus(id: Id) -> Command { From 6dac049db5824e3af06bc16df0fdf51f8809aeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 Jul 2022 04:00:06 +0200 Subject: [PATCH 11/19] Fix `clippy` lints :tada: --- glutin/src/application.rs | 6 +++--- winit/src/application.rs | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 96b982ac5b..24f315fbfa 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -197,7 +197,7 @@ async fn run_instance( use glutin::event; use iced_winit::futures::stream::StreamExt; - let mut clipboard = Clipboard::connect(&context.window()); + let mut clipboard = Clipboard::connect(context.window()); let mut cache = user_interface::Cache::default(); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); @@ -219,7 +219,7 @@ async fn run_instance( let mut user_interface = ManuallyDrop::new(application::build_user_interface( - &mut application, + &application, user_interface::Cache::default(), &mut renderer, state.logical_size(), @@ -286,7 +286,7 @@ async fn run_instance( user_interface = ManuallyDrop::new(application::build_user_interface( - &mut application, + &application, cache, &mut renderer, state.logical_size(), diff --git a/winit/src/application.rs b/winit/src/application.rs index a27adf3a51..3d58da0f61 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -280,7 +280,7 @@ async fn run_instance( runtime.track(application.subscription()); let mut user_interface = ManuallyDrop::new(build_user_interface( - &mut application, + &application, cache, &mut renderer, state.logical_size(), @@ -346,7 +346,7 @@ async fn run_instance( let should_exit = application.should_exit(); user_interface = ManuallyDrop::new(build_user_interface( - &mut application, + &application, cache, &mut renderer, state.logical_size(), @@ -659,9 +659,7 @@ pub fn run_command( } }, command::Action::Widget(action) => { - let mut current_cache = - std::mem::replace(cache, user_interface::Cache::default()); - + let mut current_cache = std::mem::take(cache); let mut current_operation = Some(action.into_operation()); while let Some(mut operation) = current_operation.take() { From 77c6864e7c772c5e228bc09fe40c2c0b8884386d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 2 Aug 2022 04:20:47 +0200 Subject: [PATCH 12/19] Implement `focus_next` operation ... as well as a `count_focusable` composable helper! --- examples/todos/src/main.rs | 57 ++++++++++++++++----- native/src/widget/operation.rs | 93 ++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++- src/widget.rs | 11 ++++ 4 files changed, 154 insertions(+), 13 deletions(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 5cbb6228fc..25d90a0be4 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,12 +1,15 @@ use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; +use iced::keyboard; +use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ - button, checkbox, column, container, row, scrollable, text, text_input, - Text, + self, button, checkbox, column, container, row, scrollable, text, + text_input, Text, }; use iced::window; use iced::{Application, Element}; -use iced::{Color, Command, Font, Length, Settings}; +use iced::{Color, Command, Font, Length, Settings, Subscription}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -48,6 +51,7 @@ enum Message { CreateTask, FilterChanged(Filter), TaskMessage(usize, TaskMessage), + TabPressed, } impl Application for Todos { @@ -94,11 +98,12 @@ impl Application for Todos { } Todos::Loaded(state) => { let mut saved = false; - let mut task_command = Command::none(); - match message { + let command = match message { Message::InputChanged(value) => { state.input_value = value; + + Command::none() } Message::CreateTask => { if !state.input_value.is_empty() { @@ -107,29 +112,44 @@ impl Application for Todos { .push(Task::new(state.input_value.clone())); state.input_value.clear(); } + + Command::none() } Message::FilterChanged(filter) => { state.filter = filter; + + Command::none() } Message::TaskMessage(i, TaskMessage::Delete) => { state.tasks.remove(i); + + Command::none() } Message::TaskMessage(i, task_message) => { if let Some(task) = state.tasks.get_mut(i) { - if matches!(task_message, TaskMessage::Edit) { - task_command = - text_input::focus(Task::text_input_id(i)); - } + let should_focus = + matches!(task_message, TaskMessage::Edit); task.update(task_message); + + if should_focus { + text_input::focus(Task::text_input_id(i)) + } else { + Command::none() + } + } else { + Command::none() } } Message::Saved(_) => { state.saving = false; saved = true; + + Command::none() } - _ => {} - } + Message::TabPressed => widget::focus_next(), + _ => Command::none(), + }; if !saved { state.dirty = true; @@ -152,7 +172,7 @@ impl Application for Todos { Command::none() }; - Command::batch(vec![task_command, save]) + Command::batch(vec![command, save]) } } } @@ -225,6 +245,19 @@ impl Application for Todos { } } } + + fn subscription(&self) -> Subscription { + subscription::events_with(|event, status| match (event, status) { + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + .. + }), + event::Status::Ignored, + ) => Some(Message::TabPressed), + _ => None, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 2cfba005c5..5a0f0c1896 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -58,3 +58,96 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FocusCount { + focused: Option, + total: usize, +} + +pub fn count_focusable(f: fn(FocusCount) -> O) -> impl Operation +where + O: Operation + 'static, +{ + struct CountFocusable { + count: FocusCount, + next: fn(FocusCount) -> O, + } + + impl Operation for CountFocusable + where + O: Operation + 'static, + { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if state.is_focused() { + self.count.focused = Some(self.count.total); + } + + self.count.total += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome { + Outcome::Chain(Box::new((self.next)(self.count))) + } + } + + CountFocusable { + count: FocusCount::default(), + next: f, + } +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: FocusCount, + current: usize, + } + + impl Operation for FocusNext { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if self.count.total == 0 { + return; + } + + match self.count.focused { + None if self.current == 0 => state.focus(), + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused + 1 == self.current => state.focus(), + Some(focused) + if focused == self.count.total - 1 && self.current == 0 => + { + state.focus() + } + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count_focusable(|count| FocusNext { count, current: 0 }) +} diff --git a/src/lib.rs b/src/lib.rs index 38ba48be71..100b9f771f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,22 +192,26 @@ use iced_wgpu as renderer; use iced_glow as renderer; pub use iced_native::theme; +pub use runtime::event; +pub use runtime::subscription; pub use application::Application; pub use element::Element; pub use error::Error; +pub use event::Event; pub use executor::Executor; pub use renderer::Renderer; pub use result::Result; pub use sandbox::Sandbox; pub use settings::Settings; +pub use subscription::Subscription; pub use theme::Theme; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ Alignment, Background, Color, Command, ContentFit, Font, Length, Padding, - Point, Rectangle, Size, Subscription, Vector, + Point, Rectangle, Size, Vector, }; #[cfg(feature = "system")] diff --git a/src/widget.rs b/src/widget.rs index abffadd505..2333aa28e3 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -211,3 +211,14 @@ pub use qr_code::QRCode; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub use svg::Svg; + +use crate::Command; +use iced_native::widget::operation; + +/// Focuses the next focusable widget. +pub fn focus_next() -> Command +where + Message: 'static, +{ + Command::widget(operation::focus_next()) +} From 54ad92ce913629d1d1f623f4b14d51244554a59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 2 Aug 2022 17:34:04 +0200 Subject: [PATCH 13/19] Build `UserInterface` only once on `Outcome::Chain` --- winit/src/application.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index 3d58da0f61..3a5c3dac6e 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -662,17 +662,16 @@ pub fn run_command( let mut current_cache = std::mem::take(cache); let mut current_operation = Some(action.into_operation()); - while let Some(mut operation) = current_operation.take() { - let mut user_interface = build_user_interface( - application, - current_cache, - renderer, - state.logical_size(), - debug, - ); + let mut user_interface = build_user_interface( + application, + current_cache, + renderer, + state.logical_size(), + debug, + ); + while let Some(mut operation) = current_operation.take() { user_interface.operate(renderer, operation.as_mut()); - current_cache = user_interface.into_cache(); match operation.finish() { operation::Outcome::None => {} @@ -687,6 +686,7 @@ pub fn run_command( } } + current_cache = user_interface.into_cache(); *cache = current_cache; } } From 6eb3dd7e5edc8847875c288c41d1dec8b1dad06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 4 Aug 2022 03:24:44 +0200 Subject: [PATCH 14/19] Implement `focus_previous` operation --- examples/todos/src/main.rs | 15 ++++++++--- native/src/widget/operation.rs | 47 ++++++++++++++++++++++++++++------ src/widget.rs | 8 ++++++ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 25d90a0be4..bb00aac67e 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -51,7 +51,7 @@ enum Message { CreateTask, FilterChanged(Filter), TaskMessage(usize, TaskMessage), - TabPressed, + TabPressed { shift: bool }, } impl Application for Todos { @@ -147,7 +147,13 @@ impl Application for Todos { Command::none() } - Message::TabPressed => widget::focus_next(), + Message::TabPressed { shift } => { + if shift { + widget::focus_previous() + } else { + widget::focus_next() + } + } _ => Command::none(), }; @@ -251,10 +257,13 @@ impl Application for Todos { ( Event::Keyboard(keyboard::Event::KeyPressed { key_code: keyboard::KeyCode::Tab, + modifiers, .. }), event::Status::Ignored, - ) => Some(Message::TabPressed), + ) => Some(Message::TabPressed { + shift: modifiers.shift(), + }), _ => None, }) } diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 5a0f0c1896..caf7ba1c5c 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -109,13 +109,13 @@ where } } -pub fn focus_next() -> impl Operation { - struct FocusNext { +pub fn focus_previous() -> impl Operation { + struct FocusPrevious { count: FocusCount, current: usize, } - impl Operation for FocusNext { + impl Operation for FocusPrevious { fn focusable( &mut self, state: &mut dyn state::Focusable, @@ -125,15 +125,46 @@ pub fn focus_next() -> impl Operation { return; } + match self.count.focused { + None if self.current == self.count.total - 1 => state.focus(), + Some(0) if self.current == 0 => state.unfocus(), + Some(0) => {} + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused - 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count_focusable(|count| FocusPrevious { count, current: 0 }) +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: FocusCount, + current: usize, + } + + impl Operation for FocusNext { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { match self.count.focused { None if self.current == 0 => state.focus(), Some(focused) if focused == self.current => state.unfocus(), Some(focused) if focused + 1 == self.current => state.focus(), - Some(focused) - if focused == self.count.total - 1 && self.current == 0 => - { - state.focus() - } _ => {} } diff --git a/src/widget.rs b/src/widget.rs index 2333aa28e3..c3b6d83b4a 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -215,6 +215,14 @@ pub use svg::Svg; use crate::Command; use iced_native::widget::operation; +/// Focuses the previous focusable widget. +pub fn focus_previous() -> Command +where + Message: 'static, +{ + Command::widget(operation::focus_previous()) +} + /// Focuses the next focusable widget. pub fn focus_next() -> Command where From 13dd1ca0a83cc95eea52e2106da9dc1ee1f37958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 4 Aug 2022 03:55:41 +0200 Subject: [PATCH 15/19] Implement `scrollable::snap_to` operation --- examples/scrollable/src/main.rs | 52 +++++-- examples/websocket/Cargo.toml | 1 + examples/websocket/src/main.rs | 22 ++- native/src/element.rs | 2 +- native/src/widget.rs | 1 - native/src/widget/action.rs | 10 +- native/src/widget/operation.rs | 173 ++-------------------- native/src/widget/operation/focusable.rs | 149 +++++++++++++++++++ native/src/widget/operation/scrollable.rs | 30 ++++ native/src/widget/scrollable.rs | 42 +++++- native/src/widget/state.rs | 6 +- native/src/widget/text_input.rs | 5 +- src/widget.rs | 6 +- 13 files changed, 294 insertions(+), 205 deletions(-) create mode 100644 native/src/widget/operation/focusable.rs create mode 100644 native/src/widget/operation/scrollable.rs diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index c9c1be7cd3..b7b3dedc28 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,8 +1,9 @@ +use iced::executor; use iced::widget::{ button, column, container, horizontal_rule, progress_bar, radio, scrollable, text, vertical_space, Row, }; -use iced::{Element, Length, Sandbox, Settings, Theme}; +use iced::{Application, Command, Element, Length, Settings, Theme}; pub fn main() -> iced::Result { ScrollableDemo::run(Settings::default()) @@ -21,43 +22,57 @@ enum Message { Scrolled(usize, f32), } -impl Sandbox for ScrollableDemo { +impl Application for ScrollableDemo { type Message = Message; - - fn new() -> Self { - ScrollableDemo { - theme: Default::default(), - variants: Variant::all(), - } + type Theme = Theme; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + ScrollableDemo { + theme: Default::default(), + variants: Variant::all(), + }, + Command::none(), + ) } fn title(&self) -> String { String::from("Scrollable - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { - Message::ThemeChanged(theme) => self.theme = theme, + Message::ThemeChanged(theme) => { + self.theme = theme; + + Command::none() + } Message::ScrollToTop(i) => { if let Some(variant) = self.variants.get_mut(i) { - // TODO - // variant.scrollable.snap_to(0.0); - variant.latest_offset = 0.0; + + scrollable::snap_to(Variant::id(i), 0.0) + } else { + Command::none() } } Message::ScrollToBottom(i) => { if let Some(variant) = self.variants.get_mut(i) { - // TODO - // variant.scrollable.snap_to(1.0); - variant.latest_offset = 1.0; + + scrollable::snap_to(Variant::id(i), 1.0) + } else { + Command::none() } } Message::Scrolled(i, offset) => { if let Some(variant) = self.variants.get_mut(i) { variant.latest_offset = offset; } + + Command::none() } } } @@ -136,6 +151,7 @@ impl Sandbox for ScrollableDemo { ); let mut scrollable = scrollable(contents) + .id(Variant::id(i)) .height(Length::Fill) .on_scroll(move |offset| Message::Scrolled(i, offset)); @@ -228,4 +244,8 @@ impl Variant { }, ] } + + pub fn id(i: usize) -> scrollable::Id { + scrollable::Id::new(format!("scrollable-{}", i)) + } } diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index db131dd74e..c582733f94 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,6 +9,7 @@ publish = false iced = { path = "../..", features = ["tokio", "debug"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } +lazy_static = "1.4" [dependencies.async-tungstenite] version = "0.16" diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 28a9de3738..3902e04c82 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -49,37 +49,42 @@ impl Application for WebSocket { match message { Message::NewMessageChanged(new_message) => { self.new_message = new_message; + + Command::none() } Message::Send(message) => match &mut self.state { State::Connected(connection) => { self.new_message.clear(); connection.send(message); + + Command::none() } - State::Disconnected => {} + State::Disconnected => Command::none(), }, Message::Echo(event) => match event { echo::Event::Connected(connection) => { self.state = State::Connected(connection); self.messages.push(echo::Message::connected()); + + Command::none() } echo::Event::Disconnected => { self.state = State::Disconnected; self.messages.push(echo::Message::disconnected()); + + Command::none() } echo::Event::MessageReceived(message) => { self.messages.push(message); - // TODO - // self.message_log.snap_to(1.0); + scrollable::snap_to(MESSAGE_LOG.clone(), 1.0) } }, - Message::Server => {} + Message::Server => Command::none(), } - - Command::none() } fn subscription(&self) -> Subscription { @@ -110,6 +115,7 @@ impl Application for WebSocket { .width(Length::Fill) .spacing(10), ) + .id(MESSAGE_LOG.clone()) .height(Length::Fill) .into() }; @@ -158,3 +164,7 @@ impl Default for State { Self::Disconnected } } + +lazy_static::lazy_static! { + static ref MESSAGE_LOG: scrollable::Id = scrollable::Id::unique(); +} diff --git a/native/src/element.rs b/native/src/element.rs index 01b71aa4a2..8b994d7331 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -274,7 +274,7 @@ where fn focusable( &mut self, - state: &mut dyn widget::state::Focusable, + state: &mut dyn widget::operation::Focusable, id: Option<&widget::Id>, ) { self.operation.focusable(state, id); diff --git a/native/src/widget.rs b/native/src/widget.rs index 56ba28c868..8890b8e798 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -27,7 +27,6 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod space; -pub mod state; pub mod svg; pub mod text; pub mod text_input; diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 697233586e..21032dbb98 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,5 +1,5 @@ -use crate::widget::state; -use crate::widget::{Id, Operation}; +use crate::widget::operation::{self, Operation}; +use crate::widget::Id; use iced_futures::MaybeSend; @@ -72,7 +72,11 @@ where .container(id, operate_on_children); } - fn focusable(&mut self, state: &mut dyn state::Focusable, id: Option<&Id>) { + fn focusable( + &mut self, + state: &mut dyn operation::Focusable, + id: Option<&Id>, + ) { self.operation.focusable(state, id); } } diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index caf7ba1c5c..4a075da92f 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,4 +1,9 @@ -use crate::widget::state; +pub mod focusable; +pub mod scrollable; + +pub use focusable::Focusable; +pub use scrollable::Scrollable; + use crate::widget::Id; pub trait Operation { @@ -8,12 +13,9 @@ pub trait Operation { operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); - fn focusable( - &mut self, - _state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - } + fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + + fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} fn finish(&self) -> Outcome { Outcome::None @@ -25,160 +27,3 @@ pub enum Outcome { Some(T), Chain(Box>), } - -pub fn focus(target: Id) -> impl Operation { - struct Focus { - target: Id, - } - - impl Operation for Focus { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - id: Option<&Id>, - ) { - match id { - Some(id) if id == &self.target => { - state.focus(); - } - _ => { - state.unfocus(); - } - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - Focus { target } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct FocusCount { - focused: Option, - total: usize, -} - -pub fn count_focusable(f: fn(FocusCount) -> O) -> impl Operation -where - O: Operation + 'static, -{ - struct CountFocusable { - count: FocusCount, - next: fn(FocusCount) -> O, - } - - impl Operation for CountFocusable - where - O: Operation + 'static, - { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - if state.is_focused() { - self.count.focused = Some(self.count.total); - } - - self.count.total += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - - fn finish(&self) -> Outcome { - Outcome::Chain(Box::new((self.next)(self.count))) - } - } - - CountFocusable { - count: FocusCount::default(), - next: f, - } -} - -pub fn focus_previous() -> impl Operation { - struct FocusPrevious { - count: FocusCount, - current: usize, - } - - impl Operation for FocusPrevious { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - if self.count.total == 0 { - return; - } - - match self.count.focused { - None if self.current == self.count.total - 1 => state.focus(), - Some(0) if self.current == 0 => state.unfocus(), - Some(0) => {} - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused - 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - count_focusable(|count| FocusPrevious { count, current: 0 }) -} - -pub fn focus_next() -> impl Operation { - struct FocusNext { - count: FocusCount, - current: usize, - } - - impl Operation for FocusNext { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - match self.count.focused { - None if self.current == 0 => state.focus(), - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused + 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - count_focusable(|count| FocusNext { count, current: 0 }) -} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs new file mode 100644 index 0000000000..20a732915c --- /dev/null +++ b/native/src/widget/operation/focusable.rs @@ -0,0 +1,149 @@ +use crate::widget::operation::{Operation, Outcome}; +use crate::widget::Id; + +pub trait Focusable { + fn is_focused(&self) -> bool; + fn focus(&mut self); + fn unfocus(&mut self); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Count { + focused: Option, + total: usize, +} + +pub fn focus(target: Id) -> impl Operation { + struct Focus { + target: Id, + } + + impl Operation for Focus { + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.focus(); + } + _ => { + state.unfocus(); + } + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Focus { target } +} + +pub fn count(f: fn(Count) -> O) -> impl Operation +where + O: Operation + 'static, +{ + struct CountFocusable { + count: Count, + next: fn(Count) -> O, + } + + impl Operation for CountFocusable + where + O: Operation + 'static, + { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + if state.is_focused() { + self.count.focused = Some(self.count.total); + } + + self.count.total += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome { + Outcome::Chain(Box::new((self.next)(self.count))) + } + } + + CountFocusable { + count: Count::default(), + next: f, + } +} + +pub fn focus_previous() -> impl Operation { + struct FocusPrevious { + count: Count, + current: usize, + } + + impl Operation for FocusPrevious { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + if self.count.total == 0 { + return; + } + + match self.count.focused { + None if self.current == self.count.total - 1 => state.focus(), + Some(0) if self.current == 0 => state.unfocus(), + Some(0) => {} + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused - 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count(|count| FocusPrevious { count, current: 0 }) +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: Count, + current: usize, + } + + impl Operation for FocusNext { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + match self.count.focused { + None if self.current == 0 => state.focus(), + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused + 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count(|count| FocusNext { count, current: 0 }) +} diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs new file mode 100644 index 0000000000..ed609d6744 --- /dev/null +++ b/native/src/widget/operation/scrollable.rs @@ -0,0 +1,30 @@ +use crate::widget::{Id, Operation}; + +pub trait Scrollable { + fn snap_to(&mut self, percentage: f32); +} + +pub fn snap_to(target: Id, percentage: f32) -> impl Operation { + struct SnapTo { + target: Id, + percentage: f32, + } + + impl Operation for SnapTo { + fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + if Some(&self.target) == id { + state.snap_to(self.percentage); + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + SnapTo { target, percentage } +} diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 91c13eb575..b7a0b6eeba 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -5,11 +5,12 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::touch; +use crate::widget; +use crate::widget::operation::{self, Operation}; use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, - Shell, Size, Vector, Widget, + Background, Clipboard, Color, Command, Element, Layout, Length, Point, + Rectangle, Shell, Size, Vector, Widget, }; use std::{f32, u32}; @@ -31,6 +32,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { + id: Option, height: Length, scrollbar_width: u16, scrollbar_margin: u16, @@ -48,6 +50,7 @@ where /// Creates a new [`Scrollable`]. pub fn new(content: impl Into>) -> Self { Scrollable { + id: None, height: Length::Shrink, scrollbar_width: 10, scrollbar_margin: 0, @@ -58,6 +61,12 @@ where } } + /// Sets the [`Id`] of the [`Scrollable`]. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + /// Sets the height of the [`Scrollable`]. pub fn height(mut self, height: Length) -> Self { self.height = height; @@ -157,6 +166,10 @@ where layout: Layout<'_>, operation: &mut dyn Operation, ) { + let state = tree.state.downcast_mut::(); + + operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); + operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], @@ -303,6 +316,23 @@ where } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(widget::Id::new(id)) + } + + pub fn unique() -> Self { + Self(widget::Id::unique()) + } +} + +pub fn snap_to(id: Id, percentage: f32) -> Command { + Command::widget(operation::scrollable::snap_to(id.0, percentage)) +} + /// Computes the layout of a [`Scrollable`]. pub fn layout( renderer: &Renderer, @@ -790,6 +820,12 @@ impl Default for State { } } +impl operation::Scrollable for State { + fn snap_to(&mut self, percentage: f32) { + State::snap_to(self, percentage); + } +} + /// The local state of a [`Scrollable`]. #[derive(Debug, Clone, Copy)] enum Offset { diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs index d1984a71d9..8b13789179 100644 --- a/native/src/widget/state.rs +++ b/native/src/widget/state.rs @@ -1,5 +1 @@ -pub trait Focusable { - fn is_focused(&self) -> bool; - fn focus(&mut self); - fn unfocus(&mut self); -} + diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index a81cfaed3b..e0216a5b6a 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -21,7 +21,6 @@ use crate::text::{self, Text}; use crate::touch; use crate::widget; use crate::widget::operation::{self, Operation}; -use crate::widget::state; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Color, Command, Element, Layout, Length, Padding, Point, @@ -329,7 +328,7 @@ impl Id { } pub fn focus(id: Id) -> Command { - Command::widget(operation::focus(id.0)) + Command::widget(operation::focusable::focus(id.0)) } /// Computes the layout of a [`TextInput`]. @@ -982,7 +981,7 @@ impl State { } } -impl state::Focusable for State { +impl operation::Focusable for State { fn is_focused(&self) -> bool { State::is_focused(self) } diff --git a/src/widget.rs b/src/widget.rs index c3b6d83b4a..817d2d3313 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -99,7 +99,7 @@ pub mod radio { pub mod scrollable { //! Navigate an endless amount of content with a scrollbar. pub use iced_native::widget::scrollable::{ - style::Scrollbar, style::Scroller, StyleSheet, + snap_to, style::Scrollbar, style::Scroller, Id, StyleSheet, }; /// A widget that can vertically display an infinite amount of content @@ -220,7 +220,7 @@ pub fn focus_previous() -> Command where Message: 'static, { - Command::widget(operation::focus_previous()) + Command::widget(operation::focusable::focus_previous()) } /// Focuses the next focusable widget. @@ -228,5 +228,5 @@ pub fn focus_next() -> Command where Message: 'static, { - Command::widget(operation::focus_next()) + Command::widget(operation::focusable::focus_next()) } From 66f7d43dc98df96c8b19cfd2aef6dcdd4187316c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 5 Aug 2022 05:15:41 +0200 Subject: [PATCH 16/19] Write missing documentation in `iced_native` --- native/src/lib.rs | 4 +-- native/src/widget/action.rs | 6 +++++ native/src/widget/id.rs | 5 ++++ native/src/widget/operation.rs | 31 +++++++++++++++++++++++ native/src/widget/operation/focusable.rs | 19 ++++++++++++++ native/src/widget/operation/scrollable.rs | 5 ++++ native/src/widget/scrollable.rs | 7 +++++ native/src/widget/text_input.rs | 6 +++++ 8 files changed, 81 insertions(+), 2 deletions(-) diff --git a/native/src/lib.rs b/native/src/lib.rs index 73a6c62484..131739011c 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -32,8 +32,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( -// missing_debug_implementations, -// missing_docs, + missing_debug_implementations, + missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 21032dbb98..766e902b70 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -3,13 +3,17 @@ use crate::widget::Id; use iced_futures::MaybeSend; +/// An operation to be performed on the widget tree. +#[allow(missing_debug_implementations)] pub struct Action(Box>); impl Action { + /// Creates a new [`Action`] with the given [`Operation`]. pub fn new(operation: impl Operation + 'static) -> Self { Self(Box::new(operation)) } + /// Maps the output of an [`Action`] using the given function. pub fn map( self, f: impl Fn(T) -> A + 'static + MaybeSend + Sync, @@ -24,11 +28,13 @@ impl Action { })) } + /// Consumes the [`Action`] and returns the internal [`Operation`]. pub fn into_operation(self) -> Box> { self.0 } } +#[allow(missing_debug_implementations)] struct Map { operation: Box>, f: Box B>, diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs index 4c0ab999e9..4b8fedf1e6 100644 --- a/native/src/widget/id.rs +++ b/native/src/widget/id.rs @@ -3,14 +3,19 @@ use std::sync::atomic::{self, AtomicUsize}; static NEXT_ID: AtomicUsize = AtomicUsize::new(0); +/// The identifier of a generic widget. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(Internal); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(Internal::Custom(id.into())) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 4a075da92f..ef636aa2ad 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,3 +1,4 @@ +//! Query or update internal widget state. pub mod focusable; pub mod scrollable; @@ -6,24 +7,54 @@ pub use scrollable::Scrollable; use crate::widget::Id; +use std::fmt; + +/// A piece of logic that can traverse the widget tree of an application in +/// order to query or update some widget state. pub trait Operation { + /// Operates on a widget that contains other widgets. + /// + /// The `operate_on_children` function can be called to return control to + /// the widget tree and keep traversing it. fn container( &mut self, id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); + /// Operates on a widget that can be focused. fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + /// Operates on a widget that can be scrolled. fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome { Outcome::None } } +/// The result of an [`Operation`]. pub enum Outcome { + /// The [`Operation`] produced no result. None, + + /// The [`Operation`] produced some result. Some(T), + + /// The [`Operation`] needs to be followed by another [`Operation`]. Chain(Box>), } + +impl fmt::Debug for Outcome +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "Outcome::None"), + Self::Some(output) => write!(f, "Outcome::Some({:?})", output), + Self::Chain(_) => write!(f, "Outcome::Chain(...)"), + } + } +} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index 20a732915c..d20bfef974 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -1,18 +1,30 @@ +//! Operate on widgets that can be focused. use crate::widget::operation::{Operation, Outcome}; use crate::widget::Id; +/// The internal state of a widget that can be focused. pub trait Focusable { + /// Returns whether the widget is focused or not. fn is_focused(&self) -> bool; + + /// Focuses the widget. fn focus(&mut self); + + /// Unfocuses the widget. fn unfocus(&mut self); } +/// A summary of the focusable widgets present on a widget tree. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Count { + /// The index of the current focused widget, if any. focused: Option, + + /// The total amount of focusable widgets. total: usize, } +/// Produces an [`Operation`] that focuses the widget with the given [`Id`]. pub fn focus(target: Id) -> impl Operation { struct Focus { target: Id, @@ -42,6 +54,7 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } +/// Produces an [`Operation`] that generates a [`Count`]. pub fn count(f: fn(Count) -> O) -> impl Operation where O: Operation + 'static, @@ -82,6 +95,9 @@ where } } +/// Produces an [`Operation`] that searches for the current focuses widget, and +/// - if found, focuses the previous focusable widget. +/// - if not found, focuses the last focusable widget. pub fn focus_previous() -> impl Operation { struct FocusPrevious { count: Count, @@ -118,6 +134,9 @@ pub fn focus_previous() -> impl Operation { count(|count| FocusPrevious { count, current: 0 }) } +/// Produces an [`Operation`] that searches for the current focuses widget, and +/// - if found, focuses the next focusable widget. +/// - if not found, focuses the first focusable widget. pub fn focus_next() -> impl Operation { struct FocusNext { count: Count, diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs index ed609d6744..2210137d53 100644 --- a/native/src/widget/operation/scrollable.rs +++ b/native/src/widget/operation/scrollable.rs @@ -1,9 +1,14 @@ +//! Operate on widgets that can be scrolled. use crate::widget::{Id, Operation}; +/// The internal state of a widget that can be scrolled. pub trait Scrollable { + /// Snaps the scroll of the widget to the given `percentage`. fn snap_to(&mut self, percentage: f32); } +/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to +/// the provided `percentage`. pub fn snap_to(target: Id, percentage: f32) -> impl Operation { struct SnapTo { target: Id, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b7a0b6eeba..4ebb07a065 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -316,19 +316,26 @@ where } } +/// The identifier of a [`Scrollable`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(widget::Id); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { Self(widget::Id::unique()) } } +/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] +/// to the provided `percentage`. pub fn snap_to(id: Id, percentage: f32) -> Command { Command::widget(operation::scrollable::snap_to(id.0, percentage)) } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e0216a5b6a..8ddbc73471 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -314,19 +314,25 @@ where } } +/// The identifier of a [`TextInput`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(widget::Id); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { Self(widget::Id::unique()) } } +/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. pub fn focus(id: Id) -> Command { Command::widget(operation::focusable::focus(id.0)) } From d5629c103cc6cb907b37937a591636beb003d24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 5 Aug 2022 05:50:22 +0200 Subject: [PATCH 17/19] Remove `widget::state` leftover file --- native/src/widget/state.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 native/src/widget/state.rs diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/native/src/widget/state.rs +++ /dev/null @@ -1 +0,0 @@ - From ad5bd0970d7106a97d455a164a582ab1d0bff18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 5 Aug 2022 06:01:54 +0200 Subject: [PATCH 18/19] Fix documentation in `operation::focusable` --- native/src/widget/operation/focusable.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index d20bfef974..f17bf17861 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -54,7 +54,8 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } -/// Produces an [`Operation`] that generates a [`Count`]. +/// Produces an [`Operation`] that generates a [`Count`] and chains it with the +/// provided function to build a new [`Operation`]. pub fn count(f: fn(Count) -> O) -> impl Operation where O: Operation + 'static, @@ -95,7 +96,7 @@ where } } -/// Produces an [`Operation`] that searches for the current focuses widget, and +/// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the previous focusable widget. /// - if not found, focuses the last focusable widget. pub fn focus_previous() -> impl Operation { @@ -134,7 +135,7 @@ pub fn focus_previous() -> impl Operation { count(|count| FocusPrevious { count, current: 0 }) } -/// Produces an [`Operation`] that searches for the current focuses widget, and +/// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the next focusable widget. /// - if not found, focuses the first focusable widget. pub fn focus_next() -> impl Operation { From c23ed7e4a0a2b62a0d7cabe6e35d7323eac543d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 5 Aug 2022 23:51:32 +0200 Subject: [PATCH 19/19] Lay out `UserInterface` after diffing --- native/src/user_interface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 42669f95e6..4bcf1e0c5a 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -95,12 +95,12 @@ where ) -> Self { let root = root.into(); - let base = - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); - let Cache { mut state } = cache; state.diff(root.as_widget()); + let base = + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); + UserInterface { root, base,