summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/text/editor.rs2
-rw-r--r--examples/download_progress/Cargo.toml2
-rw-r--r--examples/download_progress/src/download.rs14
-rw-r--r--examples/editor/src/main.rs39
-rw-r--r--examples/multi_window/src/main.rs16
-rw-r--r--examples/pokedex/Cargo.toml2
-rw-r--r--examples/pokedex/src/main.rs14
-rw-r--r--examples/slider/Cargo.toml1
-rw-r--r--examples/slider/src/main.rs43
-rw-r--r--examples/todos/src/main.rs13
-rw-r--r--examples/websocket/src/echo.rs102
-rw-r--r--examples/websocket/src/main.rs29
-rw-r--r--futures/src/backend/native/async_std.rs2
-rw-r--r--futures/src/backend/native/smol.rs2
-rw-r--r--futures/src/backend/native/tokio.rs2
-rw-r--r--futures/src/backend/wasm/wasm_bindgen.rs2
-rw-r--r--futures/src/lib.rs1
-rw-r--r--futures/src/stream.rs46
-rw-r--r--futures/src/subscription.rs364
-rw-r--r--graphics/src/text/editor.rs21
-rw-r--r--runtime/src/clipboard.rs10
-rw-r--r--runtime/src/font.rs5
-rw-r--r--runtime/src/lib.rs5
-rw-r--r--runtime/src/task.rs200
-rw-r--r--runtime/src/window.rs52
-rw-r--r--src/advanced.rs2
-rw-r--r--src/application.rs22
-rw-r--r--src/daemon.rs21
-rw-r--r--src/lib.rs18
-rw-r--r--src/program.rs111
-rw-r--r--widget/Cargo.toml1
-rw-r--r--widget/assets/iced-logo.svg2
-rw-r--r--widget/src/combo_box.rs9
-rw-r--r--widget/src/container.rs4
-rw-r--r--widget/src/helpers.rs42
-rw-r--r--widget/src/scrollable.rs33
-rw-r--r--widget/src/slider.rs2
-rw-r--r--widget/src/text_editor.rs5
-rw-r--r--widget/src/text_input.rs13
-rw-r--r--widget/src/vertical_slider.rs2
-rw-r--r--winit/src/program.rs10
-rw-r--r--winit/src/system.rs2
42 files changed, 662 insertions, 626 deletions
diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
index fbf60696..aea00921 100644
--- a/core/src/text/editor.rs
+++ b/core/src/text/editor.rs
@@ -70,6 +70,8 @@ pub enum Action {
SelectWord,
/// Select the line at the current cursor.
SelectLine,
+ /// Select the entire buffer.
+ SelectAll,
/// Perform an [`Edit`].
Edit(Edit),
/// Click the [`Editor`] at the given [`Point`].
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
index 18a49f66..f78df529 100644
--- a/examples/download_progress/Cargo.toml
+++ b/examples/download_progress/Cargo.toml
@@ -10,6 +10,6 @@ iced.workspace = true
iced.features = ["tokio"]
[dependencies.reqwest]
-version = "0.11"
+version = "0.12"
default-features = false
features = ["rustls-tls"]
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index d6cc1e24..bdf57290 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -1,4 +1,5 @@
-use iced::subscription;
+use iced::futures;
+use iced::Subscription;
use std::hash::Hash;
@@ -7,9 +8,14 @@ pub fn file<I: 'static + Hash + Copy + Send + Sync, T: ToString>(
id: I,
url: T,
) -> iced::Subscription<(I, Progress)> {
- subscription::unfold(id, State::Ready(url.to_string()), move |state| {
- download(id, state)
- })
+ Subscription::run_with_id(
+ id,
+ futures::stream::unfold(State::Ready(url.to_string()), move |state| {
+ use iced::futures::FutureExt;
+
+ download(id, state).map(Some)
+ }),
+ )
}
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs
index bed9d94a..ce3c478d 100644
--- a/examples/editor/src/main.rs
+++ b/examples/editor/src/main.rs
@@ -13,12 +13,11 @@ use std::sync::Arc;
pub fn main() -> iced::Result {
iced::application("Editor - Iced", Editor::update, Editor::view)
- .load(Editor::load)
.subscription(Editor::subscription)
.theme(Editor::theme)
.font(include_bytes!("../fonts/icons.ttf").as_slice())
.default_font(Font::MONOSPACE)
- .run()
+ .run_with(Editor::new)
}
struct Editor {
@@ -41,20 +40,22 @@ enum Message {
}
impl Editor {
- fn new() -> Self {
- Self {
- file: None,
- content: text_editor::Content::new(),
- theme: highlighter::Theme::SolarizedDark,
- is_loading: true,
- is_dirty: false,
- }
- }
-
- fn load() -> Task<Message> {
- Task::perform(
- load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))),
- Message::FileOpened,
+ fn new() -> (Self, Task<Message>) {
+ (
+ Self {
+ file: None,
+ content: text_editor::Content::new(),
+ theme: highlighter::Theme::SolarizedDark,
+ is_loading: true,
+ is_dirty: false,
+ },
+ Task::perform(
+ load_file(format!(
+ "{}/src/main.rs",
+ env!("CARGO_MANIFEST_DIR")
+ )),
+ Message::FileOpened,
+ ),
)
}
@@ -214,12 +215,6 @@ impl Editor {
}
}
-impl Default for Editor {
- fn default() -> Self {
- Self::new()
- }
-}
-
#[derive(Debug, Clone)]
pub enum Error {
DialogClosed,
diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs
index 98e753ab..460ca3b5 100644
--- a/examples/multi_window/src/main.rs
+++ b/examples/multi_window/src/main.rs
@@ -9,16 +9,12 @@ use std::collections::BTreeMap;
fn main() -> iced::Result {
iced::daemon(Example::title, Example::update, Example::view)
- .load(|| {
- window::open(window::Settings::default()).map(Message::WindowOpened)
- })
.subscription(Example::subscription)
.theme(Example::theme)
.scale_factor(Example::scale_factor)
- .run()
+ .run_with(Example::new)
}
-#[derive(Default)]
struct Example {
windows: BTreeMap<window::Id, Window>,
}
@@ -43,6 +39,16 @@ enum Message {
}
impl Example {
+ fn new() -> (Self, Task<Message>) {
+ (
+ Self {
+ windows: BTreeMap::new(),
+ },
+ window::open(window::Settings::default())
+ .map(Message::WindowOpened),
+ )
+ }
+
fn title(&self, window: window::Id) -> String {
self.windows
.get(&window)
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
index bf7e1e35..1a6d5445 100644
--- a/examples/pokedex/Cargo.toml
+++ b/examples/pokedex/Cargo.toml
@@ -16,7 +16,7 @@ version = "1.0"
features = ["derive"]
[dependencies.reqwest]
-version = "0.11"
+version = "0.12"
default-features = false
features = ["json", "rustls-tls"]
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index b22ffe7f..7414ae54 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -4,17 +4,13 @@ use iced::{Alignment, Element, Length, Task};
pub fn main() -> iced::Result {
iced::application(Pokedex::title, Pokedex::update, Pokedex::view)
- .load(Pokedex::search)
- .run()
+ .run_with(Pokedex::new)
}
-#[derive(Debug, Default)]
+#[derive(Debug)]
enum Pokedex {
- #[default]
Loading,
- Loaded {
- pokemon: Pokemon,
- },
+ Loaded { pokemon: Pokemon },
Errored,
}
@@ -25,6 +21,10 @@ enum Message {
}
impl Pokedex {
+ fn new() -> (Self, Task<Message>) {
+ (Self::Loading, Self::search())
+ }
+
fn search() -> Task<Message> {
Task::perform(Pokemon::search(), Message::PokemonFound)
}
diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml
index fad8916e..05e74d2c 100644
--- a/examples/slider/Cargo.toml
+++ b/examples/slider/Cargo.toml
@@ -7,3 +7,4 @@ publish = false
[dependencies]
iced.workspace = true
+iced.features = ["svg"]
diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs
index 0b4c29aa..fd312763 100644
--- a/examples/slider/src/main.rs
+++ b/examples/slider/src/main.rs
@@ -1,5 +1,5 @@
-use iced::widget::{center, column, container, slider, text, vertical_slider};
-use iced::{Element, Length};
+use iced::widget::{column, container, iced, slider, text, vertical_slider};
+use iced::{Alignment, Element, Length};
pub fn main() -> iced::Result {
iced::run("Slider - Iced", Slider::update, Slider::view)
@@ -12,19 +12,11 @@ pub enum Message {
pub struct Slider {
value: u8,
- default: u8,
- step: u8,
- shift_step: u8,
}
impl Slider {
fn new() -> Self {
- Slider {
- value: 50,
- default: 50,
- step: 5,
- shift_step: 1,
- }
+ Slider { value: 50 }
}
fn update(&mut self, message: Message) {
@@ -37,32 +29,27 @@ impl Slider {
fn view(&self) -> Element<Message> {
let h_slider = container(
- slider(0..=100, self.value, Message::SliderChanged)
- .default(self.default)
- .step(self.step)
- .shift_step(self.shift_step),
+ slider(1..=100, self.value, Message::SliderChanged)
+ .default(50)
+ .shift_step(5),
)
.width(250);
let v_slider = container(
- vertical_slider(0..=100, self.value, Message::SliderChanged)
- .default(self.default)
- .step(self.step)
- .shift_step(self.shift_step),
+ vertical_slider(1..=100, self.value, Message::SliderChanged)
+ .default(50)
+ .shift_step(5),
)
.height(200);
let text = text(self.value);
- center(
- column![
- container(v_slider).center_x(Length::Fill),
- container(h_slider).center_x(Length::Fill),
- container(text).center_x(Length::Fill)
- ]
- .spacing(25),
- )
- .into()
+ column![v_slider, h_slider, text, iced(self.value as f32),]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(20)
+ .padding(20)
+ .into()
}
}
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 6ed50d31..b34f71ce 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -18,16 +18,14 @@ pub fn main() -> iced::Result {
tracing_subscriber::fmt::init();
iced::application(Todos::title, Todos::update, Todos::view)
- .load(Todos::load)
.subscription(Todos::subscription)
.font(include_bytes!("../fonts/icons.ttf").as_slice())
.window_size((500.0, 800.0))
- .run()
+ .run_with(Todos::new)
}
-#[derive(Default, Debug)]
+#[derive(Debug)]
enum Todos {
- #[default]
Loading,
Loaded(State),
}
@@ -54,8 +52,11 @@ enum Message {
}
impl Todos {
- fn load() -> Command<Message> {
- Command::perform(SavedState::load(), Message::Loaded)
+ fn new() -> (Self, Command<Message>) {
+ (
+ Self::Loading,
+ Command::perform(SavedState::load(), Message::Loaded),
+ )
}
fn title(&self) -> String {
diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs
index cd32cb66..14652936 100644
--- a/examples/websocket/src/echo.rs
+++ b/examples/websocket/src/echo.rs
@@ -1,87 +1,79 @@
pub mod server;
use iced::futures;
-use iced::subscription::{self, Subscription};
+use iced::stream;
use iced::widget::text;
use futures::channel::mpsc;
use futures::sink::SinkExt;
-use futures::stream::StreamExt;
+use futures::stream::{Stream, StreamExt};
use async_tungstenite::tungstenite;
use std::fmt;
-pub fn connect() -> Subscription<Event> {
- struct Connect;
+pub fn connect() -> impl Stream<Item = Event> {
+ stream::channel(100, |mut output| async move {
+ let mut state = State::Disconnected;
- subscription::channel(
- std::any::TypeId::of::<Connect>(),
- 100,
- |mut output| async move {
- let mut state = State::Disconnected;
+ loop {
+ match &mut state {
+ State::Disconnected => {
+ const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
- loop {
- match &mut state {
- State::Disconnected => {
- const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
-
- match async_tungstenite::tokio::connect_async(
- ECHO_SERVER,
- )
+ 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;
+ {
+ Ok((websocket, _)) => {
+ let (sender, receiver) = mpsc::channel(100);
- state = State::Connected(websocket, receiver);
- }
- Err(_) => {
- tokio::time::sleep(
- tokio::time::Duration::from_secs(1),
- )
+ let _ = output
+ .send(Event::Connected(Connection(sender)))
.await;
- let _ = output.send(Event::Disconnected).await;
- }
+ state = State::Connected(websocket, receiver);
+ }
+ Err(_) => {
+ tokio::time::sleep(
+ tokio::time::Duration::from_secs(1),
+ )
+ .await;
+
+ let _ = output.send(Event::Disconnected).await;
}
}
- 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,
+ }
+ 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;
}
- }
-
- message = input.select_next_some() => {
- let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
-
- if result.is_err() {
+ Err(_) => {
let _ = output.send(Event::Disconnected).await;
state = State::Disconnected;
}
+ Ok(_) => continue,
+ }
+ }
+
+ 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;
}
}
}
}
}
- },
- )
+ }
+ })
}
#[derive(Debug)]
diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs
index 8422ce16..d8246436 100644
--- a/examples/websocket/src/main.rs
+++ b/examples/websocket/src/main.rs
@@ -9,12 +9,10 @@ use once_cell::sync::Lazy;
pub fn main() -> iced::Result {
iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view)
- .load(WebSocket::load)
.subscription(WebSocket::subscription)
- .run()
+ .run_with(WebSocket::new)
}
-#[derive(Default)]
struct WebSocket {
messages: Vec<echo::Message>,
new_message: String,
@@ -30,11 +28,18 @@ enum Message {
}
impl WebSocket {
- fn load() -> Task<Message> {
- Task::batch([
- Task::perform(echo::server::run(), |_| Message::Server),
- widget::focus_next(),
- ])
+ fn new() -> (Self, Task<Message>) {
+ (
+ Self {
+ messages: Vec::new(),
+ new_message: String::new(),
+ state: State::Disconnected,
+ },
+ Task::batch([
+ Task::perform(echo::server::run(), |_| Message::Server),
+ widget::focus_next(),
+ ]),
+ )
}
fn update(&mut self, message: Message) -> Task<Message> {
@@ -83,7 +88,7 @@ impl WebSocket {
}
fn subscription(&self) -> Subscription<Message> {
- echo::connect().map(Message::Echo)
+ Subscription::run(echo::connect).map(Message::Echo)
}
fn view(&self) -> Element<Message> {
@@ -140,10 +145,4 @@ enum State {
Connected(echo::Connection),
}
-impl Default for State {
- fn default() -> Self {
- Self::Disconnected
- }
-}
-
static MESSAGE_LOG: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs
index b7da5e90..86714f45 100644
--- a/futures/src/backend/native/async_std.rs
+++ b/futures/src/backend/native/async_std.rs
@@ -27,7 +27,7 @@ pub mod time {
pub fn every(
duration: std::time::Duration,
) -> Subscription<std::time::Instant> {
- Subscription::from_recipe(Every(duration))
+ subscription::from_recipe(Every(duration))
}
#[derive(Debug)]
diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs
index aaf1518c..8d448e7f 100644
--- a/futures/src/backend/native/smol.rs
+++ b/futures/src/backend/native/smol.rs
@@ -26,7 +26,7 @@ pub mod time {
pub fn every(
duration: std::time::Duration,
) -> Subscription<std::time::Instant> {
- Subscription::from_recipe(Every(duration))
+ subscription::from_recipe(Every(duration))
}
#[derive(Debug)]
diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs
index df91d798..9dc3593d 100644
--- a/futures/src/backend/native/tokio.rs
+++ b/futures/src/backend/native/tokio.rs
@@ -31,7 +31,7 @@ pub mod time {
pub fn every(
duration: std::time::Duration,
) -> Subscription<std::time::Instant> {
- Subscription::from_recipe(Every(duration))
+ subscription::from_recipe(Every(duration))
}
#[derive(Debug)]
diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs
index 3228dd18..f7846c01 100644
--- a/futures/src/backend/wasm/wasm_bindgen.rs
+++ b/futures/src/backend/wasm/wasm_bindgen.rs
@@ -26,7 +26,7 @@ pub mod time {
pub fn every(
duration: std::time::Duration,
) -> Subscription<wasm_timer::Instant> {
- Subscription::from_recipe(Every(duration))
+ subscription::from_recipe(Every(duration))
}
#[derive(Debug)]
diff --git a/futures/src/lib.rs b/futures/src/lib.rs
index a874a618..31738823 100644
--- a/futures/src/lib.rs
+++ b/futures/src/lib.rs
@@ -15,6 +15,7 @@ pub mod backend;
pub mod event;
pub mod executor;
pub mod keyboard;
+pub mod stream;
pub mod subscription;
pub use executor::Executor;
diff --git a/futures/src/stream.rs b/futures/src/stream.rs
new file mode 100644
index 00000000..af2f8c99
--- /dev/null
+++ b/futures/src/stream.rs
@@ -0,0 +1,46 @@
+//! Create asynchronous streams of data.
+use futures::channel::mpsc;
+use futures::stream::{self, Stream, StreamExt};
+
+use std::future::Future;
+
+/// Creates a new [`Stream`] that produces the items sent from a [`Future`]
+/// to the [`mpsc::Sender`] provided to the closure.
+///
+/// This is a more ergonomic [`stream::unfold`], which allows you to go
+/// from the "world of futures" to the "world of streams" by simply looping
+/// and publishing to an async channel from inside a [`Future`].
+pub fn channel<T, F>(
+ size: usize,
+ f: impl FnOnce(mpsc::Sender<T>) -> F,
+) -> impl Stream<Item = T>
+where
+ F: Future<Output = ()>,
+{
+ let (sender, receiver) = mpsc::channel(size);
+
+ let runner = stream::once(f(sender)).filter_map(|_| async { None });
+
+ stream::select(receiver, runner)
+}
+
+/// Creates a new [`Stream`] that produces the items sent from a [`Future`]
+/// that can fail to the [`mpsc::Sender`] provided to the closure.
+pub fn try_channel<T, E, F>(
+ size: usize,
+ f: impl FnOnce(mpsc::Sender<T>) -> F,
+) -> impl Stream<Item = Result<T, E>>
+where
+ F: Future<Output = Result<(), E>>,
+{
+ let (sender, receiver) = mpsc::channel(size);
+
+ let runner = stream::once(f(sender)).filter_map(|result| async {
+ match result {
+ Ok(()) => None,
+ Err(error) => Some(Err(error)),
+ }
+ });
+
+ stream::select(receiver.map(Ok), runner)
+}
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 316fc44d..1a0d454d 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -5,11 +5,9 @@ pub use tracker::Tracker;
use crate::core::event;
use crate::core::window;
-use crate::futures::{Future, Stream};
+use crate::futures::Stream;
use crate::{BoxStream, MaybeSend};
-use futures::channel::mpsc;
-use futures::never::Never;
use std::any::TypeId;
use std::hash::Hash;
@@ -61,20 +59,66 @@ pub type Hasher = rustc_hash::FxHasher;
/// A request to listen to external events.
///
-/// Besides performing async actions on demand with `Command`, most
+/// Besides performing async actions on demand with `Task`, most
/// applications also need to listen to external events passively.
///
-/// A [`Subscription`] is normally provided to some runtime, like a `Command`,
+/// A [`Subscription`] is normally provided to some runtime, like a `Task`,
/// and it will generate events as long as the user keeps requesting it.
///
/// For instance, you can use a [`Subscription`] to listen to a `WebSocket`
/// connection, keyboard presses, mouse events, time ticks, etc.
+///
+/// # The Lifetime of a [`Subscription`]
+/// Much like a [`Future`] or a [`Stream`], a [`Subscription`] does not produce any effects
+/// on its own. For a [`Subscription`] to run, it must be returned to the iced runtime—normally
+/// in the `subscription` function of an `application` or a `daemon`.
+///
+/// When a [`Subscription`] is provided to the runtime for the first time, the runtime will
+/// start running it asynchronously. Running a [`Subscription`] consists in building its underlying
+/// [`Stream`] and executing it in an async runtime.
+///
+/// Therefore, you can think of a [`Subscription`] as a "stream builder". It simply represents a way
+/// to build a certain [`Stream`] together with some way to _identify_ it.
+///
+/// Identification is important because when a specific [`Subscription`] stops being returned to the
+/// iced runtime, the runtime will kill its associated [`Stream`]. The runtime uses the identity of a
+/// [`Subscription`] to keep track of it.
+///
+/// This way, iced allows you to declaratively __subscribe__ to particular streams of data temporarily
+/// and whenever necessary.
+///
+/// ```
+/// # mod iced {
+/// # pub mod time {
+/// # pub use iced_futures::backend::default::time::every;
+/// # pub use std::time::{Duration, Instant};
+/// # }
+/// #
+/// # pub use iced_futures::Subscription;
+/// # }
+/// use iced::time::{self, Duration, Instant};
+/// use iced::Subscription;
+///
+/// struct State {
+/// timer_enabled: bool,
+/// }
+///
+/// fn subscription(state: &State) -> Subscription<Instant> {
+/// if state.timer_enabled {
+/// time::every(Duration::from_secs(1))
+/// } else {
+/// Subscription::none()
+/// }
+/// }
+/// ```
+///
+/// [`Future`]: std::future::Future
#[must_use = "`Subscription` must be returned to runtime to take effect"]
-pub struct Subscription<Message> {
- recipes: Vec<Box<dyn Recipe<Output = Message>>>,
+pub struct Subscription<T> {
+ recipes: Vec<Box<dyn Recipe<Output = T>>>,
}
-impl<Message> Subscription<Message> {
+impl<T> Subscription<T> {
/// Returns an empty [`Subscription`] that will not produce any output.
pub fn none() -> Self {
Self {
@@ -82,19 +126,102 @@ impl<Message> Subscription<Message> {
}
}
- /// Creates a [`Subscription`] from a [`Recipe`] describing it.
- pub fn from_recipe(
- recipe: impl Recipe<Output = Message> + 'static,
- ) -> Self {
- Self {
- recipes: vec![Box::new(recipe)],
- }
+ /// Returns a [`Subscription`] that will call the given function to create and
+ /// asynchronously run the given [`Stream`].
+ ///
+ /// # Creating an asynchronous worker with bidirectional communication
+ /// You can leverage this helper to create a [`Subscription`] that spawns
+ /// an asynchronous worker in the background and establish a channel of
+ /// communication with an `iced` application.
+ ///
+ /// You can achieve this by creating an `mpsc` channel inside the closure
+ /// and returning the `Sender` as a `Message` for the `Application`:
+ ///
+ /// ```
+ /// use iced_futures::subscription::{self, Subscription};
+ /// use iced_futures::stream;
+ /// use iced_futures::futures::channel::mpsc;
+ /// use iced_futures::futures::sink::SinkExt;
+ /// use iced_futures::futures::Stream;
+ ///
+ /// pub enum Event {
+ /// Ready(mpsc::Sender<Input>),
+ /// WorkFinished,
+ /// // ...
+ /// }
+ ///
+ /// enum Input {
+ /// DoSomeWork,
+ /// // ...
+ /// }
+ ///
+ /// fn some_worker() -> impl Stream<Item = Event> {
+ /// stream::channel(100, |mut output| async move {
+ /// // Create channel
+ /// let (sender, mut receiver) = mpsc::channel(100);
+ ///
+ /// // Send the sender back to the application
+ /// output.send(Event::Ready(sender)).await;
+ ///
+ /// loop {
+ /// use iced_futures::futures::StreamExt;
+ ///
+ /// // Read next input sent from `Application`
+ /// let input = receiver.select_next_some().await;
+ ///
+ /// match input {
+ /// Input::DoSomeWork => {
+ /// // Do some async work...
+ ///
+ /// // Finally, we can optionally produce a message to tell the
+ /// // `Application` the work is done
+ /// output.send(Event::WorkFinished).await;
+ /// }
+ /// }
+ /// }
+ /// })
+ /// }
+ ///
+ /// fn subscription() -> Subscription<Event> {
+ /// Subscription::run(some_worker)
+ /// }
+ /// ```
+ ///
+ /// Check out the [`websocket`] example, which showcases this pattern to maintain a `WebSocket`
+ /// connection open.
+ ///
+ /// [`websocket`]: https://github.com/iced-rs/iced/tree/0.12/examples/websocket
+ pub fn run<S>(builder: fn() -> S) -> Self
+ where
+ S: Stream<Item = T> + MaybeSend + 'static,
+ T: 'static,
+ {
+ from_recipe(Runner {
+ id: builder,
+ spawn: move |_| builder(),
+ })
+ }
+
+ /// Returns a [`Subscription`] that will create and asynchronously run the
+ /// given [`Stream`].
+ ///
+ /// The `id` will be used to uniquely identify the [`Subscription`].
+ pub fn run_with_id<I, S>(id: I, stream: S) -> Subscription<T>
+ where
+ I: Hash + 'static,
+ S: Stream<Item = T> + MaybeSend + 'static,
+ T: 'static,
+ {
+ from_recipe(Runner {
+ id,
+ spawn: move |_| stream,
+ })
}
/// Batches all the provided subscriptions and returns the resulting
/// [`Subscription`].
pub fn batch(
- subscriptions: impl IntoIterator<Item = Subscription<Message>>,
+ subscriptions: impl IntoIterator<Item = Subscription<T>>,
) -> Self {
Self {
recipes: subscriptions
@@ -104,18 +231,13 @@ impl<Message> Subscription<Message> {
}
}
- /// Returns the different recipes of the [`Subscription`].
- pub fn into_recipes(self) -> Vec<Box<dyn Recipe<Output = Message>>> {
- self.recipes
- }
-
/// Adds a value to the [`Subscription`] context.
///
/// The value will be part of the identity of a [`Subscription`].
- pub fn with<T>(mut self, value: T) -> Subscription<(T, Message)>
+ pub fn with<A>(mut self, value: A) -> Subscription<(A, T)>
where
- Message: 'static,
- T: std::hash::Hash + Clone + Send + Sync + 'static,
+ T: 'static,
+ A: std::hash::Hash + Clone + Send + Sync + 'static,
{
Subscription {
recipes: self
@@ -123,7 +245,7 @@ impl<Message> Subscription<Message> {
.drain(..)
.map(|recipe| {
Box::new(With::new(recipe, value.clone()))
- as Box<dyn Recipe<Output = (T, Message)>>
+ as Box<dyn Recipe<Output = (A, T)>>
})
.collect(),
}
@@ -136,8 +258,8 @@ impl<Message> Subscription<Message> {
/// will panic in debug mode otherwise.
pub fn map<F, A>(mut self, f: F) -> Subscription<A>
where
- Message: 'static,
- F: Fn(Message) -> A + MaybeSend + Clone + 'static,
+ T: 'static,
+ F: Fn(T) -> A + MaybeSend + Clone + 'static,
A: 'static,
{
debug_assert!(
@@ -159,7 +281,23 @@ impl<Message> Subscription<Message> {
}
}
-impl<Message> std::fmt::Debug for Subscription<Message> {
+/// Creates a [`Subscription`] from a [`Recipe`] describing it.
+pub fn from_recipe<T>(
+ recipe: impl Recipe<Output = T> + 'static,
+) -> Subscription<T> {
+ Subscription {
+ recipes: vec![Box::new(recipe)],
+ }
+}
+
+/// Returns the different recipes of the [`Subscription`].
+pub fn into_recipes<T>(
+ subscription: Subscription<T>,
+) -> Vec<Box<dyn Recipe<Output = T>>> {
+ subscription.recipes
+}
+
+impl<T> std::fmt::Debug for Subscription<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Subscription").finish()
}
@@ -273,65 +411,13 @@ where
}
}
-/// Returns a [`Subscription`] that will call the given function to create and
-/// asynchronously run the given [`Stream`].
-pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message>
-where
- S: Stream<Item = Message> + MaybeSend + 'static,
- Message: 'static,
-{
- Subscription::from_recipe(Runner {
- id: builder,
- spawn: move |_| builder(),
- })
-}
-
-/// Returns a [`Subscription`] that will create and asynchronously run the
-/// given [`Stream`].
-///
-/// The `id` will be used to uniquely identify the [`Subscription`].
-pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message>
-where
- I: Hash + 'static,
- S: Stream<Item = Message> + MaybeSend + 'static,
- Message: 'static,
-{
- Subscription::from_recipe(Runner {
- id,
- spawn: move |_| stream,
- })
-}
-
-/// Returns a [`Subscription`] that will create and asynchronously run a
-/// [`Stream`] that will call the provided closure to produce every `Message`.
-///
-/// The `id` will be used to uniquely identify the [`Subscription`].
-pub fn unfold<I, T, Fut, Message>(
- id: I,
- initial: T,
- mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
-) -> Subscription<Message>
+pub(crate) fn filter_map<I, F, T>(id: I, f: F) -> Subscription<T>
where
I: Hash + 'static,
- T: MaybeSend + 'static,
- Fut: Future<Output = (Message, T)> + MaybeSend + 'static,
- Message: 'static + MaybeSend,
+ F: Fn(Event) -> Option<T> + MaybeSend + 'static,
+ T: 'static + MaybeSend,
{
- use futures::future::FutureExt;
-
- run_with_id(
- id,
- futures::stream::unfold(initial, move |state| f(state).map(Some)),
- )
-}
-
-pub(crate) fn filter_map<I, F, Message>(id: I, f: F) -> Subscription<Message>
-where
- I: Hash + 'static,
- F: Fn(Event) -> Option<Message> + MaybeSend + 'static,
- Message: 'static + MaybeSend,
-{
- Subscription::from_recipe(Runner {
+ from_recipe(Runner {
id,
spawn: |events| {
use futures::future;
@@ -342,122 +428,22 @@ where
})
}
-/// Creates a [`Subscription`] that publishes the events sent from a [`Future`]
-/// to an [`mpsc::Sender`] with the given bounds.
-///
-/// # Creating an asynchronous worker with bidirectional communication
-/// You can leverage this helper to create a [`Subscription`] that spawns
-/// an asynchronous worker in the background and establish a channel of
-/// communication with an `iced` application.
-///
-/// You can achieve this by creating an `mpsc` channel inside the closure
-/// and returning the `Sender` as a `Message` for the `Application`:
-///
-/// ```
-/// use iced_futures::subscription::{self, Subscription};
-/// use iced_futures::futures::channel::mpsc;
-/// use iced_futures::futures::sink::SinkExt;
-///
-/// pub enum Event {
-/// Ready(mpsc::Sender<Input>),
-/// WorkFinished,
-/// // ...
-/// }
-///
-/// enum Input {
-/// DoSomeWork,
-/// // ...
-/// }
-///
-/// enum State {
-/// Starting,
-/// Ready(mpsc::Receiver<Input>),
-/// }
-///
-/// fn some_worker() -> Subscription<Event> {
-/// struct SomeWorker;
-///
-/// subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move {
-/// let mut state = State::Starting;
-///
-/// loop {
-/// match &mut state {
-/// State::Starting => {
-/// // Create channel
-/// let (sender, receiver) = mpsc::channel(100);
-///
-/// // Send the sender back to the application
-/// output.send(Event::Ready(sender)).await;
-///
-/// // We are ready to receive messages
-/// state = State::Ready(receiver);
-/// }
-/// State::Ready(receiver) => {
-/// use iced_futures::futures::StreamExt;
-///
-/// // Read next input sent from `Application`
-/// let input = receiver.select_next_some().await;
-///
-/// match input {
-/// Input::DoSomeWork => {
-/// // Do some async work...
-///
-/// // Finally, we can optionally produce a message to tell the
-/// // `Application` the work is done
-/// output.send(Event::WorkFinished).await;
-/// }
-/// }
-/// }
-/// }
-/// }
-/// })
-/// }
-/// ```
-///
-/// Check out the [`websocket`] example, which showcases this pattern to maintain a `WebSocket`
-/// connection open.
-///
-/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.12/examples/websocket
-pub fn channel<I, Fut, Message>(
- id: I,
- size: usize,
- f: impl FnOnce(mpsc::Sender<Message>) -> Fut + MaybeSend + 'static,
-) -> Subscription<Message>
-where
- I: Hash + 'static,
- Fut: Future<Output = Never> + MaybeSend + 'static,
- Message: 'static + MaybeSend,
-{
- use futures::stream::{self, StreamExt};
-
- Subscription::from_recipe(Runner {
- id,
- spawn: move |_| {
- let (sender, receiver) = mpsc::channel(size);
-
- let runner = stream::once(f(sender)).map(|_| unreachable!());
-
- stream::select(receiver, runner)
- },
- })
-}
-
-struct Runner<I, F, S, Message>
+struct Runner<I, F, S, T>
where
F: FnOnce(EventStream) -> S,
- S: Stream<Item = Message>,
+ S: Stream<Item = T>,
{
id: I,
spawn: F,
}
-impl<I, S, F, Message> Recipe for Runner<I, F, S, Message>
+impl<I, F, S, T> Recipe for Runner<I, F, S, T>
where
I: Hash + 'static,
F: FnOnce(EventStream) -> S,
- S: Stream<Item = Message> + MaybeSend + 'static,
+ S: Stream<Item = T> + MaybeSend + 'static,
{
- type Output = Message;
+ type Output = T;
fn hash(&self, state: &mut Hasher) {
std::any::TypeId::of::<I>().hash(state);
diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs
index c488a51c..36b4ca6e 100644
--- a/graphics/src/text/editor.rs
+++ b/graphics/src/text/editor.rs
@@ -385,6 +385,27 @@ impl editor::Editor for Editor {
}));
}
}
+ Action::SelectAll => {
+ let buffer = editor.buffer();
+ if buffer.lines.len() > 1
+ || buffer
+ .lines
+ .first()
+ .is_some_and(|line| !line.text().is_empty())
+ {
+ let cursor = editor.cursor();
+ editor.set_select_opt(Some(cosmic_text::Cursor {
+ line: 0,
+ index: 0,
+ ..cursor
+ }));
+
+ editor.action(
+ font_system.raw(),
+ motion_to_action(Motion::DocumentEnd),
+ );
+ }
+ }
// Editing events
Action::Edit(edit) => {
diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs
index 19950d01..a02cc011 100644
--- a/runtime/src/clipboard.rs
+++ b/runtime/src/clipboard.rs
@@ -1,7 +1,7 @@
//! Access the clipboard.
use crate::core::clipboard::Kind;
use crate::futures::futures::channel::oneshot;
-use crate::Task;
+use crate::task::{self, Task};
/// A clipboard action to be performed by some [`Task`].
///
@@ -27,7 +27,7 @@ pub enum Action {
/// Read the current contents of the clipboard.
pub fn read() -> Task<Option<String>> {
- Task::oneshot(|channel| {
+ task::oneshot(|channel| {
crate::Action::Clipboard(Action::Read {
target: Kind::Standard,
channel,
@@ -37,7 +37,7 @@ pub fn read() -> Task<Option<String>> {
/// Read the current contents of the primary clipboard.
pub fn read_primary() -> Task<Option<String>> {
- Task::oneshot(|channel| {
+ task::oneshot(|channel| {
crate::Action::Clipboard(Action::Read {
target: Kind::Primary,
channel,
@@ -47,7 +47,7 @@ pub fn read_primary() -> Task<Option<String>> {
/// Write the given contents to the clipboard.
pub fn write<T>(contents: String) -> Task<T> {
- Task::effect(crate::Action::Clipboard(Action::Write {
+ task::effect(crate::Action::Clipboard(Action::Write {
target: Kind::Standard,
contents,
}))
@@ -55,7 +55,7 @@ pub fn write<T>(contents: String) -> Task<T> {
/// Write the given contents to the primary clipboard.
pub fn write_primary<Message>(contents: String) -> Task<Message> {
- Task::effect(crate::Action::Clipboard(Action::Write {
+ task::effect(crate::Action::Clipboard(Action::Write {
target: Kind::Primary,
contents,
}))
diff --git a/runtime/src/font.rs b/runtime/src/font.rs
index d54eb6a8..75fdfc11 100644
--- a/runtime/src/font.rs
+++ b/runtime/src/font.rs
@@ -1,5 +1,6 @@
//! Load and use fonts.
-use crate::{Action, Task};
+use crate::task::{self, Task};
+use crate::Action;
use std::borrow::Cow;
/// An error while loading a font.
@@ -8,7 +9,7 @@ pub enum Error {}
/// Load a font from its bytes.
pub fn load(bytes: impl Into<Cow<'static, [u8]>>) -> Task<Result<(), Error>> {
- Task::oneshot(|channel| Action::LoadFont {
+ task::oneshot(|channel| Action::LoadFont {
bytes: bytes.into(),
channel,
})
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 7e46593a..f27657d1 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -15,14 +15,13 @@ pub mod keyboard;
pub mod overlay;
pub mod program;
pub mod system;
+pub mod task;
pub mod user_interface;
pub mod window;
#[cfg(feature = "multi-window")]
pub mod multi_window;
-mod task;
-
// We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled.
#[cfg(feature = "debug")]
@@ -127,5 +126,5 @@ where
/// This will normally close any application windows and
/// terminate the runtime loop.
pub fn exit<T>() -> Task<T> {
- Task::effect(Action::Exit)
+ task::effect(Action::Exit)
}
diff --git a/runtime/src/task.rs b/runtime/src/task.rs
index b8a83d6d..b75aca89 100644
--- a/runtime/src/task.rs
+++ b/runtime/src/task.rs
@@ -1,3 +1,4 @@
+//! Create runtime tasks.
use crate::core::widget;
use crate::futures::futures::channel::mpsc;
use crate::futures::futures::channel::oneshot;
@@ -29,24 +30,6 @@ impl<T> Task<T> {
Self::future(future::ready(value))
}
- /// Creates a new [`Task`] that runs the given [`Future`] and produces
- /// its output.
- pub fn future(future: impl Future<Output = T> + MaybeSend + 'static) -> Self
- where
- T: 'static,
- {
- Self::stream(stream::once(future))
- }
-
- /// Creates a new [`Task`] that runs the given [`Stream`] and produces
- /// each of its items.
- pub fn stream(stream: impl Stream<Item = T> + MaybeSend + 'static) -> Self
- where
- T: 'static,
- {
- Self(Some(boxed_stream(stream.map(Action::Output))))
- }
-
/// Creates a [`Task`] that runs the given [`Future`] to completion and maps its
/// output with the given closure.
pub fn perform<A>(
@@ -83,66 +66,6 @@ impl<T> Task<T> {
))))
}
- /// Creates a new [`Task`] that runs the given [`widget::Operation`] and produces
- /// its output.
- pub fn widget(operation: impl widget::Operation<T> + 'static) -> Task<T>
- where
- T: Send + 'static,
- {
- Self::channel(move |sender| {
- let operation =
- widget::operation::map(Box::new(operation), move |value| {
- let _ = sender.clone().try_send(value);
- });
-
- Action::Widget(Box::new(operation))
- })
- }
-
- /// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
- /// produces the value fed to the [`oneshot::Sender`].
- pub fn oneshot(f: impl FnOnce(oneshot::Sender<T>) -> Action<T>) -> Task<T>
- where
- T: MaybeSend + 'static,
- {
- let (sender, receiver) = oneshot::channel();
-
- let action = f(sender);
-
- Self(Some(boxed_stream(
- stream::once(async move { action }).chain(
- receiver.into_stream().filter_map(|result| async move {
- Some(Action::Output(result.ok()?))
- }),
- ),
- )))
- }
-
- /// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
- /// produces the values fed to the [`mpsc::Sender`].
- pub fn channel(f: impl FnOnce(mpsc::Sender<T>) -> Action<T>) -> Task<T>
- where
- T: MaybeSend + 'static,
- {
- let (sender, receiver) = mpsc::channel(1);
-
- let action = f(sender);
-
- Self(Some(boxed_stream(
- stream::once(async move { action })
- .chain(receiver.map(|result| Action::Output(result))),
- )))
- }
-
- /// Creates a new [`Task`] that executes the given [`Action`] and produces no output.
- pub fn effect(action: impl Into<Action<Never>>) -> Self {
- let action = action.into();
-
- Self(Some(boxed_stream(stream::once(async move {
- action.output().expect_err("no output")
- }))))
- }
-
/// Maps the output of a [`Task`] with the given closure.
pub fn map<O>(
self,
@@ -236,9 +159,59 @@ impl<T> Task<T> {
}
}
- /// Returns the underlying [`Stream`] of the [`Task`].
- pub fn into_stream(self) -> Option<BoxStream<Action<T>>> {
- self.0
+ /// Creates a new [`Task`] that can be aborted with the returned [`Handle`].
+ pub fn abortable(self) -> (Self, Handle)
+ where
+ T: 'static,
+ {
+ match self.0 {
+ Some(stream) => {
+ let (stream, handle) = stream::abortable(stream);
+
+ (Self(Some(boxed_stream(stream))), Handle(Some(handle)))
+ }
+ None => (Self(None), Handle(None)),
+ }
+ }
+
+ /// Creates a new [`Task`] that runs the given [`Future`] and produces
+ /// its output.
+ pub fn future(future: impl Future<Output = T> + MaybeSend + 'static) -> Self
+ where
+ T: 'static,
+ {
+ Self::stream(stream::once(future))
+ }
+
+ /// Creates a new [`Task`] that runs the given [`Stream`] and produces
+ /// each of its items.
+ pub fn stream(stream: impl Stream<Item = T> + MaybeSend + 'static) -> Self
+ where
+ T: 'static,
+ {
+ Self(Some(boxed_stream(stream.map(Action::Output))))
+ }
+}
+
+/// A handle to a [`Task`] that can be used for aborting it.
+#[derive(Debug, Clone)]
+pub struct Handle(Option<stream::AbortHandle>);
+
+impl Handle {
+ /// Aborts the [`Task`] of this [`Handle`].
+ pub fn abort(&self) {
+ if let Some(handle) = &self.0 {
+ handle.abort();
+ }
+ }
+
+ /// Returns `true` if the [`Task`] of this [`Handle`] has been aborted.
+ pub fn is_aborted(&self) -> bool {
+ if let Some(handle) = &self.0 {
+ handle.is_aborted()
+ } else {
+ true
+ }
}
}
@@ -283,3 +256,68 @@ where
Self::none()
}
}
+
+/// Creates a new [`Task`] that runs the given [`widget::Operation`] and produces
+/// its output.
+pub fn widget<T>(operation: impl widget::Operation<T> + 'static) -> Task<T>
+where
+ T: Send + 'static,
+{
+ channel(move |sender| {
+ let operation =
+ widget::operation::map(Box::new(operation), move |value| {
+ let _ = sender.clone().try_send(value);
+ });
+
+ Action::Widget(Box::new(operation))
+ })
+}
+
+/// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
+/// produces the value fed to the [`oneshot::Sender`].
+pub fn oneshot<T>(f: impl FnOnce(oneshot::Sender<T>) -> Action<T>) -> Task<T>
+where
+ T: MaybeSend + 'static,
+{
+ let (sender, receiver) = oneshot::channel();
+
+ let action = f(sender);
+
+ Task(Some(boxed_stream(
+ stream::once(async move { action }).chain(
+ receiver.into_stream().filter_map(|result| async move {
+ Some(Action::Output(result.ok()?))
+ }),
+ ),
+ )))
+}
+
+/// Creates a new [`Task`] that executes the [`Action`] returned by the closure and
+/// produces the values fed to the [`mpsc::Sender`].
+pub fn channel<T>(f: impl FnOnce(mpsc::Sender<T>) -> Action<T>) -> Task<T>
+where
+ T: MaybeSend + 'static,
+{
+ let (sender, receiver) = mpsc::channel(1);
+
+ let action = f(sender);
+
+ Task(Some(boxed_stream(
+ stream::once(async move { action })
+ .chain(receiver.map(|result| Action::Output(result))),
+ )))
+}
+
+/// Creates a new [`Task`] that executes the given [`Action`] and produces no output.
+pub fn effect<T>(action: impl Into<Action<Never>>) -> Task<T> {
+ let action = action.into();
+
+ Task(Some(boxed_stream(stream::once(async move {
+ action.output().expect_err("no output")
+ }))))
+}
+
+/// Returns the underlying [`Stream`] of the [`Task`].
+pub fn into_stream<T>(task: Task<T>) -> Option<BoxStream<Action<T>>> {
+ task.0
+}
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 3e53dd55..815827d1 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -11,7 +11,7 @@ use crate::core::{Point, Size};
use crate::futures::event;
use crate::futures::futures::channel::oneshot;
use crate::futures::Subscription;
-use crate::Task;
+use crate::task::{self, Task};
pub use raw_window_handle;
@@ -210,99 +210,99 @@ pub fn close_requests() -> Subscription<Id> {
pub fn open(settings: Settings) -> Task<Id> {
let id = Id::unique();
- Task::oneshot(|channel| {
+ task::oneshot(|channel| {
crate::Action::Window(Action::Open(id, settings, channel))
})
}
/// Closes the window with `id`.
pub fn close<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Close(id)))
+ task::effect(crate::Action::Window(Action::Close(id)))
}
/// Gets the window [`Id`] of the oldest window.
pub fn get_oldest() -> Task<Option<Id>> {
- Task::oneshot(|channel| crate::Action::Window(Action::GetOldest(channel)))
+ task::oneshot(|channel| crate::Action::Window(Action::GetOldest(channel)))
}
/// Gets the window [`Id`] of the latest window.
pub fn get_latest() -> Task<Option<Id>> {
- Task::oneshot(|channel| crate::Action::Window(Action::GetLatest(channel)))
+ task::oneshot(|channel| crate::Action::Window(Action::GetLatest(channel)))
}
/// Begins dragging the window while the left mouse button is held.
pub fn drag<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Drag(id)))
+ task::effect(crate::Action::Window(Action::Drag(id)))
}
/// Resizes the window to the given logical dimensions.
pub fn resize<T>(id: Id, new_size: Size) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Resize(id, new_size)))
+ task::effect(crate::Action::Window(Action::Resize(id, new_size)))
}
/// Get the window's size in logical dimensions.
pub fn get_size(id: Id) -> Task<Size> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::GetSize(id, channel))
})
}
/// Gets the maximized state of the window with the given [`Id`].
pub fn get_maximized(id: Id) -> Task<bool> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::GetMaximized(id, channel))
})
}
/// Maximizes the window.
pub fn maximize<T>(id: Id, maximized: bool) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Maximize(id, maximized)))
+ task::effect(crate::Action::Window(Action::Maximize(id, maximized)))
}
/// Gets the minimized state of the window with the given [`Id`].
pub fn get_minimized(id: Id) -> Task<Option<bool>> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::GetMinimized(id, channel))
})
}
/// Minimizes the window.
pub fn minimize<T>(id: Id, minimized: bool) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Minimize(id, minimized)))
+ task::effect(crate::Action::Window(Action::Minimize(id, minimized)))
}
/// Gets the position in logical coordinates of the window with the given [`Id`].
pub fn get_position(id: Id) -> Task<Option<Point>> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::GetPosition(id, channel))
})
}
/// Moves the window to the given logical coordinates.
pub fn move_to<T>(id: Id, position: Point) -> Task<T> {
- Task::effect(crate::Action::Window(Action::Move(id, position)))
+ task::effect(crate::Action::Window(Action::Move(id, position)))
}
/// Changes the [`Mode`] of the window.
pub fn change_mode<T>(id: Id, mode: Mode) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ChangeMode(id, mode)))
+ task::effect(crate::Action::Window(Action::ChangeMode(id, mode)))
}
/// Gets the current [`Mode`] of the window.
pub fn get_mode(id: Id) -> Task<Mode> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::GetMode(id, channel))
})
}
/// Toggles the window to maximized or back.
pub fn toggle_maximize<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ToggleMaximize(id)))
+ task::effect(crate::Action::Window(Action::ToggleMaximize(id)))
}
/// Toggles the window decorations.
pub fn toggle_decorations<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ToggleDecorations(id)))
+ task::effect(crate::Action::Window(Action::ToggleDecorations(id)))
}
/// Request user attention to the window. This has no effect if the application
@@ -315,7 +315,7 @@ pub fn request_user_attention<T>(
id: Id,
user_attention: Option<UserAttention>,
) -> Task<T> {
- Task::effect(crate::Action::Window(Action::RequestUserAttention(
+ task::effect(crate::Action::Window(Action::RequestUserAttention(
id,
user_attention,
)))
@@ -328,32 +328,32 @@ pub fn request_user_attention<T>(
/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
/// user experience.
pub fn gain_focus<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::GainFocus(id)))
+ task::effect(crate::Action::Window(Action::GainFocus(id)))
}
/// Changes the window [`Level`].
pub fn change_level<T>(id: Id, level: Level) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ChangeLevel(id, level)))
+ task::effect(crate::Action::Window(Action::ChangeLevel(id, level)))
}
/// Show the [system menu] at cursor position.
///
/// [system menu]: https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu
pub fn show_system_menu<T>(id: Id) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ShowSystemMenu(id)))
+ task::effect(crate::Action::Window(Action::ShowSystemMenu(id)))
}
/// Gets an identifier unique to the window, provided by the underlying windowing system. This is
/// not to be confused with [`Id`].
pub fn get_raw_id<Message>(id: Id) -> Task<u64> {
- Task::oneshot(|channel| {
+ task::oneshot(|channel| {
crate::Action::Window(Action::GetRawId(id, channel))
})
}
/// Changes the [`Icon`] of the window.
pub fn change_icon<T>(id: Id, icon: Icon) -> Task<T> {
- Task::effect(crate::Action::Window(Action::ChangeIcon(id, icon)))
+ task::effect(crate::Action::Window(Action::ChangeIcon(id, icon)))
}
/// Runs the given callback with the native window handle for the window with the given id.
@@ -366,7 +366,7 @@ pub fn run_with_handle<T>(
where
T: Send + 'static,
{
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::RunWithHandle(
id,
Box::new(move |handle| {
@@ -378,7 +378,7 @@ where
/// Captures a [`Screenshot`] from the window.
pub fn screenshot(id: Id) -> Task<Screenshot> {
- Task::oneshot(move |channel| {
+ task::oneshot(move |channel| {
crate::Action::Window(Action::Screenshot(id, channel))
})
}
diff --git a/src/advanced.rs b/src/advanced.rs
index 8d06e805..b817bbf9 100644
--- a/src/advanced.rs
+++ b/src/advanced.rs
@@ -14,6 +14,6 @@ pub use crate::renderer::graphics;
pub mod subscription {
//! Write your own subscriptions.
pub use crate::runtime::futures::subscription::{
- EventStream, Hasher, Recipe,
+ from_recipe, into_recipes, EventStream, Hasher, Recipe,
};
}
diff --git a/src/application.rs b/src/application.rs
index 5d16b40f..f5e06471 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -103,10 +103,6 @@ where
type Renderer = Renderer;
type Executor = iced_futures::backend::default::Executor;
- fn load(&self) -> Task<Self::Message> {
- Task::none()
- }
-
fn update(
&self,
state: &mut Self::State,
@@ -166,14 +162,14 @@ impl<P: Program> Application<P> {
Self: 'static,
P::State: Default,
{
- self.run_with(P::State::default)
+ self.raw.run(self.settings, Some(self.window))
}
/// Runs the [`Application`] with a closure that creates the initial state.
pub fn run_with<I>(self, initialize: I) -> Result
where
Self: 'static,
- I: Fn() -> P::State + Clone + 'static,
+ I: Fn() -> (P::State, Task<P::Message>) + Clone + 'static,
{
self.raw
.run_with(self.settings, Some(self.window), initialize)
@@ -323,20 +319,6 @@ impl<P: Program> Application<P> {
}
}
- /// Runs the [`Task`] produced by the closure at startup.
- pub fn load(
- self,
- f: impl Fn() -> Task<P::Message>,
- ) -> Application<
- impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
- > {
- Application {
- raw: program::with_load(self.raw, f),
- settings: self.settings,
- window: self.window,
- }
- }
-
/// Sets the subscription logic of the [`Application`].
pub fn subscription(
self,
diff --git a/src/daemon.rs b/src/daemon.rs
index 58293949..d2de2db7 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -55,10 +55,6 @@ where
type Renderer = Renderer;
type Executor = iced_futures::backend::default::Executor;
- fn load(&self) -> Task<Self::Message> {
- Task::none()
- }
-
fn update(
&self,
state: &mut Self::State,
@@ -116,14 +112,14 @@ impl<P: Program> Daemon<P> {
Self: 'static,
P::State: Default,
{
- self.run_with(P::State::default)
+ self.raw.run(self.settings, None)
}
/// Runs the [`Daemon`] with a closure that creates the initial state.
pub fn run_with<I>(self, initialize: I) -> Result
where
Self: 'static,
- I: Fn() -> P::State + Clone + 'static,
+ I: Fn() -> (P::State, Task<P::Message>) + Clone + 'static,
{
self.raw.run_with(self.settings, None, initialize)
}
@@ -176,19 +172,6 @@ impl<P: Program> Daemon<P> {
}
}
- /// Runs the [`Task`] produced by the closure at startup.
- pub fn load(
- self,
- f: impl Fn() -> Task<P::Message>,
- ) -> Daemon<
- impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
- > {
- Daemon {
- raw: program::with_load(self.raw, f),
- settings: self.settings,
- }
- }
-
/// Sets the subscription logic of the [`Daemon`].
pub fn subscription(
self,
diff --git a/src/lib.rs b/src/lib.rs
index bc3fe6ab..79e2f276 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -175,6 +175,7 @@ use iced_winit::core;
use iced_winit::runtime;
pub use iced_futures::futures;
+pub use iced_futures::stream;
#[cfg(feature = "highlighter")]
pub use iced_highlighter as highlighter;
@@ -201,7 +202,13 @@ pub use crate::core::{
Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size,
Theme, Transformation, Vector,
};
-pub use crate::runtime::{exit, Task};
+pub use crate::runtime::exit;
+pub use iced_futures::Subscription;
+
+pub mod task {
+ //! Create runtime tasks.
+ pub use crate::runtime::task::{Handle, Task};
+}
pub mod clipboard {
//! Access the clipboard.
@@ -255,13 +262,6 @@ pub mod mouse {
};
}
-pub mod subscription {
- //! Listen to external events in your application.
- pub use iced_futures::subscription::{
- channel, run, run_with_id, unfold, Subscription,
- };
-}
-
#[cfg(feature = "system")]
pub mod system {
//! Retrieve system information.
@@ -314,7 +314,7 @@ pub use executor::Executor;
pub use font::Font;
pub use renderer::Renderer;
pub use settings::Settings;
-pub use subscription::Subscription;
+pub use task::Task;
#[doc(inline)]
pub use application::application;
diff --git a/src/program.rs b/src/program.rs
index 3f9d2d0c..939b0047 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -27,8 +27,6 @@ pub trait Program: Sized {
/// The executor of the program.
type Executor: Executor;
- fn load(&self) -> Task<Self::Message>;
-
fn update(
&self,
state: &mut Self::State,
@@ -80,7 +78,9 @@ pub trait Program: Sized {
Self: 'static,
Self::State: Default,
{
- self.run_with(settings, window_settings, Self::State::default)
+ self.run_with(settings, window_settings, || {
+ (Self::State::default(), Task::none())
+ })
}
/// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state.
@@ -92,7 +92,7 @@ pub trait Program: Sized {
) -> Result
where
Self: 'static,
- I: Fn() -> Self::State + Clone + 'static,
+ I: Fn() -> (Self::State, Task<Self::Message>) + Clone + 'static,
{
use std::marker::PhantomData;
@@ -102,7 +102,9 @@ pub trait Program: Sized {
_initialize: PhantomData<I>,
}
- impl<P: Program, I: Fn() -> P::State> shell::Program for Instance<P, I> {
+ impl<P: Program, I: Fn() -> (P::State, Task<P::Message>)> shell::Program
+ for Instance<P, I>
+ {
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
@@ -112,8 +114,7 @@ pub trait Program: Sized {
fn new(
(program, initialize): Self::Flags,
) -> (Self, Task<Self::Message>) {
- let state = initialize();
- let command = program.load();
+ let (state, task) = initialize();
(
Self {
@@ -121,7 +122,7 @@ pub trait Program: Sized {
state,
_initialize: PhantomData,
},
- command,
+ task,
)
}
@@ -212,10 +213,6 @@ pub fn with_title<P: Program>(
type Renderer = P::Renderer;
type Executor = P::Executor;
- fn load(&self) -> Task<Self::Message> {
- self.program.load()
- }
-
fn title(&self, state: &Self::State, window: window::Id) -> String {
(self.title)(state, window)
}
@@ -267,80 +264,6 @@ pub fn with_title<P: Program>(
WithTitle { program, title }
}
-pub fn with_load<P: Program>(
- program: P,
- f: impl Fn() -> Task<P::Message>,
-) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
- struct WithLoad<P, F> {
- program: P,
- load: F,
- }
-
- impl<P: Program, F> Program for WithLoad<P, F>
- where
- F: Fn() -> Task<P::Message>,
- {
- type State = P::State;
- type Message = P::Message;
- type Theme = P::Theme;
- type Renderer = P::Renderer;
- type Executor = P::Executor;
-
- fn load(&self) -> Task<Self::Message> {
- Task::batch([self.program.load(), (self.load)()])
- }
-
- fn update(
- &self,
- state: &mut Self::State,
- message: Self::Message,
- ) -> Task<Self::Message> {
- self.program.update(state, message)
- }
-
- fn view<'a>(
- &self,
- state: &'a Self::State,
- window: window::Id,
- ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
- self.program.view(state, window)
- }
-
- fn title(&self, state: &Self::State, window: window::Id) -> String {
- self.program.title(state, window)
- }
-
- fn subscription(
- &self,
- state: &Self::State,
- ) -> Subscription<Self::Message> {
- self.program.subscription(state)
- }
-
- fn theme(
- &self,
- state: &Self::State,
- window: window::Id,
- ) -> Self::Theme {
- self.program.theme(state, window)
- }
-
- fn style(
- &self,
- state: &Self::State,
- theme: &Self::Theme,
- ) -> Appearance {
- self.program.style(state, theme)
- }
-
- fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
- self.program.scale_factor(state, window)
- }
- }
-
- WithLoad { program, load: f }
-}
-
pub fn with_subscription<P: Program>(
program: P,
f: impl Fn(&P::State) -> Subscription<P::Message>,
@@ -367,10 +290,6 @@ pub fn with_subscription<P: Program>(
(self.subscription)(state)
}
- fn load(&self) -> Task<Self::Message> {
- self.program.load()
- }
-
fn update(
&self,
state: &mut Self::State,
@@ -445,10 +364,6 @@ pub fn with_theme<P: Program>(
(self.theme)(state, window)
}
- fn load(&self) -> Task<Self::Message> {
- self.program.load()
- }
-
fn title(&self, state: &Self::State, window: window::Id) -> String {
self.program.title(state, window)
}
@@ -519,10 +434,6 @@ pub fn with_style<P: Program>(
(self.style)(state, theme)
}
- fn load(&self) -> Task<Self::Message> {
- self.program.load()
- }
-
fn title(&self, state: &Self::State, window: window::Id) -> String {
self.program.title(state, window)
}
@@ -585,10 +496,6 @@ pub fn with_scale_factor<P: Program>(
type Renderer = P::Renderer;
type Executor = P::Executor;
- fn load(&self) -> Task<Self::Message> {
- self.program.load()
- }
-
fn title(&self, state: &Self::State, window: window::Id) -> String {
self.program.title(state, window)
}
diff --git a/widget/Cargo.toml b/widget/Cargo.toml
index 3c9f6a54..498a768b 100644
--- a/widget/Cargo.toml
+++ b/widget/Cargo.toml
@@ -31,6 +31,7 @@ iced_renderer.workspace = true
iced_runtime.workspace = true
num-traits.workspace = true
+once_cell.workspace = true
rustc-hash.workspace = true
thiserror.workspace = true
unicode-segmentation.workspace = true
diff --git a/widget/assets/iced-logo.svg b/widget/assets/iced-logo.svg
new file mode 100644
index 00000000..459b7fbb
--- /dev/null
+++ b/widget/assets/iced-logo.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" fill="none" version="1.1" viewBox="35 31 179 171"><rect x="42" y="31.001" width="169.9" height="169.9" rx="49.815" fill="url(#paint1_linear)"/><path d="m182.62 65.747-28.136 28.606-6.13-6.0291 28.136-28.606 6.13 6.0291zm-26.344 0.218-42.204 42.909-6.13-6.029 42.204-42.909 6.13 6.0291zm-61.648 23.913c5.3254-5.3831 10.65-10.765 21.569-21.867l6.13 6.0291c-10.927 11.11-16.258 16.498-21.587 21.885-4.4007 4.4488-8.8009 8.8968-16.359 16.573l31.977 8.358 25.968-26.402 6.13 6.0292-25.968 26.402 8.907 31.908 42.138-42.087 6.076 6.083-49.109 49.05-45.837-12.628-13.394-45.646 1.7714-1.801c10.928-11.111 16.258-16.499 21.588-21.886zm28.419 70.99-8.846-31.689-31.831-8.32 9.1945 31.335 31.482 8.674zm47.734-56.517 7.122-7.1221-6.08-6.0797-7.147 7.1474-30.171 30.674 6.13 6.029 30.146-30.649z" clip-rule="evenodd" fill="url(#paint2_linear)" fill-rule="evenodd"/><defs><filter id="filter0_f" x="55" y="47.001" width="144" height="168" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur" stdDeviation="2"/></filter><linearGradient id="paint0_linear" x1="127" x2="127" y1="51.001" y2="211" gradientUnits="userSpaceOnUse"><stop offset=".052083"/><stop stop-opacity=".08" offset="1"/></linearGradient><linearGradient id="paint1_linear" x1="212" x2="57.5" y1="31.001" y2="189" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF" offset="0"/><stop stop-color="#30f" offset="1"/></linearGradient><linearGradient id="paint2_linear" x1="86.098" x2="206.01" y1="158.28" y2="35.327" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" offset="0"/><stop stop-color="#fff" offset="1"/></linearGradient></defs></svg>
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index 253850df..0a4624cb 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -280,6 +280,15 @@ where
}
}
+impl<T> Default for State<T>
+where
+ T: Display + Clone,
+{
+ fn default() -> Self {
+ Self::new(Vec::new())
+ }
+}
+
impl<T> Filtered<T>
where
T: Clone,
diff --git a/widget/src/container.rs b/widget/src/container.rs
index e917471f..08d5cb17 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -13,7 +13,7 @@ use crate::core::{
Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
Widget,
};
-use crate::runtime::Task;
+use crate::runtime::task::{self, Task};
/// An element decorating some content.
///
@@ -538,7 +538,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
}
}
- Task::widget(VisibleBounds {
+ task::widget(VisibleBounds {
target: id.into(),
depth: 0,
scrollables: Vec::new(),
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 62343a55..d7631959 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -12,7 +12,8 @@ use crate::pick_list::{self, PickList};
use crate::progress_bar::{self, ProgressBar};
use crate::radio::{self, Radio};
use crate::rule::{self, Rule};
-use crate::runtime::{Action, Task};
+use crate::runtime::task::{self, Task};
+use crate::runtime::Action;
use crate::scrollable::{self, Scrollable};
use crate::slider::{self, Slider};
use crate::text::{self, Text};
@@ -889,6 +890,41 @@ where
crate::Svg::new(handle)
}
+/// Creates an [`Element`] that displays the iced logo with the given `text_size`.
+///
+/// Useful for showing some love to your favorite GUI library in your "About" screen,
+/// for instance.
+#[cfg(feature = "svg")]
+pub fn iced<'a, Message, Theme, Renderer>(
+ text_size: impl Into<Pixels>,
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Renderer: core::Renderer
+ + core::text::Renderer<Font = core::Font>
+ + core::svg::Renderer
+ + 'a,
+ Theme: text::Catalog + crate::svg::Catalog + 'a,
+{
+ use crate::core::{Alignment, Font};
+ use crate::svg;
+ use once_cell::sync::Lazy;
+
+ static LOGO: Lazy<svg::Handle> = Lazy::new(|| {
+ svg::Handle::from_memory(include_bytes!("../assets/iced-logo.svg"))
+ });
+
+ let text_size = text_size.into();
+
+ row![
+ svg(LOGO.clone()).width(text_size * 1.3),
+ text("iced").size(text_size).font(Font::MONOSPACE)
+ ]
+ .spacing(text_size.0 / 3.0)
+ .align_items(Alignment::Center)
+ .into()
+}
+
/// Creates a new [`Canvas`].
///
/// [`Canvas`]: crate::Canvas
@@ -930,12 +966,12 @@ where
/// Focuses the previous focusable widget.
pub fn focus_previous<T>() -> Task<T> {
- Task::effect(Action::widget(operation::focusable::focus_previous()))
+ task::effect(Action::widget(operation::focusable::focus_previous()))
}
/// Focuses the next focusable widget.
pub fn focus_next<T>() -> Task<T> {
- Task::effect(Action::widget(operation::focusable::focus_next()))
+ task::effect(Action::widget(operation::focusable::focus_next()))
}
/// A container intercepting mouse events.
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index bd612fa6..e0875bbf 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -14,7 +14,8 @@ use crate::core::{
self, Background, Border, Clipboard, Color, Element, Layout, Length,
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
-use crate::runtime::{Action, Task};
+use crate::runtime::task::{self, Task};
+use crate::runtime::Action;
pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};
@@ -107,6 +108,32 @@ where
self
}
+ /// Inverts the alignment of the horizontal direction of the [`Scrollable`], if applicable.
+ pub fn align_x(mut self, alignment: Alignment) -> Self {
+ match &mut self.direction {
+ Direction::Horizontal(horizontal)
+ | Direction::Both { horizontal, .. } => {
+ horizontal.alignment = alignment;
+ }
+ Direction::Vertical(_) => {}
+ }
+
+ self
+ }
+
+ /// Sets the alignment of the vertical direction of the [`Scrollable`], if applicable.
+ pub fn align_y(mut self, alignment: Alignment) -> Self {
+ match &mut self.direction {
+ Direction::Vertical(vertical)
+ | Direction::Both { vertical, .. } => {
+ vertical.alignment = alignment;
+ }
+ Direction::Horizontal(_) => {}
+ }
+
+ self
+ }
+
/// Sets the style of this [`Scrollable`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
@@ -954,13 +981,13 @@ impl From<Id> for widget::Id {
/// Produces a [`Task`] that snaps the [`Scrollable`] with the given [`Id`]
/// to the provided `percentage` along the x & y axis.
pub fn snap_to<T>(id: Id, offset: RelativeOffset) -> Task<T> {
- Task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset)))
+ task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset)))
}
/// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`]
/// to the provided [`AbsoluteOffset`] along the x & y axis.
pub fn scroll_to<T>(id: Id, offset: AbsoluteOffset) -> Task<T> {
- Task::effect(Action::widget(operation::scrollable::scroll_to(
+ task::effect(Action::widget(operation::scrollable::scroll_to(
id.0, offset,
)))
}
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index a8f1d192..74e6f8d3 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -237,7 +237,7 @@ where
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
- T::from_f64(value)
+ T::from_f64(value.min(end))
};
new_value
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index fc2ade43..0156b960 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -762,6 +762,11 @@ impl Update {
{
return Some(Self::Paste);
}
+ keyboard::Key::Character("a")
+ if modifiers.command() =>
+ {
+ return Some(Self::Action(Action::SelectAll));
+ }
_ => {}
}
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 4e89236b..ba2fbc13 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -30,7 +30,8 @@ use crate::core::{
Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, Theme, Vector, Widget,
};
-use crate::runtime::{Action, Task};
+use crate::runtime::task::{self, Task};
+use crate::runtime::Action;
/// A field that can be filled with text.
///
@@ -1142,13 +1143,13 @@ impl From<Id> for widget::Id {
/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<T>(id: Id) -> Task<T> {
- Task::effect(Action::widget(operation::focusable::focus(id.0)))
+ task::effect(Action::widget(operation::focusable::focus(id.0)))
}
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// end.
pub fn move_cursor_to_end<T>(id: Id) -> Task<T> {
- Task::effect(Action::widget(operation::text_input::move_cursor_to_end(
+ task::effect(Action::widget(operation::text_input::move_cursor_to_end(
id.0,
)))
}
@@ -1156,7 +1157,7 @@ pub fn move_cursor_to_end<T>(id: Id) -> Task<T> {
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// front.
pub fn move_cursor_to_front<T>(id: Id) -> Task<T> {
- Task::effect(Action::widget(operation::text_input::move_cursor_to_front(
+ task::effect(Action::widget(operation::text_input::move_cursor_to_front(
id.0,
)))
}
@@ -1164,14 +1165,14 @@ pub fn move_cursor_to_front<T>(id: Id) -> Task<T> {
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// provided position.
pub fn move_cursor_to<T>(id: Id, position: usize) -> Task<T> {
- Task::effect(Action::widget(operation::text_input::move_cursor_to(
+ task::effect(Action::widget(operation::text_input::move_cursor_to(
id.0, position,
)))
}
/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
pub fn select_all<T>(id: Id) -> Task<T> {
- Task::effect(Action::widget(operation::text_input::select_all(id.0)))
+ task::effect(Action::widget(operation::text_input::select_all(id.0)))
}
/// The state of a [`TextInput`].
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index defb442f..33c591f5 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -239,7 +239,7 @@ where
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
- T::from_f64(value)
+ T::from_f64(value.min(end))
};
new_value
diff --git a/winit/src/program.rs b/winit/src/program.rs
index d55aedf1..3a4e2e48 100644
--- a/winit/src/program.rs
+++ b/winit/src/program.rs
@@ -203,11 +203,13 @@ where
let (program, task) = runtime.enter(|| P::new(flags));
- if let Some(stream) = task.into_stream() {
+ if let Some(stream) = runtime::task::into_stream(task) {
runtime.run(stream);
}
- runtime.track(program.subscription().map(Action::Output).into_recipes());
+ runtime.track(subscription::into_recipes(
+ program.subscription().map(Action::Output),
+ ));
let (boot_sender, boot_receiver) = oneshot::channel();
let (event_sender, event_receiver) = mpsc::unbounded();
@@ -1114,13 +1116,13 @@ fn update<P: Program, E: Executor>(
let task = runtime.enter(|| program.update(message));
debug.update_finished();
- if let Some(stream) = task.into_stream() {
+ if let Some(stream) = runtime::task::into_stream(task) {
runtime.run(stream);
}
}
let subscription = program.subscription();
- runtime.track(subscription.map(Action::Output).into_recipes());
+ runtime.track(subscription::into_recipes(subscription.map(Action::Output)));
}
fn run_action<P, C>(
diff --git a/winit/src/system.rs b/winit/src/system.rs
index 7997f311..361135be 100644
--- a/winit/src/system.rs
+++ b/winit/src/system.rs
@@ -5,7 +5,7 @@ use crate::runtime::{self, Task};
/// Query for available system information.
pub fn fetch_information() -> Task<Information> {
- Task::oneshot(|channel| {
+ runtime::task::oneshot(|channel| {
runtime::Action::System(Action::QueryInformation(channel))
})
}