Skip to content

Commit

Permalink
Merge pull request #2805 from iced-rs/feature/sipper-support
Browse files Browse the repository at this point in the history
`sipper` support and some QoL
  • Loading branch information
hecrj authored Feb 12, 2025
2 parents bf205a8 + e78c757 commit 89a4126
Show file tree
Hide file tree
Showing 17 changed files with 272 additions and 190 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ raw-window-handle = "0.6"
resvg = "0.42"
rustc-hash = "2.0"
sha2 = "0.10"
sipper = "0.1"
smol = "1.0"
smol_str = "0.2"
softbuffer = "0.4"
Expand Down
5 changes: 3 additions & 2 deletions core/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
///
/// ```no_run
/// # mod iced {
/// # pub use iced_core::Function;
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # pub mod widget {
Expand All @@ -119,7 +120,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// use counter::Counter;
///
/// use iced::widget::row;
/// use iced::Element;
/// use iced::{Element, Function};
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
Expand All @@ -142,7 +143,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// counter.map(move |message| Message::Counter(index, message))
/// counter.map(Message::Counter.with(index))
/// }),
/// )
/// .into()
Expand Down
57 changes: 57 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,60 @@ pub use smol_str::SmolStr;
pub fn never<T>(never: std::convert::Infallible) -> T {
match never {}
}

/// A trait extension for binary functions (`Fn(A, B) -> O`).
///
/// It enables you to use a bunch of nifty functional programming paradigms
/// that work well with iced.
pub trait Function<A, B, O> {
/// Applies the given first argument to a binary function and returns
/// a new function 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, create a little struct for them.
///
/// # 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`](Self::with) lets you write:
///
/// ```rust
/// # use iced_core::Function;
/// # let element: Option<()> = Some(());
/// # enum Message { ButtonPressed(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// element.map(Message::ButtonPressed.with(id))
/// # };
/// ```
///
/// Effectively creating the same closure that partially applies
/// the `id` to the message—but much more concise!
fn with(self, prefix: A) -> impl Fn(B) -> O;
}

impl<F, A, B, O> Function<A, B, O> for F
where
F: Fn(A, B) -> O,
Self: Sized,
A: Copy,
{
fn with(self, prefix: A) -> impl Fn(B) -> O {
move |result| self(prefix, result)
}
}
23 changes: 9 additions & 14 deletions examples/download_progress/src/download.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down
54 changes: 29 additions & 25 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::{Center, Element, Function, Right, Task};

pub fn main() -> iced::Result {
iced::application(
Expand All @@ -25,7 +25,7 @@ struct Example {
pub enum Message {
Add,
Download(usize),
DownloadProgressed(usize, Result<download::Progress, download::Error>),
DownloadUpdated(usize, Update),
}

impl Example {
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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
};
}
}
}
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
Loading

0 comments on commit 89a4126

Please sign in to comment.