diff options
Diffstat (limited to 'examples')
28 files changed, 383 insertions, 237 deletions
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 949bfad7..e8f0efc9 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -57,8 +57,9 @@ impl Example { mod bezier { use iced::mouse; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; + use iced::widget::canvas::{ + self, Canvas, Event, Frame, Geometry, Path, Stroke, + }; use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -87,7 +88,7 @@ mod bezier { curves: &'a [Curve], } - impl<'a> canvas::Program<Curve> for Bezier<'a> { + impl canvas::Program<Curve> for Bezier<'_> { type State = Option<Pending>; fn update( @@ -96,48 +97,47 @@ mod bezier { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Curve>) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option<canvas::Action<Curve>> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match *state { - None => { - *state = Some(Pending::One { - from: cursor_position, - }); - - None - } - Some(Pending::One { from }) => { - *state = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - *state = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) - } - } + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => Some( + match *state { + None => { + *state = Some(Pending::One { + from: cursor_position, + }); + + canvas::Action::request_redraw() } - _ => None, - }; + Some(Pending::One { from }) => { + *state = Some(Pending::Two { + from, + to: cursor_position, + }); - (event::Status::Captured, message) + canvas::Action::request_redraw() + } + Some(Pending::Two { from, to }) => { + *state = None; + + canvas::Action::publish(Curve { + from, + to, + control: cursor_position, + }) + } + } + .and_capture(), + ), + Event::Mouse(mouse::Event::CursorMoved { .. }) + if state.is_some() => + { + Some(canvas::Action::request_redraw()) } - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/changelog/Cargo.toml b/examples/changelog/Cargo.toml index eb942235..eeb7b526 100644 --- a/examples/changelog/Cargo.toml +++ b/examples/changelog/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" publish = false +[lints.clippy] +large_enum_variant = "allow" + [dependencies] iced.workspace = true iced.features = ["tokio", "markdown", "highlighter", "debug"] diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 7f21003b..1a86b168 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -89,6 +89,7 @@ impl ColorPalette { primary: *self.theme.lower.first().unwrap(), text: *self.theme.higher.last().unwrap(), success: *self.theme.lower.last().unwrap(), + warning: *self.theme.higher.last().unwrap(), danger: *self.theme.higher.last().unwrap(), }, ) diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 22f86064..02eac329 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -10,4 +10,7 @@ iced.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["webgl"] +iced.features = ["webgl", "fira-sans"] + +[dev-dependencies] +iced_test.workspace = true diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 81684c1c..18bb8cfe 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -38,3 +38,31 @@ impl Counter { .align_x(Center) } } + +#[cfg(test)] +mod tests { + use super::*; + use iced_test::selector::text; + use iced_test::{simulator, Error}; + + #[test] + fn it_counts() -> Result<(), Error> { + let mut counter = Counter { value: 0 }; + let mut ui = simulator(counter.view()); + + let _ = ui.click(text("Increment"))?; + let _ = ui.click(text("Increment"))?; + let _ = ui.click(text("Decrement"))?; + + for message in ui.into_messages() { + counter.update(message); + } + + assert_eq!(counter.value, 1); + + let mut ui = simulator(counter.view()); + assert!(ui.find(text("1")).is_ok(), "Counter should display 1!"); + + Ok(()) + } +} diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index dc425cc6..f9c07da9 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -75,7 +75,7 @@ mod quad { } } - impl<'a, Message> From<CustomQuad> for Element<'a, Message> { + impl<Message> From<CustomQuad> for Element<'_, Message> { fn from(circle: CustomQuad) -> Self { Self::new(circle) } diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 84a3e5e2..567ab00b 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -241,7 +241,7 @@ impl Pipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[Vertex::desc(), cube::Raw::desc()], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -261,7 +261,7 @@ impl Pipeline { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState { @@ -493,7 +493,7 @@ impl DepthPipeline { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -509,7 +509,7 @@ impl DepthPipeline { multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState::REPLACE), diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 58f3c54a..d561c2e0 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -62,8 +62,8 @@ mod circle { } } - impl<'a, Message, Theme, Renderer> From<Circle> - for Element<'a, Message, Theme, Renderer> + impl<Message, Theme, Renderer> From<Circle> + for Element<'_, Message, Theme, Renderer> where Renderer: renderer::Renderer, { diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index a8e7b404..d63fb906 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,24 +1,13 @@ use iced::futures::{SinkExt, Stream, StreamExt}; use iced::stream::try_channel; -use iced::Subscription; -use std::hash::Hash; use std::sync::Arc; -// Just a little utility function -pub fn file<I: 'static + Hash + Copy + Send + Sync, T: ToString>( - id: I, - url: T, -) -> iced::Subscription<(I, Result<Progress, Error>)> { - Subscription::run_with_id( - id, - download(url.to_string()).map(move |progress| (id, progress)), - ) -} - -fn download(url: String) -> impl Stream<Item = Result<Progress, Error>> { +pub fn download( + url: impl AsRef<str>, +) -> impl Stream<Item = Result<Progress, Error>> { try_channel(1, move |mut output| async move { - let response = reqwest::get(&url).await?; + let response = reqwest::get(url.as_ref()).await?; let total = response.content_length().ok_or(Error::NoContentLength)?; let _ = output.send(Progress::Downloading { percent: 0.0 }).await; diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index bcc01606..f4b07203 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,7 +1,10 @@ mod download; +use download::download; + +use iced::task; use iced::widget::{button, center, column, progress_bar, text, Column}; -use iced::{Center, Element, Right, Subscription}; +use iced::{Center, Element, Right, Task}; pub fn main() -> iced::Result { iced::application( @@ -9,7 +12,6 @@ pub fn main() -> iced::Result { Example::update, Example::view, ) - .subscription(Example::subscription) .run() } @@ -23,7 +25,7 @@ struct Example { pub enum Message { Add, Download(usize), - DownloadProgressed((usize, Result<download::Progress, download::Error>)), + DownloadProgressed(usize, Result<download::Progress, download::Error>), } impl Example { @@ -34,32 +36,38 @@ impl Example { } } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Add => { self.last_id += 1; self.downloads.push(Download::new(self.last_id)); + + Task::none() } Message::Download(index) => { - if let Some(download) = self.downloads.get_mut(index) { - download.start(); - } + let Some(download) = self.downloads.get_mut(index) else { + return Task::none(); + }; + + let task = download.start(); + + task.map(move |progress| { + Message::DownloadProgressed(index, progress) + }) } - Message::DownloadProgressed((id, progress)) => { + Message::DownloadProgressed(id, progress) => { if let Some(download) = self.downloads.iter_mut().find(|download| download.id == id) { download.progress(progress); } + + Task::none() } } } - fn subscription(&self) -> Subscription<Message> { - Subscription::batch(self.downloads.iter().map(Download::subscription)) - } - fn view(&self) -> Element<Message> { let downloads = Column::with_children(self.downloads.iter().map(Download::view)) @@ -90,7 +98,7 @@ struct Download { #[derive(Debug)] enum State { Idle, - Downloading { progress: f32 }, + Downloading { progress: f32, _task: task::Handle }, Finished, Errored, } @@ -103,14 +111,28 @@ impl Download { } } - pub fn start(&mut self) { + pub fn start( + &mut self, + ) -> Task<Result<download::Progress, download::Error>> { match self.state { State::Idle { .. } | State::Finished { .. } | State::Errored { .. } => { - self.state = State::Downloading { progress: 0.0 }; + let (task, handle) = Task::stream(download( + "https://huggingface.co/\ + mattshumer/Reflection-Llama-3.1-70B/\ + resolve/main/model-00001-of-00162.safetensors", + )) + .abortable(); + + self.state = State::Downloading { + progress: 0.0, + _task: handle.abort_on_drop(), + }; + + task } - State::Downloading { .. } => {} + State::Downloading { .. } => Task::none(), } } @@ -118,7 +140,7 @@ impl Download { &mut self, new_progress: Result<download::Progress, download::Error>, ) { - if let State::Downloading { progress } = &mut self.state { + if let State::Downloading { progress, .. } = &mut self.state { match new_progress { Ok(download::Progress::Downloading { percent }) => { *progress = percent; @@ -133,20 +155,10 @@ impl Download { } } - pub fn subscription(&self) -> Subscription<Message> { - match self.state { - State::Downloading { .. } => { - download::file(self.id, "https://huggingface.co/mattshumer/Reflection-Llama-3.1-70B/resolve/main/model-00001-of-00162.safetensors") - .map(Message::DownloadProgressed) - } - _ => Subscription::none(), - } - } - pub fn view(&self) -> Element<Message> { let current_progress = match &self.state { State::Idle { .. } => 0.0, - State::Downloading { progress } => *progress, + State::Downloading { progress, .. } => *progress, State::Finished { .. } => 100.0, State::Errored { .. } => 0.0, }; diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9dcebecc..7a7224d5 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,8 +193,9 @@ mod grid { use iced::mouse; use iced::touch; use iced::widget::canvas; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; + use iced::widget::canvas::{ + Cache, Canvas, Event, Frame, Geometry, Path, Text, + }; use iced::{ Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -383,14 +384,12 @@ mod grid { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { + ) -> Option<canvas::Action<Message>> { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { *interaction = Interaction::None; } - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + let cursor_position = cursor.position_in(bounds)?; let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -413,7 +412,12 @@ mod grid { populate.or(unpopulate) }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => { @@ -438,7 +442,12 @@ mod grid { _ => None, }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } mouse::Event::CursorMoved { .. } => { let message = match *interaction { @@ -454,12 +463,14 @@ mod grid { Interaction::None => None, }; - let event_status = match interaction { - Interaction::None => event::Status::Ignored, - _ => event::Status::Captured, - }; + let action = message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()); - (event_status, message) + Some(match interaction { + Interaction::None => action, + _ => action.and_capture(), + }) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -496,18 +507,21 @@ mod grid { None }; - ( - event::Status::Captured, - Some(Message::Scaled(scaling, translation)), + Some( + canvas::Action::publish(Message::Scaled( + scaling, + translation, + )) + .and_capture(), ) } else { - (event::Status::Captured, None) + Some(canvas::Action::capture()) } } }, - _ => (event::Status::Ignored, None), + _ => None, }, - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 3c7969c5..d53ae6a5 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -145,7 +145,7 @@ mod rainbow { } } - impl<'a, Message> From<Rainbow> for Element<'a, Message> { + impl<Message> From<Rainbow> for Element<'_, Message> { fn from(rainbow: Rainbow) -> Self { Self::new(rainbow) } diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index b2de069f..910ea9fc 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -1,5 +1,5 @@ -use iced::application; use iced::gradient; +use iced::theme; use iced::widget::{ checkbox, column, container, horizontal_space, row, slider, text, }; @@ -95,16 +95,14 @@ impl Gradient { .into() } - fn style(&self, theme: &Theme) -> application::Appearance { - use application::DefaultStyle; - + fn style(&self, theme: &Theme) -> theme::Style { if self.transparent { - application::Appearance { + theme::Style { background_color: Color::TRANSPARENT, text_color: theme.palette().text, } } else { - Theme::default_style(theme) + theme::default(theme) } } } diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 15f97e08..7ba551aa 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -72,13 +72,13 @@ fn build_pipeline( layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &vs_module, - entry_point: "main", + entry_point: Some("main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &fs_module, - entry_point: "main", + entry_point: Some("main"), targets: &[Some(wgpu::ColorTargetState { format: texture_format, blend: Some(wgpu::BlendState { diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 4280a003..e83a1f7d 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -3,7 +3,8 @@ use iced::keyboard; use iced::mouse; use iced::widget::{ button, canvas, center, checkbox, column, container, horizontal_rule, - horizontal_space, pick_list, row, scrollable, text, vertical_rule, + horizontal_space, pick_list, pin, row, scrollable, stack, text, + vertical_rule, }; use iced::{ color, Center, Element, Fill, Font, Length, Point, Rectangle, Renderer, @@ -151,6 +152,10 @@ impl Example { title: "Quotes", view: quotes, }, + Self { + title: "Pinning", + view: pinning, + }, ]; fn is_first(self) -> bool { @@ -309,6 +314,23 @@ fn quotes<'a>() -> Element<'a, Message> { .into() } +fn pinning<'a>() -> Element<'a, Message> { + column![ + "The pin widget can be used to position a widget \ + at some fixed coordinates inside some other widget.", + stack![ + container(pin("• (50, 50)").x(50).y(50)) + .width(500) + .height(500) + .style(container::bordered_box), + pin("• (300, 300)").x(300).y(300), + ] + ] + .align_x(Center) + .spacing(10) + .into() +} + fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> { struct Square; diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 9239f01f..33232fac 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -3,11 +3,10 @@ use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; use iced::widget::canvas; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, @@ -89,7 +88,7 @@ where } } -impl<'a, Theme> Default for Circular<'a, Theme> +impl<Theme> Default for Circular<'_, Theme> where Theme: StyleSheet, { @@ -262,7 +261,7 @@ where layout::atomic(limits, self.size, self.size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -272,7 +271,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); if let Event::Window(window::Event::RedrawRequested(now)) = event { @@ -283,10 +282,8 @@ where ); state.cache.clear(); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 164993c6..a10b64f0 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -3,10 +3,9 @@ use iced::advanced::layout; use iced::advanced::renderer::{self, Quad}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{Background, Color, Element, Event, Length, Rectangle, Size}; use super::easing::{self, Easing}; @@ -71,7 +70,7 @@ where } } -impl<'a, Theme> Default for Linear<'a, Theme> +impl<Theme> Default for Linear<'_, Theme> where Theme: StyleSheet, { @@ -176,7 +175,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -186,16 +185,14 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::<State>(); if let Event::Window(window::Event::RedrawRequested(now)) = event { *state = state.timed_transition(self.cycle_duration, now); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/loupe/src/main.rs b/examples/loupe/src/main.rs index 1c748d42..6b7d053a 100644 --- a/examples/loupe/src/main.rs +++ b/examples/loupe/src/main.rs @@ -74,7 +74,7 @@ mod loupe { content: Element<'a, Message>, } - impl<'a, Message> Widget<Message, Theme, Renderer> for Loupe<'a, Message> { + impl<Message> Widget<Message, Theme, Renderer> for Loupe<'_, Message> { fn tag(&self) -> widget::tree::Tag { self.content.as_widget().tag() } diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml index 2e222dfb..3f89417f 100644 --- a/examples/multi_window/Cargo.toml +++ b/examples/multi_window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "multi-window"] } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index d5e5dffa..5f4a5c90 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -3,9 +3,8 @@ //! computers like Microsoft Surface. use iced::mouse; use iced::touch; -use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -56,25 +55,25 @@ impl canvas::Program<Message> for Multitouch { fn update( &self, _state: &mut Self::State, - event: event::Event, + event: Event, _bounds: Rectangle, _cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { - match event { - event::Event::Touch(touch_event) => match touch_event { + ) -> Option<canvas::Action<Message>> { + let message = match event { + Event::Touch( touch::Event::FingerPressed { id, position } - | touch::Event::FingerMoved { id, position } => ( - event::Status::Captured, - Some(Message::FingerPressed { id, position }), - ), + | touch::Event::FingerMoved { id, position }, + ) => Some(Message::FingerPressed { id, position }), + Event::Touch( touch::Event::FingerLifted { id, .. } - | touch::Event::FingerLost { id, .. } => ( - event::Status::Captured, - Some(Message::FingerLifted { id }), - ), - }, - _ => (event::Status::Ignored, None), - } + | touch::Event::FingerLost { id, .. }, + ) => Some(Message::FingerLifted { id }), + _ => None, + }; + + message + .map(canvas::Action::publish) + .map(canvas::Action::and_capture) } fn draw( diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 99e7900a..d4d483f5 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -1,6 +1,5 @@ use iced::mouse; -use iced::widget::canvas::event::{self, Event}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::widget::{column, row, slider, text}; use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme}; @@ -80,26 +79,22 @@ impl canvas::Program<Message> for SierpinskiGraph { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option<Message>) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option<canvas::Action<Message>> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Left, - ) => Some(Message::PointAdded(cursor_position)), - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Right, - ) => Some(Message::PointRemoved), - _ => None, - }; - (event::Status::Captured, message) - } - _ => (event::Status::Ignored, None), + Event::Mouse(mouse::Event::ButtonPressed(button)) => match button { + mouse::Button::Left => Some(canvas::Action::publish( + Message::PointAdded(cursor_position), + )), + mouse::Button::Right => { + Some(canvas::Action::publish(Message::PointRemoved)) + } + _ => None, + }, + _ => None, } + .map(canvas::Action::and_capture) } fn draw( diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 534f5e32..594be4a7 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,12 +1,14 @@ +use iced::keyboard; use iced::widget::{ button, center, checkbox, column, horizontal_rule, pick_list, progress_bar, row, scrollable, slider, text, text_input, toggler, vertical_rule, vertical_space, }; -use iced::{Center, Element, Fill, Theme}; +use iced::{Center, Element, Fill, Subscription, Theme}; pub fn main() -> iced::Result { iced::application("Styling - Iced", Styling::update, Styling::view) + .subscription(Styling::subscription) .theme(Styling::theme) .run() } @@ -28,6 +30,8 @@ enum Message { SliderChanged(f32), CheckboxToggled(bool), TogglerToggled(bool), + PreviousTheme, + NextTheme, } impl Styling { @@ -41,6 +45,23 @@ impl Styling { Message::SliderChanged(value) => self.slider_value = value, Message::CheckboxToggled(value) => self.checkbox_value = value, Message::TogglerToggled(value) => self.toggler_value = value, + Message::PreviousTheme | Message::NextTheme => { + if let Some(current) = Theme::ALL + .iter() + .position(|candidate| &self.theme == candidate) + { + self.theme = if matches!(message, Message::NextTheme) { + Theme::ALL[(current + 1) % Theme::ALL.len()].clone() + } else if current == 0 { + Theme::ALL + .last() + .expect("Theme::ALL must not be empty") + .clone() + } else { + Theme::ALL[current - 1].clone() + }; + } + } } } @@ -57,9 +78,16 @@ impl Styling { .padding(10) .size(20); - let button = button("Submit") - .padding(10) - .on_press(Message::ButtonPressed); + let styled_button = |label| { + button(text(label).width(Fill).center()) + .padding(10) + .on_press(Message::ButtonPressed) + }; + + let primary = styled_button("Primary"); + let success = styled_button("Success").style(button::success); + let warning = styled_button("Warning").style(button::warning); + let danger = styled_button("Danger").style(button::danger); let slider = slider(0.0..=100.0, self.slider_value, Message::SliderChanged); @@ -85,7 +113,10 @@ impl Styling { let content = column![ choose_theme, horizontal_rule(38), - row![text_input, button].spacing(10).align_y(Center), + text_input, + row![primary, success, warning, danger] + .spacing(10) + .align_y(Center), slider, progress_bar, row![ @@ -104,6 +135,19 @@ impl Styling { center(content).into() } + fn subscription(&self) -> Subscription<Message> { + keyboard::on_key_press(|key, _modifiers| match key { + keyboard::Key::Named( + keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowLeft, + ) => Some(Message::PreviousTheme), + keyboard::Key::Named( + keyboard::key::Named::ArrowDown + | keyboard::key::Named::ArrowRight, + ) => Some(Message::NextTheme), + _ => None, + }) + } + fn theme(&self) -> Theme { self.theme.clone() } diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 363df590..afa657d8 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -7,7 +7,7 @@ pub fn main() -> iced::Result { Example::update, Example::view, ) - .run() + .run_with(Example::new) } #[derive(Default)] @@ -28,20 +28,28 @@ enum Message { } impl Example { + fn new() -> (Self, Task<Message>) { + ( + Self::Loading, + system::fetch_information().map(Message::InformationReceived), + ) + } + fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Refresh => { - *self = Self::Loading; + let (state, refresh) = Self::new(); + + *self = state; - return system::fetch_information() - .map(Message::InformationReceived); + refresh } Message::InformationReceived(information) => { *self = Self::Loaded { information }; + + Task::none() } } - - Task::none() } fn view(&self) -> Element<Message> { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 8f6a836e..a1b5886f 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -169,7 +169,6 @@ mod toast { use iced::advanced::renderer; use iced::advanced::widget::{self, Operation, Tree}; use iced::advanced::{Clipboard, Shell, Widget}; - use iced::event::{self, Event}; use iced::mouse; use iced::theme; use iced::widget::{ @@ -177,8 +176,8 @@ mod toast { }; use iced::window; use iced::{ - Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer, - Size, Theme, Vector, + Alignment, Center, Element, Event, Fill, Length, Point, Rectangle, + Renderer, Size, Theme, Vector, }; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -282,7 +281,7 @@ mod toast { } } - impl<'a, Message> Widget<Message, Theme, Renderer> for Manager<'a, Message> { + impl<Message> Widget<Message, Theme, Renderer> for Manager<'_, Message> { fn size(&self) -> Size<Length> { self.content.as_widget().size() } @@ -359,7 +358,7 @@ mod toast { }); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -369,8 +368,8 @@ mod toast { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.content.as_widget_mut().on_event( + ) { + self.content.as_widget_mut().update( &mut state.children[0], event, layout, @@ -379,7 +378,7 @@ mod toast { clipboard, shell, viewport, - ) + ); } fn draw( @@ -465,8 +464,8 @@ mod toast { timeout_secs: u64, } - impl<'a, 'b, Message> overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message> + impl<Message> overlay::Overlay<Message, Theme, Renderer> + for Overlay<'_, '_, Message> { fn layout( &mut self, @@ -490,7 +489,7 @@ mod toast { .translate(Vector::new(self.position.x, self.position.y)) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -498,10 +497,8 @@ mod toast { renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { if let Event::Window(window::Event::RedrawRequested(now)) = &event { - let mut next_redraw: Option<window::RedrawRequest> = None; - self.instants.iter_mut().enumerate().for_each( |(index, maybe_instant)| { if let Some(instant) = maybe_instant.as_mut() { @@ -512,55 +509,43 @@ mod toast { if remaining == Duration::ZERO { maybe_instant.take(); shell.publish((self.on_close)(index)); - next_redraw = - Some(window::RedrawRequest::NextFrame); } else { - let redraw_at = - window::RedrawRequest::At(*now + remaining); - next_redraw = next_redraw - .map(|redraw| redraw.min(redraw_at)) - .or(Some(redraw_at)); + shell.request_redraw_at(*now + remaining); } } }, ); - - if let Some(redraw) = next_redraw { - shell.request_redraw(redraw); - } } let viewport = layout.bounds(); - self.toasts + for (((child, state), layout), instant) in self + .toasts .iter_mut() .zip(self.state.iter_mut()) .zip(layout.children()) .zip(self.instants.iter_mut()) - .map(|(((child, state), layout), instant)| { - let mut local_messages = vec![]; - let mut local_shell = Shell::new(&mut local_messages); - - let status = child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - &mut local_shell, - &viewport, - ); + { + let mut local_messages = vec![]; + let mut local_shell = Shell::new(&mut local_messages); - if !local_shell.is_empty() { - instant.take(); - } + child.as_widget_mut().update( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + &viewport, + ); - shell.merge(local_shell, std::convert::identity); + if !local_shell.is_empty() { + instant.take(); + } - status - }) - .fold(event::Status::Ignored, event::Status::merge) + shell.merge(local_shell, std::convert::identity); + } } fn draw( diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 0d72be86..16f4fdd2 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -20,12 +20,15 @@ tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["debug", "webgl"] +iced.features = ["debug", "webgl", "fira-sans"] uuid = { version = "1.0", features = ["js"] } web-sys = { workspace = true, features = ["Window", "Storage"] } wasm-timer.workspace = true +[dev-dependencies] +iced_test.workspace = true + [package.metadata.deb] assets = [ ["target/release-opt/todos", "usr/bin/iced-todos", "755"], diff --git a/examples/todos/snapshots/creates_a_new_task.sha256 b/examples/todos/snapshots/creates_a_new_task.sha256 new file mode 100644 index 00000000..193132c5 --- /dev/null +++ b/examples/todos/snapshots/creates_a_new_task.sha256 @@ -0,0 +1 @@ +3160686067cb7c738802009cdf2f3c5f5a5bd8c89ada70517388b7adbe64c313
\ No newline at end of file diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 25e3ead2..a5bca235 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -15,7 +15,7 @@ pub fn main() -> iced::Result { iced::application(Todos::title, Todos::update, Todos::view) .subscription(Todos::subscription) - .font(include_bytes!("../fonts/icons.ttf").as_slice()) + .font(Todos::ICON_FONT) .window_size((500.0, 800.0)) .run_with(Todos::new) } @@ -48,6 +48,8 @@ enum Message { } impl Todos { + const ICON_FONT: &'static [u8] = include_bytes!("../fonts/icons.ttf"); + fn new() -> (Self, Command<Message>) { ( Self::Loading, @@ -449,11 +451,10 @@ fn empty_message(message: &str) -> Element<'_, Message> { } // Fonts -const ICONS: Font = Font::with_name("Iced-Todos-Icons"); fn icon(unicode: char) -> Text<'static> { text(unicode.to_string()) - .font(ICONS) + .font(Font::with_name("Iced-Todos-Icons")) .width(20) .align_x(Center) } @@ -584,3 +585,49 @@ impl SavedState { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + use iced::{Settings, Theme}; + use iced_test::selector::text; + use iced_test::{Error, Simulator}; + + fn simulator(todos: &Todos) -> Simulator<Message> { + Simulator::with_settings( + Settings { + fonts: vec![Todos::ICON_FONT.into()], + ..Settings::default() + }, + todos.view(), + ) + } + + #[test] + fn it_creates_a_new_task() -> Result<(), Error> { + let (mut todos, _command) = Todos::new(); + let _command = todos.update(Message::Loaded(Err(LoadError::File))); + + let mut ui = simulator(&todos); + let _input = ui.click("new-task")?; + + let _ = ui.typewrite("Create the universe"); + let _ = ui.tap_key(keyboard::key::Named::Enter); + + for message in ui.into_messages() { + let _command = todos.update(message); + } + + let mut ui = simulator(&todos); + let _ = ui.find(text("Create the universe"))?; + + let snapshot = ui.snapshot(&Theme::Dark)?; + assert!( + snapshot.matches_hash("snapshots/creates_a_new_task")?, + "snapshots should match!" + ); + + Ok(()) + } +} diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 9e984ad1..719d355f 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -14,7 +14,7 @@ tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true -iced.features = ["image", "debug", "webgl"] +iced.features = ["image", "debug", "webgl", "fira-sans"] console_error_panic_hook = "0.1" console_log = "1.0" |