diff options
Diffstat (limited to 'examples')
38 files changed, 530 insertions, 650 deletions
diff --git a/examples/README.md b/examples/README.md index 71dad13e..232b6042 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,8 +1,6 @@ # Examples -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases +__Iced moves fast and the `master` branch can contain breaking changes!__ If you want to browse examples that are compatible with the latest release, +then [switch to the `latest` branch](https://github.com/iced-rs/iced/tree/latest/examples#examples). ## [Tour](tour) A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index dc2e5382..bc6c202b 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -8,6 +8,5 @@ publish = false [dependencies] iced.workspace = true iced.features = ["canvas", "tokio", "debug"] - -time = { version = "0.3", features = ["local-offset"] } +chrono = "0.4" tracing-subscriber = "0.3" diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index d717db36..7c4685c4 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment; use iced::mouse; +use iced::time; use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ @@ -18,13 +19,13 @@ pub fn main() -> iced::Result { } struct Clock { - now: time::OffsetDateTime, + now: chrono::DateTime<chrono::Local>, clock: Cache, } #[derive(Debug, Clone, Copy)] enum Message { - Tick(time::OffsetDateTime), + Tick(chrono::DateTime<chrono::Local>), } impl Clock { @@ -54,16 +55,12 @@ impl Clock { } fn subscription(&self) -> Subscription<Message> { - iced::time::every(std::time::Duration::from_millis(500)).map(|_| { - Message::Tick( - time::OffsetDateTime::now_local() - .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), - ) - }) + time::every(time::Duration::from_millis(500)) + .map(|_| Message::Tick(chrono::offset::Local::now())) } fn theme(&self) -> Theme { - Theme::ALL[(self.now.unix_timestamp() as usize / 10) % Theme::ALL.len()] + Theme::ALL[(self.now.timestamp() as usize / 10) % Theme::ALL.len()] .clone() } } @@ -71,8 +68,7 @@ impl Clock { impl Default for Clock { fn default() -> Self { Self { - now: time::OffsetDateTime::now_local() - .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + now: chrono::offset::Local::now(), clock: Cache::default(), } } @@ -89,6 +85,8 @@ impl<Message> canvas::Program<Message> for Clock { bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec<Geometry> { + use chrono::Timelike; + let clock = self.clock.draw(renderer, bounds.size(), |frame| { let palette = theme.extended_palette(); @@ -169,7 +167,7 @@ impl<Message> canvas::Program<Message> for Clock { } } -fn hand_rotation(n: u8, total: u8) -> Degrees { +fn hand_rotation(n: u32, total: u32) -> Degrees { let turns = n as f32 / total as f32; Degrees(360.0 * turns) diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index b3eee218..b53a40d6 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -165,7 +165,7 @@ impl Example { self.border_width, self.shadow ), - text(format!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}")), + text!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}"), slider(1.0..=100.0, tl, Message::RadiusTopLeftChanged).step(0.01), slider(1.0..=100.0, tr, Message::RadiusTopRightChanged).step(0.01), slider(1.0..=100.0, br, Message::RadiusBottomRightChanged) @@ -174,7 +174,7 @@ impl Example { .step(0.01), slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged) .step(0.01), - text(format!("Shadow: {sx:.2}x{sy:.2}, {sr:.2}")), + text!("Shadow: {sx:.2}x{sy:.2}, {sr:.2}"), slider(-100.0..=100.0, sx, Message::ShadowXOffsetChanged) .step(0.01), slider(-100.0..=100.0, sy, Message::ShadowYOffsetChanged) diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 261dcb81..3cf10e22 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -114,7 +114,7 @@ impl Example { fn view(&self) -> Element<Message> { let content = column![ circle(self.radius), - text(format!("Radius: {:.2}", self.radius)), + text!("Radius: {:.2}", self.radius), slider(1.0..=100.0, self.radius, Message::RadiusChanged).step(0.01), ] .padding(20) diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index e031ac44..7974d5a0 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -160,7 +160,7 @@ impl Download { .into() } State::Downloading { .. } => { - text(format!("Downloading... {current_progress:.2}%")).into() + text!("Downloading... {current_progress:.2}%").into() } State::Errored => column![ "Something went wrong :(", diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index af05031a..ec65e2fa 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{ button, column, container, horizontal_space, pick_list, row, text, text_editor, tooltip, }; -use iced::{Alignment, Command, Element, Font, Length, Subscription, Theme}; +use iced::{Alignment, Element, Font, Length, Subscription, Task, Theme}; use std::ffi; use std::io; @@ -51,26 +51,26 @@ impl Editor { } } - fn load() -> Command<Message> { - Command::perform( + fn load() -> Task<Message> { + Task::perform( load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))), Message::FileOpened, ) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::ActionPerformed(action) => { self.is_dirty = self.is_dirty || action.is_edit(); self.content.perform(action); - Command::none() + Task::none() } Message::ThemeSelected(theme) => { self.theme = theme; - Command::none() + Task::none() } Message::NewFile => { if !self.is_loading { @@ -78,15 +78,15 @@ impl Editor { self.content = text_editor::Content::new(); } - Command::none() + Task::none() } Message::OpenFile => { if self.is_loading { - Command::none() + Task::none() } else { self.is_loading = true; - Command::perform(open_file(), Message::FileOpened) + Task::perform(open_file(), Message::FileOpened) } } Message::FileOpened(result) => { @@ -98,15 +98,15 @@ impl Editor { self.content = text_editor::Content::with_text(&contents); } - Command::none() + Task::none() } Message::SaveFile => { if self.is_loading { - Command::none() + Task::none() } else { self.is_loading = true; - Command::perform( + Task::perform( save_file(self.file.clone(), self.content.text()), Message::FileSaved, ) @@ -120,7 +120,7 @@ impl Editor { self.is_dirty = false; } - Command::none() + Task::none() } } } @@ -277,7 +277,7 @@ fn action<'a, Message: Clone + 'a>( label: &'a str, on_press: Option<Message>, ) -> Element<'a, Message> { - let action = button(container(content).center_x().width(30)); + let action = button(container(content).center_x(30)); if let Some(on_press) = on_press { tooltip( diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 999ce8ef..504ed5d8 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -2,7 +2,7 @@ use iced::alignment; use iced::event::{self, Event}; use iced::widget::{button, center, checkbox, text, Column}; use iced::window; -use iced::{Alignment, Command, Element, Length, Subscription}; +use iced::{Alignment, Element, Length, Subscription, Task}; pub fn main() -> iced::Result { iced::program("Events - Iced", Events::update, Events::view) @@ -25,7 +25,7 @@ enum Message { } impl Events { - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::EventOccurred(event) if self.enabled => { self.last.push(event); @@ -34,20 +34,19 @@ impl Events { let _ = self.last.remove(0); } - Command::none() + Task::none() } Message::EventOccurred(event) => { - if let Event::Window(id, window::Event::CloseRequested) = event - { - window::close(id) + if let Event::Window(window::Event::CloseRequested) = event { + window::close(window::Id::MAIN) } else { - Command::none() + Task::none() } } Message::Toggled(enabled) => { self.enabled = enabled; - Command::none() + Task::none() } Message::Exit => window::close(window::Id::MAIN), } @@ -61,7 +60,7 @@ impl Events { let events = Column::with_children( self.last .iter() - .map(|event| text(format!("{event:?}")).size(40)) + .map(|event| text!("{event:?}").size(40)) .map(Element::from), ); diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 2de97e20..8ba180a5 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -1,6 +1,6 @@ use iced::widget::{button, center, column}; use iced::window; -use iced::{Alignment, Command, Element}; +use iced::{Alignment, Element, Task}; pub fn main() -> iced::Result { iced::program("Exit - Iced", Exit::update, Exit::view).run() @@ -18,13 +18,13 @@ enum Message { } impl Exit { - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Confirm => window::close(window::Id::MAIN), Message::Exit => { self.show_confirm = true; - Command::none() + Task::none() } } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 0716b2a4..7e6d461d 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -9,7 +9,7 @@ use iced::time; use iced::widget::{ button, checkbox, column, container, pick_list, row, slider, text, }; -use iced::{Alignment, Command, Element, Length, Subscription, Theme}; +use iced::{Alignment, Element, Length, Subscription, Task, Theme}; use std::time::Duration; pub fn main() -> iced::Result { @@ -56,7 +56,7 @@ impl GameOfLife { } } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Grid(message, version) => { if version == self.version { @@ -75,7 +75,7 @@ impl GameOfLife { let version = self.version; - return Command::perform(task, move |message| { + return Task::perform(task, move |message| { Message::Grid(message, version) }); } @@ -103,7 +103,7 @@ impl GameOfLife { } } - Command::none() + Task::none() } fn subscription(&self) -> Subscription<Message> { @@ -163,7 +163,7 @@ fn view_controls<'a>( let speed_controls = row![ slider(1.0..=1000.0, speed as f32, Message::SpeedChanged), - text(format!("x{speed}")).size(16), + text!("x{speed}").size(16), ] .align_items(Alignment::Center) .spacing(10); diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index bf7801a9..3c7969c5 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -153,7 +153,7 @@ mod rainbow { } use iced::widget::{column, container, scrollable}; -use iced::Element; +use iced::{Element, Length}; use rainbow::rainbow; pub fn main() -> iced::Result { @@ -176,7 +176,7 @@ fn view(_state: &()) -> Element<'_, ()> { .spacing(20) .max_width(500); - let scrollable = scrollable(container(content).center_x()); + let scrollable = scrollable(container(content).center_x(Length::Fill)); - container(scrollable).center_y().into() + container(scrollable).center_y(Length::Fill).into() } diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index a4a961f8..7f8feb3f 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -8,7 +8,9 @@ publish = false [dependencies] iced_winit.workspace = true iced_wgpu.workspace = true + iced_widget.workspace = true +iced_widget.features = ["wgpu"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 28050f8a..d0654996 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -2,7 +2,7 @@ use iced_wgpu::Renderer; use iced_widget::{column, container, row, slider, text, text_input}; use iced_winit::core::alignment; use iced_winit::core::{Color, Element, Length, Theme}; -use iced_winit::runtime::{Command, Program}; +use iced_winit::runtime::{Program, Task}; pub struct Controls { background_color: Color, @@ -33,7 +33,7 @@ impl Program for Controls { type Message = Message; type Renderer = Renderer; - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; @@ -43,7 +43,7 @@ impl Program for Controls { } } - Command::none() + Task::none() } fn view(&self) -> Element<Message, Theme, Renderer> { @@ -78,9 +78,7 @@ impl Program for Controls { container( column![ text("Background color").color(Color::WHITE), - text(format!("{background_color:?}")) - .size(14) - .color(Color::WHITE), + text!("{background_color:?}").size(14).color(Color::WHITE), text_input("Placeholder", &self.input) .on_input(Message::InputChanged), sliders, diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index e1c7d62f..9818adf3 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -9,7 +9,6 @@ use iced_wgpu::{wgpu, Engine, Renderer}; use iced_winit::conversion; use iced_winit::core::mouse; use iced_winit::core::renderer; -use iced_winit::core::window; use iced_winit::core::{Color, Font, Pixels, Size, Theme}; use iced_winit::futures; use iced_winit::runtime::program; @@ -317,7 +316,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { // Map window event to iced event if let Some(event) = iced_winit::conversion::window_event( - window::Id::MAIN, event, window.scale_factor(), *modifiers, diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 5f14c03b..c40ac820 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -251,7 +251,7 @@ fn application<'a>() -> Element<'a, Message> { .align_items(Alignment::Center), ) .style(container::rounded_box) - .center_y(); + .center_y(Length::Fill); let content = container( scrollable( diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index c3f6b8de..f24c0d62 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -192,7 +192,7 @@ impl App { text_input("Add a new option", &self.input) .on_input(Message::InputChanged) .on_submit(Message::AddItem(self.input.clone())), - button(text(format!("Toggle Order ({})", self.order))) + button(text!("Toggle Order ({})", self.order)) .on_press(Message::ToggleOrder) ] .spacing(10) diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index de728af2..bf70e190 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -275,7 +275,7 @@ where ) -> event::Status { let state = tree.state.downcast_mut::<State>(); - if let Event::Window(_, window::Event::RedrawRequested(now)) = event { + if let Event::Window(window::Event::RedrawRequested(now)) = event { state.animation = state.animation.timed_transition( self.cycle_duration, self.rotation_duration, diff --git a/examples/loading_spinners/src/easing.rs b/examples/loading_spinners/src/easing.rs index 665b3329..45089ef6 100644 --- a/examples/loading_spinners/src/easing.rs +++ b/examples/loading_spinners/src/easing.rs @@ -119,10 +119,7 @@ impl Builder { fn point(p: impl Into<Point>) -> lyon_algorithms::geom::Point<f32> { let p: Point = p.into(); - lyon_algorithms::geom::point( - p.x.min(1.0).max(0.0), - p.y.min(1.0).max(0.0), - ) + lyon_algorithms::geom::point(p.x.clamp(0.0, 1.0), p.y.clamp(0.0, 1.0)) } } diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index ce375621..164993c6 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -189,7 +189,7 @@ where ) -> event::Status { let state = tree.state.downcast_mut::<State>(); - if let Event::Window(_, window::Event::RedrawRequested(now)) = event { + if let Event::Window(window::Event::RedrawRequested(now)) = event { *state = state.timed_transition(self.cycle_duration, now); shell.request_redraw(RedrawRequest::NextFrame); diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index e8d67ab5..a63c51d4 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -81,7 +81,7 @@ impl LoadingSpinners { Message::CycleDurationChanged(x / 100.0) }) .width(200.0), - text(format!("{:.2}s", self.cycle_duration)), + text!("{:.2}s", self.cycle_duration), ] .align_items(iced::Alignment::Center) .spacing(20.0), diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index a012c310..d185cf3b 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ self, button, center, column, container, horizontal_space, mouse_area, opaque, pick_list, row, stack, text, text_input, }; -use iced::{Alignment, Color, Command, Element, Length, Subscription}; +use iced::{Alignment, Color, Element, Length, Subscription, Task}; use std::fmt; @@ -39,7 +39,7 @@ impl App { event::listen().map(Message::Event) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::ShowModal => { self.show_modal = true; @@ -47,26 +47,26 @@ impl App { } Message::HideModal => { self.hide_modal(); - Command::none() + Task::none() } Message::Email(email) => { self.email = email; - Command::none() + Task::none() } Message::Password(password) => { self.password = password; - Command::none() + Task::none() } Message::Plan(plan) => { self.plan = plan; - Command::none() + Task::none() } Message::Submit => { if !self.email.is_empty() && !self.password.is_empty() { self.hide_modal(); } - Command::none() + Task::none() } Message::Event(event) => match event { Event::Keyboard(keyboard::Event::KeyPressed { @@ -85,9 +85,9 @@ impl App { .. }) => { self.hide_modal(); - Command::none() + Task::none() } - _ => Command::none(), + _ => Task::none(), }, } } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index 74339e0c..b82ad1f3 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -1,16 +1,15 @@ -use iced::event; use iced::executor; use iced::multi_window::{self, Application}; use iced::widget::{ - button, center, column, container, scrollable, text, text_input, + button, center, column, container, horizontal_space, scrollable, text, + text_input, }; use iced::window; use iced::{ - Alignment, Command, Element, Length, Point, Settings, Subscription, Theme, - Vector, + Alignment, Element, Length, Settings, Subscription, Task, Theme, Vector, }; -use std::collections::HashMap; +use std::collections::BTreeMap; fn main() -> iced::Result { Example::run(Settings::default()) @@ -18,8 +17,7 @@ fn main() -> iced::Result { #[derive(Default)] struct Example { - windows: HashMap<window::Id, Window>, - next_window_pos: window::Position, + windows: BTreeMap<window::Id, Window>, } #[derive(Debug)] @@ -33,13 +31,12 @@ struct Window { #[derive(Debug, Clone)] enum Message { + OpenWindow, + WindowOpened(window::Id), + WindowClosed(window::Id), ScaleInputChanged(window::Id, String), ScaleChanged(window::Id, String), TitleChanged(window::Id, String), - CloseWindow(window::Id), - WindowOpened(window::Id, Option<Point>), - WindowClosed(window::Id), - NewWindow, } impl multi_window::Application for Example { @@ -48,13 +45,12 @@ impl multi_window::Application for Example { type Theme = Theme; type Flags = (); - fn new(_flags: ()) -> (Self, Command<Message>) { + fn new(_flags: ()) -> (Self, Task<Message>) { ( Example { - windows: HashMap::from([(window::Id::MAIN, Window::new(1))]), - next_window_pos: window::Position::Default, + windows: BTreeMap::from([(window::Id::MAIN, Window::new(1))]), }, - Command::none(), + Task::none(), ) } @@ -62,79 +58,88 @@ impl multi_window::Application for Example { self.windows .get(&window) .map(|window| window.title.clone()) - .unwrap_or("Example".to_string()) + .unwrap_or_default() } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { - Message::ScaleInputChanged(id, scale) => { - let window = - self.windows.get_mut(&id).expect("Window not found!"); - window.scale_input = scale; - - Command::none() - } - Message::ScaleChanged(id, scale) => { - let window = - self.windows.get_mut(&id).expect("Window not found!"); - - window.current_scale = scale - .parse::<f64>() - .unwrap_or(window.current_scale) - .clamp(0.5, 5.0); - - Command::none() + Message::OpenWindow => { + let Some(last_window) = self.windows.keys().last() else { + return Task::none(); + }; + + window::fetch_position(*last_window) + .then(|last_position| { + let position = last_position.map_or( + window::Position::Default, + |last_position| { + window::Position::Specific( + last_position + Vector::new(20.0, 20.0), + ) + }, + ); + + window::open(window::Settings { + position, + ..window::Settings::default() + }) + }) + .map(Message::WindowOpened) } - Message::TitleChanged(id, title) => { - let window = - self.windows.get_mut(&id).expect("Window not found."); + Message::WindowOpened(id) => { + let window = Window::new(self.windows.len() + 1); + let focus_input = text_input::focus(window.input_id.clone()); - window.title = title; + self.windows.insert(id, window); - Command::none() + focus_input } - Message::CloseWindow(id) => window::close(id), Message::WindowClosed(id) => { self.windows.remove(&id); - Command::none() + + Task::none() } - Message::WindowOpened(id, position) => { - if let Some(position) = position { - self.next_window_pos = window::Position::Specific( - position + Vector::new(20.0, 20.0), - ); + Message::ScaleInputChanged(id, scale) => { + if let Some(window) = self.windows.get_mut(&id) { + window.scale_input = scale; } - if let Some(window) = self.windows.get(&id) { - text_input::focus(window.input_id.clone()) - } else { - Command::none() - } + Task::none() } - Message::NewWindow => { - let count = self.windows.len() + 1; - - let (id, spawn_window) = window::spawn(window::Settings { - position: self.next_window_pos, - exit_on_close_request: count % 2 == 0, - ..Default::default() - }); + Message::ScaleChanged(id, scale) => { + if let Some(window) = self.windows.get_mut(&id) { + window.current_scale = scale + .parse::<f64>() + .unwrap_or(window.current_scale) + .clamp(0.5, 5.0); + } - self.windows.insert(id, Window::new(count)); + Task::none() + } + Message::TitleChanged(id, title) => { + if let Some(window) = self.windows.get_mut(&id) { + window.title = title; + } - spawn_window + Task::none() } } } - fn view(&self, window: window::Id) -> Element<Message> { - let content = self.windows.get(&window).unwrap().view(window); - - center(content).into() + fn view(&self, window_id: window::Id) -> Element<Message> { + if let Some(window) = self.windows.get(&window_id) { + center(window.view(window_id)).into() + } else { + horizontal_space().into() + } } - fn theme(&self, window: window::Id) -> Self::Theme { - self.windows.get(&window).unwrap().theme.clone() + fn theme(&self, window: window::Id) -> Theme { + if let Some(window) = self.windows.get(&window) { + window.theme.clone() + } else { + Theme::default() + } } fn scale_factor(&self, window: window::Id) -> f64 { @@ -145,22 +150,7 @@ impl multi_window::Application for Example { } fn subscription(&self) -> Subscription<Self::Message> { - event::listen_with(|event, _| { - if let iced::Event::Window(id, window_event) = event { - match window_event { - window::Event::CloseRequested => { - Some(Message::CloseWindow(id)) - } - window::Event::Opened { position, .. } => { - Some(Message::WindowOpened(id, position)) - } - window::Event::Closed => Some(Message::WindowClosed(id)), - _ => None, - } - } else { - None - } - }) + window::close_events().map(Message::WindowClosed) } } @@ -170,11 +160,7 @@ impl Window { title: format!("Window_{}", count), scale_input: "1.0".to_string(), current_scale: 1.0, - theme: if count % 2 == 0 { - Theme::Light - } else { - Theme::Dark - }, + theme: Theme::ALL[count % Theme::ALL.len()].clone(), input_id: text_input::Id::unique(), } } @@ -198,7 +184,7 @@ impl Window { ]; let new_window_button = - button(text("New Window")).on_press(Message::NewWindow); + button(text("New Window")).on_press(Message::OpenWindow); let content = scrollable( column![scale_input, title_input, new_window_button] @@ -207,6 +193,6 @@ impl Window { .align_items(Alignment::Center), ); - container(content).center_x().width(200).into() + container(content).center_x(200).into() } } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index e74ea1ee..6b5bd332 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -284,14 +284,15 @@ fn view_content<'a>( .spacing(5) .max_width(160); - let content = column![ - text(format!("{}x{}", size.width, size.height)).size(24), - controls, - ] - .spacing(10) - .align_items(Alignment::Center); - - container(scrollable(content)).center_y().padding(5).into() + let content = + column![text!("{}x{}", size.width, size.height).size(24), controls,] + .spacing(10) + .align_items(Alignment::Center); + + container(scrollable(content)) + .center_y(Length::Fill) + .padding(5) + .into() } fn view_controls<'a>( diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index be20094d..e62ed70b 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::futures; use iced::widget::{self, center, column, image, row, text}; -use iced::{Alignment, Command, Element, Length}; +use iced::{Alignment, Element, Length, Task}; pub fn main() -> iced::Result { iced::program(Pokedex::title, Pokedex::update, Pokedex::view) @@ -25,8 +25,8 @@ enum Message { } impl Pokedex { - fn search() -> Command<Message> { - Command::perform(Pokemon::search(), Message::PokemonFound) + fn search() -> Task<Message> { + Task::perform(Pokemon::search(), Message::PokemonFound) } fn title(&self) -> String { @@ -39,20 +39,20 @@ impl Pokedex { format!("{subtitle} - Pokédex") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::PokemonFound(Ok(pokemon)) => { *self = Pokedex::Loaded { pokemon }; - Command::none() + Task::none() } Message::PokemonFound(Err(_error)) => { *self = Pokedex::Errored; - Command::none() + Task::none() } Message::Search => match self { - Pokedex::Loading => Command::none(), + Pokedex::Loading => Task::none(), _ => { *self = Pokedex::Loading; @@ -104,9 +104,7 @@ impl Pokemon { column![ row![ text(&self.name).size(30).width(Length::Fill), - text(format!("#{}", self.number)) - .size(20) - .color([0.5, 0.5, 0.5]), + text!("#{}", self.number).size(20).color([0.5, 0.5, 0.5]), ] .align_items(Alignment::Center) .spacing(20), diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 82495a1a..78d3e9ff 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window; use iced::window::screenshot::{self, Screenshot}; use iced::{ - Alignment, Command, ContentFit, Element, Length, Rectangle, Subscription, + Alignment, ContentFit, Element, Length, Rectangle, Subscription, Task, }; use ::image as img; @@ -34,7 +34,7 @@ struct Example { enum Message { Crop, Screenshot, - ScreenshotData(Screenshot), + Screenshotted(Screenshot), Png, PngSaved(Result<String, PngError>), XInputChanged(Option<u32>), @@ -44,22 +44,20 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Screenshot => { - return iced::window::screenshot( - window::Id::MAIN, - Message::ScreenshotData, - ); + return iced::window::screenshot(window::Id::MAIN) + .map(Message::Screenshotted); } - Message::ScreenshotData(screenshot) => { + Message::Screenshotted(screenshot) => { self.screenshot = Some(screenshot); } Message::Png => { if let Some(screenshot) = &self.screenshot { self.png_saving = true; - return Command::perform( + return Task::perform( save_to_png(screenshot.clone()), Message::PngSaved, ); @@ -103,7 +101,7 @@ impl Example { } } - Command::none() + Task::none() } fn view(&self) -> Element<'_, Message> { @@ -123,10 +121,9 @@ impl Example { }; let image = container(image) - .center_y() + .center_y(Length::FillPortion(2)) .padding(10) - .style(container::rounded_box) - .width(Length::FillPortion(2)); + .style(container::rounded_box); let crop_origin_controls = row![ text("X:") @@ -161,7 +158,7 @@ impl Example { .push_maybe( self.crop_error .as_ref() - .map(|error| text(format!("Crop error! \n{error}"))), + .map(|error| text!("Crop error! \n{error}")), ) .spacing(10) .align_items(Alignment::Center); @@ -211,7 +208,7 @@ impl Example { .spacing(40) }; - let side_content = container(controls).center_y(); + let side_content = container(controls).center_y(Length::Fill); let content = row![side_content, image] .spacing(10) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index bbb6497f..a0dcf82c 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{ button, column, container, horizontal_space, progress_bar, radio, row, scrollable, slider, text, vertical_space, Scrollable, }; -use iced::{Alignment, Border, Color, Command, Element, Length, Theme}; +use iced::{Alignment, Border, Color, Element, Length, Task, Theme}; use once_cell::sync::Lazy; @@ -59,7 +59,7 @@ impl ScrollableDemo { } } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::SwitchDirection(direction) => { self.current_scroll_offset = scrollable::RelativeOffset::START; @@ -82,17 +82,17 @@ impl ScrollableDemo { Message::ScrollbarWidthChanged(width) => { self.scrollbar_width = width; - Command::none() + Task::none() } Message::ScrollbarMarginChanged(margin) => { self.scrollbar_margin = margin; - Command::none() + Task::none() } Message::ScrollerWidthChanged(width) => { self.scroller_width = width; - Command::none() + Task::none() } Message::ScrollToBeginning => { self.current_scroll_offset = scrollable::RelativeOffset::START; @@ -113,7 +113,7 @@ impl ScrollableDemo { Message::Scrolled(viewport) => { self.current_scroll_offset = viewport.relative_offset(); - Command::none() + Task::none() } } } diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 9cd6237f..7dd7be5e 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -54,7 +54,7 @@ impl SierpinskiEmulator { .width(Length::Fill) .height(Length::Fill), row![ - text(format!("Iteration: {:?}", self.graph.iteration)), + text!("Iteration: {:?}", self.graph.iteration), slider(0..=10000, self.graph.iteration, Message::IterationSet) ] .padding(10) diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index 5ffdc9c6..0b4c29aa 100644 --- a/examples/slider/src/main.rs +++ b/examples/slider/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{center, column, container, slider, text, vertical_slider}; -use iced::Element; +use iced::{Element, Length}; pub fn main() -> iced::Result { iced::run("Slider - Iced", Slider::update, Slider::view) @@ -56,9 +56,9 @@ impl Slider { center( column![ - container(v_slider).center_x(), - container(h_slider).center_x(), - container(text).center_x() + container(v_slider).center_x(Length::Fill), + container(h_slider).center_x(Length::Fill), + container(text).center_x(Length::Fill) ] .spacing(25), ) diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index bbe9d0ff..a8149753 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -92,13 +92,13 @@ impl Stopwatch { let seconds = self.duration.as_secs(); - let duration = text(format!( + let duration = text!( "{:0>2}:{:0>2}:{:0>2}.{:0>2}", seconds / HOUR, (seconds % HOUR) / MINUTE, seconds % MINUTE, self.duration.subsec_millis() / 10, - )) + ) .size(40); let button = |label| { diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 45b46716..e071c3af 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -45,7 +45,7 @@ impl Tiger { .on_toggle(Message::ToggleColorFilter); center( - column![svg, container(apply_color_filter).center_x()] + column![svg, container(apply_color_filter).center_x(Length::Fill)] .spacing(20) .height(Length::Fill), ) diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 89a8383a..e2808edd 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -1,5 +1,5 @@ -use iced::widget::{button, column, container, text}; -use iced::{system, Command, Element}; +use iced::widget::{button, center, column, text}; +use iced::{system, Element, Task}; pub fn main() -> iced::Result { iced::program("System Information - Iced", Example::update, Example::view) @@ -24,19 +24,20 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Refresh => { *self = Self::Loading; - return system::fetch_information(Message::InformationReceived); + return system::fetch_information() + .map(Message::InformationReceived); } Message::InformationReceived(information) => { *self = Self::Loaded { information }; } } - Command::none() + Task::none() } fn view(&self) -> Element<Message> { @@ -45,56 +46,56 @@ impl Example { let content: Element<_> = match self { Example::Loading => text("Loading...").size(40).into(), Example::Loaded { information } => { - let system_name = text(format!( + let system_name = text!( "System name: {}", information .system_name .as_ref() .unwrap_or(&"unknown".to_string()) - )); + ); - let system_kernel = text(format!( + let system_kernel = text!( "System kernel: {}", information .system_kernel .as_ref() .unwrap_or(&"unknown".to_string()) - )); + ); - let system_version = text(format!( + let system_version = text!( "System version: {}", information .system_version .as_ref() .unwrap_or(&"unknown".to_string()) - )); + ); - let system_short_version = text(format!( + let system_short_version = text!( "System short version: {}", information .system_short_version .as_ref() .unwrap_or(&"unknown".to_string()) - )); + ); let cpu_brand = - text(format!("Processor brand: {}", information.cpu_brand)); + text!("Processor brand: {}", information.cpu_brand); - let cpu_cores = text(format!( + let cpu_cores = text!( "Processor cores: {}", information .cpu_cores .map_or("unknown".to_string(), |cores| cores .to_string()) - )); + ); let memory_readable = ByteSize::b(information.memory_total).to_string(); - let memory_total = text(format!( + let memory_total = text!( "Memory (total): {} bytes ({memory_readable})", information.memory_total, - )); + ); let memory_text = if let Some(memory_used) = information.memory_used @@ -106,17 +107,13 @@ impl Example { String::from("None") }; - let memory_used = text(format!("Memory (used): {memory_text}")); + let memory_used = text!("Memory (used): {memory_text}"); - let graphics_adapter = text(format!( - "Graphics adapter: {}", - information.graphics_adapter - )); + let graphics_adapter = + text!("Graphics adapter: {}", information.graphics_adapter); - let graphics_backend = text(format!( - "Graphics backend: {}", - information.graphics_backend - )); + let graphics_backend = + text!("Graphics backend: {}", information.graphics_backend); column![ system_name.size(30), @@ -136,6 +133,6 @@ impl Example { } }; - container(content).center().into() + center(content).into() } } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 0fcf08c4..aee2479e 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -4,7 +4,7 @@ use iced::keyboard::key; use iced::widget::{ self, button, center, column, pick_list, row, slider, text, text_input, }; -use iced::{Alignment, Command, Element, Length, Subscription}; +use iced::{Alignment, Element, Length, Subscription, Task}; use toast::{Status, Toast}; @@ -49,7 +49,7 @@ impl App { event::listen().map(Message::Event) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Add => { if !self.editing.title.is_empty() @@ -57,27 +57,27 @@ impl App { { self.toasts.push(std::mem::take(&mut self.editing)); } - Command::none() + Task::none() } Message::Close(index) => { self.toasts.remove(index); - Command::none() + Task::none() } Message::Title(title) => { self.editing.title = title; - Command::none() + Task::none() } Message::Body(body) => { self.editing.body = body; - Command::none() + Task::none() } Message::Status(status) => { self.editing.status = status; - Command::none() + Task::none() } Message::Timeout(timeout) => { self.timeout_secs = timeout as u64; - Command::none() + Task::none() } Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(key::Named::Tab), @@ -88,7 +88,7 @@ impl App { key: keyboard::Key::Named(key::Named::Tab), .. })) => widget::focus_next(), - Message::Event(_) => Command::none(), + Message::Event(_) => Task::none(), } } @@ -131,7 +131,7 @@ impl App { subtitle( "Timeout", row![ - text(format!("{:0>2} sec", self.timeout_secs)), + text!("{:0>2} sec", self.timeout_secs), slider( 1.0..=30.0, self.timeout_secs as f64, @@ -347,7 +347,7 @@ mod toast { state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation<Message>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -499,9 +499,7 @@ mod toast { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - if let Event::Window(_, window::Event::RedrawRequested(now)) = - &event - { + if let Event::Window(window::Event::RedrawRequested(now)) = &event { let mut next_redraw: Option<window::RedrawRequest> = None; self.instants.iter_mut().enumerate().for_each( @@ -591,7 +589,7 @@ mod toast { &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, + operation: &mut dyn widget::Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.toasts diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 8119bc91..c21e1a96 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ scrollable, text, text_input, Text, }; use iced::window; -use iced::{Command, Element, Font, Length, Subscription}; +use iced::{Element, Font, Length, Subscription, Task as Command}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -238,7 +238,10 @@ impl Todos { .spacing(20) .max_width(800); - scrollable(container(content).center_x().padding(40)).into() + scrollable( + container(content).center_x(Length::Fill).padding(40), + ) + .into() } } } @@ -396,10 +399,10 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { }; row![ - text(format!( + text!( "{tasks_left} {} left", if tasks_left == 1 { "task" } else { "tasks" } - )) + ) .width(Length::Fill), row![ filter_button("All", Filter::All, current_filter), diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index bae6490d..78086ce9 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -21,190 +21,27 @@ pub fn main() -> iced::Result { .run() } -#[derive(Default)] pub struct Tour { - steps: Steps, + screen: Screen, + slider: u8, + layout: Layout, + spacing: u16, + text_size: u16, + text_color: Color, + language: Option<Language>, + toggler: bool, + image_width: u16, + image_filter_method: image::FilterMethod, + input_value: String, + input_is_secure: bool, + input_is_showing_icon: bool, debug: bool, } -impl Tour { - 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 controls = - row![] - .push_maybe(steps.has_previous().then(|| { - padded_button("Back") - .on_press(Message::BackPressed) - .style(button::secondary) - })) - .push(horizontal_space()) - .push_maybe(steps.can_continue().then(|| { - padded_button("Next").on_press(Message::NextPressed) - })); - - let content: Element<_> = column![ - steps.view(self.debug).map(Message::StepMessage), - controls, - ] - .max_width(540) - .spacing(20) - .padding(20) - .into(); - - let scrollable = scrollable( - container(if self.debug { - content.explain(Color::BLACK) - } else { - content - }) - .center_x(), - ); - - container(scrollable).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, - filter_method: image::FilterMethod::Linear, - }, - Step::Scrollable, - Step::TextInput { - value: String::new(), - is_secure: false, - is_showing_icon: 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() - } -} - -impl Default for Steps { - fn default() -> Self { - Steps::new() - } -} - -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, - filter_method: image::FilterMethod, - }, - Scrollable, - TextInput { - value: String, - is_secure: bool, - is_showing_icon: bool, - }, - Debugger, - End, -} - -#[derive(Debug, Clone)] -pub enum StepMessage { SliderChanged(u8), LayoutChanged(Layout), SpacingChanged(u16), @@ -220,147 +57,145 @@ pub enum StepMessage { 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; +impl Tour { + fn title(&self) -> String { + let screen = match self.screen { + Screen::Welcome => "Welcome", + Screen::Radio => "Radio button", + Screen::Toggler => "Toggler", + Screen::Slider => "Slider", + Screen::Text => "Text", + Screen::Image => "Image", + Screen::RowsAndColumns => "Rows and columns", + Screen::Scrollable => "Scrollable", + Screen::TextInput => "Text input", + Screen::Debugger => "Debugger", + Screen::End => "End", + }; + + format!("{} - Iced", screen) + } + + fn update(&mut self, event: Message) { + match event { + Message::BackPressed => { + if let Some(screen) = self.screen.previous() { + self.screen = screen; } } - StepMessage::LanguageSelected(language) => { - if let Step::Radio { selection } = self { - *selection = Some(language); + Message::NextPressed => { + if let Some(screen) = self.screen.next() { + self.screen = screen; } } - StepMessage::SliderChanged(new_value) => { - if let Step::Slider { value, .. } = self { - *value = new_value; - } + Message::SliderChanged(value) => { + self.slider = value; } - StepMessage::TextSizeChanged(new_size) => { - if let Step::Text { size, .. } = self { - *size = new_size; - } + Message::LayoutChanged(layout) => { + self.layout = layout; } - StepMessage::TextColorChanged(new_color) => { - if let Step::Text { color, .. } = self { - *color = new_color; - } + Message::SpacingChanged(spacing) => { + self.spacing = spacing; } - StepMessage::LayoutChanged(new_layout) => { - if let Step::RowsAndColumns { layout, .. } = self { - *layout = new_layout; - } + Message::TextSizeChanged(text_size) => { + self.text_size = text_size; } - StepMessage::SpacingChanged(new_spacing) => { - if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing; - } + Message::TextColorChanged(text_color) => { + self.text_color = text_color; } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width; - } + Message::LanguageSelected(language) => { + self.language = Some(language); } - StepMessage::ImageUseNearestToggled(use_nearest) => { - if let Step::Image { filter_method, .. } = self { - *filter_method = if use_nearest { - image::FilterMethod::Nearest - } else { - image::FilterMethod::Linear - }; - } + Message::ImageWidthChanged(image_width) => { + self.image_width = image_width; } - StepMessage::InputChanged(new_value) => { - if let Step::TextInput { value, .. } = self { - *value = new_value; - } + Message::ImageUseNearestToggled(use_nearest) => { + self.image_filter_method = if use_nearest { + image::FilterMethod::Nearest + } else { + image::FilterMethod::Linear + }; } - StepMessage::ToggleSecureInput(toggle) => { - if let Step::TextInput { is_secure, .. } = self { - *is_secure = toggle; - } + Message::InputChanged(input_value) => { + self.input_value = input_value; } - StepMessage::TogglerChanged(value) => { - if let Step::Toggler { can_continue, .. } = self { - *can_continue = value; - } + Message::ToggleSecureInput(is_secure) => { + self.input_is_secure = is_secure; } - StepMessage::ToggleTextInputIcon(toggle) => { - if let Step::TextInput { - is_showing_icon, .. - } = self - { - *is_showing_icon = toggle; - } + Message::ToggleTextInputIcon(show_icon) => { + self.input_is_showing_icon = show_icon; + } + Message::DebugToggled(debug) => { + self.debug = debug; + } + Message::TogglerChanged(toggler) => { + self.toggler = toggler; } - }; - } - - 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) -> Element<Message> { + let controls = + row![] + .push_maybe(self.screen.previous().is_some().then(|| { + padded_button("Back") + .on_press(Message::BackPressed) + .style(button::secondary) + })) + .push(horizontal_space()) + .push_maybe(self.can_continue().then(|| { + padded_button("Next").on_press(Message::NextPressed) + })); - 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, - filter_method, - } => Self::image(*width, *filter_method), - Step::RowsAndColumns { layout, spacing } => { - Self::rows_and_columns(*layout, *spacing) - } - Step::Scrollable => Self::scrollable(), - Step::TextInput { - value, - is_secure, - is_showing_icon, - } => Self::text_input(value, *is_secure, *is_showing_icon), - Step::Debugger => Self::debugger(debug), - Step::End => Self::end(), - } - .into() + let screen = match self.screen { + Screen::Welcome => self.welcome(), + Screen::Radio => self.radio(), + Screen::Toggler => self.toggler(), + Screen::Slider => self.slider(), + Screen::Text => self.text(), + Screen::Image => self.image(), + Screen::RowsAndColumns => self.rows_and_columns(), + Screen::Scrollable => self.scrollable(), + Screen::TextInput => self.text_input(), + Screen::Debugger => self.debugger(), + Screen::End => self.end(), + }; + + let content: Element<_> = column![screen, controls,] + .max_width(540) + .spacing(20) + .padding(20) + .into(); + + let scrollable = scrollable( + container(if self.debug { + content.explain(Color::BLACK) + } else { + content + }) + .center_x(Length::Fill), + ); + + container(scrollable).center_y(Length::Fill).into() } - fn container(title: &str) -> Column<'_, StepMessage> { - column![text(title).size(50)].spacing(20) + fn can_continue(&self) -> bool { + match self.screen { + Screen::Welcome => true, + Screen::Radio => self.language == Some(Language::Rust), + Screen::Toggler => self.toggler, + Screen::Slider => true, + Screen::Text => true, + Screen::Image => true, + Screen::RowsAndColumns => true, + Screen::Scrollable => true, + Screen::TextInput => !self.input_value.is_empty(), + Screen::Debugger => true, + Screen::End => false, + } } - fn welcome() -> Column<'a, StepMessage> { + fn welcome(&self) -> Column<Message> { Self::container("Welcome!") .push( "This is a simple tour meant to showcase a bunch of widgets \ @@ -389,7 +224,7 @@ impl<'a> Step { ) } - fn slider(value: u8) -> Column<'a, StepMessage> { + fn slider(&self) -> Column<Message> { Self::container("Slider") .push( "A slider allows you to smoothly select a value from a range \ @@ -399,47 +234,48 @@ impl<'a> Step { "The following slider lets you choose an integer from \ 0 to 100:", ) - .push(slider(0..=100, value, StepMessage::SliderChanged)) + .push(slider(0..=100, self.slider, Message::SliderChanged)) .push( - text(value.to_string()) + text(self.slider.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); + fn rows_and_columns(&self) -> Column<Message> { + let row_radio = radio( + "Row", + Layout::Row, + Some(self.layout), + Message::LayoutChanged, + ); let column_radio = radio( "Column", Layout::Column, - Some(layout), - StepMessage::LayoutChanged, + Some(self.layout), + Message::LayoutChanged, ); - let layout_section: Element<_> = match layout { + let layout_section: Element<_> = match self.layout { Layout::Row => { - row![row_radio, column_radio].spacing(spacing).into() - } - Layout::Column => { - column![row_radio, column_radio].spacing(spacing).into() + row![row_radio, column_radio].spacing(self.spacing).into() } + Layout::Column => column![row_radio, column_radio] + .spacing(self.spacing) + .into(), }; let spacing_section = column![ - slider(0..=80, spacing, StepMessage::SpacingChanged), - text(format!("{spacing} px")) + slider(0..=80, self.spacing, Message::SpacingChanged), + text!("{} px", self.spacing) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ] .spacing(10); Self::container("Rows and columns") - .spacing(spacing) + .spacing(self.spacing) .push( "Iced uses a layout model based on flexbox to position UI \ elements.", @@ -453,11 +289,14 @@ impl<'a> Step { .push(spacing_section) } - fn text(size: u16, color: Color) -> Column<'a, StepMessage> { + fn text(&self) -> Column<Message> { + let size = self.text_size; + let color = self.text_color; + let size_section = column![ "You can change its size:", - text(format!("This text is {size} pixels")).size(size), - slider(10..=70, size, StepMessage::TextSizeChanged), + text!("This text is {size} pixels").size(size), + slider(10..=70, size, Message::TextSizeChanged), ] .padding(20) .spacing(20); @@ -471,7 +310,7 @@ impl<'a> Step { let color_section = column![ "And its color:", - text(format!("{color:?}")).color(color), + text!("{color:?}").color(color), color_sliders, ] .padding(20) @@ -486,7 +325,7 @@ impl<'a> Step { .push(color_section) } - fn radio(selection: Option<Language>) -> Column<'a, StepMessage> { + fn radio(&self) -> Column<Message> { let question = column![ text("Iced is written in...").size(24), column( @@ -497,8 +336,8 @@ impl<'a> Step { radio( language, language, - selection, - StepMessage::LanguageSelected, + self.language, + Message::LanguageSelected, ) }) .map(Element::from) @@ -521,29 +360,29 @@ impl<'a> Step { ) } - fn toggler(can_continue: bool) -> Column<'a, StepMessage> { + fn toggler(&self) -> Column<Message> { 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, + self.toggler, + Message::TogglerChanged, )) .padding([0, 40]), ) } - fn image( - width: u16, - filter_method: image::FilterMethod, - ) -> Column<'a, StepMessage> { + fn image(&self) -> Column<Message> { + let width = self.image_width; + let filter_method = self.image_filter_method; + Self::container("Image") .push("An image that tries to keep its aspect ratio.") .push(ferris(width, filter_method)) - .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) + .push(slider(100..=500, width, Message::ImageWidthChanged)) .push( - text(format!("Width: {width} px")) + text!("Width: {width} px") .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) @@ -552,12 +391,12 @@ impl<'a> Step { "Use nearest interpolation", filter_method == image::FilterMethod::Nearest, ) - .on_toggle(StepMessage::ImageUseNearestToggled), + .on_toggle(Message::ImageUseNearestToggled), ) .align_items(Alignment::Center) } - fn scrollable() -> Column<'a, StepMessage> { + fn scrollable(&self) -> Column<Message> { Self::container("Scrollable") .push( "Iced supports scrollable content. Try it out! Find the \ @@ -584,13 +423,13 @@ impl<'a> Step { ) } - fn text_input( - value: &str, - is_secure: bool, - is_showing_icon: bool, - ) -> Column<'_, StepMessage> { + fn text_input(&self) -> Column<Message> { + let value = &self.input_value; + let is_secure = self.input_is_secure; + let is_showing_icon = self.input_is_showing_icon; + let mut text_input = text_input("Type something to continue...", value) - .on_input(StepMessage::InputChanged) + .on_input(Message::InputChanged) .padding(10) .size(30); @@ -609,11 +448,11 @@ impl<'a> Step { .push(text_input.secure(is_secure)) .push( checkbox("Enable password mode", is_secure) - .on_toggle(StepMessage::ToggleSecureInput), + .on_toggle(Message::ToggleSecureInput), ) .push( checkbox("Show icon", is_showing_icon) - .on_toggle(StepMessage::ToggleTextInputIcon), + .on_toggle(Message::ToggleTextInputIcon), ) .push( "A text input produces a message every time it changes. It is \ @@ -630,7 +469,7 @@ impl<'a> Step { ) } - fn debugger(debug: bool) -> Column<'a, StepMessage> { + fn debugger(&self) -> Column<Message> { Self::container("Debugger") .push( "You can ask Iced to visually explain the layouting of the \ @@ -641,23 +480,85 @@ impl<'a> Step { see element boundaries.", ) .push( - checkbox("Explain layout", debug) - .on_toggle(StepMessage::DebugToggled), + checkbox("Explain layout", self.debug) + .on_toggle(Message::DebugToggled), ) .push("Feel free to go back and take a look.") } - fn end() -> Column<'a, StepMessage> { + fn end(&self) -> Column<Message> { 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 container(title: &str) -> Column<'_, Message> { + column![text(title).size(50)].spacing(20) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Screen { + Welcome, + Slider, + RowsAndColumns, + Text, + Radio, + Toggler, + Image, + Scrollable, + TextInput, + Debugger, + End, +} + +impl Screen { + const ALL: &'static [Self] = &[ + Self::Welcome, + Self::Slider, + Self::RowsAndColumns, + Self::Text, + Self::Radio, + Self::Toggler, + Self::Image, + Self::Scrollable, + Self::TextInput, + Self::Debugger, + Self::End, + ]; + + pub fn next(self) -> Option<Screen> { + Self::ALL + .get( + Self::ALL + .iter() + .copied() + .position(|screen| screen == self) + .expect("Screen must exist") + + 1, + ) + .copied() + } + + pub fn previous(self) -> Option<Screen> { + let position = Self::ALL + .iter() + .copied() + .position(|screen| screen == self) + .expect("Screen must exist"); + + if position > 0 { + Some(Self::ALL[position - 1]) + } else { + None + } + } } fn ferris<'a>( width: u16, filter_method: image::FilterMethod, -) -> Container<'a, StepMessage> { +) -> Container<'a, Message> { container( // This should go away once we unify resource loading on native // platforms @@ -669,7 +570,7 @@ fn ferris<'a>( .filter_method(filter_method) .width(width), ) - .center_x() + .center_x(Length::Fill) } fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> { @@ -679,9 +580,9 @@ fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> { fn color_slider<'a>( component: f32, update: impl Fn(f32) -> Color + 'a, -) -> Slider<'a, f64, StepMessage> { +) -> Slider<'a, f64, Message> { slider(0.0..=1.0, f64::from(component), move |c| { - StepMessage::TextColorChanged(update(c as f32)) + Message::TextColorChanged(update(c as f32)) }) .step(0.01) } @@ -727,3 +628,24 @@ pub enum Layout { Row, Column, } + +impl Default for Tour { + fn default() -> Self { + Self { + screen: Screen::Welcome, + slider: 50, + layout: Layout::Row, + spacing: 20, + text_size: 30, + text_color: Color::BLACK, + language: None, + toggler: false, + image_width: 300, + image_filter_method: image::FilterMethod::Linear, + input_value: String::new(), + input_is_secure: false, + input_is_showing_icon: false, + debug: false, + } + } +} diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 800a188b..3ab19252 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,4 +1,4 @@ -use iced::event::{self, Event}; +use iced::event; use iced::widget::{center, text}; use iced::{Element, Subscription}; @@ -15,27 +15,20 @@ struct App { #[derive(Debug, Clone)] enum Message { - EventOccurred(Event), + UrlReceived(String), } impl App { fn update(&mut self, message: Message) { match message { - Message::EventOccurred(event) => { - if let Event::PlatformSpecific( - event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( - url, - )), - ) = event - { - self.url = Some(url); - } + Message::UrlReceived(url) => { + self.url = Some(url); } } } fn subscription(&self) -> Subscription<Message> { - event::listen().map(Message::EventOccurred) + event::listen_url().map(Message::UrlReceived) } fn view(&self) -> Element<Message> { diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index a7391e23..1ed7a2b1 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -52,11 +52,7 @@ impl VectorialText { fn view(&self) -> Element<Message> { let slider_with_label = |label, range, value, message: fn(f32) -> _| { column![ - row![ - text(label), - horizontal_space(), - text(format!("{:.2}", value)) - ], + row![text(label), horizontal_space(), text!("{:.2}", value)], slider(range, value, message).step(0.01) ] .spacing(2) diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 332b6a7b..b43c0cca 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::{ }; use iced::window; use iced::{ - Alignment, Color, Command, Element, Font, Length, Point, Rectangle, - Subscription, Theme, + Alignment, Color, Element, Font, Length, Point, Rectangle, Subscription, + Task, Theme, }; pub fn main() -> iced::Result { @@ -33,14 +33,14 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::MouseMoved(position) => { self.mouse_position = Some(position); - Command::none() + Task::none() } - Message::Scrolled | Message::WindowResized => Command::batch(vec![ + Message::Scrolled | Message::WindowResized => Task::batch(vec![ container::visible_bounds(OUTER_CONTAINER.clone()) .map(Message::OuterBoundsFetched), container::visible_bounds(INNER_CONTAINER.clone()) @@ -49,12 +49,12 @@ impl Example { Message::OuterBoundsFetched(outer_bounds) => { self.outer_bounds = outer_bounds; - Command::none() + Task::none() } Message::InnerBoundsFetched(inner_bounds) => { self.inner_bounds = inner_bounds; - Command::none() + Task::none() } } } @@ -145,11 +145,11 @@ impl Example { } fn subscription(&self) -> Subscription<Message> { - event::listen_with(|event, _| match event { + event::listen_with(|event, _status, _window| match event { Event::Mouse(mouse::Event::CursorMoved { position }) => { Some(Message::MouseMoved(position)) } - Event::Window(_, window::Event::Resized { .. }) => { + Event::Window(window::Event::Resized { .. }) => { Some(Message::WindowResized) } _ => None, diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ba1e1029..8c0fa1d0 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -4,7 +4,7 @@ use iced::alignment::{self, Alignment}; use iced::widget::{ self, button, center, column, row, scrollable, text, text_input, }; -use iced::{color, Command, Element, Length, Subscription}; +use iced::{color, Element, Length, Subscription, Task}; use once_cell::sync::Lazy; pub fn main() -> iced::Result { @@ -30,19 +30,19 @@ enum Message { } impl WebSocket { - fn load() -> Command<Message> { - Command::batch([ - Command::perform(echo::server::run(), |_| Message::Server), + fn load() -> Task<Message> { + Task::batch([ + Task::perform(echo::server::run(), |_| Message::Server), widget::focus_next(), ]) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::NewMessageChanged(new_message) => { self.new_message = new_message; - Command::none() + Task::none() } Message::Send(message) => match &mut self.state { State::Connected(connection) => { @@ -50,9 +50,9 @@ impl WebSocket { connection.send(message); - Command::none() + Task::none() } - State::Disconnected => Command::none(), + State::Disconnected => Task::none(), }, Message::Echo(event) => match event { echo::Event::Connected(connection) => { @@ -60,14 +60,14 @@ impl WebSocket { self.messages.push(echo::Message::connected()); - Command::none() + Task::none() } echo::Event::Disconnected => { self.state = State::Disconnected; self.messages.push(echo::Message::disconnected()); - Command::none() + Task::none() } echo::Event::MessageReceived(message) => { self.messages.push(message); @@ -78,7 +78,7 @@ impl WebSocket { ) } }, - Message::Server => Command::none(), + Message::Server => Task::none(), } } |