diff options
author | 2022-07-27 06:49:20 +0200 | |
---|---|---|
committer | 2022-07-27 06:49:20 +0200 | |
commit | ff2519b1d43d481987351a83b6dd7237524c21f0 (patch) | |
tree | 5731eeb7eb1247d4a8951de0d5bc5d8102640559 /examples | |
parent | c44267b85f7aaa2997e3caf1323b837d95818c22 (diff) | |
download | iced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.gz iced-ff2519b1d43d481987351a83b6dd7237524c21f0.tar.bz2 iced-ff2519b1d43d481987351a83b6dd7237524c21f0.zip |
Replace stateful widgets with new `iced_pure` API
Diffstat (limited to 'examples')
55 files changed, 1139 insertions, 5288 deletions
diff --git a/examples/pure/arc/Cargo.toml b/examples/arc/Cargo.toml index 22113cf1..299f5a3e 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 index 303253da..303253da 100644 --- a/examples/pure/arc/README.md +++ b/examples/arc/README.md diff --git a/examples/pure/arc/src/main.rs b/examples/arc/src/main.rs index df0e1e8a..df0e1e8a 100644 --- a/examples/pure/arc/src/main.rs +++ b/examples/arc/src/main.rs diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 11e4828e..7c3916d4 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<bezier::Curve>, - button_state: button::State, } #[derive(Debug, Clone, Copy)] @@ -47,44 +45,34 @@ impl Sandbox for Example { } } - fn view(&mut self) -> Element<Message> { - 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<Message> { + 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<Pending>, 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<Curve> for Bezier<'a> { + type State = Option<Pending>; + 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 48b4cd7b..8818fb54 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<Message> { - let canvas = Canvas::new(self).width(Length::Fill).height(Length::Fill); + fn view(&self) -> Element<Message> { + 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<Message> canvas::Program<Message> 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 95a23f48..f90020b1 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 cc1af750..42149965 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<Message> { + fn view(&self) -> Element<Message> { 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<Message> { + pub fn view(&self) -> Element<Message> { Canvas::new(self) .width(Length::Fill) .height(Length::Fill) @@ -236,8 +238,11 @@ impl Theme { } impl<Message> canvas::Program<Message> 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<C: ColorSpace> { - sliders: [slider::State; 3], color_space: PhantomData<C>, } @@ -282,37 +286,30 @@ trait ColorSpace: Sized { fn to_string(&self) -> String; } -impl<C: 'static + ColorSpace + Copy> ColorPicker<C> { - fn view(&mut self, color: C) -> Element<C> { +impl<C: ColorSpace + Copy> ColorPicker<C> { + fn view(&self, color: C) -> Element<C> { let [c1, c2, c3] = color.components(); - let [s1, s2, s3] = &mut self.sliders; let [cr1, cr2, cr3] = C::COMPONENT_RANGES; - fn slider<C: Clone>( - state: &mut slider::State, + fn slider<'a, C: Clone>( range: RangeInclusive<f64>, component: f32, - update: impl Fn(f32) -> C + 'static, - ) -> Slider<f64, C, iced::Renderer> { - 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 6a8f53e2..06b1e53a 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<u32>, } @@ -35,39 +36,31 @@ impl Sandbox for Component { } } - fn view(&mut self) -> Element<Message> { - Container::new(NumericInput::new( - &mut self.numeric_input, - self.value, - Message::NumericInputChanged, - )) - .padding(20) - .height(Length::Fill) - .center_y() - .into() + fn view(&self) -> Element<Message> { + 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<Message> { value: Option<u32>, on_change: Box<dyn Fn(Option<u32>) -> Message>, } - #[derive(Default)] - pub struct State { - input: text_input::State, - decrement_button: button::State, - increment_button: button::State, + pub fn numeric_input<Message>( + value: Option<u32>, + on_change: impl Fn(Option<u32>) -> Message + 'static, + ) -> NumericInput<Message> { + NumericInput::new(value, on_change) } #[derive(Debug, Clone)] @@ -77,31 +70,33 @@ mod numeric_input { DecrementPressed, } - impl<'a, Message> NumericInput<'a, Message> { + impl<Message> NumericInput<Message> { pub fn new( - state: &'a mut State, value: Option<u32>, on_change: impl Fn(Option<u32>) -> Message + 'static, ) -> Self { Self { - state, value, on_change: Box::new(on_change), } } } - impl<'a, Message, Renderer> Component<Message, Renderer> - for NumericInput<'a, Message> + impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message> 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<Message> { + fn update( + &mut self, + _state: &mut Self::State, + event: Event, + ) -> Option<Message> { 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<Event, Renderer> { - let button = |state, label, on_press| { - Button::new( - state, - Text::new(label) + fn view(&self, _state: &Self::State) -> Element<Event, Renderer> { + 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<NumericInput<'a, Message>> + impl<'a, Message, Renderer> From<NumericInput<Message>> 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<Message>) -> Self { + iced_lazy::component(numeric_input) } } } diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index e92f07f2..f5ba7fae 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<Message> { - 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<Message> { + 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 d1a7bb06..c37a1a12 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<Message, Renderer> Widget<Message, Renderer> 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<Message> { - 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<Message> { + 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 2999bc7e..3ef9ef7a 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<Download>, 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<Message> { - 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<Message> { + 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<Message> { + pub fn view(&self) -> Element<Message> { 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 c87fbc72..234e1423 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<iced_native::Event>, enabled: bool, - exit: button::State, should_exit: bool, } @@ -76,23 +75,23 @@ impl Application for Events { self.should_exit } - fn view(&mut self) -> Element<Message> { - let events = self.last.iter().fold( - Column::new().spacing(10), - |column, event| { - column.push(Text::new(format!("{:?}", event)).size(40)) - }, + fn view(&self) -> Element<Message> { + 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 c45a8205..5d518d2f 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<Message> { + fn view(&self) -> Element<Message> { 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 62ecc2d1..a2030275 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<Message> { + fn view(&self) -> Element<Message> { 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<Vector>), Ticked { result: Result<Life, TickError>, 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<Message> { + pub fn view(&self) -> Element<Message> { Canvas::new(self) .width(Length::Fill) .height(Length::Fill) @@ -329,14 +393,17 @@ mod grid { } impl canvas::Program<Message> for Grid { + type State = Interaction; + fn update( - &mut self, + &self, + interaction: &mut Interaction, event: Event, bounds: Rectangle, cursor: Cursor, ) -> (event::Status, Option<Message>) { 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<Preset>, -} -impl Controls { - fn view( - &mut self, - is_playing: bool, - is_grid_enabled: bool, - speed: usize, - preset: Preset, - ) -> Element<Message> { - 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 03eac69e..d8b99ab3 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<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow where B: Backend, @@ -50,6 +55,7 @@ mod rainbow { fn draw( &self, + _tree: &widget::Tree, renderer: &mut Renderer<B, T>, _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 fdaa29d5..076d37d3 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<Message, Renderer> { - let [r, g, b] = &mut self.sliders; + fn view(&self) -> Element<Message, Renderer> { 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 2f1daa91..6c41738c 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<Message, Renderer> { - let [r, g, b] = &mut self.sliders; - let t = &mut self.text_input; + fn view(&self) -> Element<Message, Renderer> { 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 5fbcea2c..ae8fa22b 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<Message> { + fn view(&self) -> Element<Message> { 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<Message> { } 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<Message> { - 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<Message> { - 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 52303d70..9df1f5c7 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<Language>, selected_language: Option<Language>, } @@ -38,26 +34,25 @@ impl Sandbox for Example { } } - fn view(&mut self) -> Element<Message> { - let pick_list = PickList::new( - &mut self.pick_list, + fn view(&self) -> Element<Message> { + 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 a1cf68e8..0744d991 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<Message> { 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<Message> { + fn view(&self) -> Element<Message> { 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<Message> { - 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<Message> { + 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<Pokemon, Error> { @@ -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<reqwest::Error> 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 c9a8e798..d4ebe4d3 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<Message> { - 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<Message> { + 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 d08309d5..00000000 --- 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 <clark@clarkmoody.com>"] -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 f90020b1..00000000 --- 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. - -<div align="center"> - <a href="https://gfycat.com/dirtylonebighornsheep"> - <img src="screenshot.png"> - </a> -</div> - -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 Binary files differdeleted file mode 100644 index e8da35c4..00000000 --- a/examples/pure/color_palette/screenshot.png +++ /dev/null diff --git a/examples/pure/color_palette/src/main.rs b/examples/pure/color_palette/src/main.rs deleted file mode 100644 index 8a58afa7..00000000 --- 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<Color>, - hsl: ColorPicker<palette::Hsl>, - hsv: ColorPicker<palette::Hsv>, - hwb: ColorPicker<palette::Hwb>, - lab: ColorPicker<palette::Lab>, - lch: ColorPicker<palette::Lch>, -} - -#[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<Message> { - 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<Color>, - base: Color, - higher: Vec<Color>, - canvas_cache: canvas::Cache, -} - -impl Theme { - pub fn new(base: impl Into<Color>) -> 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<Item = &Color> { - self.lower - .iter() - .chain(std::iter::once(&self.base)) - .chain(self.higher.iter()) - } - - pub fn view(&self) -> Element<Message> { - 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<Message> canvas::Program<Message> for Theme { - type State = (); - - fn draw( - &self, - _state: &Self::State, - _theme: &iced::Theme, - bounds: Rectangle, - _cursor: Cursor, - ) -> Vec<Geometry> { - 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<C: ColorSpace> { - color_space: PhantomData<C>, -} - -trait ColorSpace: Sized { - const LABEL: &'static str; - const COMPONENT_RANGES: [RangeInclusive<f64>; 3]; - - fn new(a: f32, b: f32, c: f32) -> Self; - - fn components(&self) -> [f32; 3]; - - fn to_string(&self) -> String; -} - -impl<C: ColorSpace + Copy> ColorPicker<C> { - fn view(&self, color: C) -> Element<C> { - let [c1, c2, c3] = color.components(); - let [cr1, cr2, cr3] = C::COMPONENT_RANGES; - - fn slider<'a, C: Clone>( - range: RangeInclusive<f64>, - 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<f64>; 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<f64>; 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<f64>; 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<f64>; 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<f64>; 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<f64>; 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 b6c7a513..00000000 --- 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 <hector0193@gmail.com>"] -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 db22d019..00000000 --- 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<u32>, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - NumericInputChanged(Option<u32>), -} - -impl Sandbox for Component { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Component - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::NumericInputChanged(value) => { - self.value = value; - } - } - } - - fn view(&self) -> Element<Message> { - 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<Message> { - value: Option<u32>, - on_change: Box<dyn Fn(Option<u32>) -> Message>, - } - - pub fn numeric_input<Message>( - value: Option<u32>, - on_change: impl Fn(Option<u32>) -> Message + 'static, - ) -> NumericInput<Message> { - NumericInput::new(value, on_change) - } - - #[derive(Debug, Clone)] - pub enum Event { - InputChanged(String), - IncrementPressed, - DecrementPressed, - } - - impl<Message> NumericInput<Message> { - pub fn new( - value: Option<u32>, - on_change: impl Fn(Option<u32>) -> Message + 'static, - ) -> Self { - Self { - value, - on_change: Box::new(on_change), - } - } - } - - impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message> - 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<Message> { - match event { - Event::IncrementPressed => Some((self.on_change)(Some( - self.value.unwrap_or_default().saturating_add(1), - ))), - Event::DecrementPressed => Some((self.on_change)(Some( - self.value.unwrap_or_default().saturating_sub(1), - ))), - Event::InputChanged(value) => { - if value.is_empty() { - Some((self.on_change)(None)) - } else { - value - .parse() - .ok() - .map(Some) - .map(self.on_change.as_ref()) - } - } - } - } - - fn view(&self, _state: &Self::State) -> Element<Event, Renderer> { - 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<NumericInput<Message>> - 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<Message>) -> Self { - pure::component(numeric_input) - } - } -} diff --git a/examples/pure/counter/Cargo.toml b/examples/pure/counter/Cargo.toml deleted file mode 100644 index 2fcd22d4..00000000 --- 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 <hector0193@gmail.com>"] -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 726009df..00000000 --- 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<Message> { - 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 22e38f00..00000000 --- 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 <hector0193@gmail.com>"] -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 aa39201c..00000000 --- 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. - -<div align="center"> - <a href="https://gfycat.com/WhichPaltryChick"> - <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif"> - </a> -</div> - -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 cf6560d6..00000000 --- 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<usize>, - 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<Message>) { - ( - Self { - speed: 5, - ..Self::default() - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Game of Life - Iced") - } - - fn update(&mut self, message: Message) -> Command<Message> { - 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<Message> { - if self.is_playing { - time::every(Duration::from_millis(1000 / self.speed as u64)) - .map(Message::Tick) - } else { - Subscription::none() - } - } - - fn view(&self) -> Element<Message> { - 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<Vector>), - Ticked { - result: Result<Life, TickError>, - 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<impl Future<Output = Message>> { - 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<Message> { - 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<Message> for Grid { - type State = Interaction; - - fn update( - &self, - interaction: &mut Interaction, - event: Event, - bounds: Rectangle, - cursor: Cursor, - ) -> (event::Status, Option<Message>) { - 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<Geometry> { - 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<Cell>, - 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<Item = &Cell> { - 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<impl Future<Output = Result<Life, TickError>>> { - 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<Cell>, - } - - 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<Item = &Cell> { - self.cells.iter() - } - } - - impl std::iter::FromIterator<Cell> for Life { - fn from_iter<I: IntoIterator<Item = Cell>>(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<Item = Cell> { - 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<Item = Cell> { - 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<isize> { - 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<isize> { - 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<Item = &'a Cell>, - ) -> impl Iterator<Item = &'a Cell> { - 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 964b9120..00000000 --- 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 a51cdaf0..00000000 --- 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 <hector0193@gmail.com>"] -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 e85ed78d..00000000 --- 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<Pane>, - panes_created: usize, - focus: Option<pane_grid::Pane>, -} - -#[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<Message>) { - 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<Message> { - 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<Message> { - 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<Message> { - 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<Message> { - 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 c0fcac3c..00000000 --- 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 <hector0193@gmail.com>"] -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 b9947107..00000000 --- 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<Language>, -} - -#[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<Message> { - 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 217179e8..00000000 --- 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 <hector0193@gmail.com>"] -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 9a74ea71..00000000 --- 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<Task>, - dirty: bool, - saving: bool, -} - -#[derive(Debug, Clone)] -enum Message { - Loaded(Result<SavedState, LoadError>), - 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<Message>) { - ( - 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<Message> { - 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<Message> { - 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<TaskMessage> { - 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<Message> { - 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<Task>, -} - -#[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<SavedState, LoadError> { - 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<web_sys::Storage> { - let window = web_sys::window()?; - - window.local_storage().ok()? - } - - async fn load() -> Result<SavedState, LoadError> { - 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 d84dfb37..00000000 --- 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 <hector0193@gmail.com>", "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 e9a6c111..00000000 --- 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<Message> { - 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 8ce5f198..00000000 --- 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 <hector0193@gmail.com>"] -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 05c269c3..00000000 --- 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<Message> { - 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<Step>, - 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<StepMessage> { - 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<Language> }, - 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<StepMessage> { - 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<Language>) -> 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<Language> 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 3e9ba921..6f487e4c 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<qr_code::State>, } @@ -46,13 +42,12 @@ impl Sandbox for QRGenerator { } } - fn view(&mut self) -> Element<Message> { - let title = Text::new("QR Code Generator") + fn view(&self) -> Element<Message> { + 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 f66d2180..405c8daf 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<Message> { + fn view(&self) -> Element<Message> { 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<u16>, scrollbar_margin: Option<u16>, scroller_width: Option<u16>, @@ -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 fc53d8f7..c59d73a8 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<Message> { - Canvas::new(&mut self.state) + fn view(&self) -> Element<Message> { + canvas(&self.state) .width(Length::Fill) .height(Length::Fill) .into() @@ -145,8 +146,11 @@ impl State { } impl<Message> canvas::Program<Message> 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 b7c816ff..b8cee807 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<Message> { + fn view(&self) -> Element<Message> { 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 aa90d17c..cda53e87 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<Message> { + fn view(&self) -> Element<Message> { 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 8707fa3b..27d175da 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 54fc635f..af67742f 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<Message> { - let content: Element<Message> = 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<Message> { + 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 363e3fb8..7dde235a 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<Task>, - 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<Message>) { @@ -140,26 +143,22 @@ impl Application for Todos { } } - fn view(&mut self) -> Element<Message> { + fn view(&self) -> Element<Message> { 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<TaskMessage> { - match &mut self.state { - TaskState::Idle { edit_button } => { - let checkbox = Checkbox::new( - self.completed, + fn view(&self) -> Element<TaskMessage> { + 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<Message> { - 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<Message> { + 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 7c800112..35b862a8 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<Message> { - 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<Message> { + 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 d85f2916..75d2ce08 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<Message> { - let Tour { - steps, - scroll, - back_button, - next_button, - .. - } = self; + fn view(&self) -> Element<Message> { + 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<StepMessage> { + fn view(&self, debug: bool) -> Element<StepMessage> { 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<Language>, - }, - 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<Language> }, + 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<StepMessage> { + fn view(&self, debug: bool) -> Element<StepMessage> { 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<Language>) -> 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<f64, StepMessage, iced::Renderer> { - 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 3695b6b7..3257b519 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<Message> { + fn view(&self) -> Element<Message> { 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 64addc8f..28a9de37 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<echo::Message>, - 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<Message> { - let message_log = if self.messages.is_empty() { - Container::new( - Text::new("Your messages will appear here...") + fn view(&self) -> Element<Message> { + 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) |