From e55dfa75510aa11ec197796668a772f3be4c52c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 5 Dec 2019 06:10:47 +0100 Subject: Add `clock` example --- examples/clock.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 examples/clock.rs (limited to 'examples') diff --git a/examples/clock.rs b/examples/clock.rs new file mode 100644 index 00000000..b1ee8ab1 --- /dev/null +++ b/examples/clock.rs @@ -0,0 +1,166 @@ +use iced::{ + Align, Application, Checkbox, Column, Command, Container, Element, Length, + Settings, Subscription, Text, +}; + +pub fn main() { + Clock::run(Settings::default()) +} + +#[derive(Debug)] +struct Clock { + time: chrono::DateTime, + enabled: bool, +} + +#[derive(Debug, Clone)] +enum Message { + Ticked(chrono::DateTime), + Toggled(bool), +} + +impl Application for Clock { + type Message = Message; + + fn new() -> (Clock, Command) { + ( + Clock { + time: chrono::Local::now(), + enabled: false, + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Ticked(time) => { + self.time = time; + } + Message::Toggled(enabled) => { + self.enabled = enabled; + } + }; + + Command::none() + } + + fn subscriptions(&self) -> Subscription { + if self.enabled { + time::every(std::time::Duration::from_millis(500), Message::Ticked) + } else { + Subscription::none() + } + } + + fn view(&mut self) -> Element { + let clock = Text::new(format!("{}", self.time.format("%H:%M:%S"))) + .size(40) + .width(Length::Shrink); + + let toggle = Checkbox::new(self.enabled, "Enabled", Message::Toggled) + .width(Length::Shrink); + + let content = Column::new() + .width(Length::Shrink) + .align_items(Align::Center) + .spacing(20) + .push(clock) + .push(toggle); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod time { + use std::sync::{Arc, Mutex}; + + pub fn every( + duration: std::time::Duration, + f: impl Fn(chrono::DateTime) -> Message + + 'static + + Send + + Sync, + ) -> iced::Subscription + where + Message: Send + 'static, + { + Tick { + duration, + message: Arc::new(f), + } + .into() + } + + struct Tick { + duration: std::time::Duration, + message: Arc< + dyn Fn(chrono::DateTime) -> Message + Send + Sync, + >, + } + + struct TickState { + alive: Arc>, + } + + impl iced::subscription::Handle for TickState { + fn cancel(&mut self) { + match self.alive.lock() { + Ok(mut guard) => *guard = false, + _ => {} + } + } + } + + impl iced::subscription::Definition for Tick + where + Message: 'static, + { + type Message = Message; + + fn id(&self) -> u64 { + 0 + } + + fn stream( + &self, + ) -> ( + futures::stream::BoxStream<'static, Message>, + Box, + ) { + use futures::StreamExt; + + let duration = self.duration.clone(); + let function = self.message.clone(); + let alive = Arc::new(Mutex::new(true)); + + let state = TickState { + alive: alive.clone(), + }; + + let stream = futures::stream::poll_fn(move |_| { + std::thread::sleep(duration); + + if !*alive.lock().unwrap() { + return std::task::Poll::Ready(None); + } + + let now = chrono::Local::now(); + + std::task::Poll::Ready(Some(now)) + }) + .map(move |time| function(time)); + + (stream.boxed(), Box::new(state)) + } + } +} -- cgit From 48145ba51e045f8b0b4788f3a75d20b9d9b7e6ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Dec 2019 08:51:44 +0100 Subject: Use `oneshot` and `future::select` to cancel streams --- examples/clock.rs | 49 ++++++++++--------------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) (limited to 'examples') diff --git a/examples/clock.rs b/examples/clock.rs index b1ee8ab1..5a404bfa 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -82,7 +82,7 @@ impl Application for Clock { } mod time { - use std::sync::{Arc, Mutex}; + use std::sync::Arc; pub fn every( duration: std::time::Duration, @@ -108,59 +108,30 @@ mod time { >, } - struct TickState { - alive: Arc>, - } - - impl iced::subscription::Handle for TickState { - fn cancel(&mut self) { - match self.alive.lock() { - Ok(mut guard) => *guard = false, - _ => {} - } - } - } - - impl iced::subscription::Definition for Tick + impl iced::subscription::Handle for Tick where Message: 'static, { - type Message = Message; + type Output = Message; fn id(&self) -> u64 { 0 } - fn stream( - &self, - ) -> ( - futures::stream::BoxStream<'static, Message>, - Box, - ) { + fn stream(&self) -> futures::stream::BoxStream<'static, Message> { use futures::StreamExt; let duration = self.duration.clone(); let function = self.message.clone(); - let alive = Arc::new(Mutex::new(true)); - - let state = TickState { - alive: alive.clone(), - }; - - let stream = futures::stream::poll_fn(move |_| { - std::thread::sleep(duration); - - if !*alive.lock().unwrap() { - return std::task::Poll::Ready(None); - } - let now = chrono::Local::now(); + let stream = + futures::stream::iter(std::iter::repeat(())).map(move |_| { + std::thread::sleep(duration); - std::task::Poll::Ready(Some(now)) - }) - .map(move |time| function(time)); + function(chrono::Local::now()) + }); - (stream.boxed(), Box::new(state)) + stream.boxed() } } } -- cgit From 98160406f714728afe718f305bf9d12be1676b2d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 8 Dec 2019 08:21:26 +0100 Subject: Allow listening to runtime events in subscriptions --- examples/clock.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'examples') diff --git a/examples/clock.rs b/examples/clock.rs index 5a404bfa..f06762bd 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -4,6 +4,8 @@ use iced::{ }; pub fn main() { + env_logger::init(); + Clock::run(Settings::default()) } @@ -108,30 +110,26 @@ mod time { >, } - impl iced::subscription::Handle for Tick + impl iced_native::subscription::Connection for Tick where Message: 'static, { + type Input = iced_native::subscription::Input; type Output = Message; fn id(&self) -> u64 { 0 } - fn stream(&self) -> futures::stream::BoxStream<'static, Message> { + fn stream( + &self, + input: iced_native::subscription::Input, + ) -> futures::stream::BoxStream<'static, Message> { use futures::StreamExt; - let duration = self.duration.clone(); let function = self.message.clone(); - let stream = - futures::stream::iter(std::iter::repeat(())).map(move |_| { - std::thread::sleep(duration); - - function(chrono::Local::now()) - }); - - stream.boxed() + input.map(move |_| function(chrono::Local::now())).boxed() } } } -- cgit From e189c22bb09e471e8f899ef184d2a99e2e22c484 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 9 Dec 2019 22:39:28 +0100 Subject: Rename `clock` example to `events` --- examples/clock.rs | 135 ----------------------------------------------------- examples/events.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 135 deletions(-) delete mode 100644 examples/clock.rs create mode 100644 examples/events.rs (limited to 'examples') diff --git a/examples/clock.rs b/examples/clock.rs deleted file mode 100644 index f06762bd..00000000 --- a/examples/clock.rs +++ /dev/null @@ -1,135 +0,0 @@ -use iced::{ - Align, Application, Checkbox, Column, Command, Container, Element, Length, - Settings, Subscription, Text, -}; - -pub fn main() { - env_logger::init(); - - Clock::run(Settings::default()) -} - -#[derive(Debug)] -struct Clock { - time: chrono::DateTime, - enabled: bool, -} - -#[derive(Debug, Clone)] -enum Message { - Ticked(chrono::DateTime), - Toggled(bool), -} - -impl Application for Clock { - type Message = Message; - - fn new() -> (Clock, Command) { - ( - Clock { - time: chrono::Local::now(), - enabled: false, - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Clock - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Ticked(time) => { - self.time = time; - } - Message::Toggled(enabled) => { - self.enabled = enabled; - } - }; - - Command::none() - } - - fn subscriptions(&self) -> Subscription { - if self.enabled { - time::every(std::time::Duration::from_millis(500), Message::Ticked) - } else { - Subscription::none() - } - } - - fn view(&mut self) -> Element { - let clock = Text::new(format!("{}", self.time.format("%H:%M:%S"))) - .size(40) - .width(Length::Shrink); - - let toggle = Checkbox::new(self.enabled, "Enabled", Message::Toggled) - .width(Length::Shrink); - - let content = Column::new() - .width(Length::Shrink) - .align_items(Align::Center) - .spacing(20) - .push(clock) - .push(toggle); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -mod time { - use std::sync::Arc; - - pub fn every( - duration: std::time::Duration, - f: impl Fn(chrono::DateTime) -> Message - + 'static - + Send - + Sync, - ) -> iced::Subscription - where - Message: Send + 'static, - { - Tick { - duration, - message: Arc::new(f), - } - .into() - } - - struct Tick { - duration: std::time::Duration, - message: Arc< - dyn Fn(chrono::DateTime) -> Message + Send + Sync, - >, - } - - impl iced_native::subscription::Connection for Tick - where - Message: 'static, - { - type Input = iced_native::subscription::Input; - type Output = Message; - - fn id(&self) -> u64 { - 0 - } - - fn stream( - &self, - input: iced_native::subscription::Input, - ) -> futures::stream::BoxStream<'static, Message> { - use futures::StreamExt; - - let function = self.message.clone(); - - input.map(move |_| function(chrono::Local::now())).boxed() - } - } -} diff --git a/examples/events.rs b/examples/events.rs new file mode 100644 index 00000000..290aa975 --- /dev/null +++ b/examples/events.rs @@ -0,0 +1,129 @@ +use iced::{ + Align, Application, Checkbox, Column, Command, Container, Element, Length, + Settings, Subscription, Text, +}; + +pub fn main() { + env_logger::init(); + + Events::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct Events { + last: Vec, + enabled: bool, +} + +#[derive(Debug, Clone)] +enum Message { + EventOccurred(iced_native::Event), + Toggled(bool), +} + +impl Application for Events { + type Message = Message; + + fn new() -> (Events, Command) { + (Events::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Events - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::EventOccurred(event) => { + self.last.push(event); + + if self.last.len() > 5 { + let _ = self.last.remove(0); + } + } + Message::Toggled(enabled) => { + self.enabled = enabled; + } + }; + + Command::none() + } + + fn subscriptions(&self) -> Subscription { + if self.enabled { + events::all(Message::EventOccurred) + } else { + Subscription::none() + } + } + + fn view(&mut self) -> Element { + let events = self.last.iter().fold( + Column::new().width(Length::Shrink).spacing(10), + |column, event| { + column.push( + Text::new(format!("{:?}", event)) + .size(40) + .width(Length::Shrink), + ) + }, + ); + + let toggle = Checkbox::new(self.enabled, "Enabled", Message::Toggled) + .width(Length::Shrink); + + let content = Column::new() + .width(Length::Shrink) + .align_items(Align::Center) + .spacing(20) + .push(events) + .push(toggle); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod events { + use std::sync::Arc; + + pub fn all( + f: impl Fn(iced_native::Event) -> Message + 'static + Send + Sync, + ) -> iced::Subscription + where + Message: Send + 'static, + { + All(Arc::new(f)).into() + } + + struct All( + Arc Message + Send + Sync>, + ); + + impl iced_native::subscription::Connection for All + where + Message: 'static, + { + type Input = iced_native::subscription::Input; + type Output = Message; + + fn id(&self) -> u64 { + 0 + } + + fn stream( + &self, + input: iced_native::subscription::Input, + ) -> futures::stream::BoxStream<'static, Message> { + use futures::StreamExt; + + let function = self.0.clone(); + + input.map(move |event| function(event)).boxed() + } + } +} -- cgit From cdb7acf6c20fe13a09e75ea1c47d53ced6174698 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Dec 2019 03:43:00 +0100 Subject: Implement `Subscription::map` and `from_recipe` --- examples/events.rs | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index 290aa975..10758519 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -51,7 +51,7 @@ impl Application for Events { fn subscriptions(&self) -> Subscription { if self.enabled { - events::all(Message::EventOccurred) + events::all().map(Message::EventOccurred) } else { Subscription::none() } @@ -89,41 +89,33 @@ impl Application for Events { } mod events { - use std::sync::Arc; - - pub fn all( - f: impl Fn(iced_native::Event) -> Message + 'static + Send + Sync, - ) -> iced::Subscription - where - Message: Send + 'static, - { - All(Arc::new(f)).into() + pub fn all() -> iced::Subscription { + iced::Subscription::from_recipe(All) } - struct All( - Arc Message + Send + Sync>, - ); + struct All; - impl iced_native::subscription::Connection for All + impl + iced_native::subscription::Recipe + for All where - Message: 'static, + H: std::hash::Hasher, { - type Input = iced_native::subscription::Input; - type Output = Message; + type Output = iced_native::Event; - fn id(&self) -> u64 { - 0 + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); } fn stream( &self, input: iced_native::subscription::Input, - ) -> futures::stream::BoxStream<'static, Message> { + ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::StreamExt; - let function = self.0.clone(); - - input.map(move |event| function(event)).boxed() + input.boxed() } } } -- cgit From e06a4d1ce45be2d361879be480c4179e4532839d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Dec 2019 04:06:12 +0100 Subject: Simplify `events` example --- examples/events.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index 10758519..03e8d995 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -95,15 +95,15 @@ mod events { struct All; - impl - iced_native::subscription::Recipe - for All - where - H: std::hash::Hasher, + impl + iced_native::subscription::Recipe< + iced_native::Hasher, + iced_native::subscription::Input, + > for All { type Output = iced_native::Event; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut iced_native::Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); -- cgit From ffa46898d983fc15ce6051a427622c058ce4e151 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Dec 2019 23:58:23 +0100 Subject: Add `timer` example --- examples/events.rs | 2 - examples/timer.rs | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 examples/timer.rs (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index 03e8d995..0a3d3ed3 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -4,8 +4,6 @@ use iced::{ }; pub fn main() { - env_logger::init(); - Events::run(Settings::default()) } diff --git a/examples/timer.rs b/examples/timer.rs new file mode 100644 index 00000000..b64b3ef5 --- /dev/null +++ b/examples/timer.rs @@ -0,0 +1,180 @@ +use iced::{ + button, Align, Application, Background, Button, Color, Column, Command, + Container, Element, HorizontalAlignment, Length, Row, Settings, + Subscription, Text, +}; +use std::time::{Duration, Instant}; + +pub fn main() { + Timer::run(Settings::default()) +} + +struct Timer { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Timer { + type Message = Message; + + fn new() -> (Timer, Command) { + ( + Timer { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Timer - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscriptions(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + seconds / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .width(Length::Shrink) + .size(40); + + let button = |state, label, color: [f32; 3]| { + Button::new( + state, + Text::new(label) + .color(Color::WHITE) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .background(Background::Color(color.into())) + .border_radius(10) + .padding(10) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", [0.11, 0.42, 0.87]), + State::Ticking { .. } => ("Stop", [0.9, 0.4, 0.4]), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = button(&mut self.reset, "Reset", [0.7, 0.7, 0.7]) + .on_press(Message::Reset); + + let controls = Row::new() + .width(Length::Shrink) + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .width(Length::Shrink) + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod time { + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe + for Every + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut iced_native::Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + } + + fn stream( + &self, + _input: Input, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} -- cgit From f0381a7fb305a371208df62d339540ce3d48f9da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 00:32:05 +0100 Subject: Use `surf` in `pokedex` example --- examples/pokedex.rs | 23 +++++++++-------------- examples/timer.rs | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) (limited to 'examples') diff --git a/examples/pokedex.rs b/examples/pokedex.rs index b9daeabd..2d595ec4 100644 --- a/examples/pokedex.rs +++ b/examples/pokedex.rs @@ -150,7 +150,6 @@ impl Pokemon { async fn search() -> Result { use rand::Rng; use serde::Deserialize; - use std::io::Read; #[derive(Debug, Deserialize)] struct Entry { @@ -179,7 +178,11 @@ impl Pokemon { let url = format!("https://pokeapi.co/api/v2/pokemon-species/{}", id); let sprite = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id); - let entry: Entry = reqwest::get(&url)?.json()?; + let (entry, sprite): (Entry, _) = futures::future::try_join( + surf::get(&url).recv_json(), + surf::get(&sprite).recv_bytes(), + ) + .await?; let description = entry .flavor_text_entries @@ -188,13 +191,6 @@ impl Pokemon { .next() .ok_or(Error::LanguageError)?; - let mut sprite = reqwest::get(&sprite)?; - let mut bytes = Vec::new(); - - sprite - .read_to_end(&mut bytes) - .map_err(|_| Error::ImageError)?; - Ok(Pokemon { number: id, name: entry.name.to_uppercase(), @@ -203,7 +199,7 @@ impl Pokemon { .chars() .map(|c| if c.is_control() { ' ' } else { c }) .collect(), - image: image::Handle::from_memory(bytes), + image: image::Handle::from_memory(sprite), }) } } @@ -211,13 +207,12 @@ impl Pokemon { #[derive(Debug, Clone)] enum Error { APIError, - ImageError, LanguageError, } -impl From for Error { - fn from(error: reqwest::Error) -> Error { - dbg!(&error); +impl From for Error { + fn from(exception: surf::Exception) -> Error { + dbg!(&exception); Error::APIError } diff --git a/examples/timer.rs b/examples/timer.rs index b64b3ef5..367c5b2b 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -92,7 +92,7 @@ impl Application for Timer { let duration = Text::new(format!( "{:0>2}:{:0>2}:{:0>2}.{:0>2}", seconds / HOUR, - seconds / MINUTE, + (seconds % HOUR) / MINUTE, seconds % MINUTE, self.duration.subsec_millis() / 10, )) -- cgit From 69ed631d449e74b38054aa052c620368cb65c72c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 00:45:38 +0100 Subject: Rename `timer` example to `stopwatch` --- examples/stopwatch.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++ examples/timer.rs | 180 -------------------------------------------------- 2 files changed, 180 insertions(+), 180 deletions(-) create mode 100644 examples/stopwatch.rs delete mode 100644 examples/timer.rs (limited to 'examples') diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs new file mode 100644 index 00000000..4f931278 --- /dev/null +++ b/examples/stopwatch.rs @@ -0,0 +1,180 @@ +use iced::{ + button, Align, Application, Background, Button, Color, Column, Command, + Container, Element, HorizontalAlignment, Length, Row, Settings, + Subscription, Text, +}; +use std::time::{Duration, Instant}; + +pub fn main() { + Stopwatch::run(Settings::default()) +} + +struct Stopwatch { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Stopwatch { + type Message = Message; + + fn new() -> (Stopwatch, Command) { + ( + Stopwatch { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Stopwatch - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscriptions(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + (seconds % HOUR) / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .width(Length::Shrink) + .size(40); + + let button = |state, label, color: [f32; 3]| { + Button::new( + state, + Text::new(label) + .color(Color::WHITE) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .background(Background::Color(color.into())) + .border_radius(10) + .padding(10) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", [0.11, 0.42, 0.87]), + State::Ticking { .. } => ("Stop", [0.9, 0.4, 0.4]), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = button(&mut self.reset, "Reset", [0.7, 0.7, 0.7]) + .on_press(Message::Reset); + + let controls = Row::new() + .width(Length::Shrink) + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .width(Length::Shrink) + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod time { + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe + for Every + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut iced_native::Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + } + + fn stream( + &self, + _input: Input, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} diff --git a/examples/timer.rs b/examples/timer.rs deleted file mode 100644 index 367c5b2b..00000000 --- a/examples/timer.rs +++ /dev/null @@ -1,180 +0,0 @@ -use iced::{ - button, Align, Application, Background, Button, Color, Column, Command, - Container, Element, HorizontalAlignment, Length, Row, Settings, - Subscription, Text, -}; -use std::time::{Duration, Instant}; - -pub fn main() { - Timer::run(Settings::default()) -} - -struct Timer { - duration: Duration, - state: State, - toggle: button::State, - reset: button::State, -} - -enum State { - Idle, - Ticking { last_tick: Instant }, -} - -#[derive(Debug, Clone)] -enum Message { - Toggle, - Reset, - Tick(Instant), -} - -impl Application for Timer { - type Message = Message; - - fn new() -> (Timer, Command) { - ( - Timer { - duration: Duration::default(), - state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Timer - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Toggle => match self.state { - State::Idle => { - self.state = State::Ticking { - last_tick: Instant::now(), - }; - } - State::Ticking { .. } => { - self.state = State::Idle; - } - }, - Message::Tick(now) => match &mut self.state { - State::Ticking { last_tick } => { - self.duration += now - *last_tick; - *last_tick = now; - } - _ => {} - }, - Message::Reset => { - self.duration = Duration::default(); - } - } - - Command::none() - } - - fn subscriptions(&self) -> Subscription { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element { - const MINUTE: u64 = 60; - const HOUR: u64 = 60 * MINUTE; - - let seconds = self.duration.as_secs(); - - let duration = Text::new(format!( - "{:0>2}:{:0>2}:{:0>2}.{:0>2}", - seconds / HOUR, - (seconds % HOUR) / MINUTE, - seconds % MINUTE, - self.duration.subsec_millis() / 10, - )) - .width(Length::Shrink) - .size(40); - - let button = |state, label, color: [f32; 3]| { - Button::new( - state, - Text::new(label) - .color(Color::WHITE) - .horizontal_alignment(HorizontalAlignment::Center), - ) - .min_width(80) - .background(Background::Color(color.into())) - .border_radius(10) - .padding(10) - }; - - let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", [0.11, 0.42, 0.87]), - State::Ticking { .. } => ("Stop", [0.9, 0.4, 0.4]), - }; - - button(&mut self.toggle, label, color).on_press(Message::Toggle) - }; - - let reset_button = button(&mut self.reset, "Reset", [0.7, 0.7, 0.7]) - .on_press(Message::Reset); - - let controls = Row::new() - .width(Length::Shrink) - .spacing(20) - .push(toggle_button) - .push(reset_button); - - let content = Column::new() - .width(Length::Shrink) - .align_items(Align::Center) - .spacing(20) - .push(duration) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -mod time { - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe - for Every - { - type Output = std::time::Instant; - - fn hash(&self, state: &mut iced_native::Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - } - - fn stream( - &self, - _input: Input, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } - } -} -- cgit From c688452d7beb1b17ef8416fc101f8868767fc457 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 01:13:01 +0100 Subject: Consume `Recipe` when building a `Stream` --- examples/events.rs | 2 +- examples/stopwatch.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index 0a3d3ed3..f9e606d8 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -108,7 +108,7 @@ mod events { } fn stream( - &self, + self: Box, input: iced_native::subscription::Input, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::StreamExt; diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 4f931278..b902baae 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -167,7 +167,7 @@ mod time { } fn stream( - &self, + self: Box, _input: Input, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; -- cgit From 77154869061594706185709d7b79ac520c8a125a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 02:12:25 +0100 Subject: Use generic `Hasher` in `stopwatch` --- examples/stopwatch.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'examples') diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index b902baae..686d239d 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -155,12 +155,13 @@ mod time { struct Every(std::time::Duration); - impl iced_native::subscription::Recipe - for Every + impl iced_native::subscription::Recipe for Every + where + Hasher: std::hash::Hasher, { type Output = std::time::Instant; - fn hash(&self, state: &mut iced_native::Hasher) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); -- cgit From c13ef73e8be5ba38451731c8f7e99ee54acf42f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 02:20:45 +0100 Subject: Hash `Duration` of `time::Every` in `stopwatch` --- examples/stopwatch.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'examples') diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 686d239d..d21bdaa8 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -165,6 +165,7 @@ mod time { use std::hash::Hash; std::any::TypeId::of::().hash(state); + self.0.hash(state); } fn stream( -- cgit From ba06d458d33d98bfaa5e66b3512ce7f063e8d7ba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 04:12:42 +0100 Subject: Move native events subscription to `iced_native` --- examples/events.rs | 42 +++++++----------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index f9e606d8..0b944495 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -49,7 +49,7 @@ impl Application for Events { fn subscriptions(&self) -> Subscription { if self.enabled { - events::all().map(Message::EventOccurred) + iced_native::subscription::events().map(Message::EventOccurred) } else { Subscription::none() } @@ -67,8 +67,12 @@ impl Application for Events { }, ); - let toggle = Checkbox::new(self.enabled, "Enabled", Message::Toggled) - .width(Length::Shrink); + let toggle = Checkbox::new( + self.enabled, + "Listen to runtime events", + Message::Toggled, + ) + .width(Length::Shrink); let content = Column::new() .width(Length::Shrink) @@ -85,35 +89,3 @@ impl Application for Events { .into() } } - -mod events { - pub fn all() -> iced::Subscription { - iced::Subscription::from_recipe(All) - } - - struct All; - - impl - iced_native::subscription::Recipe< - iced_native::Hasher, - iced_native::subscription::Input, - > for All - { - type Output = iced_native::Event; - - fn hash(&self, state: &mut iced_native::Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - } - - fn stream( - self: Box, - input: iced_native::subscription::Input, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::StreamExt; - - input.boxed() - } - } -} -- cgit From 293314405f5b8d4003db5ef8f428e659ae36872d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 04:49:13 +0100 Subject: Make `iced_native` subscription input opaque --- examples/stopwatch.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index d21bdaa8..0d52a091 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -155,13 +155,13 @@ mod time { struct Every(std::time::Duration); - impl iced_native::subscription::Recipe for Every + impl iced_native::subscription::Recipe for Every where - Hasher: std::hash::Hasher, + H: std::hash::Hasher, { type Output = std::time::Instant; - fn hash(&self, state: &mut Hasher) { + fn hash(&self, state: &mut H) { use std::hash::Hash; std::any::TypeId::of::().hash(state); @@ -170,7 +170,7 @@ mod time { fn stream( self: Box, - _input: Input, + _input: I, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; -- cgit From d6c3da21f7fe7a79bcfbc2a180dc111e42300a04 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 05:56:46 +0100 Subject: Write docs for subscriptions and reorganize a bit --- examples/events.rs | 2 +- examples/stopwatch.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/events.rs b/examples/events.rs index 0b944495..7d83fbd8 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -47,7 +47,7 @@ impl Application for Events { Command::none() } - fn subscriptions(&self) -> Subscription { + fn subscription(&self) -> Subscription { if self.enabled { iced_native::subscription::events().map(Message::EventOccurred) } else { diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 0d52a091..7a7f0793 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -74,7 +74,7 @@ impl Application for Stopwatch { Command::none() } - fn subscriptions(&self) -> Subscription { + fn subscription(&self) -> Subscription { match self.state { State::Idle => Subscription::none(), State::Ticking { .. } => { -- cgit From 430ab6e44432d044f8444575053d97651f0f7d20 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Dec 2019 20:48:32 +0100 Subject: Port `todos` to `async_std` --- examples/todos.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'examples') diff --git a/examples/todos.rs b/examples/todos.rs index 5f435fdc..42e88f65 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -517,21 +517,23 @@ impl SavedState { } async fn load() -> Result { - use std::io::Read; + use async_std::prelude::*; let mut contents = String::new(); - let mut file = std::fs::File::open(Self::path()) + let mut file = async_std::fs::File::open(Self::path()) + .await .map_err(|_| LoadError::FileError)?; file.read_to_string(&mut contents) + .await .map_err(|_| LoadError::FileError)?; serde_json::from_str(&contents).map_err(|_| LoadError::FormatError) } async fn save(self) -> Result<(), SaveError> { - use std::io::Write; + use async_std::prelude::*; let json = serde_json::to_string_pretty(&self) .map_err(|_| SaveError::FormatError)?; @@ -539,20 +541,23 @@ impl SavedState { let path = Self::path(); if let Some(dir) = path.parent() { - std::fs::create_dir_all(dir) + async_std::fs::create_dir_all(dir) + .await .map_err(|_| SaveError::DirectoryError)?; } - let mut file = - std::fs::File::create(path).map_err(|_| SaveError::FileError)?; + { + let mut file = async_std::fs::File::create(path) + .await + .map_err(|_| SaveError::FileError)?; - file.write_all(json.as_bytes()) - .map_err(|_| SaveError::WriteError)?; + file.write_all(json.as_bytes()) + .await + .map_err(|_| SaveError::WriteError)?; + } // This is a simple way to save at most once every couple seconds - // We will be able to get rid of it once we implement event - // subscriptions - std::thread::sleep(std::time::Duration::from_secs(2)); + async_std::task::sleep(std::time::Duration::from_secs(2)).await; Ok(()) } -- cgit