diff options
author | 2023-01-18 15:01:17 -0800 | |
---|---|---|
committer | 2023-01-18 15:01:17 -0800 | |
commit | 70d487ba20a50c06c73f0ffcd8198f1a7eac7f37 (patch) | |
tree | afb8e161b18236d4440cba8bb0e0ce896858d653 | |
parent | 790fa3e7a01a790aa3f07083fe9abf6b68fa7ba1 (diff) | |
parent | 5ef0648bf447aaca8b96782643401e54a2bf7759 (diff) | |
download | iced-70d487ba20a50c06c73f0ffcd8198f1a7eac7f37.tar.gz iced-70d487ba20a50c06c73f0ffcd8198f1a7eac7f37.tar.bz2 iced-70d487ba20a50c06c73f0ffcd8198f1a7eac7f37.zip |
Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts:
# examples/events/src/main.rs
# glutin/src/application.rs
# native/src/window.rs
# winit/src/window.rs
Diffstat (limited to '')
57 files changed, 812 insertions, 443 deletions
diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index d58a4feb..e93a01ae 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -63,7 +63,7 @@ body: If you are using an older release, please upgrade to the latest one before filing an issue. options: - master - - 0.6 + - 0.7 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cdef58..d3e7641b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.0] - 2023-01-14 +### Added +- Widget-driven animations. [#1647](https://github.com/iced-rs/iced/pull/1647) +- Multidirectional scrolling support for `Scrollable`. [#1550](https://github.com/iced-rs/iced/pull/1550) +- `VerticalSlider` widget. [#1596](https://github.com/iced-rs/iced/pull/1596) +- `Shift+Click` text selection support in `TextInput`. [#1622](https://github.com/iced-rs/iced/pull/1622) +- Profiling support with the `chrome-trace` feature. [#1565](https://github.com/iced-rs/iced/pull/1565) +- Customization of the handle of a `PickList`. [#1562](https://github.com/iced-rs/iced/pull/1562) +- `window` action to request user attention. [#1584](https://github.com/iced-rs/iced/pull/1584) +- `window` action to gain focus. [#1585](https://github.com/iced-rs/iced/pull/1585) +- `window` action to toggle decorations. [#1588](https://github.com/iced-rs/iced/pull/1588) +- `Copy` implementation for `gradient::Location`. [#1636](https://github.com/iced-rs/iced/pull/1636) + +### Changed +- Replaced `Application::should_exit` with a `window::close` action. [#1606](https://github.com/iced-rs/iced/pull/1606) +- Made `focusable::Count` fields public. [#1635](https://github.com/iced-rs/iced/pull/1635) +- Added `Dependency` argument to the closure of `Lazy`. [#1646](https://github.com/iced-rs/iced/pull/1646) +- Switched arguments order of `Toggler::new` for consistency. [#1616](https://github.com/iced-rs/iced/pull/1616) +- Switched arguments order of `Checkbox::new` for consistency. [#1633](https://github.com/iced-rs/iced/pull/1633) + +### Fixed +- Compilation error in `iced_glow` when the `image` feature is enabled but `svg` isn't. [#1593](https://github.com/iced-rs/iced/pull/1593) +- Widget operations for `Responsive` widget. [#1615](https://github.com/iced-rs/iced/pull/1615) +- Overlay placement for `Responsive`. [#1638](https://github.com/iced-rs/iced/pull/1638) +- `overlay` implementation for `Lazy`. [#1644](https://github.com/iced-rs/iced/pull/1644) +- Minor typo in documentation. [#1624](https://github.com/iced-rs/iced/pull/1624) +- Links in documentation. [#1634](https://github.com/iced-rs/iced/pull/1634) +- Missing comment in documentation. [#1648](https://github.com/iced-rs/iced/pull/1648) + +Many thanks to... + +- @13r0ck +- @Araxeus +- @ben-wallis +- @bungoboingo +- @casperstorm +- @nicksenger +- @Night-Hunter-NF +- @rpitasky +- @rs017991 +- @tarkah +- @wiktor-k + ## [0.6.0] - 2022-12-07 ### Added - Support for non-uniform border radius for `Primitive::Quad`. [#1506](https://github.com/iced-rs/iced/pull/1506) @@ -321,7 +364,8 @@ Many thanks to... ### Added - First release! :tada: -[Unreleased]: https://github.com/iced-rs/iced/compare/0.6.0...HEAD +[Unreleased]: https://github.com/iced-rs/iced/compare/0.7.0...HEAD +[0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0 [0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0 [0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2 @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.6.0" +version = "0.7.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A cross-platform GUI library inspired by Elm" @@ -68,13 +68,13 @@ members = [ ] [dependencies] -iced_core = { version = "0.6", path = "core" } +iced_core = { version = "0.7", path = "core" } iced_futures = { version = "0.5", path = "futures" } -iced_native = { version = "0.7", path = "native" } -iced_graphics = { version = "0.5", path = "graphics" } -iced_winit = { version = "0.6", path = "winit", features = ["application"] } -iced_glutin = { version = "0.5", path = "glutin", optional = true } -iced_glow = { version = "0.5", path = "glow", optional = true } +iced_native = { version = "0.8", path = "native" } +iced_graphics = { version = "0.6", path = "graphics" } +iced_winit = { version = "0.7", path = "winit", features = ["application"] } +iced_glutin = { version = "0.6", path = "glutin", optional = true } +iced_glow = { version = "0.6", path = "glow", optional = true } thiserror = "1.0" [dependencies.image_rs] @@ -83,10 +83,10 @@ package = "image" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.7", path = "wgpu", optional = true } +iced_wgpu = { version = "0.8", path = "wgpu", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.7", path = "wgpu", features = ["webgl"], optional = true } +iced_wgpu = { version = "0.8", path = "wgpu", features = ["webgl"], optional = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] @@ -68,7 +68,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], Add `iced` as a dependency in your `Cargo.toml`: ```toml -iced = "0.6" +iced = "0.7" ``` If your project is using a Rust edition older than 2021, then you will need to @@ -215,7 +215,7 @@ cargo run --features iced/glow --package game_of_life and then use it in your project with ```toml -iced = { version = "0.6", default-features = false, features = ["glow"] } +iced = { version = "0.7", default-features = false, features = ["glow"] } ``` __NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0, diff --git a/core/Cargo.toml b/core/Cargo.toml index c401f30a..eebd2fe3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.6.2" +version = "0.7.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The essential concepts of Iced" @@ -15,4 +15,4 @@ version = "0.6" optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-timer = { version = "0.2" } +instant = "0.1" diff --git a/core/README.md b/core/README.md index bbb7983c..cecbc1e4 100644 --- a/core/README.md +++ b/core/README.md @@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime. Add `iced_core` as a dependency in your `Cargo.toml`: ```toml -iced_core = "0.4" +iced_core = "0.7" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/core/src/lib.rs b/core/src/lib.rs index f95d61f6..3aa5defe 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,7 +7,7 @@ //!  //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native //! [`iced_web`]: https://github.com/iced-rs/iced_web #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" diff --git a/core/src/time.rs b/core/src/time.rs index f496d1a4..9355ae6d 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -1,9 +1,13 @@ //! Keep track of time, both in native and web platforms! #[cfg(target_arch = "wasm32")] -pub use wasm_timer::Instant; +pub use instant::Instant; + +#[cfg(target_arch = "wasm32")] +pub use instant::Duration; #[cfg(not(target_arch = "wasm32"))] pub use std::time::Instant; +#[cfg(not(target_arch = "wasm32"))] pub use std::time::Duration; diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml deleted file mode 100644 index 2c7edde2..00000000 --- a/examples/cached/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "cached" -version = "0.1.0" -authors = ["Nick Senger <dev@nsenger.com>"] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../..", features = ["debug"] } -iced_lazy = { path = "../../lazy" } diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs deleted file mode 100644 index 8845b874..00000000 --- a/examples/cached/src/main.rs +++ /dev/null @@ -1,139 +0,0 @@ -use iced::theme; -use iced::widget::{ - button, column, horizontal_space, row, scrollable, text, text_input, -}; -use iced::{Element, Length, Sandbox, Settings}; -use iced_lazy::lazy; - -use std::collections::HashSet; - -pub fn main() -> iced::Result { - App::run(Settings::default()) -} - -struct App { - options: HashSet<String>, - input: String, - order: Order, -} - -impl Default for App { - fn default() -> Self { - Self { - options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] - .into_iter() - .map(ToString::to_string) - .collect(), - input: Default::default(), - order: Order::Ascending, - } - } -} - -#[derive(Debug, Clone)] -enum Message { - InputChanged(String), - ToggleOrder, - DeleteOption(String), - AddOption(String), -} - -impl Sandbox for App { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Cached - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::InputChanged(input) => { - self.input = input; - } - Message::ToggleOrder => { - self.order = match self.order { - Order::Ascending => Order::Descending, - Order::Descending => Order::Ascending, - } - } - Message::AddOption(option) => { - self.options.insert(option); - self.input.clear(); - } - Message::DeleteOption(option) => { - self.options.remove(&option); - } - } - } - - fn view(&self) -> Element<Message> { - let options = lazy((&self.order, self.options.len()), || { - let mut options: Vec<_> = self.options.iter().collect(); - - options.sort_by(|a, b| match self.order { - Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), - Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), - }); - - column( - options - .into_iter() - .map(|option| { - row![ - text(option), - horizontal_space(Length::Fill), - button("Delete") - .on_press(Message::DeleteOption( - option.to_string(), - ),) - .style(theme::Button::Destructive) - ] - .into() - }) - .collect(), - ) - .spacing(10) - }); - - column![ - scrollable(options).height(Length::Fill), - row![ - text_input( - "Add a new option", - &self.input, - Message::InputChanged, - ) - .on_submit(Message::AddOption(self.input.clone())), - button(text(format!("Toggle Order ({})", self.order))) - .on_press(Message::ToggleOrder) - ] - .spacing(10) - ] - .spacing(20) - .padding(20) - .into() - } -} - -#[derive(Debug, Hash)] -enum Order { - Ascending, - Descending, -} - -impl std::fmt::Display for Order { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Ascending => "Ascending", - Self::Descending => "Descending", - } - ) - } -} diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 8ad04a36..8c56e471 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } iced_native = { path = "../../native" } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index e9709377..f519fc3d 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,11 +1,12 @@ use iced::alignment; use iced::executor; use iced::widget::{button, checkbox, container, text, Column}; +use iced::window; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::{window, Event}; +use iced_native::Event; pub fn main() -> iced::Result { Events::run(Settings { @@ -18,14 +19,13 @@ pub fn main() -> iced::Result { struct Events { last: Vec<iced_native::Event>, enabled: bool, - should_exit: bool, } #[derive(Debug, Clone)] enum Message { EventOccurred(iced_native::Event), Toggled(bool), - Exit, + Exit(window::Id), } impl Application for Events { @@ -50,31 +50,29 @@ impl Application for Events { if self.last.len() > 5 { let _ = self.last.remove(0); } + + Command::none() } Message::EventOccurred(event) => { - if let Event::Window(_, window::Event::CloseRequested) = event { - self.should_exit = true; + if let Event::Window(id, window::Event::CloseRequested) = event { + window::close(id) + } else { + Command::none() } } Message::Toggled(enabled) => { self.enabled = enabled; - } - Message::Exit => { - self.should_exit = true; - } - }; - Command::none() + Command::none() + } + Message::Exit(id) => window::close(id), + } } fn subscription(&self) -> Subscription<Message> { iced_native::subscription::events().map(Message::EventOccurred) } - fn should_exit(&self) -> bool { - self.should_exit - } - fn view(&self) -> Element<Message> { let events = Column::with_children( self.last diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 5d518d2f..6152f627 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -1,5 +1,7 @@ +use iced::executor; use iced::widget::{button, column, container}; -use iced::{Alignment, Element, Length, Sandbox, Settings}; +use iced::window; +use iced::{Alignment, Application, Command, Element, Length, Settings, Theme}; pub fn main() -> iced::Result { Exit::run(Settings::default()) @@ -8,7 +10,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Exit { show_confirm: bool, - exit: bool, } #[derive(Debug, Clone, Copy)] @@ -17,28 +18,27 @@ enum Message { Exit, } -impl Sandbox for Exit { +impl Application for Exit { + type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Flags = (); - fn new() -> Self { - Self::default() + fn new(_flags: ()) -> (Self, Command<Message>) { + (Self::default(), Command::none()) } fn title(&self) -> String { String::from("Exit - Iced") } - fn should_exit(&self) -> bool { - self.exit - } - - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command<Message> { match message { - Message::Confirm => { - self.exit = true; - } + Message::Confirm => window::close(), Message::Exit => { self.show_confirm = true; + + Command::none() } } } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 8845b874..6512106f 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -1,18 +1,21 @@ use iced::theme; use iced::widget::{ - button, column, horizontal_space, row, scrollable, text, text_input, + button, column, horizontal_space, pick_list, row, scrollable, text, + text_input, }; use iced::{Element, Length, Sandbox, Settings}; use iced_lazy::lazy; use std::collections::HashSet; +use std::hash::Hash; pub fn main() -> iced::Result { App::run(Settings::default()) } struct App { - options: HashSet<String>, + version: u8, + items: HashSet<Item>, input: String, order: Order, } @@ -20,9 +23,10 @@ struct App { impl Default for App { fn default() -> Self { Self { - options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + version: 0, + items: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] .into_iter() - .map(ToString::to_string) + .map(From::from) .collect(), input: Default::default(), order: Order::Ascending, @@ -30,12 +34,92 @@ impl Default for App { } } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +enum Color { + #[default] + Black, + Red, + Orange, + Yellow, + Green, + Blue, + Purple, +} + +impl Color { + const ALL: &[Color] = &[ + Color::Black, + Color::Red, + Color::Orange, + Color::Yellow, + Color::Green, + Color::Blue, + Color::Purple, + ]; +} + +impl std::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Black => "Black", + Self::Red => "Red", + Self::Orange => "Orange", + Self::Yellow => "Yellow", + Self::Green => "Green", + Self::Blue => "Blue", + Self::Purple => "Purple", + }) + } +} + +impl From<Color> for iced::Color { + fn from(value: Color) -> Self { + match value { + Color::Black => iced::Color::from_rgb8(0, 0, 0), + Color::Red => iced::Color::from_rgb8(220, 50, 47), + Color::Orange => iced::Color::from_rgb8(203, 75, 22), + Color::Yellow => iced::Color::from_rgb8(181, 137, 0), + Color::Green => iced::Color::from_rgb8(133, 153, 0), + Color::Blue => iced::Color::from_rgb8(38, 139, 210), + Color::Purple => iced::Color::from_rgb8(108, 113, 196), + } + } +} + +#[derive(Clone, Debug, Eq)] +struct Item { + name: String, + color: Color, +} + +impl Hash for Item { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.name.eq(&other.name) + } +} + +impl From<&str> for Item { + fn from(s: &str) -> Self { + Self { + name: s.to_owned(), + color: Default::default(), + } + } +} + #[derive(Debug, Clone)] enum Message { InputChanged(String), ToggleOrder, - DeleteOption(String), - AddOption(String), + DeleteItem(Item), + AddItem(String), + ItemColorChanged(Item, Color), } impl Sandbox for App { @@ -46,7 +130,7 @@ impl Sandbox for App { } fn title(&self) -> String { - String::from("Cached - Iced") + String::from("Lazy - Iced") } fn update(&mut self, message: Message) { @@ -55,43 +139,71 @@ impl Sandbox for App { self.input = input; } Message::ToggleOrder => { + self.version = self.version.wrapping_add(1); self.order = match self.order { Order::Ascending => Order::Descending, Order::Descending => Order::Ascending, } } - Message::AddOption(option) => { - self.options.insert(option); + Message::AddItem(name) => { + self.version = self.version.wrapping_add(1); + self.items.insert(name.as_str().into()); self.input.clear(); } - Message::DeleteOption(option) => { - self.options.remove(&option); + Message::DeleteItem(item) => { + self.version = self.version.wrapping_add(1); + self.items.remove(&item); + } + Message::ItemColorChanged(item, color) => { + self.version = self.version.wrapping_add(1); + if self.items.remove(&item) { + self.items.insert(Item { + name: item.name, + color, + }); + } } } } fn view(&self) -> Element<Message> { - let options = lazy((&self.order, self.options.len()), || { - let mut options: Vec<_> = self.options.iter().collect(); + let options = lazy(self.version, |_| { + let mut items: Vec<_> = self.items.iter().cloned().collect(); - options.sort_by(|a, b| match self.order { - Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), - Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + items.sort_by(|a, b| match self.order { + Order::Ascending => { + a.name.to_lowercase().cmp(&b.name.to_lowercase()) + } + Order::Descending => { + b.name.to_lowercase().cmp(&a.name.to_lowercase()) + } }); column( - options + items .into_iter() - .map(|option| { + .map(|item| { + let button = button("Delete") + .on_press(Message::DeleteItem(item.clone())) + .style(theme::Button::Destructive); + row![ - text(option), + text(&item.name) + .style(theme::Text::Color(item.color.into())), horizontal_space(Length::Fill), - button("Delete") - .on_press(Message::DeleteOption( - option.to_string(), - ),) - .style(theme::Button::Destructive) + pick_list( + Color::ALL, + Some(item.color), + move |color| { + Message::ItemColorChanged( + item.clone(), + color, + ) + } + ), + button ] + .spacing(20) .into() }) .collect(), @@ -107,7 +219,7 @@ impl Sandbox for App { &self.input, Message::InputChanged, ) - .on_submit(Message::AddOption(self.input.clone())), + .on_submit(Message::AddItem(self.input.clone())), button(text(format!("Toggle Order ({})", self.order))) .on_press(Message::ToggleOrder) ] diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9e303576..9a4ee754 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -9,7 +9,6 @@ use iced::application; use iced::executor; use iced::theme::{self, Theme}; -use iced::time; use iced::widget::canvas; use iced::widget::canvas::gradient::{self, Gradient}; use iced::widget::canvas::stroke::{self, Stroke}; @@ -90,7 +89,7 @@ impl Application for SolarSystem { } fn subscription(&self) -> Subscription<Message> { - time::every(time::Duration::from_millis(10)).map(Message::Tick) + window::frames().map(Message::Tick) } } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 1d4b68a6..e96fa704 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -125,9 +125,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { /// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how /// to listen to time. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.6/examples -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.6/examples/download_progress -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.6/examples/stopwatch +/// [examples]: https://github.com/iced-rs/iced/tree/0.7/examples +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.7/examples/download_progress +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.7/examples/stopwatch pub trait Recipe<Hasher: std::hash::Hasher, Event> { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. diff --git a/glow/Cargo.toml b/glow/Cargo.toml index c126a511..cf5dfb8a 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glow" -version = "0.5.1" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A glow renderer for iced" @@ -34,11 +34,11 @@ bytemuck = "1.4" log = "0.4" [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" [dependencies.iced_graphics] -version = "0.5" +version = "0.6" path = "../graphics" features = ["font-fallback", "font-icons", "opengl"] diff --git a/glow/README.md b/glow/README.md index 00f38f64..38449c64 100644 --- a/glow/README.md +++ b/glow/README.md @@ -28,7 +28,7 @@ Currently, `iced_glow` supports the following primitives: Add `iced_glow` as a dependency in your `Cargo.toml`: ```toml -iced_glow = "0.2" +iced_glow = "0.6" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 710ac36d..a12c45b8 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -3,7 +3,7 @@ //!  //! //! [`glow`]: https://github.com/grovesNL/glow -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 5197b076..3c0a910c 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glutin" -version = "0.5.0" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A glutin runtime for Iced" @@ -19,26 +19,26 @@ multi_window = ["iced_winit/multi_window"] [dependencies.raw-window-handle] version = "0.5.0" -[dependencies.log] -version = "0.4" +[dependencies] +log = "0.4" [dependencies.glutin] version = "0.30" [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" [dependencies.iced_winit] -version = "0.6" +version = "0.7" path = "../winit" features = ["application"] [dependencies.iced_graphics] -version = "0.5" +version = "0.6" path = "../graphics" features = ["opengl"] [dependencies.tracing] version = "0.1.6" -optional = true
\ No newline at end of file +optional = true diff --git a/glutin/README.md b/glutin/README.md index 263cc0af..1d873874 100644 --- a/glutin/README.md +++ b/glutin/README.md @@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_glutin` as a dependency in your `Cargo.toml`: ```toml -iced_glutin = "0.2" +iced_glutin = "0.6" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a6479597..71f44dac 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -11,9 +11,10 @@ use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; use iced_winit::renderer; +use iced_winit::time::Instant; use iced_winit::user_interface; use iced_winit::winit; -use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; +use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; use glutin::config::{ Config, ConfigSurfaceTypes, ConfigTemplateBuilder, GlConfig, @@ -280,7 +281,8 @@ where let context = { context.make_not_current().expect("make context current") }; - let (mut sender, receiver) = mpsc::unbounded(); + let (mut event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::<A, E, C>( @@ -290,7 +292,8 @@ where runtime, proxy, debug, - receiver, + event_receiver, + control_sender, window, surface, context, @@ -330,14 +333,20 @@ where }; if let Some(event) = event { - sender.start_send(event).expect("Send event"); + event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, - }; + match poll { + task::Poll::Pending => { + if let Ok(Some(flow)) = control_receiver.try_next() { + *control_flow = flow; + } + } + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + } + } } }); @@ -351,7 +360,8 @@ async fn run_instance<A, E, C>( mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, mut proxy: winit::event_loop::EventLoopProxy<A::Message>, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, + mut event_receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>, >, + mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, window: winit::window::Window, surface: Surface<WindowSurface>, context: NotCurrentContext, @@ -364,6 +374,7 @@ async fn run_instance<A, E, C>( <A::Renderer as iced_native::Renderer>::Theme: StyleSheet, { use iced_winit::futures::stream::StreamExt; + use winit::event_loop::ControlFlow; use winit::event; let context = { @@ -406,13 +417,22 @@ async fn run_instance<A, E, C>( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut redraw_pending = false; debug.startup_finished(); - while let Some(event) = receiver.next().await { + while let Some(event) = event_receiver.next().await { match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::Poll + | event::StartCause::ResumeTimeReached { .. } + ); + } event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { + if !redraw_pending && events.is_empty() && messages.is_empty() { continue; } @@ -474,6 +494,24 @@ async fn run_instance<A, E, C>( } } + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. + let redraw_event = Event::Window( + crate::window::Id::MAIN, + crate::window::Event::RedrawRequested(Instant::now()), + ); + + let (interface_state, _) = user_interface.update( + &[redraw_event.clone()], + state.cursor_position(), + &mut renderer, + &mut clipboard, + &mut messages, + ); + debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -494,6 +532,23 @@ async fn run_instance<A, E, C>( } window.request_redraw(); + runtime.broadcast((redraw_event, crate::event::Status::Ignored)); + + let _ = control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + crate::window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + crate::window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 823a05f4..664bb19f 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.5.0" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" @@ -44,11 +44,11 @@ version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" [dependencies.iced_style] -version = "0.5" +version = "0.6" path = "../style" [dependencies.lyon] diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml index 1b26e5c9..657da5ca 100644 --- a/lazy/Cargo.toml +++ b/lazy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_lazy" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "Lazy widgets for Iced" @@ -14,5 +14,5 @@ categories = ["gui"] ouroboros = "0.13" [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index ec35e8f0..933def96 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -9,16 +9,17 @@ use iced_native::Element; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; use ouroboros::self_referencing; -use std::cell::{Ref, RefCell, RefMut}; +use std::cell::RefCell; use std::hash::{Hash, Hasher as H}; -use std::marker::PhantomData; use std::rc::Rc; #[allow(missing_debug_implementations)] pub struct Lazy<'a, Message, Renderer, Dependency, View> { dependency: Dependency, - view: Box<dyn Fn() -> View + 'a>, - element: RefCell<Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>, + view: Box<dyn Fn(&Dependency) -> View + 'a>, + element: RefCell< + Option<Rc<RefCell<Option<Element<'static, Message, Renderer>>>>>, + >, } impl<'a, Message, Renderer, Dependency, View> @@ -27,7 +28,10 @@ where Dependency: Hash + 'a, View: Into<Element<'static, Message, Renderer>>, { - pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { + pub fn new( + dependency: Dependency, + view: impl Fn(&Dependency) -> View + 'a, + ) -> Self { Self { dependency, view: Box::new(view), @@ -37,21 +41,35 @@ where fn with_element<T>( &self, - f: impl FnOnce(Ref<Element<Message, Renderer>>) -> T, + f: impl FnOnce(&Element<Message, Renderer>) -> T, ) -> T { - f(self.element.borrow().as_ref().unwrap().borrow()) + f(self + .element + .borrow() + .as_ref() + .unwrap() + .borrow() + .as_ref() + .unwrap()) } fn with_element_mut<T>( &self, - f: impl FnOnce(RefMut<Element<Message, Renderer>>) -> T, + f: impl FnOnce(&mut Element<Message, Renderer>) -> T, ) -> T { - f(self.element.borrow().as_ref().unwrap().borrow_mut()) + f(self + .element + .borrow() + .as_ref() + .unwrap() + .borrow_mut() + .as_mut() + .unwrap()) } } struct Internal<Message, Renderer> { - element: Rc<RefCell<Element<'static, Message, Renderer>>>, + element: Rc<RefCell<Option<Element<'static, Message, Renderer>>>>, hash: u64, } @@ -73,7 +91,8 @@ where self.dependency.hash(&mut hasher); let hash = hasher.finish(); - let element = Rc::new(RefCell::new((self.view)().into())); + let element = + Rc::new(RefCell::new(Some((self.view)(&self.dependency).into()))); (*self.element.borrow_mut()) = Some(element.clone()); @@ -81,9 +100,7 @@ where } fn children(&self) -> Vec<Tree> { - vec![Tree::new( - self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )] + self.with_element(|element| vec![Tree::new(element.as_widget())]) } fn diff(&self, tree: &mut Tree) { @@ -96,13 +113,13 @@ where if current.hash != new_hash { current.hash = new_hash; - let element = (self.view)().into(); - current.element = Rc::new(RefCell::new(element)); + let element = (self.view)(&self.dependency).into(); + current.element = Rc::new(RefCell::new(Some(element))); (*self.element.borrow_mut()) = Some(current.element.clone()); - tree.diff_children(std::slice::from_ref( - &self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )); + self.with_element(|element| { + tree.diff_children(std::slice::from_ref(&element.as_widget())) + }); } else { (*self.element.borrow_mut()) = Some(current.element.clone()); } @@ -153,7 +170,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - self.with_element_mut(|mut element| { + self.with_element_mut(|element| { element.as_widget_mut().on_event( &mut tree.children[0], event, @@ -214,23 +231,27 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { - let overlay = OverlayBuilder { - cached: self, - tree: &mut tree.children[0], - types: PhantomData, - overlay_builder: |cached, tree| { - Rc::get_mut(cached.element.get_mut().as_mut().unwrap()) + let overlay = Overlay(Some( + InnerBuilder { + cell: self.element.borrow().as_ref().unwrap().clone(), + element: self + .element + .borrow() + .as_ref() .unwrap() - .get_mut() - .as_widget_mut() - .overlay(tree, layout, renderer) - }, - } - .build(); - - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); + .borrow_mut() + .take() + .unwrap(), + tree: &mut tree.children[0], + overlay_builder: |element, tree| { + element.as_widget_mut().overlay(tree, layout, renderer) + }, + } + .build(), + )); + + let has_overlay = overlay + .with_overlay_maybe(|overlay| overlay::Element::position(overlay)); has_overlay .map(|position| overlay::Element::new(position, Box::new(overlay))) @@ -238,37 +259,50 @@ where } #[self_referencing] -struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>, +struct Inner<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'a, +{ + cell: Rc<RefCell<Option<Element<'static, Message, Renderer>>>>, + element: Element<'static, Message, Renderer>, tree: &'a mut Tree, - types: PhantomData<(Message, Dependency, View)>, - #[borrows(mut cached, mut tree)] + #[borrows(mut element, mut tree)] #[covariant] overlay: Option<overlay::Element<'this, Message, Renderer>>, } -impl<'a, 'b, Message, Renderer, Dependency, View> - Overlay<'a, 'b, Message, Renderer, Dependency, View> -{ +struct Overlay<'a, Message, Renderer>(Option<Inner<'a, Message, Renderer>>); + +impl<'a, Message, Renderer> Drop for Overlay<'a, Message, Renderer> { + fn drop(&mut self) { + let heads = self.0.take().unwrap().into_heads(); + (*heads.cell.borrow_mut()) = Some(heads.element); + } +} + +impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> { fn with_overlay_maybe<T>( &self, f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.borrow_overlay().as_ref().map(f) + self.0.as_ref().unwrap().borrow_overlay().as_ref().map(f) } fn with_overlay_mut_maybe<T>( &mut self, f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, ) -> Option<T> { - self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + self.0 + .as_mut() + .unwrap() + .with_overlay_mut(|overlay| overlay.as_mut().map(f)) } } -impl<'a, 'b, Message, Renderer, Dependency, View> - overlay::Overlay<Message, Renderer> - for Overlay<'a, 'b, Message, Renderer, Dependency, View> +impl<'a, Message, Renderer> overlay::Overlay<Message, Renderer> + for Overlay<'a, Message, Renderer> where Renderer: iced_native::Renderer, { diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index f49fe4b6..41a28773 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -33,7 +33,7 @@ use std::hash::Hash; pub fn lazy<'a, Message, Renderer, Dependency, View>( dependency: Dependency, - view: impl Fn() -> View + 'a, + view: impl Fn(&Dependency) -> View + 'a, ) -> Lazy<'a, Message, Renderer, Dependency, View> where Dependency: Hash + 'a, diff --git a/native/Cargo.toml b/native/Cargo.toml index bbf92951..79e4dac4 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_native" -version = "0.7.0" +version = "0.8.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A renderer-agnostic library for native GUIs" @@ -16,7 +16,7 @@ unicode-segmentation = "1.6" num-traits = "0.2" [dependencies.iced_core] -version = "0.6" +version = "0.7" path = "../core" [dependencies.iced_futures] @@ -25,5 +25,5 @@ path = "../futures" features = ["thread-pool"] [dependencies.iced_style] -version = "0.5.1" +version = "0.6.0" path = "../style" diff --git a/native/README.md b/native/README.md index c1e160c7..9e1f65fb 100644 --- a/native/README.md +++ b/native/README.md @@ -28,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces: Add `iced_native` as a dependency in your `Cargo.toml`: ```toml -iced_native = "0.4" +iced_native = "0.8" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/native/src/element.rs b/native/src/element.rs index 2409b1c9..0a677d20 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -9,6 +9,7 @@ use crate::{ Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget, }; +use std::any::Any; use std::borrow::Borrow; /// A generic [`Widget`]. @@ -333,6 +334,10 @@ where ) { self.operation.text_input(state, id); } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { + self.operation.custom(state, id); + } } self.widget.operate( diff --git a/native/src/lib.rs b/native/src/lib.rs index ce7c010d..124423a6 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -23,8 +23,8 @@ //! - Build a new renderer, see the [renderer] module. //! - Build a custom widget, start at the [`Widget`] trait. //! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.6/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.6/winit +//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.7/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.7/winit //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [renderer]: crate::renderer diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 498e9ae3..41a8a597 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -7,6 +7,8 @@ use crate::renderer; use crate::widget; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; +use std::any::Any; + /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] pub struct Element<'a, Message, Renderer> { @@ -188,6 +190,10 @@ where ) { self.operation.text_input(state, id) } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { + self.operation.custom(state, id); + } } self.content diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 5e776be6..d5329acd 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -36,11 +36,11 @@ pub trait Renderer: Sized { f: impl FnOnce(&mut Self), ); - /// Clears all of the recorded primitives in the [`Renderer`]. - fn clear(&mut self); - /// Fills a [`Quad`] with the provided [`Background`]. fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>); + + /// Clears all of the recorded primitives in the [`Renderer`]. + fn clear(&mut self); } /// A polygon with four sides. diff --git a/native/src/shell.rs b/native/src/shell.rs index b96d23e5..f1ddb48e 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -1,3 +1,5 @@ +use crate::window; + /// A connection to the state of a shell. /// /// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application, @@ -7,6 +9,7 @@ #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec<Message>, + redraw_request: Option<window::RedrawRequest>, is_layout_invalid: bool, are_widgets_invalid: bool, } @@ -16,31 +19,40 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec<Message>) -> Self { Self { messages, + redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, } } - /// Triggers the given function if the layout is invalid, cleaning it in the - /// process. - pub fn revalidate_layout(&mut self, f: impl FnOnce()) { - if self.is_layout_invalid { - self.is_layout_invalid = false; + /// Publish the given `Message` for an application to process it. + pub fn publish(&mut self, message: Message) { + self.messages.push(message); + } - f() + /// Requests a new frame to be drawn at the given [`Instant`]. + pub fn request_redraw(&mut self, request: window::RedrawRequest) { + match self.redraw_request { + None => { + self.redraw_request = Some(request); + } + Some(current) if request < current => { + self.redraw_request = Some(request); + } + _ => {} } } + /// Returns the requested [`Instant`] a redraw should happen, if any. + pub fn redraw_request(&self) -> Option<window::RedrawRequest> { + self.redraw_request + } + /// Returns whether the current layout is invalid or not. pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid } - /// Publish the given `Message` for an application to process it. - pub fn publish(&mut self, message: Message) { - self.messages.push(message); - } - /// Invalidates the current application layout. /// /// The shell will relayout the application widgets. @@ -48,6 +60,22 @@ impl<'a, Message> Shell<'a, Message> { self.is_layout_invalid = true; } + /// Triggers the given function if the layout is invalid, cleaning it in the + /// process. + pub fn revalidate_layout(&mut self, f: impl FnOnce()) { + if self.is_layout_invalid { + self.is_layout_invalid = false; + + f() + } + } + + /// Returns whether the widgets of the current application have been + /// invalidated. + pub fn are_widgets_invalid(&self) -> bool { + self.are_widgets_invalid + } + /// Invalidates the current application widgets. /// /// The shell will rebuild and relayout the widget tree. @@ -62,16 +90,14 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); + if let Some(at) = other.redraw_request { + self.request_redraw(at); + } + self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; } - - /// Returns whether the widgets of the current application have been - /// invalidated. - pub fn are_widgets_invalid(&self) -> bool { - self.are_widgets_invalid - } } diff --git a/native/src/subscription.rs b/native/src/subscription.rs index c60b1281..8c92efad 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,5 +1,6 @@ //! Listen to external events in your application. use crate::event::{self, Event}; +use crate::window; use crate::Hasher; use iced_futures::futures::{self, Future, Stream}; @@ -33,7 +34,7 @@ pub type Tracker = pub use iced_futures::subscription::Recipe; -/// Returns a [`Subscription`] to all the runtime events. +/// Returns a [`Subscription`] to all the ignored runtime events. /// /// This subscription will notify your application of any [`Event`] that was /// not captured by any widget. @@ -58,8 +59,36 @@ pub fn events_with<Message>( where Message: 'static + MaybeSend, { + #[derive(Hash)] + struct EventsWith; + + Subscription::from_recipe(Runner { + id: (EventsWith, f), + spawn: move |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }) + }) + }, + }) +} + +pub(crate) fn raw_events<Message>( + f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct RawEvents; + Subscription::from_recipe(Runner { - id: f, + id: (RawEvents, f), spawn: move |events| { use futures::future; use futures::stream::StreamExt; @@ -155,7 +184,7 @@ where /// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket /// connection open. /// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.6/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.7/examples/websocket pub fn unfold<I, T, Fut, Message>( id: I, initial: T, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 2b43829d..29cc3472 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -5,6 +5,7 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; +use crate::window; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -18,8 +19,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The [`integration_opengl`] & [`integration_wgpu`] examples use a /// [`UserInterface`] to integrate Iced in an existing graphical application. /// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_wgpu +/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.7/examples/integration_opengl +/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.7/examples/integration_wgpu #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, @@ -188,7 +189,9 @@ where ) -> (State, Vec<event::Status>) { use std::mem::ManuallyDrop; - let mut state = State::Updated; + let mut outdated = false; + let mut redraw_request = None; + let mut manual_overlay = ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, @@ -217,6 +220,16 @@ where event_statuses.push(event_status); + match (redraw_request, shell.redraw_request()) { + (None, Some(at)) => { + redraw_request = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_request = Some(new); + } + _ => {} + } + if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); @@ -244,7 +257,7 @@ where } if shell.are_widgets_invalid() { - state = State::Outdated; + outdated = true; } } @@ -289,6 +302,16 @@ where self.overlay = None; } + match (redraw_request, shell.redraw_request()) { + (None, Some(at)) => { + redraw_request = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_request = Some(new); + } + _ => {} + } + shell.revalidate_layout(|| { self.base = renderer.layout( &self.root, @@ -299,14 +322,21 @@ where }); if shell.are_widgets_invalid() { - state = State::Outdated; + outdated = true; } event_status.merge(overlay_status) }) .collect(); - (state, event_statuses) + ( + if outdated { + State::Outdated + } else { + State::Updated { redraw_request } + }, + event_statuses, + ) } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -559,5 +589,8 @@ pub enum State { /// The [`UserInterface`] is up-to-date and can be reused without /// rebuilding. - Updated, + Updated { + /// The [`Instant`] when a redraw should be performed. + redraw_request: Option<window::RedrawRequest>, + }, } diff --git a/native/src/widget.rs b/native/src/widget.rs index f714e28a..fb759ec8 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -110,12 +110,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; /// - [`geometry`], a custom widget showcasing how to draw geometry with the /// `Mesh2D` primitive in [`iced_wgpu`]. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.6/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.6/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.6/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.6/examples/geometry +/// [examples]: https://github.com/iced-rs/iced/tree/0.7/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.7/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.7/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.7/examples/geometry /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.7/wgpu pub trait Widget<Message, Renderer> where Renderer: crate::Renderer, diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 9aa79dec..1e21ff38 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -3,6 +3,7 @@ use crate::widget::Id; use iced_futures::MaybeSend; +use std::any::Any; use std::rc::Rc; /// An operation to be performed on the widget tree. @@ -84,6 +85,10 @@ where ) { self.operation.focusable(state, id); } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } } let Self { operation, .. } = self; @@ -118,6 +123,10 @@ where self.operation.text_input(state, id); } + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + fn finish(&self) -> operation::Outcome<B> { match self.operation.finish() { operation::Outcome::None => operation::Outcome::None, diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index a0aa4117..73e6a6b0 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -9,6 +9,7 @@ pub use text_input::TextInput; use crate::widget::Id; +use std::any::Any; use std::fmt; /// A piece of logic that can traverse the widget tree of an application in @@ -33,6 +34,9 @@ pub trait Operation<T> { /// Operates on a widget that has text input. fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} + /// Operates on a custom widget with some state. + fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} + /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome<T> { Outcome::None diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index f8dbab74..8dbd1825 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -6,7 +6,7 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.7/examples/pane_grid mod axis; mod configuration; mod content; diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 8b4514e3..8755b85d 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -18,10 +18,12 @@ use crate::layout; use crate::mouse::{self, click}; use crate::renderer; use crate::text::{self, Text}; +use crate::time::{Duration, Instant}; use crate::touch; use crate::widget; use crate::widget::operation::{self, Operation}; use crate::widget::tree::{self, Tree}; +use crate::window; use crate::{ Clipboard, Color, Command, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, @@ -425,7 +427,18 @@ where let state = state(); let is_clicked = layout.bounds().contains(cursor_position); - state.is_focused = is_clicked; + state.is_focused = if is_clicked { + state.is_focused.or_else(|| { + let now = Instant::now(); + + Some(Focus { + updated_at: now, + now, + }) + }) + } else { + None + }; if is_clicked { let text_layout = layout.children().next().unwrap(); @@ -541,26 +554,30 @@ where Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { let state = state(); - if state.is_focused - && state.is_pasting.is_none() - && !state.keyboard_modifiers.command() - && !c.is_control() - { - let mut editor = Editor::new(value, &mut state.cursor); + if let Some(focus) = &mut state.is_focused { + if state.is_pasting.is_none() + && !state.keyboard_modifiers.command() + && !c.is_control() + { + let mut editor = Editor::new(value, &mut state.cursor); - editor.insert(c); + editor.insert(c); - let message = (on_change)(editor.contents()); - shell.publish(message); + let message = (on_change)(editor.contents()); + shell.publish(message); - return event::Status::Captured; + focus.updated_at = Instant::now(); + + return event::Status::Captured; + } } } Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { let state = state(); - if state.is_focused { + if let Some(focus) = &mut state.is_focused { let modifiers = state.keyboard_modifiers; + focus.updated_at = Instant::now(); match key_code { keyboard::KeyCode::Enter @@ -721,7 +738,7 @@ where state.cursor.select_all(value); } keyboard::KeyCode::Escape => { - state.is_focused = false; + state.is_focused = None; state.is_dragging = false; state.is_pasting = None; @@ -742,7 +759,7 @@ where Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { let state = state(); - if state.is_focused { + if state.is_focused.is_some() { match key_code { keyboard::KeyCode::V => { state.is_pasting = None; @@ -765,6 +782,21 @@ where state.keyboard_modifiers = modifiers; } + Event::Window(window::Event::RedrawRequested(now)) => { + let state = state(); + + if let Some(focus) = &mut state.is_focused { + focus.now = now; + + let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS + - (now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw(window::RedrawRequest::At( + now + Duration::from_millis(millis_until_redraw as u64), + )); + } + } _ => {} } @@ -820,7 +852,7 @@ pub fn draw<Renderer>( let text = value.to_string(); let size = size.unwrap_or_else(|| renderer.default_size()); - let (cursor, offset) = if state.is_focused() { + let (cursor, offset) = if let Some(focus) = &state.is_focused { match state.cursor.state(value) { cursor::State::Index(position) => { let (text_value_width, offset) = @@ -833,7 +865,13 @@ pub fn draw<Renderer>( font.clone(), ); - ( + let is_cursor_visible = ((focus.now - focus.updated_at) + .as_millis() + / CURSOR_BLINK_INTERVAL_MILLIS) + % 2 + == 0; + + let cursor = if is_cursor_visible { Some(( renderer::Quad { bounds: Rectangle { @@ -847,9 +885,12 @@ pub fn draw<Renderer>( border_color: Color::TRANSPARENT, }, theme.value_color(style), - )), - offset, - ) + )) + } else { + None + }; + + (cursor, offset) } cursor::State::Selection { start, end } => { let left = start.min(end); @@ -958,7 +999,7 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - is_focused: bool, + is_focused: Option<Focus>, is_dragging: bool, is_pasting: Option<Value>, last_click: Option<mouse::Click>, @@ -967,6 +1008,12 @@ pub struct State { // TODO: Add stateful horizontal scrolling offset } +#[derive(Debug, Clone, Copy)] +struct Focus { + updated_at: Instant, + now: Instant, +} + impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { @@ -976,7 +1023,7 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - is_focused: true, + is_focused: None, is_dragging: false, is_pasting: None, last_click: None, @@ -987,7 +1034,7 @@ impl State { /// Returns whether the [`TextInput`] is currently focused or not. pub fn is_focused(&self) -> bool { - self.is_focused + self.is_focused.is_some() } /// Returns the [`Cursor`] of the [`TextInput`]. @@ -997,13 +1044,19 @@ impl State { /// Focuses the [`TextInput`]. pub fn focus(&mut self) { - self.is_focused = true; + let now = Instant::now(); + + self.is_focused = Some(Focus { + updated_at: now, + now, + }); + self.move_cursor_to_end(); } /// Unfocuses the [`TextInput`]. pub fn unfocus(&mut self) { - self.is_focused = false; + self.is_focused = None; } /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. @@ -1156,3 +1209,5 @@ where ) .map(text::Hit::cursor) } + +const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/native/src/window.rs b/native/src/window.rs index 96a5fe61..d3c8c96f 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -6,6 +6,7 @@ mod id; mod mode; mod position; mod settings; +mod redraw_request; mod user_attention; pub use action::Action; @@ -15,4 +16,23 @@ pub use id::Id; pub use mode::Mode; pub use position::Position; pub use settings::Settings; +pub use redraw_request::RedrawRequest; pub use user_attention::UserAttention; + +use crate::subscription::{self, Subscription}; +use crate::time::Instant; + +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// In any case, this [`Subscription`] is useful to smoothly draw application-driven +/// animations without missing any frames. +pub fn frames() -> Subscription<Instant> { + subscription::raw_events(|event, _status| match event { + crate::Event::Window(Event::RedrawRequested(at)) => Some(at), + _ => None, + }) +} diff --git a/native/src/window/event.rs b/native/src/window/event.rs index 86321ac0..e2fb5e66 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -1,3 +1,5 @@ +use crate::time::Instant; + use std::path::PathBuf; /// A window-related event. @@ -19,6 +21,11 @@ pub enum Event { height: u32, }, + /// A window redraw was requested. + /// + /// The [`Instant`] contains the current time. + RedrawRequested(Instant), + /// The user has requested for the window to close. /// /// Usually, you will want to terminate the execution whenever this event diff --git a/native/src/window/redraw_request.rs b/native/src/window/redraw_request.rs new file mode 100644 index 00000000..3b4f0fd3 --- /dev/null +++ b/native/src/window/redraw_request.rs @@ -0,0 +1,38 @@ +use crate::time::Instant; + +/// A request to redraw a window. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum RedrawRequest { + /// Redraw the next frame. + NextFrame, + + /// Redraw at the given time. + At(Instant), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{Duration, Instant}; + + #[test] + fn ordering() { + let now = Instant::now(); + let later = now + Duration::from_millis(10); + + assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame); + assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now)); + + assert!(RedrawRequest::NextFrame < RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame); + assert!(RedrawRequest::At(now) < RedrawRequest::At(later)); + assert!(RedrawRequest::At(later) > RedrawRequest::At(now)); + + assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame); + assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame); + assert!(RedrawRequest::At(now) <= RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) <= RedrawRequest::At(later)); + assert!(RedrawRequest::At(later) >= RedrawRequest::At(now)); + } +} diff --git a/src/application.rs b/src/application.rs index f2b7c955..96f4e9a6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -39,15 +39,15 @@ pub use iced_native::application::{Appearance, StyleSheet}; /// to listen to time. /// - [`todos`], a todos tracker inspired by [TodoMVC]. /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.6/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.6/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.6/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.6/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.6/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.6/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.6/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.6/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.6/examples/todos +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.7/examples +/// [`clock`]: https://github.com/iced-rs/iced/tree/0.7/examples/clock +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.7/examples/download_progress +/// [`events`]: https://github.com/iced-rs/iced/tree/0.7/examples/events +/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.7/examples/game_of_life +/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.7/examples/pokedex +/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.7/examples/solar_system +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.7/examples/stopwatch +/// [`todos`]: https://github.com/iced-rs/iced/tree/0.7/examples/todos /// [`Sandbox`]: crate::Sandbox /// [`Canvas`]: crate::widget::Canvas /// [PokéAPI]: https://pokeapi.co/ @@ -180,13 +180,6 @@ pub trait Application: Sized { 1.0 } - /// Returns whether the [`Application`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -24,13 +24,13 @@ //! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui //! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md -//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.6/native +//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.7/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.6/wgpu -//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.6/winit +//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.7/wgpu +//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.7/winit //! [`dodrio`]: https://github.com/fitzgen/dodrio //! [web runtime]: https://github.com/iced-rs/iced_web -//! [examples]: https://github.com/iced-rs/iced/tree/0.6/examples +//! [examples]: https://github.com/iced-rs/iced/tree/0.7/examples //! [repository]: https://github.com/iced-rs/iced //! //! # Overview @@ -97,6 +97,7 @@ //! text(self.value).size(50), //! //! // The decrement button. We tell it to produce a +//! // `DecrementPressed` message when pressed //! button("-").on_press(Message::DecrementPressed), //! ] //! } diff --git a/src/sandbox.rs b/src/sandbox.rs index 47bad831..31e861ed 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -34,19 +34,19 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// - [`tour`], a simple UI tour that can run both on native platforms and the /// web! /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.6/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.6/examples/bezier_tool -/// [`counter`]: https://github.com/iced-rs/iced/tree/0.6/examples/counter -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.6/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.6/examples/geometry -/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid -/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.6/examples/progress_bar -/// [`styling`]: https://github.com/iced-rs/iced/tree/0.6/examples/styling -/// [`svg`]: https://github.com/iced-rs/iced/tree/0.6/examples/svg -/// [`tour`]: https://github.com/iced-rs/iced/tree/0.6/examples/tour +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.7/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.7/examples/bezier_tool +/// [`counter`]: https://github.com/iced-rs/iced/tree/0.7/examples/counter +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.7/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.7/examples/geometry +/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.7/examples/pane_grid +/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.7/examples/progress_bar +/// [`styling`]: https://github.com/iced-rs/iced/tree/0.7/examples/styling +/// [`svg`]: https://github.com/iced-rs/iced/tree/0.7/examples/svg +/// [`tour`]: https://github.com/iced-rs/iced/tree/0.7/examples/tour /// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.7/wgpu /// [`Svg` widget]: crate::widget::Svg /// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg /// @@ -140,13 +140,6 @@ pub trait Sandbox { 1.0 } - /// Returns whether the [`Sandbox`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - /// Runs the [`Sandbox`]. /// /// On native platforms, this method will take control of the current thread @@ -203,8 +196,4 @@ where fn scale_factor(&self) -> f64 { T::scale_factor(self) } - - fn should_exit(&self) -> bool { - T::should_exit(self) - } } diff --git a/src/widget.rs b/src/widget.rs index f71bf7ff..f0058f57 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -56,7 +56,7 @@ pub mod pane_grid { //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! - //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid + //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.7/examples/pane_grid pub use iced_native::widget::pane_grid::{ Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent, Split, State, StyleSheet, diff --git a/style/Cargo.toml b/style/Cargo.toml index 9f7d904a..2be3e78d 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_style" -version = "0.5.1" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The default set of styles of Iced" @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [dependencies.iced_core] -version = "0.6" +version = "0.7" path = "../core" features = ["palette"] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 8dc4b990..352802b8 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.7.0" +version = "0.8.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A wgpu renderer for Iced" @@ -42,11 +42,11 @@ version = "1.9" features = ["derive"] [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" [dependencies.iced_graphics] -version = "0.5" +version = "0.6" path = "../graphics" features = ["font-fallback", "font-icons"] diff --git a/wgpu/README.md b/wgpu/README.md index 016af179..8ef68c62 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -30,7 +30,7 @@ Currently, `iced_wgpu` supports the following primitives: Add `iced_wgpu` as a dependency in your `Cargo.toml`: ```toml -iced_wgpu = "0.4" +iced_wgpu = "0.8" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index e4a38005..5198276d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -16,7 +16,7 @@ //! - Meshes of triangles, useful to draw geometry freely. //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 2152e7da..b0368d62 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.6.0" +version = "0.7.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A winit runtime for Iced" @@ -29,11 +29,11 @@ git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" [dependencies.iced_native] -version = "0.7" +version = "0.8" path = "../native" [dependencies.iced_graphics] -version = "0.5" +version = "0.6" path = "../graphics" [dependencies.iced_futures] diff --git a/winit/README.md b/winit/README.md index 3ca46fff..44286c2c 100644 --- a/winit/README.md +++ b/winit/README.md @@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_winit` as a dependency in your `Cargo.toml`: ```toml -iced_winit = "0.3" +iced_winit = "0.7" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/winit/src/application.rs b/winit/src/application.rs index 76553988..c66e08b2 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -9,7 +9,7 @@ use crate::mouse; use crate::renderer; use crate::widget::operation; use crate::{ - Command, Debug, Error, Executor, Proxy, Runtime, Settings, Size, + Command, Debug, Error, Event, Executor, Proxy, Runtime, Settings, Size, Subscription, }; @@ -18,6 +18,7 @@ use iced_futures::futures::channel::mpsc; use iced_graphics::compositor; use iced_graphics::window; use iced_native::program::Program; +use iced_native::time::Instant; use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; @@ -184,7 +185,8 @@ where let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; - let (mut sender, receiver) = mpsc::unbounded(); + let (mut event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::<A, E, C>( @@ -194,7 +196,8 @@ where runtime, proxy, debug, - receiver, + event_receiver, + control_sender, init_command, window, settings.exit_on_close_request, @@ -232,13 +235,19 @@ where }; if let Some(event) = event { - sender.start_send(event).expect("Send event"); + event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, + match poll { + task::Poll::Pending => { + if let Ok(Some(flow)) = control_receiver.try_next() { + *control_flow = flow; + } + } + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + } }; } }) @@ -251,7 +260,10 @@ async fn run_instance<A, E, C>( mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, mut proxy: winit::event_loop::EventLoopProxy<A::Message>, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, + mut event_receiver: mpsc::UnboundedReceiver< + winit::event::Event<'_, A::Message>, + >, + mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, init_command: Command<A::Message>, window: winit::window::Window, exit_on_close_request: bool, @@ -263,6 +275,7 @@ async fn run_instance<A, E, C>( { use iced_futures::futures::stream::StreamExt; use winit::event; + use winit::event_loop::ControlFlow; let mut clipboard = Clipboard::connect(&window); let mut cache = user_interface::Cache::default(); @@ -307,13 +320,22 @@ async fn run_instance<A, E, C>( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut redraw_pending = false; debug.startup_finished(); - while let Some(event) = receiver.next().await { + while let Some(event) = event_receiver.next().await { match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::Poll + | event::StartCause::ResumeTimeReached { .. } + ); + } event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { + if !redraw_pending && events.is_empty() && messages.is_empty() { continue; } @@ -336,7 +358,7 @@ async fn run_instance<A, E, C>( if !messages.is_empty() || matches!( interface_state, - user_interface::State::Outdated, + user_interface::State::Outdated ) { let mut cache = @@ -374,6 +396,23 @@ async fn run_instance<A, E, C>( } } + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. + let redraw_event = Event::Window( + crate::window::Event::RedrawRequested(Instant::now()), + ); + + let (interface_state, _) = user_interface.update( + &[redraw_event.clone()], + state.cursor_position(), + &mut renderer, + &mut clipboard, + &mut messages, + ); + debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -394,6 +433,24 @@ async fn run_instance<A, E, C>( } window.request_redraw(); + runtime + .broadcast((redraw_event, crate::event::Status::Ignored)); + + let _ = control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + crate::window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + crate::window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 6c809d19..111afd83 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -1,7 +1,7 @@ //! Convert [`winit`] types into [`iced_native`] types, and viceversa. //! //! [`winit`]: https://github.com/rust-windowing/winit -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native use crate::keyboard; use crate::mouse; use crate::touch; @@ -228,7 +228,7 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode { /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native pub fn mouse_interaction( interaction: mouse::Interaction, ) -> winit::window::CursorIcon { @@ -252,7 +252,7 @@ pub fn mouse_interaction( /// Converts a `MouseButton` from [`winit`] to an [`iced_native`] mouse button. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { match mouse_button { winit::event::MouseButton::Left => mouse::Button::Left, @@ -268,7 +268,7 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { /// modifiers state. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native pub fn modifiers( modifiers: winit::event::ModifiersState, ) -> keyboard::Modifiers { @@ -295,7 +295,7 @@ pub fn cursor_position( /// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native pub fn touch_event( touch: winit::event::Touch, scale_factor: f64, @@ -326,7 +326,7 @@ pub fn touch_event( /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native pub fn key_code( virtual_keycode: winit::event::VirtualKeyCode, ) -> keyboard::KeyCode { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 99a46850..76339a76 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -11,7 +11,7 @@ //! Additionally, a [`conversion`] module is available for users that decide to //! implement a custom event loop. //! -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.7/native //! [`winit`]: https://github.com/rust-windowing/winit //! [`conversion`]: crate::conversion #![doc( diff --git a/winit/src/window.rs b/winit/src/window.rs index 5a8ff6df..cc486fc8 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Event, Id, Mode, UserAttention}; +pub use window::{Event, Id, Mode, RedrawRequest, frames, UserAttention}; /// Closes the window. pub fn close<Message>(id: window::Id) -> Command<Message> { |