Introduce with helper and use sipper in gallery example
This commit is contained in:
parent
9f21eae152
commit
0c528be2ea
8 changed files with 143 additions and 105 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1875,6 +1875,7 @@ dependencies = [
|
||||||
"image",
|
"image",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sipper",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,3 +93,63 @@ pub use smol_str::SmolStr;
|
||||||
pub fn never<T>(never: std::convert::Infallible) -> T {
|
pub fn never<T>(never: std::convert::Infallible) -> T {
|
||||||
match never {}
|
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,7 +4,7 @@ use download::download;
|
||||||
|
|
||||||
use iced::task;
|
use iced::task;
|
||||||
use iced::widget::{button, center, column, progress_bar, text, Column};
|
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 {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(
|
iced::application(
|
||||||
|
|
@ -52,7 +52,7 @@ impl Example {
|
||||||
|
|
||||||
let task = download.start();
|
let task = download.start();
|
||||||
|
|
||||||
task.map_with(index, Message::DownloadUpdated)
|
task.map(with(Message::DownloadUpdated, index))
|
||||||
}
|
}
|
||||||
Message::DownloadUpdated(id, update) => {
|
Message::DownloadUpdated(id, update) => {
|
||||||
if let Some(download) =
|
if let Some(download) =
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ serde.features = ["derive"]
|
||||||
|
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
|
sipper.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
||||||
blurhash = "0.2.3"
|
blurhash = "0.2.3"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use sipper::{sipper, Straw};
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -45,26 +46,39 @@ impl Image {
|
||||||
self,
|
self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<Rgba, Error> {
|
) -> Result<Blurhash, Error> {
|
||||||
task::spawn_blocking(move || {
|
task::spawn_blocking(move || {
|
||||||
let pixels = blurhash::decode(&self.hash, width, height, 1.0)?;
|
let pixels = blurhash::decode(&self.hash, width, height, 1.0)?;
|
||||||
|
|
||||||
Ok::<_, Error>(Rgba {
|
Ok::<_, Error>(Blurhash {
|
||||||
|
rgba: Rgba {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels: Bytes::from(pixels),
|
pixels: Bytes::from(pixels),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download(self, size: Size) -> Result<Rgba, Error> {
|
pub fn download(self, size: Size) -> impl Straw<Rgba, Blurhash, Error> {
|
||||||
|
sipper(move |mut sender| async move {
|
||||||
let client = reqwest::Client::new();
|
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
|
let bytes = client
|
||||||
.get(match size {
|
.get(match size {
|
||||||
Size::Original => self.url,
|
Size::Original => self.url,
|
||||||
Size::Thumbnail { width } => self
|
Size::Thumbnail { width, .. } => self
|
||||||
.url
|
.url
|
||||||
.split("/")
|
.split("/")
|
||||||
.map(|part| {
|
.map(|part| {
|
||||||
|
|
@ -98,6 +112,7 @@ impl Image {
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
pixels: Bytes::from(image.into_raw()),
|
pixels: Bytes::from(image.into_raw()),
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +121,11 @@ impl Image {
|
||||||
)]
|
)]
|
||||||
pub struct Id(u32);
|
pub struct Id(u32);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Blurhash {
|
||||||
|
pub rgba: Rgba,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Rgba {
|
pub struct Rgba {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
|
|
@ -125,7 +145,7 @@ impl fmt::Debug for Rgba {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Size {
|
pub enum Size {
|
||||||
Original,
|
Original,
|
||||||
Thumbnail { width: u32 },
|
Thumbnail { width: u32, height: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
color, Animation, ContentFit, Element, Fill, Subscription, Task, Theme,
|
color, with, Animation, ContentFit, Element, Fill, Subscription, Task,
|
||||||
|
Theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -40,7 +41,7 @@ enum Message {
|
||||||
ImageDownloaded(Result<Rgba, Error>),
|
ImageDownloaded(Result<Rgba, Error>),
|
||||||
ThumbnailDownloaded(Id, Result<Rgba, Error>),
|
ThumbnailDownloaded(Id, Result<Rgba, Error>),
|
||||||
ThumbnailHovered(Id, bool),
|
ThumbnailHovered(Id, bool),
|
||||||
BlurhashDecoded(Id, Result<Rgba, Error>),
|
BlurhashDecoded(Id, civitai::Blurhash),
|
||||||
Open(Id),
|
Open(Id),
|
||||||
Close,
|
Close,
|
||||||
Animate(Instant),
|
Animate(Instant),
|
||||||
|
|
@ -94,16 +95,14 @@ impl Gallery {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
|
||||||
Task::batch([
|
Task::sip(
|
||||||
Task::future(
|
image.download(Size::Thumbnail {
|
||||||
image.clone().blurhash(Preview::WIDTH, Preview::HEIGHT),
|
|
||||||
)
|
|
||||||
.map_with(id, Message::BlurhashDecoded),
|
|
||||||
Task::future(image.download(Size::Thumbnail {
|
|
||||||
width: Preview::WIDTH,
|
width: Preview::WIDTH,
|
||||||
}))
|
height: Preview::HEIGHT,
|
||||||
.map_with(id, Message::ThumbnailDownloaded),
|
}),
|
||||||
])
|
with(Message::BlurhashDecoded, id),
|
||||||
|
with(Message::ThumbnailDownloaded, id),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Message::ImageDownloaded(Ok(rgba)) => {
|
Message::ImageDownloaded(Ok(rgba)) => {
|
||||||
self.viewer.show(rgba);
|
self.viewer.show(rgba);
|
||||||
|
|
@ -129,9 +128,11 @@ impl Gallery {
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::BlurhashDecoded(id, Ok(rgba)) => {
|
Message::BlurhashDecoded(id, blurhash) => {
|
||||||
if !self.previews.contains_key(&id) {
|
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()
|
Task::none()
|
||||||
|
|
@ -165,8 +166,7 @@ impl Gallery {
|
||||||
}
|
}
|
||||||
Message::ImagesListed(Err(error))
|
Message::ImagesListed(Err(error))
|
||||||
| Message::ImageDownloaded(Err(error))
|
| Message::ImageDownloaded(Err(error))
|
||||||
| Message::ThumbnailDownloaded(_, Err(error))
|
| Message::ThumbnailDownloaded(_, Err(error)) => {
|
||||||
| Message::BlurhashDecoded(_, Err(error)) => {
|
|
||||||
dbg!(error);
|
dbg!(error);
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ impl<T> Task<T> {
|
||||||
/// progress with the first closure and the output with the second one.
|
/// progress with the first closure and the output with the second one.
|
||||||
pub fn sip<S>(
|
pub fn sip<S>(
|
||||||
sipper: 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,
|
on_output: impl FnOnce(<S as Future>::Output) -> T + MaybeSend + 'static,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
|
|
@ -98,50 +98,6 @@ impl<T> Task<T> {
|
||||||
self.then(move |output| Task::done(f(output)))
|
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
|
/// Performs a new [`Task`] for every output of the current [`Task`] using the
|
||||||
/// given closure.
|
/// given closure.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,7 @@ pub use crate::core::gradient;
|
||||||
pub use crate::core::padding;
|
pub use crate::core::padding;
|
||||||
pub use crate::core::theme;
|
pub use crate::core::theme;
|
||||||
pub use crate::core::{
|
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,
|
Degrees, Gradient, Length, Padding, Pixels, Point, Radians, Rectangle,
|
||||||
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector,
|
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue