summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Bingus <shankern@protonmail.com>2023-01-18 15:01:17 -0800
committerLibravatar Bingus <shankern@protonmail.com>2023-01-18 15:01:17 -0800
commit70d487ba20a50c06c73f0ffcd8198f1a7eac7f37 (patch)
treeafb8e161b18236d4440cba8bb0e0ce896858d653
parent790fa3e7a01a790aa3f07083fe9abf6b68fa7ba1 (diff)
parent5ef0648bf447aaca8b96782643401e54a2bf7759 (diff)
downloadiced-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 '')
-rw-r--r--.github/ISSUE_TEMPLATE/BUG-REPORT.yml2
-rw-r--r--CHANGELOG.md46
-rw-r--r--Cargo.toml18
-rw-r--r--README.md4
-rw-r--r--core/Cargo.toml4
-rw-r--r--core/README.md2
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/time.rs6
-rw-r--r--examples/cached/Cargo.toml10
-rw-r--r--examples/cached/src/main.rs139
-rw-r--r--examples/events/Cargo.toml2
-rw-r--r--examples/events/src/main.rs28
-rw-r--r--examples/exit/src/main.rs26
-rw-r--r--examples/lazy/src/main.rs162
-rw-r--r--examples/solar_system/src/main.rs3
-rw-r--r--futures/src/subscription.rs6
-rw-r--r--glow/Cargo.toml6
-rw-r--r--glow/README.md2
-rw-r--r--glow/src/lib.rs2
-rw-r--r--glutin/Cargo.toml14
-rw-r--r--glutin/README.md2
-rw-r--r--glutin/src/application.rs77
-rw-r--r--graphics/Cargo.toml6
-rw-r--r--lazy/Cargo.toml4
-rw-r--r--lazy/src/lazy.rs130
-rw-r--r--lazy/src/lib.rs2
-rw-r--r--native/Cargo.toml6
-rw-r--r--native/README.md2
-rw-r--r--native/src/element.rs5
-rw-r--r--native/src/lib.rs4
-rw-r--r--native/src/overlay/element.rs6
-rw-r--r--native/src/renderer.rs6
-rw-r--r--native/src/shell.rs60
-rw-r--r--native/src/subscription.rs35
-rw-r--r--native/src/user_interface.rs47
-rw-r--r--native/src/widget.rs10
-rw-r--r--native/src/widget/action.rs9
-rw-r--r--native/src/widget/operation.rs4
-rw-r--r--native/src/widget/pane_grid.rs2
-rw-r--r--native/src/widget/text_input.rs103
-rw-r--r--native/src/window.rs20
-rw-r--r--native/src/window/event.rs7
-rw-r--r--native/src/window/redraw_request.rs38
-rw-r--r--src/application.rs25
-rw-r--r--src/lib.rs9
-rw-r--r--src/sandbox.rs33
-rw-r--r--src/widget.rs2
-rw-r--r--style/Cargo.toml4
-rw-r--r--wgpu/Cargo.toml6
-rw-r--r--wgpu/README.md2
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--winit/Cargo.toml6
-rw-r--r--winit/README.md2
-rw-r--r--winit/src/application.rs79
-rw-r--r--winit/src/conversion.rs12
-rw-r--r--winit/src/lib.rs2
-rw-r--r--winit/src/window.rs2
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
diff --git a/Cargo.toml b/Cargo.toml
index 36465a29..6af8e5a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"]
diff --git a/README.md b/README.md
index 457a02ae..b3790478 100644
--- a/README.md
+++ b/README.md
@@ -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 @@
//! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
//!
//! [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 @@
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`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
diff --git a/src/lib.rs b/src/lib.rs
index 6cda8c41..78504e44 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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> {