Replace with function with Function trait

This commit is contained in:
Héctor Ramón Jiménez 2025-02-11 10:36:45 +01:00
parent 080db34849
commit eab723866e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
7 changed files with 75 additions and 79 deletions

View file

@ -94,62 +94,59 @@ 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 trait extension for binary functions (`Fn(A, B) -> O`).
/// a new closure that takes the other argument.
/// ///
/// This lets you partially "apply" a function—equivalent to currying, /// It enables you to use a bunch of nifty functional programming paradigms
/// but it only works with binary functions. If you want to apply an /// that work well with iced.
/// arbitrary number of arguments, use the [`with!`] macro instead. pub trait Function<A, B, O> {
/// /// Applies the given first argument to a binary function and returns
/// # When is this useful? /// a new function that takes the other argument.
/// Sometimes you will want to identify the source or target ///
/// of some message in your user interface. This can be achieved through /// This lets you partially "apply" a function—equivalent to currying,
/// normal means by defining a closure and moving the identifier /// but it only works with binary functions. If you want to apply an
/// inside: /// arbitrary number of arguments, create a little struct for them.
/// ///
/// ```rust /// # When is this useful?
/// # let element: Option<()> = Some(()); /// Sometimes you will want to identify the source or target
/// # enum Message { ButtonPressed(u32, ()) } /// of some message in your user interface. This can be achieved through
/// let id = 123; /// normal means by defining a closure and moving the identifier
/// /// inside:
/// # let _ = { ///
/// element.map(move |result| Message::ButtonPressed(id, result)) /// ```rust
/// # }; /// # let element: Option<()> = Some(());
/// ``` /// # enum Message { ButtonPressed(u32, ()) }
/// /// let id = 123;
/// That's quite a mouthful. [`with()`] lets you write: ///
/// /// # let _ = {
/// ```rust /// element.map(move |result| Message::ButtonPressed(id, result))
/// # use iced_core::with; /// # };
/// # let element: Option<()> = Some(()); /// ```
/// # enum Message { ButtonPressed(u32, ()) } ///
/// let id = 123; /// That's quite a mouthful. [`with`] lets you write:
/// ///
/// # let _ = { /// ```rust
/// element.map(with(Message::ButtonPressed, id)) /// # use iced_core::Function;
/// # }; /// # let element: Option<()> = Some(());
/// ``` /// # enum Message { ButtonPressed(u32, ()) }
/// /// let id = 123;
/// Effectively creating the same closure that partially applies ///
/// the `id` to the message—but much more concise! /// # let _ = {
pub fn with<T, R, O>( /// element.map(Message::ButtonPressed.with(id))
mut f: impl FnMut(T, R) -> O, /// # };
prefix: T, /// ```
) -> impl FnMut(R) -> O ///
where /// Effectively creating the same closure that partially applies
T: Copy, /// the `id` to the message—but much more concise!
{ fn with(self, prefix: A) -> impl Fn(B) -> O;
move |result| f(prefix, result)
} }
/// Applies the given prefix values to the provided closure in the first impl<F, A, B, O> Function<A, B, O> for F
/// argument and returns a new closure that takes its last argument. where
/// F: Fn(A, B) -> O,
/// This is a variadic version of [`with()`] which works with any number of Self: Sized,
/// arguments. A: Copy,
#[macro_export] {
macro_rules! with { fn with(self, prefix: A) -> impl Fn(B) -> O {
($f:expr, $($x:expr),+ $(,)?) => { move |result| self(prefix, result)
move |result| $f($($x),+, result) }
};
} }

View file

@ -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::{with, Center, Element, Right, Task}; use iced::{Center, Element, Function, 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(Message::DownloadUpdated, index)) task.map(Message::DownloadUpdated.with(index))
} }
Message::DownloadUpdated(id, update) => { Message::DownloadUpdated(id, update) => {
if let Some(download) = if let Some(download) =

View file

@ -14,7 +14,7 @@ use iced::widget::{
}; };
use iced::window; use iced::window;
use iced::{ use iced::{
color, with, Animation, ContentFit, Element, Fill, Subscription, Task, color, Animation, ContentFit, Element, Fill, Function, Subscription, Task,
Theme, Theme,
}; };
@ -100,8 +100,8 @@ impl Gallery {
width: Preview::WIDTH, width: Preview::WIDTH,
height: Preview::HEIGHT, height: Preview::HEIGHT,
}), }),
with(Message::BlurhashDecoded, id), Message::BlurhashDecoded.with(id),
with(Message::ThumbnailDownloaded, id), Message::ThumbnailDownloaded.with(id),
) )
} }
Message::ImageDownloaded(Ok(rgba)) => { Message::ImageDownloaded(Ok(rgba)) => {

View file

@ -9,7 +9,7 @@ use iced::time::{self, milliseconds};
use iced::widget::{ use iced::widget::{
button, checkbox, column, container, pick_list, row, slider, text, 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 { pub fn main() -> iced::Result {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
@ -37,7 +37,7 @@ struct GameOfLife {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
Grid(grid::Message, usize), Grid(usize, grid::Message),
Tick, Tick,
TogglePlayback, TogglePlayback,
ToggleGrid(bool), ToggleGrid(bool),
@ -61,7 +61,7 @@ impl GameOfLife {
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Grid(message, version) => { Message::Grid(version, message) => {
if version == self.version { if version == self.version {
self.grid.update(message); self.grid.update(message);
} }
@ -78,9 +78,7 @@ impl GameOfLife {
let version = self.version; let version = self.version;
return Task::perform(task, move |message| { return Task::perform(task, Message::Grid.with(version));
Message::Grid(message, version)
});
} }
} }
Message::TogglePlayback => { Message::TogglePlayback => {
@ -129,9 +127,7 @@ impl GameOfLife {
); );
let content = column![ let content = column![
self.grid self.grid.view().map(Message::Grid.with(version)),
.view()
.map(move |message| Message::Grid(message, version)),
controls, controls,
] ]
.height(Fill); .height(Fill);

View file

@ -3,7 +3,9 @@ use iced::widget::{
text_input, text_input,
}; };
use iced::window; 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; use std::collections::BTreeMap;
@ -169,7 +171,7 @@ impl Window {
let scale_input = column![ let scale_input = column![
text("Window scale factor:"), text("Window scale factor:"),
text_input("Window Scale", &self.scale_input) 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( .on_submit(Message::ScaleChanged(
id, id,
self.scale_input.to_string() self.scale_input.to_string()
@ -179,7 +181,7 @@ impl Window {
let title_input = column![ let title_input = column![
text("Window title:"), text("Window title:"),
text_input("Window Title", &self.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}")) .id(format!("input-{id}"))
]; ];

View file

@ -4,7 +4,9 @@ use iced::widget::{
scrollable, text, text_input, Text, scrollable, text, text_input, Text,
}; };
use iced::window; 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 serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
@ -215,9 +217,8 @@ impl Todos {
.map(|(i, task)| { .map(|(i, task)| {
( (
task.id, task.id,
task.view(i).map(move |message| { task.view(i)
Message::TaskMessage(i, message) .map(Message::TaskMessage.with(i)),
}),
) )
}), }),
) )

View file

@ -505,9 +505,9 @@ 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, with, Alignment, Animation, Background, Border, Color, ContentFit, never, Alignment, Animation, Background, Border, Color, ContentFit,
Degrees, Gradient, Length, Padding, Pixels, Point, Radians, Rectangle, Degrees, Function, Gradient, Length, Padding, Pixels, Point, Radians,
Rotation, Settings, Shadow, Size, Theme, Transformation, Vector, Rectangle, Rotation, Settings, Shadow, Size, Theme, Transformation, Vector,
}; };
pub use crate::runtime::exit; pub use crate::runtime::exit;
pub use iced_futures::Subscription; pub use iced_futures::Subscription;