diff options
Diffstat (limited to 'examples')
58 files changed, 830 insertions, 891 deletions
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index b1e8402a..18873259 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -4,7 +4,7 @@ use iced::mouse; use iced::widget::canvas::{ self, stroke, Cache, Canvas, Geometry, Path, Stroke, }; -use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; +use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { iced::application("Arc - Iced", Arc::update, Arc::view) @@ -30,10 +30,7 @@ impl Arc { } fn view(&self) -> Element<Message> { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() + Canvas::new(self).width(Fill).height(Fill).into() } fn subscription(&self) -> Subscription<Message> { diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index eaf84b97..949bfad7 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -1,7 +1,6 @@ //! This example showcases an interactive `Canvas` for drawing Bézier curves. -use iced::alignment; use iced::widget::{button, container, horizontal_space, hover}; -use iced::{Element, Length, Theme}; +use iced::{Element, Fill, Theme}; pub fn main() -> iced::Result { iced::application("Bezier Tool - Iced", Example::update, Example::view) @@ -48,8 +47,7 @@ impl Example { .on_press(Message::Clear), ) .padding(10) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right) + .align_right(Fill) }, )) .padding(20) @@ -61,7 +59,7 @@ mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; - use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; + use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { @@ -74,8 +72,8 @@ mod bezier { state: self, curves, }) - .width(Length::Fill) - .height(Length::Fill) + .width(Fill) + .height(Fill) .into() } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 4584a0c7..ef3064c7 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,7 +4,7 @@ use iced::time; use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::{canvas, container}; use iced::{ - Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription, + Degrees, Element, Fill, Font, Point, Rectangle, Renderer, Subscription, Theme, Vector, }; @@ -43,15 +43,9 @@ impl Clock { } fn view(&self) -> Element<Message> { - let canvas = canvas(self as &Self) - .width(Length::Fill) - .height(Length::Fill); - - container(canvas) - .width(Length::Fill) - .height(Length::Fill) - .padding(20) - .into() + let canvas = canvas(self as &Self).width(Fill).height(Fill); + + container(canvas).padding(20).into() } fn subscription(&self) -> Subscription<Message> { diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index e4b19731..7f21003b 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,10 +1,10 @@ -use iced::alignment::{self, Alignment}; +use iced::alignment; use iced::mouse; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size, - Vector, + Center, Color, Element, Fill, Font, Pixels, Point, Rectangle, Renderer, + Size, Vector, }; use palette::{convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue}; use std::marker::PhantomData; @@ -150,10 +150,7 @@ impl Theme { } pub fn view(&self) -> Element<Message> { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() + Canvas::new(self).width(Fill).height(Fill).into() } fn draw(&self, frame: &mut Frame, text_color: Color) { @@ -320,7 +317,7 @@ impl<C: ColorSpace + Copy> ColorPicker<C> { text(color.to_string()).width(185).size(12), ] .spacing(10) - .align_items(Alignment::Center) + .align_y(Center) .into() } } diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index ff759ab4..af53b17a 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -1,7 +1,7 @@ use iced::widget::{ center, column, combo_box, scrollable, text, vertical_space, }; -use iced::{Alignment, Element, Length}; +use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { iced::run("Combo Box - Iced", Example::update, Example::view) @@ -64,8 +64,8 @@ impl Example { combo_box, vertical_space().height(150), ] - .width(Length::Fill) - .align_items(Alignment::Center) + .width(Fill) + .align_x(Center) .spacing(10); center(scrollable(content)).into() diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs deleted file mode 100644 index 5625f12a..00000000 --- a/examples/component/src/main.rs +++ /dev/null @@ -1,156 +0,0 @@ -use iced::widget::center; -use iced::Element; - -use numeric_input::numeric_input; - -pub fn main() -> iced::Result { - iced::run("Component - Iced", Component::update, Component::view) -} - -#[derive(Default)] -struct Component { - value: Option<u32>, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - NumericInputChanged(Option<u32>), -} - -impl Component { - fn update(&mut self, message: Message) { - match message { - Message::NumericInputChanged(value) => { - self.value = value; - } - } - } - - fn view(&self) -> Element<Message> { - center(numeric_input(self.value, Message::NumericInputChanged)) - .padding(20) - .into() - } -} - -mod numeric_input { - use iced::alignment::{self, Alignment}; - use iced::widget::{button, component, row, text, text_input, Component}; - use iced::{Element, Length, Size}; - - pub struct NumericInput<Message> { - value: Option<u32>, - on_change: Box<dyn Fn(Option<u32>) -> Message>, - } - - pub fn numeric_input<Message>( - value: Option<u32>, - on_change: impl Fn(Option<u32>) -> Message + 'static, - ) -> NumericInput<Message> { - NumericInput::new(value, on_change) - } - - #[derive(Debug, Clone)] - pub enum Event { - InputChanged(String), - IncrementPressed, - DecrementPressed, - } - - impl<Message> NumericInput<Message> { - pub fn new( - value: Option<u32>, - on_change: impl Fn(Option<u32>) -> Message + 'static, - ) -> Self { - Self { - value, - on_change: Box::new(on_change), - } - } - } - - impl<Message, Theme> Component<Message, Theme> for NumericInput<Message> - where - Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static, - { - type State = (); - type Event = Event; - - fn update( - &mut self, - _state: &mut Self::State, - event: Event, - ) -> Option<Message> { - match event { - Event::IncrementPressed => Some((self.on_change)(Some( - self.value.unwrap_or_default().saturating_add(1), - ))), - Event::DecrementPressed => Some((self.on_change)(Some( - self.value.unwrap_or_default().saturating_sub(1), - ))), - Event::InputChanged(value) => { - if value.is_empty() { - Some((self.on_change)(None)) - } else { - value - .parse() - .ok() - .map(Some) - .map(self.on_change.as_ref()) - } - } - } - } - - fn view(&self, _state: &Self::State) -> Element<'_, Event, Theme> { - let button = |label, on_press| { - button( - text(label) - .width(Length::Fill) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center), - ) - .width(40) - .height(40) - .on_press(on_press) - }; - - row![ - button("-", Event::DecrementPressed), - text_input( - "Type a number", - self.value - .as_ref() - .map(u32::to_string) - .as_deref() - .unwrap_or(""), - ) - .on_input(Event::InputChanged) - .padding(10), - button("+", Event::IncrementPressed), - ] - .align_items(Alignment::Center) - .spacing(10) - .into() - } - - fn size_hint(&self) -> Size<Length> { - Size { - width: Length::Fill, - height: Length::Shrink, - } - } - } - - impl<'a, Message, Theme> From<NumericInput<Message>> - for Element<'a, Message, Theme> - where - Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static, - Message: 'a, - { - fn from(numeric_input: NumericInput<Message>) -> Self { - component(numeric_input) - } - } -} diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 0dd7a976..81684c1c 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{button, column, text, Column}; -use iced::Alignment; +use iced::Center; pub fn main() -> iced::Result { iced::run("A cool counter", Counter::update, Counter::view) @@ -35,6 +35,6 @@ impl Counter { button("Decrement").on_press(Message::Decrement) ] .padding(20) - .align_items(Alignment::Center) + .align_x(Center) } } diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index b53a40d6..dc425cc6 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -3,12 +3,13 @@ mod quad { use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{self, Widget}; + use iced::border; use iced::mouse; use iced::{Border, Color, Element, Length, Rectangle, Shadow, Size}; pub struct CustomQuad { size: f32, - radius: [f32; 4], + radius: border::Radius, border_width: f32, shadow: Shadow, } @@ -16,7 +17,7 @@ mod quad { impl CustomQuad { pub fn new( size: f32, - radius: [f32; 4], + radius: border::Radius, border_width: f32, shadow: Shadow, ) -> Self { @@ -63,7 +64,7 @@ mod quad { renderer::Quad { bounds: layout.bounds(), border: Border { - radius: self.radius.into(), + radius: self.radius, width: self.border_width, color: Color::from_rgb(1.0, 0.0, 0.0), }, @@ -81,15 +82,16 @@ mod quad { } } +use iced::border; use iced::widget::{center, column, slider, text}; -use iced::{Alignment, Color, Element, Shadow, Vector}; +use iced::{Center, Color, Element, Shadow, Vector}; pub fn main() -> iced::Result { iced::run("Custom Quad - Iced", Example::update, Example::view) } struct Example { - radius: [f32; 4], + radius: border::Radius, border_width: f32, shadow: Shadow, } @@ -110,7 +112,7 @@ enum Message { impl Example { fn new() -> Self { Self { - radius: [50.0; 4], + radius: border::radius(50), border_width: 0.0, shadow: Shadow { color: Color::from_rgba(0.0, 0.0, 0.0, 0.8), @@ -121,19 +123,18 @@ impl Example { } fn update(&mut self, message: Message) { - let [tl, tr, br, bl] = self.radius; match message { Message::RadiusTopLeftChanged(radius) => { - self.radius = [radius, tr, br, bl]; + self.radius = self.radius.top_left(radius); } Message::RadiusTopRightChanged(radius) => { - self.radius = [tl, radius, br, bl]; + self.radius = self.radius.top_right(radius); } Message::RadiusBottomRightChanged(radius) => { - self.radius = [tl, tr, radius, bl]; + self.radius = self.radius.bottom_right(radius); } Message::RadiusBottomLeftChanged(radius) => { - self.radius = [tl, tr, br, radius]; + self.radius = self.radius.bottom_left(radius); } Message::BorderWidthChanged(width) => { self.border_width = width; @@ -151,7 +152,13 @@ impl Example { } fn view(&self) -> Element<Message> { - let [tl, tr, br, bl] = self.radius; + let border::Radius { + top_left, + top_right, + bottom_right, + bottom_left, + } = self.radius; + let Shadow { offset: Vector { x: sx, y: sy }, blur_radius: sr, @@ -165,12 +172,12 @@ impl Example { self.border_width, self.shadow ), - 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) + text!("Radius: {top_left:.2}/{top_right:.2}/{bottom_right:.2}/{bottom_left:.2}"), + slider(1.0..=100.0, top_left, Message::RadiusTopLeftChanged).step(0.01), + slider(1.0..=100.0, top_right, Message::RadiusTopRightChanged).step(0.01), + slider(1.0..=100.0, bottom_right, Message::RadiusBottomRightChanged) .step(0.01), - slider(1.0..=100.0, bl, Message::RadiusBottomLeftChanged) + slider(1.0..=100.0, bottom_left, Message::RadiusBottomLeftChanged) .step(0.01), slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged) .step(0.01), @@ -185,7 +192,7 @@ impl Example { .padding(20) .spacing(20) .max_width(500) - .align_items(Alignment::Center); + .align_x(Center); center(content).into() } diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index b04a8183..5886f6bb 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -6,7 +6,7 @@ use iced::time::Instant; use iced::widget::shader::wgpu; use iced::widget::{center, checkbox, column, row, shader, slider, text}; use iced::window; -use iced::{Alignment, Color, Element, Length, Subscription}; +use iced::{Center, Color, Element, Fill, Subscription}; fn main() -> iced::Result { iced::application( @@ -122,12 +122,11 @@ impl IcedCubes { let controls = column![top_controls, bottom_controls,] .spacing(10) .padding(20) - .align_items(Alignment::Center); + .align_x(Center); - let shader = - shader(&self.scene).width(Length::Fill).height(Length::Fill); + let shader = shader(&self.scene).width(Fill).height(Fill); - center(column![shader, controls].align_items(Alignment::Center)).into() + center(column![shader, controls].align_x(Center)).into() } fn subscription(&self) -> Subscription<Message> { diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 3cf10e22..58f3c54a 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -1,19 +1,11 @@ //! This example showcases a simple native custom widget that draws a circle. mod circle { - // For now, to implement a custom native widget you will need to add - // `iced_native` and `iced_wgpu` to your dependencies. - // - // Then, you simply need to define your widget type and implement the - // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. - // - // Of course, you can choose to make the implementation renderer-agnostic, - // if you wish to, by creating your own `Renderer` trait, which could be - // implemented by `iced_wgpu` and other renderers. use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{self, Widget}; + use iced::border; use iced::mouse; - use iced::{Border, Color, Element, Length, Rectangle, Size}; + use iced::{Color, Element, Length, Rectangle, Size}; pub struct Circle { radius: f32, @@ -62,7 +54,7 @@ mod circle { renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border: Border::rounded(self.radius), + border: border::rounded(self.radius), ..renderer::Quad::default() }, Color::BLACK, @@ -83,7 +75,7 @@ mod circle { use circle::circle; use iced::widget::{center, column, slider, text}; -use iced::{Alignment, Element}; +use iced::{Center, Element}; pub fn main() -> iced::Result { iced::run("Custom Widget - Iced", Example::update, Example::view) @@ -120,7 +112,7 @@ impl Example { .padding(20) .spacing(20) .max_width(500) - .align_items(Alignment::Center); + .align_x(Center); center(content).into() } diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 18a49f66..61a1b257 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -10,6 +10,6 @@ iced.workspace = true iced.features = ["tokio"] [dependencies.reqwest] -version = "0.11" +version = "0.12" default-features = false -features = ["rustls-tls"] +features = ["stream", "rustls-tls"] diff --git a/examples/download_progress/index.html b/examples/download_progress/index.html new file mode 100644 index 00000000..c79e32c1 --- /dev/null +++ b/examples/download_progress/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en" style="height: 100%"> +<head> + <meta charset="utf-8" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Download_Progress - Iced</title> + <base data-trunk-public-url /> +</head> +<body style="height: 100%; margin: 0"> +<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="download_progress" /> +</body> +</html> diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index d6cc1e24..a8e7b404 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,85 +1,62 @@ -use iced::subscription; +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, Progress)> { - subscription::unfold(id, State::Ready(url.to_string()), move |state| { - download(id, state) - }) +) -> iced::Subscription<(I, Result<Progress, Error>)> { + Subscription::run_with_id( + id, + download(url.to_string()).map(move |progress| (id, progress)), + ) } -async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) { - match state { - State::Ready(url) => { - let response = reqwest::get(&url).await; +fn download(url: String) -> impl Stream<Item = Result<Progress, Error>> { + try_channel(1, move |mut output| async move { + let response = reqwest::get(&url).await?; + let total = response.content_length().ok_or(Error::NoContentLength)?; - match response { - Ok(response) => { - if let Some(total) = response.content_length() { - ( - (id, Progress::Started), - State::Downloading { - response, - total, - downloaded: 0, - }, - ) - } else { - ((id, Progress::Errored), State::Finished) - } - } - Err(_) => ((id, Progress::Errored), State::Finished), - } - } - State::Downloading { - mut response, - total, - downloaded, - } => match response.chunk().await { - Ok(Some(chunk)) => { - let downloaded = downloaded + chunk.len() as u64; + let _ = output.send(Progress::Downloading { percent: 0.0 }).await; + + let mut byte_stream = response.bytes_stream(); + let mut downloaded = 0; - let percentage = (downloaded as f32 / total as f32) * 100.0; + while let Some(next_bytes) = byte_stream.next().await { + let bytes = next_bytes?; + downloaded += bytes.len(); - ( - (id, Progress::Advanced(percentage)), - State::Downloading { - response, - total, - downloaded, - }, - ) - } - Ok(None) => ((id, Progress::Finished), State::Finished), - Err(_) => ((id, Progress::Errored), State::Finished), - }, - State::Finished => { - // We do not let the stream die, as it would start a - // new download repeatedly if the user is not careful - // in case of errors. - iced::futures::future::pending().await + let _ = output + .send(Progress::Downloading { + percent: 100.0 * downloaded as f32 / total as f32, + }) + .await; } - } + + let _ = output.send(Progress::Finished).await; + + Ok(()) + }) } #[derive(Debug, Clone)] pub enum Progress { - Started, - Advanced(f32), + Downloading { percent: f32 }, Finished, - Errored, } -pub enum State { - Ready(String), - Downloading { - response: reqwest::Response, - total: u64, - downloaded: u64, - }, - Finished, +#[derive(Debug, Clone)] +pub enum Error { + RequestFailed(Arc<reqwest::Error>), + NoContentLength, +} + +impl From<reqwest::Error> for Error { + fn from(error: reqwest::Error) -> Self { + Error::RequestFailed(Arc::new(error)) + } } diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index d91e5eab..bcc01606 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,7 +1,7 @@ mod download; use iced::widget::{button, center, column, progress_bar, text, Column}; -use iced::{Alignment, Element, Subscription}; +use iced::{Center, Element, Right, Subscription}; pub fn main() -> iced::Result { iced::application( @@ -23,7 +23,7 @@ struct Example { pub enum Message { Add, Download(usize), - DownloadProgressed((usize, download::Progress)), + DownloadProgressed((usize, Result<download::Progress, download::Error>)), } impl Example { @@ -69,7 +69,7 @@ impl Example { .padding(10), ) .spacing(20) - .align_items(Alignment::End); + .align_x(Right); center(downloads).padding(20).into() } @@ -114,19 +114,19 @@ impl Download { } } - pub fn progress(&mut self, new_progress: download::Progress) { + pub fn progress( + &mut self, + new_progress: Result<download::Progress, download::Error>, + ) { if let State::Downloading { progress } = &mut self.state { match new_progress { - download::Progress::Started => { - *progress = 0.0; + Ok(download::Progress::Downloading { percent }) => { + *progress = percent; } - download::Progress::Advanced(percentage) => { - *progress = percentage; - } - download::Progress::Finished => { + Ok(download::Progress::Finished) => { self.state = State::Finished; } - download::Progress::Errored => { + Err(_error) => { self.state = State::Errored; } } @@ -136,7 +136,7 @@ impl Download { pub fn subscription(&self) -> Subscription<Message> { match self.state { State::Downloading { .. } => { - download::file(self.id, "https://speed.hetzner.de/100MB.bin?") + 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(), @@ -160,7 +160,7 @@ impl Download { State::Finished => { column!["Download finished!", button("Start again")] .spacing(10) - .align_items(Alignment::Center) + .align_x(Center) .into() } State::Downloading { .. } => { @@ -171,14 +171,14 @@ impl Download { button("Try again").on_press(Message::Download(self.id)), ] .spacing(10) - .align_items(Alignment::Center) + .align_x(Center) .into(), }; Column::new() .spacing(10) .padding(10) - .align_items(Alignment::Center) + .align_x(Center) .push(progress_bar) .push(control) .into() diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index bed9d94a..d55f9bdf 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,10 +1,10 @@ -use iced::highlighter::{self, Highlighter}; +use iced::highlighter; use iced::keyboard; use iced::widget::{ - button, column, container, horizontal_space, pick_list, row, text, - text_editor, tooltip, + self, button, column, container, horizontal_space, pick_list, row, text, + text_editor, toggler, tooltip, }; -use iced::{Alignment, Element, Font, Length, Subscription, Task, Theme}; +use iced::{Center, Element, Fill, Font, Task, Theme}; use std::ffi; use std::io; @@ -13,18 +13,17 @@ use std::sync::Arc; pub fn main() -> iced::Result { iced::application("Editor - Iced", Editor::update, Editor::view) - .load(Editor::load) - .subscription(Editor::subscription) .theme(Editor::theme) .font(include_bytes!("../fonts/icons.ttf").as_slice()) .default_font(Font::MONOSPACE) - .run() + .run_with(Editor::new) } struct Editor { file: Option<PathBuf>, content: text_editor::Content, theme: highlighter::Theme, + word_wrap: bool, is_loading: bool, is_dirty: bool, } @@ -33,6 +32,7 @@ struct Editor { enum Message { ActionPerformed(text_editor::Action), ThemeSelected(highlighter::Theme), + WordWrapToggled(bool), NewFile, OpenFile, FileOpened(Result<(PathBuf, Arc<String>), Error>), @@ -41,20 +41,26 @@ enum Message { } impl Editor { - fn new() -> Self { - Self { - file: None, - content: text_editor::Content::new(), - theme: highlighter::Theme::SolarizedDark, - is_loading: true, - is_dirty: false, - } - } - - fn load() -> Task<Message> { - Task::perform( - load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))), - Message::FileOpened, + fn new() -> (Self, Task<Message>) { + ( + Self { + file: None, + content: text_editor::Content::new(), + theme: highlighter::Theme::SolarizedDark, + word_wrap: true, + is_loading: true, + is_dirty: false, + }, + Task::batch([ + Task::perform( + load_file(format!( + "{}/src/main.rs", + env!("CARGO_MANIFEST_DIR") + )), + Message::FileOpened, + ), + widget::focus_next(), + ]), ) } @@ -72,6 +78,11 @@ impl Editor { Task::none() } + Message::WordWrapToggled(word_wrap) => { + self.word_wrap = word_wrap; + + Task::none() + } Message::NewFile => { if !self.is_loading { self.file = None; @@ -125,15 +136,6 @@ impl Editor { } } - fn subscription(&self) -> Subscription<Message> { - keyboard::on_key_press(|key, modifiers| match key.as_ref() { - keyboard::Key::Character("s") if modifiers.command() => { - Some(Message::SaveFile) - } - _ => None, - }) - } - fn view(&self) -> Element<Message> { let controls = row![ action(new_icon(), "New file", Some(Message::NewFile)), @@ -148,6 +150,9 @@ impl Editor { self.is_dirty.then_some(Message::SaveFile) ), horizontal_space(), + toggler(self.word_wrap) + .label("Word Wrap") + .on_toggle(Message::WordWrapToggled), pick_list( highlighter::Theme::ALL, Some(self.theme), @@ -157,7 +162,7 @@ impl Editor { .padding([5, 10]) ] .spacing(10) - .align_items(Alignment::Center); + .align_y(Center); let status = row![ text(if let Some(path) = &self.file { @@ -183,21 +188,33 @@ impl Editor { column![ controls, text_editor(&self.content) - .height(Length::Fill) + .height(Fill) .on_action(Message::ActionPerformed) - .highlight::<Highlighter>( - highlighter::Settings { - theme: self.theme, - extension: self - .file - .as_deref() - .and_then(Path::extension) - .and_then(ffi::OsStr::to_str) - .map(str::to_string) - .unwrap_or(String::from("rs")), - }, - |highlight, _theme| highlight.to_format() - ), + .wrapping(if self.word_wrap { + text::Wrapping::Word + } else { + text::Wrapping::None + }) + .highlight( + self.file + .as_deref() + .and_then(Path::extension) + .and_then(ffi::OsStr::to_str) + .unwrap_or("rs"), + self.theme, + ) + .key_binding(|key_press| { + match key_press.key.as_ref() { + keyboard::Key::Character("s") + if key_press.modifiers.command() => + { + Some(text_editor::Binding::Custom( + Message::SaveFile, + )) + } + _ => text_editor::Binding::from_key_press(key_press), + } + }), status, ] .spacing(10) @@ -214,12 +231,6 @@ impl Editor { } } -impl Default for Editor { - fn default() -> Self { - Self::new() - } -} - #[derive(Debug, Clone)] pub enum Error { DialogClosed, diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 2cd3c5d8..5bada9b5 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,8 +1,7 @@ -use iced::alignment; use iced::event::{self, Event}; use iced::widget::{button, center, checkbox, text, Column}; use iced::window; -use iced::{Alignment, Element, Length, Subscription, Task}; +use iced::{Center, Element, Fill, Subscription, Task}; pub fn main() -> iced::Result { iced::application("Events - Iced", Events::update, Events::view) @@ -67,17 +66,13 @@ impl Events { let toggle = checkbox("Listen to runtime events", self.enabled) .on_toggle(Message::Toggled); - let exit = button( - text("Exit") - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .width(100) - .padding(10) - .on_press(Message::Exit); + let exit = button(text("Exit").width(Fill).align_x(Center)) + .width(100) + .padding(10) + .on_press(Message::Exit); let content = Column::new() - .align_items(Alignment::Center) + .align_x(Center) .spacing(20) .push(events) .push(toggle) diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 1f108df2..48b0864c 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, Element, Task}; +use iced::{Center, Element, Task}; pub fn main() -> iced::Result { iced::application("Exit - Iced", Exit::update, Exit::view).run() @@ -44,7 +44,7 @@ impl Exit { ] } .spacing(10) - .align_items(Alignment::Center); + .align_x(Center); center(content).padding(20).into() } diff --git a/examples/ferris/src/main.rs b/examples/ferris/src/main.rs index 88006898..eaf51354 100644 --- a/examples/ferris/src/main.rs +++ b/examples/ferris/src/main.rs @@ -4,8 +4,8 @@ use iced::widget::{ }; use iced::window; use iced::{ - Alignment, Color, ContentFit, Degrees, Element, Length, Radians, Rotation, - Subscription, Theme, + Bottom, Center, Color, ContentFit, Degrees, Element, Fill, Radians, + Rotation, Subscription, Theme, }; pub fn main() -> iced::Result { @@ -108,7 +108,7 @@ impl Image { "I am Ferris!" ] .spacing(20) - .align_items(Alignment::Center); + .align_x(Center); let fit = row![ pick_list( @@ -122,7 +122,7 @@ impl Image { Some(self.content_fit), Message::ContentFitChanged ) - .width(Length::Fill), + .width(Fill), pick_list( [RotationStrategy::Floating, RotationStrategy::Solid], Some(match self.rotation { @@ -131,10 +131,10 @@ impl Image { }), Message::RotationStrategyChanged, ) - .width(Length::Fill), + .width(Fill), ] .spacing(10) - .align_items(Alignment::End); + .align_y(Bottom); let properties = row![ with_value( @@ -159,12 +159,12 @@ impl Image { .size(12) ] .spacing(10) - .align_items(Alignment::Center), + .align_y(Center), format!("Rotation: {:.0}°", f32::from(self.rotation.degrees())) ) ] .spacing(10) - .align_items(Alignment::End); + .align_y(Bottom); container(column![fit, center(i_am_ferris), properties].spacing(10)) .padding(10) @@ -206,6 +206,6 @@ fn with_value<'a>( ) -> Element<'a, Message> { column![control.into(), text(value).size(12).line_height(1.0)] .spacing(2) - .align_items(Alignment::Center) + .align_x(Center) .into() } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 421f862a..9dcebecc 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, Element, Length, Subscription, Task, Theme}; +use iced::{Center, Element, Fill, Subscription, Task, Theme}; use std::time::Duration; pub fn main() -> iced::Result { @@ -135,12 +135,9 @@ impl GameOfLife { .map(move |message| Message::Grid(message, version)), controls, ] - .height(Length::Fill); + .height(Fill); - container(content) - .width(Length::Fill) - .height(Length::Fill) - .into() + container(content).width(Fill).height(Fill).into() } } @@ -169,7 +166,7 @@ fn view_controls<'a>( slider(1.0..=1000.0, speed as f32, Message::SpeedChanged), text!("x{speed}").size(16), ] - .align_items(Alignment::Center) + .align_y(Center) .spacing(10); row![ @@ -186,7 +183,7 @@ fn view_controls<'a>( ] .padding(10) .spacing(20) - .align_items(Alignment::Center) + .align_y(Center) .into() } @@ -199,7 +196,7 @@ mod grid { use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; use iced::{ - Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, + Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -333,10 +330,7 @@ mod grid { } pub fn view(&self) -> Element<Message> { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() + Canvas::new(self).width(Fill).height(Fill).into() } pub fn clear(&mut self) { diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index e5b19443..b2de069f 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -3,7 +3,7 @@ use iced::gradient; use iced::widget::{ checkbox, column, container, horizontal_space, row, slider, text, }; -use iced::{Alignment, Color, Element, Length, Radians, Theme}; +use iced::{Center, Color, Element, Fill, Radians, Theme}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); @@ -67,8 +67,8 @@ impl Gradient { gradient.into() }) - .width(Length::Fill) - .height(Length::Fill); + .width(Fill) + .height(Fill); let angle_picker = row![ text("Angle").width(64), @@ -77,7 +77,7 @@ impl Gradient { ] .spacing(8) .padding(8) - .align_items(Alignment::Center); + .align_y(Center); let transparency_toggle = iced::widget::Container::new( checkbox("Transparent window", transparent) @@ -129,6 +129,6 @@ fn color_picker(label: &str, color: Color) -> Element<'_, Color> { ] .spacing(8) .padding(8) - .align_items(Alignment::Center) + .align_y(Center) .into() } diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index d0654996..0b11a323 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,7 +1,6 @@ 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::core::{Color, Element, Length::*, Theme}; use iced_winit::runtime::{Program, Task}; pub struct Controls { @@ -86,8 +85,7 @@ impl Program for Controls { .spacing(10), ) .padding(10) - .height(Length::Fill) - .align_y(alignment::Vertical::Bottom) + .align_bottom(Fill) .into() } } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 9818adf3..5b64cbd1 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -68,7 +68,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { Size::new(physical_size.width, physical_size.height), window.scale_factor(), ); - let clipboard = Clipboard::connect(&window); + let clipboard = Clipboard::connect(window.clone()); let backend = wgpu::util::backend_bits_from_env().unwrap_or_default(); diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 2e774415..cb33369b 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -1,3 +1,4 @@ +use iced::border; use iced::keyboard; use iced::mouse; use iced::widget::{ @@ -5,7 +6,7 @@ use iced::widget::{ pick_list, row, scrollable, text, }; use iced::{ - color, Alignment, Element, Font, Length, Point, Rectangle, Renderer, + color, Center, Element, Fill, Font, Length, Point, Rectangle, Renderer, Subscription, Theme, }; @@ -74,7 +75,7 @@ impl Layout { pick_list(Theme::ALL, Some(&self.theme), Message::ThemeSelected), ] .spacing(20) - .align_items(Alignment::Center); + .align_y(Center); let example = center(if self.explain { self.example.view().explain(color!(0x0000ff)) @@ -85,7 +86,7 @@ impl Layout { let palette = theme.extended_palette(); container::Style::default() - .with_border(palette.background.strong.color, 4.0) + .border(border::color(palette.background.strong.color).width(4)) }) .padding(4); @@ -234,13 +235,13 @@ fn application<'a>() -> Element<'a, Message> { square(40), ] .padding(10) - .align_items(Alignment::Center), + .align_y(Center), ) .style(|theme| { let palette = theme.extended_palette(); container::Style::default() - .with_border(palette.background.strong.color, 1) + .border(border::color(palette.background.strong.color).width(1)) }); let sidebar = container( @@ -248,25 +249,26 @@ fn application<'a>() -> Element<'a, Message> { .spacing(40) .padding(10) .width(200) - .align_items(Alignment::Center), + .align_x(Center), ) .style(container::rounded_box) - .center_y(Length::Fill); + .center_y(Fill); let content = container( scrollable( column![ "Content!", - square(400), - square(200), - square(400), + row((1..10).map(|i| square(if i % 2 == 0 { 80 } else { 160 }))) + .spacing(20) + .align_y(Center) + .wrap(), "The end" ] .spacing(40) - .align_items(Alignment::Center) - .width(Length::Fill), + .align_x(Center) + .width(Fill), ) - .height(Length::Fill), + .height(Fill), ) .padding(10); diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index f24c0d62..8f756210 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -2,7 +2,7 @@ use iced::widget::{ button, column, horizontal_space, lazy, pick_list, row, scrollable, text, text_input, }; -use iced::{Element, Length}; +use iced::{Element, Fill}; use std::collections::HashSet; use std::hash::Hash; @@ -187,7 +187,7 @@ impl App { }); column![ - scrollable(options).height(Length::Fill), + scrollable(options).height(Fill), row![ text_input("Add a new option", &self.input) .on_input(Message::InputChanged) diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index 503f2d7a..3b178148 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{center, column, row, slider, text}; -use iced::Element; +use iced::{Center, Element}; use std::time::Duration; @@ -67,7 +67,7 @@ impl LoadingSpinners { Duration::from_secs_f32(self.cycle_duration) ) ] - .align_items(iced::Alignment::Center) + .align_y(Center) .spacing(20.0), ) }) @@ -83,7 +83,7 @@ impl LoadingSpinners { .width(200.0), text!("{:.2}s", self.cycle_duration), ] - .align_items(iced::Alignment::Center) + .align_y(Center) .spacing(20.0), ), ) diff --git a/examples/loupe/src/main.rs b/examples/loupe/src/main.rs index c4d3b449..1c748d42 100644 --- a/examples/loupe/src/main.rs +++ b/examples/loupe/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{button, center, column, text}; -use iced::{Alignment, Element}; +use iced::{Center, Element}; use loupe::loupe; @@ -39,7 +39,7 @@ impl Loupe { button("Decrement").on_press(Message::Decrement) ] .padding(20) - .align_items(Alignment::Center), + .align_x(Center), )) .into() } diff --git a/examples/component/Cargo.toml b/examples/markdown/Cargo.toml index 83b7b8a4..cb74b954 100644 --- a/examples/component/Cargo.toml +++ b/examples/markdown/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "component" +name = "markdown" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" @@ -7,4 +7,6 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "lazy"] +iced.features = ["markdown", "highlighter", "debug"] + +open = "5.3" diff --git a/examples/markdown/overview.md b/examples/markdown/overview.md new file mode 100644 index 00000000..66336c5b --- /dev/null +++ b/examples/markdown/overview.md @@ -0,0 +1,93 @@ +# Overview + +Inspired by [The Elm Architecture], Iced expects you to split user interfaces into four different concepts: + +* __State__ — the state of your application +* __Messages__ — user interactions or meaningful events that you care about +* __View logic__ — a way to display your __state__ as widgets that may produce __messages__ on user interaction +* __Update logic__ — a way to react to __messages__ and update your __state__ + +We can build something to see how this works! Let's say we want a simple counter that can be incremented and decremented using two buttons. + +We start by modelling the __state__ of our application: + +```rust +#[derive(Default)] +struct Counter { + value: i32, +} +``` + +Next, we need to define the possible user interactions of our counter: the button presses. These interactions are our __messages__: + +```rust +#[derive(Debug, Clone, Copy)] +pub enum Message { + Increment, + Decrement, +} +``` + +Now, let's show the actual counter by putting it all together in our __view logic__: + +```rust +use iced::widget::{button, column, text, Column}; + +impl Counter { + pub fn view(&self) -> Column<Message> { + // We use a column: a simple vertical layout + column![ + // The increment button. We tell it to produce an + // `Increment` message when pressed + button("+").on_press(Message::Increment), + + // We show the value of the counter here + text(self.value).size(50), + + // The decrement button. We tell it to produce a + // `Decrement` message when pressed + button("-").on_press(Message::Decrement), + ] + } +} +``` + +Finally, we need to be able to react to any produced __messages__ and change our __state__ accordingly in our __update logic__: + +```rust +impl Counter { + // ... + + pub fn update(&mut self, message: Message) { + match message { + Message::Increment => { + self.value += 1; + } + Message::Decrement => { + self.value -= 1; + } + } + } +} +``` + +And that's everything! We just wrote a whole user interface. Let's run it: + +```rust +fn main() -> iced::Result { + iced::run("A cool counter", Counter::update, Counter::view) +} +``` + +Iced will automatically: + + 1. Take the result of our __view logic__ and layout its widgets. + 1. Process events from our system and produce __messages__ for our __update logic__. + 1. Draw the resulting user interface. + +Read the [book], the [documentation], and the [examples] to learn more! + +[book]: https://book.iced.rs/ +[documentation]: https://docs.rs/iced/ +[examples]: https://github.com/iced-rs/iced/tree/master/examples#examples +[The Elm Architecture]: https://guide.elm-lang.org/architecture/ diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs new file mode 100644 index 00000000..5605478f --- /dev/null +++ b/examples/markdown/src/main.rs @@ -0,0 +1,82 @@ +use iced::highlighter; +use iced::widget::{self, markdown, row, scrollable, text_editor}; +use iced::{Element, Fill, Font, Task, Theme}; + +pub fn main() -> iced::Result { + iced::application("Markdown - Iced", Markdown::update, Markdown::view) + .theme(Markdown::theme) + .run_with(Markdown::new) +} + +struct Markdown { + content: text_editor::Content, + items: Vec<markdown::Item>, + theme: Theme, +} + +#[derive(Debug, Clone)] +enum Message { + Edit(text_editor::Action), + LinkClicked(markdown::Url), +} + +impl Markdown { + fn new() -> (Self, Task<Message>) { + const INITIAL_CONTENT: &str = include_str!("../overview.md"); + + let theme = Theme::TokyoNight; + + ( + Self { + content: text_editor::Content::with_text(INITIAL_CONTENT), + items: markdown::parse(INITIAL_CONTENT).collect(), + theme, + }, + widget::focus_next(), + ) + } + + fn update(&mut self, message: Message) { + match message { + Message::Edit(action) => { + let is_edit = action.is_edit(); + + self.content.perform(action); + + if is_edit { + self.items = + markdown::parse(&self.content.text()).collect(); + } + } + Message::LinkClicked(link) => { + let _ = open::that_in_background(link.to_string()); + } + } + } + + fn view(&self) -> Element<Message> { + let editor = text_editor(&self.content) + .placeholder("Type your Markdown here...") + .on_action(Message::Edit) + .height(Fill) + .padding(10) + .font(Font::MONOSPACE) + .highlight("markdown", highlighter::Theme::Base16Ocean); + + let preview = markdown( + &self.items, + markdown::Settings::default(), + markdown::Style::from_palette(self.theme.palette()), + ) + .map(Message::LinkClicked); + + row![editor, scrollable(preview).spacing(10).height(Fill)] + .spacing(10) + .padding(10) + .into() + } + + fn theme(&self) -> Theme { + self.theme.clone() + } +} diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 413485e7..067ca24d 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, Element, Length, Subscription, Task}; +use iced::{Bottom, Color, Element, Fill, Subscription, Task}; use std::fmt; @@ -96,18 +96,17 @@ impl App { let content = container( column![ row![text("Top Left"), horizontal_space(), text("Top Right")] - .align_items(Alignment::Start) - .height(Length::Fill), + .height(Fill), center(button(text("Show Modal")).on_press(Message::ShowModal)), row![ text("Bottom Left"), horizontal_space(), text("Bottom Right") ] - .align_items(Alignment::End) - .height(Length::Fill), + .align_y(Bottom) + .height(Fill), ] - .height(Length::Fill), + .height(Fill), ) .padding(10); @@ -202,19 +201,21 @@ where { stack![ base.into(), - mouse_area(center(opaque(content)).style(|_theme| { - container::Style { - background: Some( - Color { - a: 0.8, - ..Color::BLACK - } - .into(), - ), - ..container::Style::default() - } - })) - .on_press(on_blur) + opaque( + mouse_area(center(opaque(content)).style(|_theme| { + container::Style { + background: Some( + Color { + a: 0.8, + ..Color::BLACK + } + .into(), + ), + ..container::Style::default() + } + })) + .on_press(on_blur) + ) ] .into() } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index 98e753ab..ab09116e 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -3,22 +3,18 @@ use iced::widget::{ text_input, }; use iced::window; -use iced::{Alignment, Element, Length, Subscription, Task, Theme, Vector}; +use iced::{Center, Element, Fill, Subscription, Task, Theme, Vector}; use std::collections::BTreeMap; fn main() -> iced::Result { iced::daemon(Example::title, Example::update, Example::view) - .load(|| { - window::open(window::Settings::default()).map(Message::WindowOpened) - }) .subscription(Example::subscription) .theme(Example::theme) .scale_factor(Example::scale_factor) - .run() + .run_with(Example::new) } -#[derive(Default)] struct Example { windows: BTreeMap<window::Id, Window>, } @@ -43,6 +39,17 @@ enum Message { } impl Example { + fn new() -> (Self, Task<Message>) { + let (_id, open) = window::open(window::Settings::default()); + + ( + Self { + windows: BTreeMap::new(), + }, + open.map(Message::WindowOpened), + ) + } + fn title(&self, window: window::Id) -> String { self.windows .get(&window) @@ -68,10 +75,12 @@ impl Example { }, ); - window::open(window::Settings { + let (_id, open) = window::open(window::Settings { position, ..window::Settings::default() - }) + }); + + open }) .map(Message::WindowOpened) } @@ -182,8 +191,8 @@ impl Window { let content = scrollable( column![scale_input, title_input, new_window_button] .spacing(50) - .width(Length::Fill) - .align_items(Alignment::Center), + .width(Fill) + .align_x(Center), ); container(content).center_x(200).into() diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 69717310..a0105a8a 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -6,7 +6,7 @@ use iced::touch; use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{self, Canvas, Geometry}; -use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme}; +use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -46,10 +46,7 @@ impl Multitouch { } fn view(&self) -> Element<Message> { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() + Canvas::new(self).width(Fill).height(Fill).into() } } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index db9f7a05..67f4d27f 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,10 +1,9 @@ -use iced::alignment::{self, Alignment}; use iced::keyboard; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{ button, column, container, responsive, row, scrollable, text, }; -use iced::{Color, Element, Length, Size, Subscription}; +use iced::{Center, Color, Element, Fill, Size, Subscription}; pub fn main() -> iced::Result { iced::application("Pane Grid - Iced", Example::update, Example::view) @@ -155,11 +154,23 @@ impl Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(view_controls( - id, - total_panes, - pane.is_pinned, - is_maximized, + .controls(pane_grid::Controls::dynamic( + view_controls( + id, + total_panes, + pane.is_pinned, + is_maximized, + ), + button(text("X").size(14)) + .style(button::danger) + .padding(3) + .on_press_maybe( + if total_panes > 1 && !pane.is_pinned { + Some(Message::Close(id)) + } else { + None + }, + ), )) .padding(10) .style(if is_focused { @@ -178,16 +189,16 @@ impl Example { style::pane_active }) }) - .width(Length::Fill) - .height(Length::Fill) + .width(Fill) + .height(Fill) .spacing(10) .on_click(Message::Clicked) .on_drag(Message::Dragged) .on_resize(10, Message::Resized); container(pane_grid) - .width(Length::Fill) - .height(Length::Fill) + .width(Fill) + .height(Fill) .padding(10) .into() } @@ -255,15 +266,10 @@ fn view_content<'a>( size: Size, ) -> Element<'a, Message> { let button = |label, message| { - button( - text(label) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .size(16), - ) - .width(Length::Fill) - .padding(8) - .on_press(message) + button(text(label).width(Fill).align_x(Center).size(16)) + .width(Fill) + .padding(8) + .on_press(message) }; let controls = column![ @@ -287,10 +293,10 @@ fn view_content<'a>( let content = column![text!("{}x{}", size.width, size.height).size(24), controls,] .spacing(10) - .align_items(Alignment::Center); + .align_x(Center); container(scrollable(content)) - .center_y(Length::Fill) + .center_y(Fill) .padding(5) .into() } diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 2be6f5b0..d8b2b389 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{column, pick_list, scrollable, vertical_space}; -use iced::{Alignment, Element, Length}; +use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { iced::run("Pick List - Iced", Example::update, Example::view) @@ -38,8 +38,8 @@ impl Example { pick_list, vertical_space().height(600), ] - .width(Length::Fill) - .align_items(Alignment::Center) + .width(Fill) + .align_x(Center) .spacing(10); scrollable(content).into() diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index bf7e1e35..1a6d5445 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -16,7 +16,7 @@ version = "1.0" features = ["derive"] [dependencies.reqwest] -version = "0.11" +version = "0.12" default-features = false features = ["json", "rustls-tls"] diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index b22ffe7f..2e972f6b 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,20 +1,16 @@ use iced::futures; use iced::widget::{self, center, column, image, row, text}; -use iced::{Alignment, Element, Length, Task}; +use iced::{Center, Element, Fill, Right, Task}; pub fn main() -> iced::Result { iced::application(Pokedex::title, Pokedex::update, Pokedex::view) - .load(Pokedex::search) - .run() + .run_with(Pokedex::new) } -#[derive(Debug, Default)] +#[derive(Debug)] enum Pokedex { - #[default] Loading, - Loaded { - pokemon: Pokemon, - }, + Loaded { pokemon: Pokemon }, Errored, } @@ -25,6 +21,10 @@ enum Message { } impl Pokedex { + fn new() -> (Self, Task<Message>) { + (Self::Loading, Self::search()) + } + fn search() -> Task<Message> { Task::perform(Pokemon::search(), Message::PokemonFound) } @@ -63,10 +63,9 @@ impl Pokedex { } fn view(&self) -> Element<Message> { - let content = match self { + let content: Element<_> = match self { Pokedex::Loading => { - column![text("Searching for Pokémon...").size(40),] - .width(Length::Shrink) + text("Searching for Pokémon...").size(40).into() } Pokedex::Loaded { pokemon } => column![ pokemon.view(), @@ -74,13 +73,15 @@ impl Pokedex { ] .max_width(500) .spacing(20) - .align_items(Alignment::End), + .align_x(Right) + .into(), Pokedex::Errored => column![ text("Whoops! Something went wrong...").size(40), button("Try again").on_press(Message::Search) ] .spacing(20) - .align_items(Alignment::End), + .align_x(Right) + .into(), }; center(content).into() @@ -103,17 +104,17 @@ impl Pokemon { image::viewer(self.image.clone()), column![ row![ - text(&self.name).size(30).width(Length::Fill), + text(&self.name).size(30).width(Fill), text!("#{}", self.number).size(20).color([0.5, 0.5, 0.5]), ] - .align_items(Alignment::Center) + .align_y(Center) .spacing(20), self.description.as_ref(), ] .spacing(20), ] .spacing(20) - .align_items(Alignment::Center) + .align_y(Center) .into() } diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs index b30ecf15..f1b654e0 100644 --- a/examples/qr_code/src/main.rs +++ b/examples/qr_code/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{center, column, pick_list, qr_code, row, text, text_input}; -use iced::{Alignment, Element, Theme}; +use iced::{Center, Element, Theme}; pub fn main() -> iced::Result { iced::application( @@ -58,7 +58,7 @@ impl QRGenerator { pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged,) ] .spacing(10) - .align_items(Alignment::Center); + .align_y(Center); let content = column![title, input, choose_theme] .push_maybe( @@ -68,7 +68,7 @@ impl QRGenerator { ) .width(700) .spacing(20) - .align_items(Alignment::Center); + .align_x(Center); center(content).padding(20).into() } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index acde8367..5c105f6c 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -1,10 +1,10 @@ -use iced::alignment; use iced::keyboard; use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window; use iced::window::screenshot::{self, Screenshot}; use iced::{ - Alignment, ContentFit, Element, Length, Rectangle, Subscription, Task, + Center, ContentFit, Element, Fill, FillPortion, Rectangle, Subscription, + Task, }; use ::image as img; @@ -20,7 +20,7 @@ fn main() -> iced::Result { #[derive(Default)] struct Example { - screenshot: Option<Screenshot>, + screenshot: Option<(Screenshot, image::Handle)>, saved_png_path: Option<Result<String, PngError>>, png_saving: bool, crop_error: Option<screenshot::CropError>, @@ -52,10 +52,17 @@ impl Example { .map(Message::Screenshotted); } Message::Screenshotted(screenshot) => { - self.screenshot = Some(screenshot); + self.screenshot = Some(( + screenshot.clone(), + image::Handle::from_rgba( + screenshot.size.width, + screenshot.size.height, + screenshot.bytes, + ), + )); } Message::Png => { - if let Some(screenshot) = &self.screenshot { + if let Some((screenshot, _handle)) = &self.screenshot { self.png_saving = true; return Task::perform( @@ -81,7 +88,7 @@ impl Example { self.height_input_value = new_value; } Message::Crop => { - if let Some(screenshot) = &self.screenshot { + if let Some((screenshot, _handle)) = &self.screenshot { let cropped = screenshot.crop(Rectangle::<u32> { x: self.x_input_value.unwrap_or(0), y: self.y_input_value.unwrap_or(0), @@ -91,7 +98,14 @@ impl Example { match cropped { Ok(screenshot) => { - self.screenshot = Some(screenshot); + self.screenshot = Some(( + screenshot.clone(), + image::Handle::from_rgba( + screenshot.size.width, + screenshot.size.height, + screenshot.bytes, + ), + )); self.crop_error = None; } Err(crop_error) => { @@ -106,53 +120,41 @@ impl Example { } fn view(&self) -> Element<'_, Message> { - let image: Element<Message> = if let Some(screenshot) = &self.screenshot - { - image(image::Handle::from_rgba( - screenshot.size.width, - screenshot.size.height, - screenshot.clone(), - )) - .content_fit(ContentFit::Contain) - .width(Length::Fill) - .height(Length::Fill) - .into() - } else { - text("Press the button to take a screenshot!").into() - }; + let image: Element<Message> = + if let Some((_screenshot, handle)) = &self.screenshot { + image(handle) + .content_fit(ContentFit::Contain) + .width(Fill) + .height(Fill) + .into() + } else { + text("Press the button to take a screenshot!").into() + }; let image = container(image) - .center_y(Length::FillPortion(2)) + .center_y(FillPortion(2)) .padding(10) .style(container::rounded_box); let crop_origin_controls = row![ - text("X:") - .vertical_alignment(alignment::Vertical::Center) - .width(30), + text("X:").width(30), numeric_input("0", self.x_input_value).map(Message::XInputChanged), - text("Y:") - .vertical_alignment(alignment::Vertical::Center) - .width(30), + text("Y:").width(30), numeric_input("0", self.y_input_value).map(Message::YInputChanged) ] .spacing(10) - .align_items(Alignment::Center); + .align_y(Center); let crop_dimension_controls = row![ - text("W:") - .vertical_alignment(alignment::Vertical::Center) - .width(30), + text("W:").width(30), numeric_input("0", self.width_input_value) .map(Message::WidthInputChanged), - text("H:") - .vertical_alignment(alignment::Vertical::Center) - .width(30), + text("H:").width(30), numeric_input("0", self.height_input_value) .map(Message::HeightInputChanged) ] .spacing(10) - .align_items(Alignment::Center); + .align_y(Center); let crop_controls = column![crop_origin_controls, crop_dimension_controls] @@ -162,7 +164,7 @@ impl Example { .map(|error| text!("Crop error! \n{error}")), ) .spacing(10) - .align_items(Alignment::Center); + .align_x(Center); let controls = { let save_result = @@ -178,8 +180,8 @@ impl Example { column![ column![ button(centered_text("Screenshot!")) - .padding([10, 20, 10, 20]) - .width(Length::Fill) + .padding([10, 20]) + .width(Fill) .on_press(Message::Screenshot), if !self.png_saving { button(centered_text("Save as png")).on_press_maybe( @@ -190,8 +192,8 @@ impl Example { .style(button::secondary) } .style(button::secondary) - .padding([10, 20, 10, 20]) - .width(Length::Fill) + .padding([10, 20]) + .width(Fill) ] .spacing(10), column![ @@ -199,23 +201,23 @@ impl Example { button(centered_text("Crop")) .on_press(Message::Crop) .style(button::danger) - .padding([10, 20, 10, 20]) - .width(Length::Fill), + .padding([10, 20]) + .width(Fill), ] .spacing(10) - .align_items(Alignment::Center), + .align_x(Center), ] .push_maybe(save_result.map(text)) .spacing(40) }; - let side_content = container(controls).center_y(Length::Fill); + let side_content = container(controls).center_y(Fill); let content = row![side_content, image] .spacing(10) - .width(Length::Fill) - .height(Length::Fill) - .align_items(Alignment::Center); + .width(Fill) + .height(Fill) + .align_y(Center); container(content).padding(10).into() } @@ -276,8 +278,5 @@ fn numeric_input( } fn centered_text(content: &str) -> Element<'_, Message> { - text(content) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center) - .into() + text(content).width(Fill).align_x(Center).into() } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index f2a853e1..de4f2f9a 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,9 +1,8 @@ -use iced::widget::scrollable::Properties; use iced::widget::{ button, column, container, horizontal_space, progress_bar, radio, row, - scrollable, slider, text, vertical_space, Scrollable, + scrollable, slider, text, vertical_space, }; -use iced::{Alignment, Border, Color, Element, Length, Task, Theme}; +use iced::{Border, Center, Color, Element, Fill, Task, Theme}; use once_cell::sync::Lazy; @@ -25,7 +24,7 @@ struct ScrollableDemo { scrollbar_margin: u16, scroller_width: u16, current_scroll_offset: scrollable::RelativeOffset, - alignment: scrollable::Alignment, + anchor: scrollable::Anchor, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] @@ -38,7 +37,7 @@ enum Direction { #[derive(Debug, Clone)] enum Message { SwitchDirection(Direction), - AlignmentChanged(scrollable::Alignment), + AlignmentChanged(scrollable::Anchor), ScrollbarWidthChanged(u16), ScrollbarMarginChanged(u16), ScrollerWidthChanged(u16), @@ -55,7 +54,7 @@ impl ScrollableDemo { scrollbar_margin: 0, scroller_width: 10, current_scroll_offset: scrollable::RelativeOffset::START, - alignment: scrollable::Alignment::Start, + anchor: scrollable::Anchor::Start, } } @@ -72,7 +71,7 @@ impl ScrollableDemo { } Message::AlignmentChanged(alignment) => { self.current_scroll_offset = scrollable::RelativeOffset::START; - self.alignment = alignment; + self.anchor = alignment; scrollable::snap_to( SCROLLABLE_ID.clone(), @@ -169,14 +168,14 @@ impl ScrollableDemo { text("Scrollable alignment:"), radio( "Start", - scrollable::Alignment::Start, - Some(self.alignment), + scrollable::Anchor::Start, + Some(self.anchor), Message::AlignmentChanged, ), radio( "End", - scrollable::Alignment::End, - Some(self.alignment), + scrollable::Anchor::End, + Some(self.anchor), Message::AlignmentChanged, ) ] @@ -203,7 +202,7 @@ impl ScrollableDemo { let scrollable_content: Element<Message> = Element::from(match self.scrollable_direction { - Direction::Vertical => Scrollable::with_direction( + Direction::Vertical => scrollable( column![ scroll_to_end_button(), text("Beginning!"), @@ -213,22 +212,22 @@ impl ScrollableDemo { text("End!"), scroll_to_beginning_button(), ] - .align_items(Alignment::Center) - .padding([40, 0, 40, 0]) + .align_x(Center) + .padding([40, 0]) .spacing(40), - scrollable::Direction::Vertical( - Properties::new() - .width(self.scrollbar_width) - .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width) - .alignment(self.alignment), - ), ) - .width(Length::Fill) - .height(Length::Fill) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new() + .width(self.scrollbar_width) + .margin(self.scrollbar_margin) + .scroller_width(self.scroller_width) + .anchor(self.anchor), + )) + .width(Fill) + .height(Fill) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), - Direction::Horizontal => Scrollable::with_direction( + Direction::Horizontal => scrollable( row![ scroll_to_end_button(), text("Beginning!"), @@ -239,22 +238,22 @@ impl ScrollableDemo { scroll_to_beginning_button(), ] .height(450) - .align_items(Alignment::Center) - .padding([0, 40, 0, 40]) + .align_y(Center) + .padding([0, 40]) .spacing(40), - scrollable::Direction::Horizontal( - Properties::new() - .width(self.scrollbar_width) - .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width) - .alignment(self.alignment), - ), ) - .width(Length::Fill) - .height(Length::Fill) + .direction(scrollable::Direction::Horizontal( + scrollable::Scrollbar::new() + .width(self.scrollbar_width) + .margin(self.scrollbar_margin) + .scroller_width(self.scroller_width) + .anchor(self.anchor), + )) + .width(Fill) + .height(Fill) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), - Direction::Multi => Scrollable::with_direction( + Direction::Multi => scrollable( //horizontal content row![ column![ @@ -281,24 +280,24 @@ impl ScrollableDemo { text("Horizontal - End!"), scroll_to_beginning_button(), ] - .align_items(Alignment::Center) - .padding([0, 40, 0, 40]) + .align_y(Center) + .padding([0, 40]) .spacing(40), - { - let properties = Properties::new() - .width(self.scrollbar_width) - .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width) - .alignment(self.alignment); - - scrollable::Direction::Both { - horizontal: properties, - vertical: properties, - } - }, ) - .width(Length::Fill) - .height(Length::Fill) + .direction({ + let scrollbar = scrollable::Scrollbar::new() + .width(self.scrollbar_width) + .margin(self.scrollbar_margin) + .scroller_width(self.scroller_width) + .anchor(self.anchor); + + scrollable::Direction::Both { + horizontal: scrollbar, + vertical: scrollbar, + } + }) + .width(Fill) + .height(Fill) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), }); @@ -323,7 +322,7 @@ impl ScrollableDemo { let content: Element<Message> = column![scroll_controls, scrollable_content, progress_bars] - .align_items(Alignment::Center) + .align_x(Center) .spacing(10) .into(); diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 4c751937..99e7900a 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -2,7 +2,7 @@ use iced::mouse; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas, Geometry}; use iced::widget::{column, row, slider, text}; -use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme}; +use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme}; use rand::Rng; use std::fmt::Debug; @@ -50,9 +50,7 @@ impl SierpinskiEmulator { fn view(&self) -> iced::Element<'_, Message> { column![ - Canvas::new(&self.graph) - .width(Length::Fill) - .height(Length::Fill), + Canvas::new(&self.graph).width(Fill).height(Fill), row![ text!("Iteration: {:?}", self.graph.iteration), slider(0..=10000, self.graph.iteration, Message::IterationSet) @@ -60,7 +58,7 @@ impl SierpinskiEmulator { .padding(10) .spacing(20), ] - .align_items(iced::Alignment::Center) + .align_x(Center) .into() } } diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml index fad8916e..05e74d2c 100644 --- a/examples/slider/Cargo.toml +++ b/examples/slider/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced.workspace = true +iced.features = ["svg"] diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index 0b4c29aa..ffb5475f 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, Length}; +use iced::widget::{column, container, iced, slider, text, vertical_slider}; +use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { iced::run("Slider - Iced", Slider::update, Slider::view) @@ -12,19 +12,11 @@ pub enum Message { pub struct Slider { value: u8, - default: u8, - step: u8, - shift_step: u8, } impl Slider { fn new() -> Self { - Slider { - value: 50, - default: 50, - step: 5, - shift_step: 1, - } + Slider { value: 50 } } fn update(&mut self, message: Message) { @@ -37,32 +29,27 @@ impl Slider { fn view(&self) -> Element<Message> { let h_slider = container( - slider(0..=100, self.value, Message::SliderChanged) - .default(self.default) - .step(self.step) - .shift_step(self.shift_step), + slider(1..=100, self.value, Message::SliderChanged) + .default(50) + .shift_step(5), ) .width(250); let v_slider = container( - vertical_slider(0..=100, self.value, Message::SliderChanged) - .default(self.default) - .step(self.step) - .shift_step(self.shift_step), + vertical_slider(1..=100, self.value, Message::SliderChanged) + .default(50) + .shift_step(5), ) .height(200); let text = text(self.value); - center( - column![ - container(v_slider).center_x(Length::Fill), - container(h_slider).center_x(Length::Fill), - container(text).center_x(Length::Fill) - ] - .spacing(25), - ) - .into() + column![v_slider, h_slider, text, iced(self.value as f32),] + .width(Fill) + .align_x(Center) + .spacing(20) + .padding(20) + .into() } } diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index ca64da14..e2c18c50 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "canvas", "tokio"] +iced.features = ["debug", "canvas", "image", "tokio"] rand = "0.8.3" tracing-subscriber = "0.3" diff --git a/examples/solar_system/assets/earth.png b/examples/solar_system/assets/earth.png Binary files differnew file mode 100644 index 00000000..e81321d9 --- /dev/null +++ b/examples/solar_system/assets/earth.png diff --git a/examples/solar_system/assets/moon.png b/examples/solar_system/assets/moon.png Binary files differnew file mode 100644 index 00000000..03f10cb7 --- /dev/null +++ b/examples/solar_system/assets/moon.png diff --git a/examples/solar_system/assets/sun.png b/examples/solar_system/assets/sun.png Binary files differnew file mode 100644 index 00000000..29a928a7 --- /dev/null +++ b/examples/solar_system/assets/sun.png diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 2a67e23e..1e74f2bd 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -7,13 +7,12 @@ //! //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::mouse; -use iced::widget::canvas; -use iced::widget::canvas::gradient; use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{Geometry, Path}; +use iced::widget::{canvas, image}; use iced::window; use iced::{ - Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription, + Color, Element, Fill, Point, Rectangle, Renderer, Size, Subscription, Theme, Vector, }; @@ -52,10 +51,7 @@ impl SolarSystem { } fn view(&self) -> Element<Message> { - canvas(&self.state) - .width(Length::Fill) - .height(Length::Fill) - .into() + canvas(&self.state).width(Fill).height(Fill).into() } fn theme(&self) -> Theme { @@ -69,6 +65,9 @@ impl SolarSystem { #[derive(Debug)] struct State { + sun: image::Handle, + earth: image::Handle, + moon: image::Handle, space_cache: canvas::Cache, system_cache: canvas::Cache, start: Instant, @@ -88,6 +87,15 @@ impl State { let size = window::Settings::default().size; State { + sun: image::Handle::from_bytes( + include_bytes!("../assets/sun.png").as_slice(), + ), + earth: image::Handle::from_bytes( + include_bytes!("../assets/earth.png").as_slice(), + ), + moon: image::Handle::from_bytes( + include_bytes!("../assets/moon.png").as_slice(), + ), space_cache: canvas::Cache::default(), system_cache: canvas::Cache::default(), start: now, @@ -135,6 +143,8 @@ impl<Message> canvas::Program<Message> for State { let background = self.space_cache.draw(renderer, bounds.size(), |frame| { + frame.fill_rectangle(Point::ORIGIN, frame.size(), Color::BLACK); + let stars = Path::new(|path| { for (p, size) in &self.stars { path.rectangle(*p, Size::new(*size, *size)); @@ -147,17 +157,18 @@ impl<Message> canvas::Program<Message> for State { let system = self.system_cache.draw(renderer, bounds.size(), |frame| { let center = frame.center(); + frame.translate(Vector::new(center.x, center.y)); - let sun = Path::circle(center, Self::SUN_RADIUS); - let orbit = Path::circle(center, Self::ORBIT_RADIUS); + frame.draw_image( + Rectangle::with_radius(Self::SUN_RADIUS), + &self.sun, + ); - frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); + let orbit = Path::circle(Point::ORIGIN, Self::ORBIT_RADIUS); frame.stroke( &orbit, Stroke { - style: stroke::Style::Solid(Color::from_rgba8( - 0, 153, 255, 0.1, - )), + style: stroke::Style::Solid(Color::WHITE.scale_alpha(0.1)), width: 1.0, line_dash: canvas::LineDash { offset: 0, @@ -171,30 +182,21 @@ impl<Message> canvas::Program<Message> for State { let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32 + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32; - frame.with_save(|frame| { - frame.translate(Vector::new(center.x, center.y)); - frame.rotate(rotation); - frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); - - let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); - - let earth_fill = gradient::Linear::new( - Point::new(-Self::EARTH_RADIUS, 0.0), - Point::new(Self::EARTH_RADIUS, 0.0), - ) - .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0)) - .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47)); + frame.rotate(rotation); + frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); - frame.fill(&earth, earth_fill); + frame.draw_image( + Rectangle::with_radius(Self::EARTH_RADIUS), + canvas::Image::new(&self.earth).rotation(-rotation * 20.0), + ); - frame.with_save(|frame| { - frame.rotate(rotation * 10.0); - frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); + frame.rotate(rotation * 10.0); + frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); - let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); - frame.fill(&moon, Color::WHITE); - }); - }); + frame.draw_image( + Rectangle::with_radius(Self::MOON_RADIUS), + &self.moon, + ); }); vec![background, system] diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index bd56785a..0d824d36 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,8 +1,7 @@ -use iced::alignment; use iced::keyboard; use iced::time; use iced::widget::{button, center, column, row, text}; -use iced::{Alignment, Element, Subscription, Theme}; +use iced::{Center, Element, Subscription, Theme}; use std::time::{Duration, Instant}; @@ -101,13 +100,8 @@ impl Stopwatch { ) .size(40); - let button = |label| { - button( - text(label).horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(10) - .width(80) - }; + let button = + |label| button(text(label).align_x(Center)).padding(10).width(80); let toggle_button = { let label = match self.state { @@ -124,9 +118,7 @@ impl Stopwatch { let controls = row![toggle_button, reset_button].spacing(20); - let content = column![duration, controls] - .align_items(Alignment::Center) - .spacing(20); + let content = column![duration, controls].align_x(Center).spacing(20); center(content).into() } diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 3124493b..534f5e32 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{ row, scrollable, slider, text, text_input, toggler, vertical_rule, vertical_space, }; -use iced::{Alignment, Element, Length, Theme}; +use iced::{Center, Element, Fill, Theme}; pub fn main() -> iced::Result { iced::application("Styling - Iced", Styling::update, Styling::view) @@ -48,7 +48,7 @@ impl Styling { let choose_theme = column![ text("Theme:"), pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged) - .width(Length::Fill), + .width(Fill), ] .spacing(10); @@ -71,26 +71,21 @@ impl Styling { vertical_space().height(800), "You did it!" ]) - .width(Length::Fill) + .width(Fill) .height(100); let checkbox = checkbox("Check me!", self.checkbox_value) .on_toggle(Message::CheckboxToggled); - let toggler = toggler( - String::from("Toggle me!"), - self.toggler_value, - Message::TogglerToggled, - ) - .width(Length::Shrink) - .spacing(10); + let toggler = toggler(self.toggler_value) + .label("Toggle me!") + .on_toggle(Message::TogglerToggled) + .spacing(10); let content = column![ choose_theme, horizontal_rule(38), - row![text_input, button] - .spacing(10) - .align_items(Alignment::Center), + row![text_input, button].spacing(10).align_y(Center), slider, progress_bar, row![ @@ -100,7 +95,7 @@ impl Styling { ] .spacing(10) .height(100) - .align_items(Alignment::Center), + .align_y(Center), ] .spacing(20) .padding(20) diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index e071c3af..02cb85cc 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{center, checkbox, column, container, svg}; -use iced::{color, Element, Length}; +use iced::{color, Element, Fill}; pub fn main() -> iced::Result { iced::run("SVG - Iced", Tiger::update, Tiger::view) @@ -30,24 +30,26 @@ impl Tiger { env!("CARGO_MANIFEST_DIR") )); - let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( - |_theme, _status| svg::Style { - color: if self.apply_color_filter { - Some(color!(0x0000ff)) - } else { - None - }, - }, - ); + let svg = + svg(handle) + .width(Fill) + .height(Fill) + .style(|_theme, _status| svg::Style { + color: if self.apply_color_filter { + Some(color!(0x0000ff)) + } else { + None + }, + }); let apply_color_filter = checkbox("Apply a color filter", self.apply_color_filter) .on_toggle(Message::ToggleColorFilter); center( - column![svg, container(apply_color_filter).center_x(Length::Fill)] + column![svg, container(apply_color_filter).center_x(Fill)] .spacing(20) - .height(Length::Fill), + .height(Fill), ) .padding(20) .into() diff --git a/examples/the_matrix/src/main.rs b/examples/the_matrix/src/main.rs index 2ae1cc3a..0ed52dda 100644 --- a/examples/the_matrix/src/main.rs +++ b/examples/the_matrix/src/main.rs @@ -2,8 +2,7 @@ use iced::mouse; use iced::time::{self, Instant}; use iced::widget::canvas; use iced::{ - Color, Element, Font, Length, Point, Rectangle, Renderer, Subscription, - Theme, + Color, Element, Fill, Font, Point, Rectangle, Renderer, Subscription, Theme, }; use std::cell::RefCell; @@ -37,10 +36,7 @@ impl TheMatrix { } fn view(&self) -> Element<Message> { - canvas(self as &Self) - .width(Length::Fill) - .height(Length::Fill) - .into() + canvas(self as &Self).width(Fill).height(Fill).into() } fn subscription(&self) -> Subscription<Message> { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 232133b1..8f6a836e 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, Element, Length, Subscription, Task}; +use iced::{Center, Element, Fill, Subscription, Task}; use toast::{Status, Toast}; @@ -125,7 +125,7 @@ impl App { Some(self.editing.status), Message::Status ) - .width(Length::Fill) + .width(Fill) .into() ), subtitle( @@ -142,7 +142,7 @@ impl App { .spacing(5) .into() ), - column![add_toast].align_items(Alignment::End) + column![add_toast].align_x(Center) ] .spacing(10) .max_width(200), @@ -177,8 +177,8 @@ mod toast { }; use iced::window; use iced::{ - Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, - Vector, + Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer, + Size, Theme, Vector, }; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -245,9 +245,9 @@ mod toast { .on_press((on_close)(index)) .padding(3), ] - .align_items(Alignment::Center) + .align_y(Center) ) - .width(Length::Fill) + .width(Fill) .padding(5) .style(match toast.status { Status::Primary => primary, @@ -257,7 +257,7 @@ mod toast { }), horizontal_rule(1), container(text(toast.body.as_str())) - .width(Length::Fill) + .width(Fill) .padding(5) .style(container::rounded_box), ]) @@ -347,7 +347,7 @@ mod toast { state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation<()>, + operation: &mut dyn Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -479,8 +479,8 @@ mod toast { layout::flex::Axis::Vertical, renderer, &limits, - Length::Fill, - Length::Fill, + Fill, + Fill, 10.into(), 10.0, Alignment::End, @@ -589,7 +589,7 @@ mod toast { &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation<()>, + 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 6ed50d31..a5f7b36a 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,11 +1,10 @@ -use iced::alignment::{self, Alignment}; use iced::keyboard; use iced::widget::{ self, button, center, checkbox, column, container, keyed_column, row, scrollable, text, text_input, Text, }; use iced::window; -use iced::{Element, Font, Length, Subscription, Task as Command}; +use iced::{Center, Element, Fill, Font, Subscription, Task as Command}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -18,16 +17,14 @@ pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); iced::application(Todos::title, Todos::update, Todos::view) - .load(Todos::load) .subscription(Todos::subscription) .font(include_bytes!("../fonts/icons.ttf").as_slice()) .window_size((500.0, 800.0)) - .run() + .run_with(Todos::new) } -#[derive(Default, Debug)] +#[derive(Debug)] enum Todos { - #[default] Loading, Loaded(State), } @@ -54,8 +51,11 @@ enum Message { } impl Todos { - fn load() -> Command<Message> { - Command::perform(SavedState::load(), Message::Loaded) + fn new() -> (Self, Command<Message>) { + ( + Self::Loading, + Command::perform(SavedState::load(), Message::Loaded), + ) } fn title(&self) -> String { @@ -192,17 +192,18 @@ impl Todos { .. }) => { let title = text("todos") - .width(Length::Fill) + .width(Fill) .size(100) .color([0.5, 0.5, 0.5]) - .horizontal_alignment(alignment::Horizontal::Center); + .align_x(Center); let input = text_input("What needs to be done?", input_value) .id(INPUT_ID.clone()) .on_input(Message::InputChanged) .on_submit(Message::CreateTask) .padding(15) - .size(30); + .size(30) + .align_x(Center); let controls = view_controls(tasks, *filter); let filtered_tasks = @@ -239,10 +240,7 @@ impl Todos { .spacing(20) .max_width(800); - scrollable( - container(content).center_x(Length::Fill).padding(40), - ) - .into() + scrollable(container(content).center_x(Fill).padding(40)).into() } } } @@ -342,7 +340,7 @@ impl Task { TaskState::Idle => { let checkbox = checkbox(&self.description, self.completed) .on_toggle(TaskMessage::Completed) - .width(Length::Fill) + .width(Fill) .size(17) .text_shaping(text::Shaping::Advanced); @@ -354,7 +352,7 @@ impl Task { .style(button::text), ] .spacing(20) - .align_items(Alignment::Center) + .align_y(Center) .into() } TaskState::Editing => { @@ -370,14 +368,14 @@ impl Task { button( row![delete_icon(), "Delete"] .spacing(10) - .align_items(Alignment::Center) + .align_y(Center) ) .on_press(TaskMessage::Delete) .padding(10) .style(button::danger) ] .spacing(20) - .align_items(Alignment::Center) + .align_y(Center) .into() } } @@ -404,17 +402,16 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { "{tasks_left} {} left", if tasks_left == 1 { "task" } else { "tasks" } ) - .width(Length::Fill), + .width(Fill), row![ filter_button("All", Filter::All, current_filter), filter_button("Active", Filter::Active, current_filter), filter_button("Completed", Filter::Completed, current_filter,), ] - .width(Length::Shrink) .spacing(10) ] .spacing(20) - .align_items(Alignment::Center) + .align_y(Center) .into() } @@ -439,20 +436,15 @@ impl Filter { } fn loading_message<'a>() -> Element<'a, Message> { - center( - text("Loading...") - .horizontal_alignment(alignment::Horizontal::Center) - .size(50), - ) - .into() + center(text("Loading...").width(Fill).align_x(Center).size(50)).into() } fn empty_message(message: &str) -> Element<'_, Message> { center( text(message) - .width(Length::Fill) + .width(Fill) .size(25) - .horizontal_alignment(alignment::Horizontal::Center) + .align_x(Center) .color([0.7, 0.7, 0.7]), ) .height(200) @@ -466,7 +458,7 @@ fn icon(unicode: char) -> Text<'static> { text(unicode.to_string()) .font(ICONS) .width(20) - .horizontal_alignment(alignment::Horizontal::Center) + .align_x(Center) } fn edit_icon() -> Text<'static> { diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 94ba78ee..d8c0b29a 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,10 +1,9 @@ -use iced::alignment::{self, Alignment}; use iced::widget::{ button, checkbox, column, container, horizontal_space, image, radio, row, scrollable, slider, text, text_input, toggler, vertical_space, }; use iced::widget::{Button, Column, Container, Slider}; -use iced::{Color, Element, Font, Length, Pixels}; +use iced::{Center, Color, Element, Fill, Font, Pixels}; pub fn main() -> iced::Result { #[cfg(target_arch = "wasm32")] @@ -173,10 +172,10 @@ impl Tour { } else { content }) - .center_x(Length::Fill), + .center_x(Fill), ); - container(scrollable).center_y(Length::Fill).into() + container(scrollable).center_y(Fill).into() } fn can_continue(&self) -> bool { @@ -235,11 +234,7 @@ impl Tour { 0 to 100:", ) .push(slider(0..=100, self.slider, Message::SliderChanged)) - .push( - text(self.slider.to_string()) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) + .push(text(self.slider.to_string()).width(Fill).align_x(Center)) } fn rows_and_columns(&self) -> Column<Message> { @@ -268,9 +263,7 @@ impl Tour { let spacing_section = column![ slider(0..=80, self.spacing, Message::SpacingChanged), - text!("{} px", self.spacing) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), + text!("{} px", self.spacing).width(Fill).align_x(Center), ] .spacing(10); @@ -364,11 +357,11 @@ impl Tour { Self::container("Toggler") .push("A toggler is mostly used to enable or disable something.") .push( - Container::new(toggler( - "Toggle me to continue...".to_owned(), - self.toggler, - Message::TogglerChanged, - )) + Container::new( + toggler(self.toggler) + .label("Toggle me to continue...") + .on_toggle(Message::TogglerChanged), + ) .padding([0, 40]), ) } @@ -381,11 +374,7 @@ impl Tour { .push("An image that tries to keep its aspect ratio.") .push(ferris(width, filter_method)) .push(slider(100..=500, width, Message::ImageWidthChanged)) - .push( - text!("Width: {width} px") - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) + .push(text!("Width: {width} px").width(Fill).align_x(Center)) .push( checkbox( "Use nearest interpolation", @@ -393,7 +382,7 @@ impl Tour { ) .on_toggle(Message::ImageUseNearestToggled), ) - .align_items(Alignment::Center) + .align_x(Center) } fn scrollable(&self) -> Column<Message> { @@ -409,18 +398,13 @@ impl Tour { .push(vertical_space().height(4096)) .push( text("You are halfway there!") - .width(Length::Fill) + .width(Fill) .size(30) - .horizontal_alignment(alignment::Horizontal::Center), + .align_x(Center), ) .push(vertical_space().height(4096)) .push(ferris(300, image::FilterMethod::Linear)) - .push( - text("You made it!") - .width(Length::Fill) - .size(50) - .horizontal_alignment(alignment::Horizontal::Center), - ) + .push(text("You made it!").width(Fill).size(50).align_x(Center)) } fn text_input(&self) -> Column<Message> { @@ -464,8 +448,8 @@ impl Tour { } else { value }) - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), + .width(Fill) + .align_x(Center), ) } @@ -570,7 +554,7 @@ fn ferris<'a>( .filter_method(filter_method) .width(width), ) - .center_x(Length::Fill) + .center_x(Fill) } fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> { diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index 6dd3273a..ce34d826 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -1,9 +1,9 @@ -use iced::alignment::{self, Alignment}; +use iced::alignment; use iced::mouse; use iced::widget::{ canvas, checkbox, column, horizontal_space, row, slider, text, }; -use iced::{Element, Length, Point, Rectangle, Renderer, Theme, Vector}; +use iced::{Center, Element, Fill, Point, Rectangle, Renderer, Theme, Vector}; pub fn main() -> iced::Result { iced::application( @@ -59,7 +59,7 @@ impl VectorialText { }; column![ - canvas(&self.state).width(Length::Fill).height(Length::Fill), + canvas(&self.state).width(Fill).height(Fill), column![ checkbox("Use Japanese", self.state.use_japanese,) .on_toggle(Message::ToggleJapanese), @@ -85,7 +85,7 @@ impl VectorialText { ] .spacing(20), ] - .align_items(Alignment::Center) + .align_x(Center) .spacing(10) ] .spacing(10) diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index e46d1ff0..77fec65e 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, Element, Font, Length, Point, Rectangle, Subscription, - Task, Theme, + Center, Color, Element, Fill, Font, Point, Rectangle, Subscription, Task, + Theme, }; pub fn main() -> iced::Result { @@ -70,7 +70,7 @@ impl Example { .color_maybe(color), ] .height(40) - .align_items(Alignment::Center) + .align_y(Center) }; let view_bounds = |label, bounds: Option<Rectangle>| { @@ -130,13 +130,13 @@ impl Example { .padding(20) ) .on_scroll(|_| Message::Scrolled) - .width(Length::Fill) + .width(Fill) .height(300), ] .padding(20) ) .on_scroll(|_| Message::Scrolled) - .width(Length::Fill) + .width(Fill) .height(300), ] .spacing(10) diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index cd32cb66..14652936 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -1,87 +1,79 @@ pub mod server; use iced::futures; -use iced::subscription::{self, Subscription}; +use iced::stream; use iced::widget::text; use futures::channel::mpsc; use futures::sink::SinkExt; -use futures::stream::StreamExt; +use futures::stream::{Stream, StreamExt}; use async_tungstenite::tungstenite; use std::fmt; -pub fn connect() -> Subscription<Event> { - struct Connect; +pub fn connect() -> impl Stream<Item = Event> { + stream::channel(100, |mut output| async move { + let mut state = State::Disconnected; - subscription::channel( - std::any::TypeId::of::<Connect>(), - 100, - |mut output| async move { - let mut state = State::Disconnected; + loop { + match &mut state { + State::Disconnected => { + const ECHO_SERVER: &str = "ws://127.0.0.1:3030"; - loop { - match &mut state { - State::Disconnected => { - const ECHO_SERVER: &str = "ws://127.0.0.1:3030"; - - match async_tungstenite::tokio::connect_async( - ECHO_SERVER, - ) + match async_tungstenite::tokio::connect_async(ECHO_SERVER) .await - { - Ok((websocket, _)) => { - let (sender, receiver) = mpsc::channel(100); - - let _ = output - .send(Event::Connected(Connection(sender))) - .await; + { + Ok((websocket, _)) => { + let (sender, receiver) = mpsc::channel(100); - state = State::Connected(websocket, receiver); - } - Err(_) => { - tokio::time::sleep( - tokio::time::Duration::from_secs(1), - ) + let _ = output + .send(Event::Connected(Connection(sender))) .await; - let _ = output.send(Event::Disconnected).await; - } + state = State::Connected(websocket, receiver); + } + Err(_) => { + tokio::time::sleep( + tokio::time::Duration::from_secs(1), + ) + .await; + + let _ = output.send(Event::Disconnected).await; } } - State::Connected(websocket, input) => { - let mut fused_websocket = websocket.by_ref().fuse(); - - futures::select! { - received = fused_websocket.select_next_some() => { - match received { - Ok(tungstenite::Message::Text(message)) => { - let _ = output.send(Event::MessageReceived(Message::User(message))).await; - } - Err(_) => { - let _ = output.send(Event::Disconnected).await; - - state = State::Disconnected; - } - Ok(_) => continue, + } + State::Connected(websocket, input) => { + let mut fused_websocket = websocket.by_ref().fuse(); + + futures::select! { + received = fused_websocket.select_next_some() => { + match received { + Ok(tungstenite::Message::Text(message)) => { + let _ = output.send(Event::MessageReceived(Message::User(message))).await; } - } - - message = input.select_next_some() => { - let result = websocket.send(tungstenite::Message::Text(message.to_string())).await; - - if result.is_err() { + Err(_) => { let _ = output.send(Event::Disconnected).await; state = State::Disconnected; } + Ok(_) => continue, + } + } + + message = input.select_next_some() => { + let result = websocket.send(tungstenite::Message::Text(message.to_string())).await; + + if result.is_err() { + let _ = output.send(Event::Disconnected).await; + + state = State::Disconnected; } } } } } - }, - ) + } + }) } #[derive(Debug)] diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 8422ce16..8b1efb41 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -1,20 +1,17 @@ mod echo; -use iced::alignment::{self, Alignment}; use iced::widget::{ self, button, center, column, row, scrollable, text, text_input, }; -use iced::{color, Element, Length, Subscription, Task}; +use iced::{color, Center, Element, Fill, Subscription, Task}; use once_cell::sync::Lazy; pub fn main() -> iced::Result { iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view) - .load(WebSocket::load) .subscription(WebSocket::subscription) - .run() + .run_with(WebSocket::new) } -#[derive(Default)] struct WebSocket { messages: Vec<echo::Message>, new_message: String, @@ -30,11 +27,18 @@ enum Message { } impl WebSocket { - fn load() -> Task<Message> { - Task::batch([ - Task::perform(echo::server::run(), |_| Message::Server), - widget::focus_next(), - ]) + fn new() -> (Self, Task<Message>) { + ( + Self { + messages: Vec::new(), + new_message: String::new(), + state: State::Disconnected, + }, + Task::batch([ + Task::perform(echo::server::run(), |_| Message::Server), + widget::focus_next(), + ]), + ) } fn update(&mut self, message: Message) -> Task<Message> { @@ -83,7 +87,7 @@ impl WebSocket { } fn subscription(&self) -> Subscription<Message> { - echo::connect().map(Message::Echo) + Subscription::run(echo::connect).map(Message::Echo) } fn view(&self) -> Element<Message> { @@ -99,7 +103,7 @@ impl WebSocket { .spacing(10), ) .id(MESSAGE_LOG.clone()) - .height(Length::Fill) + .height(Fill) .into() }; @@ -108,12 +112,8 @@ impl WebSocket { .on_input(Message::NewMessageChanged) .padding(10); - let mut button = button( - text("Send") - .height(40) - .vertical_alignment(alignment::Vertical::Center), - ) - .padding([0, 20]); + let mut button = button(text("Send").height(40).align_y(Center)) + .padding([0, 20]); if matches!(self.state, State::Connected(_)) { if let Some(message) = echo::Message::new(&self.new_message) { @@ -122,13 +122,11 @@ impl WebSocket { } } - row![input, button] - .spacing(10) - .align_items(Alignment::Center) + row![input, button].spacing(10).align_y(Center) }; column![message_log, new_message_input] - .height(Length::Fill) + .height(Fill) .padding(20) .spacing(10) .into() @@ -140,10 +138,4 @@ enum State { Connected(echo::Connection), } -impl Default for State { - fn default() -> Self { - Self::Disconnected - } -} - static MESSAGE_LOG: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique); |