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  #