Skip to content

Commit

Permalink
Introduce with helper and use sipper in gallery example
Browse files Browse the repository at this point in the history
  • Loading branch information
hecrj committed Feb 11, 2025
1 parent 9f21eae commit 0c528be
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 106 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,63 @@ pub use smol_str::SmolStr;
pub fn never<T>(never: std::convert::Infallible) -> T {
match never {}
}

/// Applies the given prefix value to the provided closure and returns
/// a new closure that takes the other argument.
///
/// This lets you partially "apply" a function—equivalent to currying,
/// but it only works with binary functions. If you want to apply an
/// arbitrary number of arguments, use the [`with!`] macro instead.
///
/// # When is this useful?
/// Sometimes you will want to identify the source or target
/// of some message in your user interface. This can be achieved through
/// normal means by defining a closure and moving the identifier
/// inside:
///
/// ```rust
/// # let element: Option<()> = Some(());
/// # enum Message { ButtonPressed(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// element.map(move |result| Message::ButtonPressed(id, result))
/// # };
/// ```
///
/// That's quite a mouthful. [`with()`] lets you write:
///
/// ```rust
/// # use iced_core::with;
/// # let element: Option<()> = Some(());
/// # enum Message { ButtonPressed(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// element.map(with(Message::ButtonPressed, id))
/// # };
/// ```
///
/// Effectively creating the same closure that partially applies
/// the `id` to the message—but much more concise!
pub fn with<T, R, O>(
mut f: impl FnMut(T, R) -> O,
prefix: T,
) -> impl FnMut(R) -> O
where
T: Clone,
{
move |result| f(prefix.clone(), result)
}

/// Applies the given prefix values to the provided closure in the first
/// argument and returns a new closure that takes its last argument.
///
/// This is variadic version of [`with()`] which works with any number of
/// arguments.
#[macro_export]
macro_rules! with {
($f:expr, $($x:expr),+ $(,)?) => {
move |result| $f($($x),+, result)
};
}
4 changes: 2 additions & 2 deletions examples/download_progress/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{with, Center, Element, Right, Task};

pub fn main() -> iced::Result {
iced::application(
Expand Down Expand Up @@ -52,7 +52,7 @@ impl Example {

let task = download.start();

task.map_with(index, Message::DownloadUpdated)
task.map(with(Message::DownloadUpdated, index))
}
Message::DownloadUpdated(id, update) => {
if let Some(download) =
Expand Down
1 change: 1 addition & 0 deletions examples/gallery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde.features = ["derive"]

bytes.workspace = true
image.workspace = true
sipper.workspace = true
tokio.workspace = true

blurhash = "0.2.3"
Expand Down
106 changes: 63 additions & 43 deletions examples/gallery/src/civitai.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bytes::Bytes;
use serde::Deserialize;
use sipper::{sipper, Straw};
use tokio::task;

use std::fmt;
Expand Down Expand Up @@ -45,58 +46,72 @@ impl Image {
self,
width: u32,
height: u32,
) -> Result<Rgba, Error> {
) -> Result<Blurhash, Error> {
task::spawn_blocking(move || {
let pixels = blurhash::decode(&self.hash, width, height, 1.0)?;

Ok::<_, Error>(Rgba {
width,
height,
pixels: Bytes::from(pixels),
Ok::<_, Error>(Blurhash {
rgba: Rgba {
width,
height,
pixels: Bytes::from(pixels),
},
})
})
.await?
}

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 { width } => self
.url
.split("/")
.map(|part| {
if part.starts_with("width=") {
format!("width={}", width * 2) // High DPI
} else {
part.to_owned()
}
})
.collect::<Vec<_>>()
.join("/"),
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(),
)
})
.send()
.await?
.error_for_status()?
.bytes()
.await?;
.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()),
Ok(Rgba {
width: image.width(),
height: image.height(),
pixels: Bytes::from(image.into_raw()),
})
})
}
}
Expand All @@ -106,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,
Expand All @@ -125,7 +145,7 @@ impl fmt::Debug for Rgba {
#[derive(Debug, Clone, Copy)]
pub enum Size {
Original,
Thumbnail { width: u32 },
Thumbnail { width: u32, height: u32 },
}

#[derive(Debug, Clone)]
Expand Down
30 changes: 15 additions & 15 deletions examples/gallery/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use iced::widget::{
};
use iced::window;
use iced::{
color, Animation, ContentFit, Element, Fill, Subscription, Task, Theme,
color, with, Animation, ContentFit, Element, Fill, Subscription, Task,
Theme,
};

use std::collections::HashMap;
Expand All @@ -40,7 +41,7 @@ enum Message {
ImageDownloaded(Result<Rgba, Error>),
ThumbnailDownloaded(Id, Result<Rgba, Error>),
ThumbnailHovered(Id, bool),
BlurhashDecoded(Id, Result<Rgba, Error>),
BlurhashDecoded(Id, civitai::Blurhash),
Open(Id),
Close,
Animate(Instant),
Expand Down Expand Up @@ -94,16 +95,14 @@ impl Gallery {
return Task::none();
};

Task::batch([
Task::future(
image.clone().blurhash(Preview::WIDTH, Preview::HEIGHT),
)
.map_with(id, Message::BlurhashDecoded),
Task::future(image.download(Size::Thumbnail {
Task::sip(
image.download(Size::Thumbnail {
width: Preview::WIDTH,
}))
.map_with(id, Message::ThumbnailDownloaded),
])
height: Preview::HEIGHT,
}),
with(Message::BlurhashDecoded, id),
with(Message::ThumbnailDownloaded, id),
)
}
Message::ImageDownloaded(Ok(rgba)) => {
self.viewer.show(rgba);
Expand All @@ -129,9 +128,11 @@ impl Gallery {

Task::none()
}
Message::BlurhashDecoded(id, Ok(rgba)) => {
Message::BlurhashDecoded(id, blurhash) => {
if !self.previews.contains_key(&id) {
let _ = self.previews.insert(id, Preview::loading(rgba));
let _ = self
.previews
.insert(id, Preview::loading(blurhash.rgba));
}

Task::none()
Expand Down Expand Up @@ -165,8 +166,7 @@ impl Gallery {
}
Message::ImagesListed(Err(error))
| Message::ImageDownloaded(Err(error))
| Message::ThumbnailDownloaded(_, Err(error))
| Message::BlurhashDecoded(_, Err(error)) => {
| Message::ThumbnailDownloaded(_, Err(error)) => {
dbg!(error);

Task::none()
Expand Down
46 changes: 1 addition & 45 deletions runtime/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl<T> Task<T> {
/// progress with the first closure and the output with the second one.
pub fn sip<S>(
sipper: S,
on_progress: impl Fn(S::Progress) -> T + MaybeSend + 'static,
on_progress: impl FnMut(S::Progress) -> T + MaybeSend + 'static,
on_output: impl FnOnce(<S as Future>::Output) -> T + MaybeSend + 'static,
) -> Self
where
Expand Down Expand Up @@ -98,50 +98,6 @@ impl<T> Task<T> {
self.then(move |output| Task::done(f(output)))
}

/// Combines a prefix value with the result of the [`Task`] using
/// the provided closure.
///
/// Sometimes you will want to identify the source or target
/// of some [`Task`] in your UI. This can be achieved through
/// normal means by using [`map`]:
///
/// ```rust
/// # use iced_runtime::Task;
/// # let task = Task::none();
/// # enum Message { TaskCompleted(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// task.map(move |result| Message::TaskCompleted(id, result))
/// # };
/// ```
///
/// Quite a mouthful. [`map_with`] lets you write:
///
/// ```rust
/// # use iced_runtime::Task;
/// # let task = Task::none();
/// # enum Message { TaskCompleted(u32, ()) }
/// # let id = 123;
/// # let _ = {
/// task.map_with(id, Message::TaskCompleted)
/// # };
/// ```
///
/// Much nicer!
pub fn map_with<P, O>(
self,
prefix: P,
mut f: impl FnMut(P, T) -> O + MaybeSend + 'static,
) -> Task<O>
where
T: MaybeSend + 'static,
P: MaybeSend + Clone + 'static,
O: MaybeSend + 'static,
{
self.map(move |result| f(prefix.clone(), result))
}

/// Performs a new [`Task`] for every output of the current [`Task`] using the
/// given closure.
///
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ pub use crate::core::gradient;
pub use crate::core::padding;
pub use crate::core::theme;
pub use crate::core::{
never, Alignment, Animation, Background, Border, Color, ContentFit,
never, with, Alignment, Animation, Background, Border, Color, ContentFit,
Degrees, Gradient, Length, Padding, Pixels, Point, Radians, Rectangle,
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector,
};
Expand Down

0 comments on commit 0c528be

Please sign in to comment.