diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/download_progress/src/download.rs | 23 | ||||
-rw-r--r-- | examples/download_progress/src/main.rs | 54 | ||||
-rw-r--r-- | examples/gallery/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/gallery/src/civitai.rs | 119 | ||||
-rw-r--r-- | examples/gallery/src/main.rs | 190 | ||||
-rw-r--r-- | examples/game_of_life/src/main.rs | 14 | ||||
-rw-r--r-- | examples/multi_window/src/main.rs | 8 | ||||
-rw-r--r-- | examples/scrollable/src/main.rs | 12 | ||||
-rw-r--r-- | examples/todos/src/main.rs | 9 | ||||
-rw-r--r-- | examples/tour/src/main.rs | 14 | ||||
-rw-r--r-- | examples/websocket/src/echo.rs | 96 |
11 files changed, 339 insertions, 203 deletions
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index d63fb906..5b81f7a2 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,16 +1,14 @@ -use iced::futures::{SinkExt, Stream, StreamExt}; -use iced::stream::try_channel; +use iced::futures::StreamExt; +use iced::task::{sipper, Straw}; use std::sync::Arc; -pub fn download( - url: impl AsRef<str>, -) -> impl Stream<Item = Result<Progress, Error>> { - try_channel(1, move |mut output| async move { +pub fn download(url: impl AsRef<str>) -> impl Straw<(), Progress, Error> { + sipper(move |mut progress| async move { let response = reqwest::get(url.as_ref()).await?; let total = response.content_length().ok_or(Error::NoContentLength)?; - let _ = output.send(Progress::Downloading { percent: 0.0 }).await; + let _ = progress.send(Progress { percent: 0.0 }).await; let mut byte_stream = response.bytes_stream(); let mut downloaded = 0; @@ -19,23 +17,20 @@ pub fn download( let bytes = next_bytes?; downloaded += bytes.len(); - let _ = output - .send(Progress::Downloading { + let _ = progress + .send(Progress { percent: 100.0 * downloaded as f32 / total as f32, }) .await; } - let _ = output.send(Progress::Finished).await; - Ok(()) }) } #[derive(Debug, Clone)] -pub enum Progress { - Downloading { percent: f32 }, - Finished, +pub struct Progress { + pub percent: f32, } #[derive(Debug, Clone)] diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index f4b07203..8082eccd 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -4,7 +4,7 @@ use download::download; use iced::task; use iced::widget::{button, center, column, progress_bar, text, Column}; -use iced::{Center, Element, Right, Task}; +use iced::{Center, Element, Function, Right, Task}; pub fn main() -> iced::Result { iced::application( @@ -25,7 +25,7 @@ struct Example { pub enum Message { Add, Download(usize), - DownloadProgressed(usize, Result<download::Progress, download::Error>), + DownloadUpdated(usize, Update), } impl Example { @@ -52,15 +52,13 @@ impl Example { let task = download.start(); - task.map(move |progress| { - Message::DownloadProgressed(index, progress) - }) + task.map(Message::DownloadUpdated.with(index)) } - Message::DownloadProgressed(id, progress) => { + Message::DownloadUpdated(id, update) => { if let Some(download) = self.downloads.iter_mut().find(|download| download.id == id) { - download.progress(progress); + download.update(update); } Task::none() @@ -95,6 +93,12 @@ struct Download { state: State, } +#[derive(Debug, Clone)] +pub enum Update { + Downloading(download::Progress), + Finished(Result<(), download::Error>), +} + #[derive(Debug)] enum State { Idle, @@ -111,18 +115,20 @@ impl Download { } } - pub fn start( - &mut self, - ) -> Task<Result<download::Progress, download::Error>> { + pub fn start(&mut self) -> Task<Update> { match self.state { State::Idle { .. } | State::Finished { .. } | State::Errored { .. } => { - let (task, handle) = Task::stream(download( - "https://huggingface.co/\ + let (task, handle) = Task::sip( + download( + "https://huggingface.co/\ mattshumer/Reflection-Llama-3.1-70B/\ resolve/main/model-00001-of-00162.safetensors", - )) + ), + Update::Downloading, + Update::Finished, + ) .abortable(); self.state = State::Downloading { @@ -136,20 +142,18 @@ impl Download { } } - pub fn progress( - &mut self, - new_progress: Result<download::Progress, download::Error>, - ) { + pub fn update(&mut self, update: Update) { if let State::Downloading { progress, .. } = &mut self.state { - match new_progress { - Ok(download::Progress::Downloading { percent }) => { - *progress = percent; - } - Ok(download::Progress::Finished) => { - self.state = State::Finished; + match update { + Update::Downloading(new_progress) => { + *progress = new_progress.percent; } - Err(_error) => { - self.state = State::Errored; + Update::Finished(result) => { + self.state = if result.is_ok() { + State::Finished + } else { + State::Errored + }; } } } diff --git a/examples/gallery/Cargo.toml b/examples/gallery/Cargo.toml index 573389b1..6e8aba06 100644 --- a/examples/gallery/Cargo.toml +++ b/examples/gallery/Cargo.toml @@ -17,7 +17,10 @@ serde.features = ["derive"] bytes.workspace = true image.workspace = true +sipper.workspace = true tokio.workspace = true +blurhash = "0.2.3" + [lints] workspace = true diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs index 986b6bf2..04589030 100644 --- a/examples/gallery/src/civitai.rs +++ b/examples/gallery/src/civitai.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use serde::Deserialize; +use sipper::{sipper, Straw}; use tokio::task; use std::fmt; @@ -10,6 +11,7 @@ use std::sync::Arc; pub struct Image { pub id: Id, url: String, + hash: String, } impl Image { @@ -40,45 +42,76 @@ impl Image { Ok(response.items) } - pub async fn download(self, size: Size) -> Result<Rgba, Error> { - let client = reqwest::Client::new(); - - let bytes = client - .get(match size { - Size::Original => self.url, - Size::Thumbnail => self - .url - .split("/") - .map(|part| { - if part.starts_with("width=") { - "width=640" - } else { - part - } - }) - .collect::<Vec<_>>() - .join("/"), + pub async fn blurhash( + self, + width: u32, + height: u32, + ) -> Result<Blurhash, Error> { + task::spawn_blocking(move || { + let pixels = blurhash::decode(&self.hash, width, height, 1.0)?; + + Ok::<_, Error>(Blurhash { + rgba: Rgba { + width, + height, + pixels: Bytes::from(pixels), + }, }) - .send() - .await? - .error_for_status()? - .bytes() - .await?; - - let image = task::spawn_blocking(move || { - Ok::<_, Error>( - image::ImageReader::new(io::Cursor::new(bytes)) - .with_guessed_format()? - .decode()? - .to_rgba8(), - ) }) - .await??; + .await? + } - Ok(Rgba { - width: image.width(), - height: image.height(), - pixels: Bytes::from(image.into_raw()), + pub fn download(self, size: Size) -> impl Straw<Rgba, Blurhash, Error> { + sipper(move |mut sender| async move { + let client = reqwest::Client::new(); + + if let Size::Thumbnail { width, height } = size { + let image = self.clone(); + + drop(task::spawn(async move { + if let Ok(blurhash) = image.blurhash(width, height).await { + sender.send(blurhash).await; + } + })); + } + + let bytes = client + .get(match size { + Size::Original => self.url, + Size::Thumbnail { width, .. } => self + .url + .split("/") + .map(|part| { + if part.starts_with("width=") { + format!("width={}", width * 2) // High DPI + } else { + part.to_owned() + } + }) + .collect::<Vec<_>>() + .join("/"), + }) + .send() + .await? + .error_for_status()? + .bytes() + .await?; + + let image = task::spawn_blocking(move || { + Ok::<_, Error>( + image::ImageReader::new(io::Cursor::new(bytes)) + .with_guessed_format()? + .decode()? + .to_rgba8(), + ) + }) + .await??; + + Ok(Rgba { + width: image.width(), + height: image.height(), + pixels: Bytes::from(image.into_raw()), + }) }) } } @@ -88,6 +121,11 @@ impl Image { )] pub struct Id(u32); +#[derive(Debug, Clone)] +pub struct Blurhash { + pub rgba: Rgba, +} + #[derive(Clone)] pub struct Rgba { pub width: u32, @@ -107,7 +145,7 @@ impl fmt::Debug for Rgba { #[derive(Debug, Clone, Copy)] pub enum Size { Original, - Thumbnail, + Thumbnail { width: u32, height: u32 }, } #[derive(Debug, Clone)] @@ -117,6 +155,7 @@ pub enum Error { IOFailed(Arc<io::Error>), JoinFailed(Arc<task::JoinError>), ImageDecodingFailed(Arc<image::ImageError>), + BlurhashDecodingFailed(Arc<blurhash::Error>), } impl From<reqwest::Error> for Error { @@ -142,3 +181,9 @@ impl From<image::ImageError> for Error { Self::ImageDecodingFailed(Arc::new(error)) } } + +impl From<blurhash::Error> for Error { + fn from(error: blurhash::Error) -> Self { + Self::BlurhashDecodingFailed(Arc::new(error)) + } +} diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 290fa6a0..abafaf2d 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -7,14 +7,15 @@ mod civitai; use crate::civitai::{Error, Id, Image, Rgba, Size}; use iced::animation; -use iced::time::Instant; +use iced::time::{milliseconds, Instant}; use iced::widget::{ button, center_x, container, horizontal_space, image, mouse_area, opaque, pop, row, scrollable, stack, }; use iced::window; use iced::{ - color, Animation, ContentFit, Element, Fill, Subscription, Task, Theme, + color, Animation, ContentFit, Element, Fill, Function, Subscription, Task, + Theme, }; use std::collections::HashMap; @@ -28,7 +29,7 @@ fn main() -> iced::Result { struct Gallery { images: Vec<Image>, - thumbnails: HashMap<Id, Thumbnail>, + previews: HashMap<Id, Preview>, viewer: Viewer, now: Instant, } @@ -40,6 +41,7 @@ enum Message { ImageDownloaded(Result<Rgba, Error>), ThumbnailDownloaded(Id, Result<Rgba, Error>), ThumbnailHovered(Id, bool), + BlurhashDecoded(Id, civitai::Blurhash), Open(Id), Close, Animate(Instant), @@ -50,7 +52,7 @@ impl Gallery { ( Self { images: Vec::new(), - thumbnails: HashMap::new(), + previews: HashMap::new(), viewer: Viewer::new(), now: Instant::now(), }, @@ -64,9 +66,9 @@ impl Gallery { pub fn subscription(&self) -> Subscription<Message> { let is_animating = self - .thumbnails + .previews .values() - .any(|thumbnail| thumbnail.is_animating(self.now)) + .any(|preview| preview.is_animating(self.now)) || self.viewer.is_animating(self.now); if is_animating { @@ -93,9 +95,14 @@ impl Gallery { return Task::none(); }; - Task::perform(image.download(Size::Thumbnail), move |result| { - Message::ThumbnailDownloaded(id, result) - }) + Task::sip( + image.download(Size::Thumbnail { + width: Preview::WIDTH, + height: Preview::HEIGHT, + }), + Message::BlurhashDecoded.with(id), + Message::ThumbnailDownloaded.with(id), + ) } Message::ImageDownloaded(Ok(rgba)) => { self.viewer.show(rgba); @@ -103,14 +110,29 @@ impl Gallery { Task::none() } Message::ThumbnailDownloaded(id, Ok(rgba)) => { - let thumbnail = Thumbnail::new(rgba); - let _ = self.thumbnails.insert(id, thumbnail); + let thumbnail = if let Some(preview) = self.previews.remove(&id) + { + preview.load(rgba) + } else { + Preview::ready(rgba) + }; + + let _ = self.previews.insert(id, thumbnail); Task::none() } Message::ThumbnailHovered(id, is_hovered) => { - if let Some(thumbnail) = self.thumbnails.get_mut(&id) { - thumbnail.zoom.go_mut(is_hovered); + if let Some(preview) = self.previews.get_mut(&id) { + preview.toggle_zoom(is_hovered); + } + + Task::none() + } + Message::BlurhashDecoded(id, blurhash) => { + if !self.previews.contains_key(&id) { + let _ = self + .previews + .insert(id, Preview::loading(blurhash.rgba)); } Task::none() @@ -157,7 +179,7 @@ impl Gallery { row((0..=Image::LIMIT).map(|_| placeholder())) } else { row(self.images.iter().map(|image| { - card(image, self.thumbnails.get(&image.id), self.now) + card(image, self.previews.get(&image.id), self.now) })) } .spacing(10) @@ -174,33 +196,52 @@ impl Gallery { fn card<'a>( metadata: &'a Image, - thumbnail: Option<&'a Thumbnail>, + preview: Option<&'a Preview>, now: Instant, ) -> Element<'a, Message> { - let image: Element<'_, _> = if let Some(thumbnail) = thumbnail { - image(&thumbnail.handle) - .width(Fill) - .height(Fill) - .content_fit(ContentFit::Cover) - .opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)) - .scale(thumbnail.zoom.interpolate(1.0, 1.1, now)) - .into() + let image = if let Some(preview) = preview { + let thumbnail: Element<'_, _> = + if let Preview::Ready { thumbnail, .. } = &preview { + image(&thumbnail.handle) + .width(Fill) + .height(Fill) + .content_fit(ContentFit::Cover) + .opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)) + .scale(thumbnail.zoom.interpolate(1.0, 1.1, now)) + .into() + } else { + horizontal_space().into() + }; + + if let Some(blurhash) = preview.blurhash(now) { + let blurhash = image(&blurhash.handle) + .width(Fill) + .height(Fill) + .content_fit(ContentFit::Cover) + .opacity(blurhash.fade_in.interpolate(0.0, 1.0, now)); + + stack![blurhash, thumbnail].into() + } else { + thumbnail + } } else { horizontal_space().into() }; let card = mouse_area( container(image) - .width(Thumbnail::WIDTH) - .height(Thumbnail::HEIGHT) + .width(Preview::WIDTH) + .height(Preview::HEIGHT) .style(container::dark), ) .on_enter(Message::ThumbnailHovered(metadata.id, true)) .on_exit(Message::ThumbnailHovered(metadata.id, false)); - if thumbnail.is_some() { + if let Some(preview) = preview { + let is_thumbnail = matches!(preview, Preview::Ready { .. }); + button(card) - .on_press(Message::Open(metadata.id)) + .on_press_maybe(is_thumbnail.then_some(Message::Open(metadata.id))) .padding(0) .style(button::text) .into() @@ -213,23 +254,102 @@ fn card<'a>( fn placeholder<'a>() -> Element<'a, Message> { container(horizontal_space()) - .width(Thumbnail::WIDTH) - .height(Thumbnail::HEIGHT) + .width(Preview::WIDTH) + .height(Preview::HEIGHT) .style(container::dark) .into() } +enum Preview { + Loading { + blurhash: Blurhash, + }, + Ready { + blurhash: Option<Blurhash>, + thumbnail: Thumbnail, + }, +} + +struct Blurhash { + handle: image::Handle, + fade_in: Animation<bool>, +} + struct Thumbnail { handle: image::Handle, fade_in: Animation<bool>, zoom: Animation<bool>, } -impl Thumbnail { - const WIDTH: u16 = 320; - const HEIGHT: u16 = 410; +impl Preview { + const WIDTH: u32 = 320; + const HEIGHT: u32 = 410; + + fn loading(rgba: Rgba) -> Self { + Self::Loading { + blurhash: Blurhash { + fade_in: Animation::new(false) + .duration(milliseconds(700)) + .easing(animation::Easing::EaseIn) + .go(true), + handle: image::Handle::from_rgba( + rgba.width, + rgba.height, + rgba.pixels, + ), + }, + } + } - fn new(rgba: Rgba) -> Self { + fn ready(rgba: Rgba) -> Self { + Self::Ready { + blurhash: None, + thumbnail: Thumbnail::new(rgba), + } + } + + fn load(self, rgba: Rgba) -> Self { + let Self::Loading { blurhash } = self else { + return self; + }; + + Self::Ready { + blurhash: Some(blurhash), + thumbnail: Thumbnail::new(rgba), + } + } + + fn toggle_zoom(&mut self, enabled: bool) { + if let Self::Ready { thumbnail, .. } = self { + thumbnail.zoom.go_mut(enabled); + } + } + + fn is_animating(&self, now: Instant) -> bool { + match &self { + Self::Loading { blurhash } => blurhash.fade_in.is_animating(now), + Self::Ready { thumbnail, .. } => { + thumbnail.fade_in.is_animating(now) + || thumbnail.zoom.is_animating(now) + } + } + } + + fn blurhash(&self, now: Instant) -> Option<&Blurhash> { + match self { + Self::Loading { blurhash, .. } => Some(blurhash), + Self::Ready { + blurhash: Some(blurhash), + thumbnail, + .. + } if thumbnail.fade_in.is_animating(now) => Some(blurhash), + Self::Ready { .. } => None, + } + } +} + +impl Thumbnail { + pub fn new(rgba: Rgba) -> Self { Self { handle: image::Handle::from_rgba( rgba.width, @@ -242,10 +362,6 @@ impl Thumbnail { .easing(animation::Easing::EaseInOut), } } - - fn is_animating(&self, now: Instant) -> bool { - self.fade_in.is_animating(now) || self.zoom.is_animating(now) - } } struct Viewer { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index dec3df7f..9516f832 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -9,7 +9,7 @@ use iced::time::{self, milliseconds}; use iced::widget::{ button, checkbox, column, container, pick_list, row, slider, text, }; -use iced::{Center, Element, Fill, Subscription, Task, Theme}; +use iced::{Center, Element, Fill, Function, Subscription, Task, Theme}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); @@ -37,7 +37,7 @@ struct GameOfLife { #[derive(Debug, Clone)] enum Message { - Grid(grid::Message, usize), + Grid(usize, grid::Message), Tick, TogglePlayback, ToggleGrid(bool), @@ -61,7 +61,7 @@ impl GameOfLife { fn update(&mut self, message: Message) -> Task<Message> { match message { - Message::Grid(message, version) => { + Message::Grid(version, message) => { if version == self.version { self.grid.update(message); } @@ -78,9 +78,7 @@ impl GameOfLife { let version = self.version; - return Task::perform(task, move |message| { - Message::Grid(message, version) - }); + return Task::perform(task, Message::Grid.with(version)); } } Message::TogglePlayback => { @@ -129,9 +127,7 @@ impl GameOfLife { ); let content = column![ - self.grid - .view() - .map(move |message| Message::Grid(message, version)), + self.grid.view().map(Message::Grid.with(version)), controls, ] .height(Fill); diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index f9021c8d..8cec9d4c 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -3,7 +3,9 @@ use iced::widget::{ text_input, }; use iced::window; -use iced::{Center, Element, Fill, Subscription, Task, Theme, Vector}; +use iced::{ + Center, Element, Fill, Function, Subscription, Task, Theme, Vector, +}; use std::collections::BTreeMap; @@ -169,7 +171,7 @@ impl Window { let scale_input = column![ text("Window scale factor:"), text_input("Window Scale", &self.scale_input) - .on_input(move |msg| { Message::ScaleInputChanged(id, msg) }) + .on_input(Message::ScaleInputChanged.with(id)) .on_submit(Message::ScaleChanged( id, self.scale_input.to_string() @@ -179,7 +181,7 @@ impl Window { let title_input = column![ text("Window title:"), text_input("Window Title", &self.title) - .on_input(move |msg| { Message::TitleChanged(id, msg) }) + .on_input(Message::TitleChanged.with(id)) .id(format!("input-{id}")) ]; diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 6359fb5a..fec4e1b4 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -21,9 +21,9 @@ pub fn main() -> iced::Result { struct ScrollableDemo { scrollable_direction: Direction, - scrollbar_width: u16, - scrollbar_margin: u16, - scroller_width: u16, + scrollbar_width: u32, + scrollbar_margin: u32, + scroller_width: u32, current_scroll_offset: scrollable::RelativeOffset, anchor: scrollable::Anchor, } @@ -39,9 +39,9 @@ enum Direction { enum Message { SwitchDirection(Direction), AlignmentChanged(scrollable::Anchor), - ScrollbarWidthChanged(u16), - ScrollbarMarginChanged(u16), - ScrollerWidthChanged(u16), + ScrollbarWidthChanged(u32), + ScrollbarMarginChanged(u32), + ScrollerWidthChanged(u32), ScrollToBeginning, ScrollToEnd, Scrolled(scrollable::Viewport), diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 7faf742e..dfb73d96 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -4,7 +4,9 @@ use iced::widget::{ scrollable, text, text_input, Text, }; use iced::window; -use iced::{Center, Element, Fill, Font, Subscription, Task as Command}; +use iced::{ + Center, Element, Fill, Font, Function, Subscription, Task as Command, +}; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -215,9 +217,8 @@ impl Todos { .map(|(i, task)| { ( task.id, - task.view(i).map(move |message| { - Message::TaskMessage(i, message) - }), + task.view(i) + .map(Message::TaskMessage.with(i)), ) }), ) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 32720c47..2ca1df44 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -24,12 +24,12 @@ pub struct Tour { screen: Screen, slider: u8, layout: Layout, - spacing: u16, - text_size: u16, + spacing: u32, + text_size: u32, text_color: Color, language: Option<Language>, toggler: bool, - image_width: u16, + image_width: u32, image_filter_method: image::FilterMethod, input_value: String, input_is_secure: bool, @@ -43,11 +43,11 @@ pub enum Message { NextPressed, SliderChanged(u8), LayoutChanged(Layout), - SpacingChanged(u16), - TextSizeChanged(u16), + SpacingChanged(u32), + TextSizeChanged(u32), TextColorChanged(Color), LanguageSelected(Language), - ImageWidthChanged(u16), + ImageWidthChanged(u32), ImageUseNearestToggled(bool), InputChanged(String), ToggleSecureInput(bool), @@ -537,7 +537,7 @@ impl Screen { } fn ferris<'a>( - width: u16, + width: u32, filter_method: image::FilterMethod, ) -> Container<'a, Message> { center_x( diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index 14652936..149a260c 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -1,73 +1,59 @@ pub mod server; use iced::futures; -use iced::stream; +use iced::task::{sipper, Never, Sipper}; use iced::widget::text; use futures::channel::mpsc; use futures::sink::SinkExt; -use futures::stream::{Stream, StreamExt}; +use futures::stream::StreamExt; use async_tungstenite::tungstenite; use std::fmt; -pub fn connect() -> impl Stream<Item = Event> { - stream::channel(100, |mut output| async move { - let mut state = State::Disconnected; - +pub fn connect() -> impl Sipper<Never, Event> { + sipper(|mut output| async move { loop { - match &mut state { - State::Disconnected => { - const ECHO_SERVER: &str = "ws://127.0.0.1:3030"; + const ECHO_SERVER: &str = "ws://127.0.0.1:3030"; - match async_tungstenite::tokio::connect_async(ECHO_SERVER) - .await - { - Ok((websocket, _)) => { - let (sender, receiver) = mpsc::channel(100); + let (mut websocket, mut input) = + 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; + output.send(Event::Connected(Connection(sender))).await; - state = State::Connected(websocket, receiver); - } - Err(_) => { - tokio::time::sleep( - tokio::time::Duration::from_secs(1), - ) + (websocket.fuse(), receiver) + } + Err(_) => { + tokio::time::sleep(tokio::time::Duration::from_secs(1)) .await; - let _ = output.send(Event::Disconnected).await; - } + output.send(Event::Disconnected).await; + 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; - } - Err(_) => { - let _ = output.send(Event::Disconnected).await; - - state = State::Disconnected; - } - Ok(_) => continue, + }; + + loop { + futures::select! { + received = websocket.select_next_some() => { + match received { + Ok(tungstenite::Message::Text(message)) => { + output.send(Event::MessageReceived(Message::User(message))).await; + } + Err(_) => { + output.send(Event::Disconnected).await; + break; } + Ok(_) => {}, } + } + message = input.select_next_some() => { + let result = websocket.send(tungstenite::Message::Text(message.to_string())).await; - 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; - } + if result.is_err() { + output.send(Event::Disconnected).await; } } } @@ -76,18 +62,6 @@ pub fn connect() -> impl Stream<Item = Event> { }) } -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum State { - Disconnected, - Connected( - async_tungstenite::WebSocketStream< - async_tungstenite::tokio::ConnectStream, - >, - mpsc::Receiver<Message>, - ), -} - #[derive(Debug, Clone)] pub enum Event { Connected(Connection), |