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(),          }      }  | 
