diff options
100 files changed, 2125 insertions, 423 deletions
diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index 96dcc3b6..d58a4feb 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -58,12 +58,12 @@ body: attributes: label: Version description: | - We only offer support for the `0.4` release on crates.io and the `master` branch on this repository. Which version are you using? Please make sure you are using the latest patch available (e.g. run `cargo update`). + We only offer support for the latest release on crates.io and the `master` branch on this repository. Which version are you using? Please make sure you are using the latest patch available (e.g. run `cargo update`). - If you are using an older release, please upgrade to `0.4` before filing an issue. + If you are using an older release, please upgrade to the latest one before filing an issue. options: - master - - 0.4 + - 0.6 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index d098e8ec..29cdef58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [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) +- Operation to query the current focused widget. [#1526](https://github.com/iced-rs/iced/pull/1526) +- Additional operations for `TextInput`. [#1529](https://github.com/iced-rs/iced/pull/1529) +- Styling support for `Svg`. [#1578](https://github.com/iced-rs/iced/pull/1578) + +### Changed +- Triangle geometry using a solid color is now drawn in a single draw call. [#1538](https://github.com/iced-rs/iced/pull/1538) + +### Fixed +- Gradients for WebAssembly target. [#1524](https://github.com/iced-rs/iced/pull/1524) +- `Overlay` layout cache not being invalidated. [#1528](https://github.com/iced-rs/iced/pull/1528) +- Operations not working for `PaneGrid`. [#1533](https://github.com/iced-rs/iced/pull/1533) +- Mapped `widget::Operation` always returning `Outcome::None`. [#1536](https://github.com/iced-rs/iced/pull/1536) +- Padding of `TextInput` with `Length::Units` width. [#1539](https://github.com/iced-rs/iced/pull/1539) +- Clipping of `Image` and `Svg` widgets in `iced_glow`. [#1557](https://github.com/iced-rs/iced/pull/1557) +- Invalid links in documentation. [#1560](https://github.com/iced-rs/iced/pull/1560) +- `Custom` style of `PickList` widget. [#1570](https://github.com/iced-rs/iced/pull/1570) +- Scroller in `Scrollable` always being drawn. [#1574](https://github.com/iced-rs/iced/pull/1574) + +Many thanks to... + +- @bungoboingo +- @l1Dan +- @mmstick +- @mtkennerly +- @PolyMeilex +- @rksm +- @rs017991 +- @tarkah +- @wash2 + ## [0.5.0] - 2022-11-10 ### Added - __[Stabilization of stateless widgets][stateless]__ (#1393) @@ -288,7 +321,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release! :tada: -[Unreleased]: https://github.com/iced-rs/iced/compare/0.5.0...HEAD +[Unreleased]: https://github.com/iced-rs/iced/compare/0.6.0...HEAD +[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 [0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1 @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.5.2" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A cross-platform GUI library inspired by Elm" @@ -55,50 +55,17 @@ members = [ "style", "wgpu", "winit", - "examples/arc", - "examples/bezier_tool", - "examples/clock", - "examples/color_palette", - "examples/component", - "examples/counter", - "examples/custom_widget", - "examples/download_progress", - "examples/events", - "examples/exit", - "examples/game_of_life", - "examples/geometry", - "examples/integration_opengl", - "examples/integration_wgpu", - "examples/lazy", - "examples/modern_art", - "examples/multitouch", - "examples/pane_grid", - "examples/pick_list", - "examples/pokedex", - "examples/progress_bar", - "examples/qr_code", - "examples/scrollable", - "examples/sierpinski_triangle", - "examples/solar_system", - "examples/stopwatch", - "examples/styling", - "examples/svg", - "examples/system_information", - "examples/todos", - "examples/tooltip", - "examples/tour", - "examples/url_handler", - "examples/websocket", + "examples/*", ] [dependencies] iced_core = { version = "0.6", path = "core" } iced_futures = { version = "0.5", path = "futures" } -iced_native = { version = "0.6", path = "native" } -iced_graphics = { version = "0.4", path = "graphics" } -iced_winit = { version = "0.5", path = "winit", features = ["application"] } -iced_glutin = { version = "0.4", path = "glutin", optional = true } -iced_glow = { version = "0.4", path = "glow", optional = true } +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 } thiserror = "1.0" [dependencies.image_rs] @@ -107,10 +74,10 @@ package = "image" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.6", path = "wgpu", optional = true } +iced_wgpu = { version = "0.7", path = "wgpu", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.6", path = "wgpu", features = ["webgl"], optional = true } +iced_wgpu = { version = "0.7", 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.5" +iced = "0.6" ``` 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.5", default-features = false, features = ["glow"] } +iced = { version = "0.6", 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 6fd0a38c..c401f30a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.6.1" +version = "0.6.2" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The essential concepts of Iced" diff --git a/core/src/color.rs b/core/src/color.rs index 212c1214..fe0a1856 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -89,6 +89,17 @@ impl Color { } } + /// Converts the [`Color`] into its RGBA8 equivalent. + #[must_use] + pub fn into_rgba8(self) -> [u8; 4] { + [ + (self.r * 255.0).round() as u8, + (self.g * 255.0).round() as u8, + (self.b * 255.0).round() as u8, + (self.a * 255.0).round() as u8, + ] + } + /// Converts the [`Color`] into its linear values. pub fn into_linear(self) -> [f32; 4] { // As described in: @@ -148,24 +159,26 @@ impl From<[f32; 4]> for Color { #[macro_export] macro_rules! color { ($r:expr, $g:expr, $b:expr) => { - Color::from_rgb8($r, $g, $b) + $crate::Color::from_rgb8($r, $g, $b) }; ($r:expr, $g:expr, $b:expr, $a:expr) => { - Color::from_rgba8($r, $g, $b, $a) + $crate::Color::from_rgba8($r, $g, $b, $a) }; ($hex:expr) => {{ let hex = $hex as u32; let r = (hex & 0xff0000) >> 16; let g = (hex & 0xff00) >> 8; let b = (hex & 0xff); - Color::from_rgb8(r as u8, g as u8, b as u8) + + $crate::Color::from_rgb8(r as u8, g as u8, b as u8) }}; ($hex:expr, $a:expr) => {{ let hex = $hex as u32; let r = (hex & 0xff0000) >> 16; let g = (hex & 0xff00) >> 8; let b = (hex & 0xff); - Color::from_rgba8(r as u8, g as u8, b as u8, $a) + + $crate::Color::from_rgba8(r as u8, g as u8, b as u8, $a) }}; } diff --git a/core/src/lib.rs b/core/src/lib.rs index 383b4f73..f95d61f6 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.5/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/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/rectangle.rs b/core/src/rectangle.rs index 4e082051..4fe91519 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -116,8 +116,8 @@ impl std::ops::Mul<f32> for Rectangle<f32> { fn mul(self, scale: f32) -> Self { Self { - x: self.x as f32 * scale, - y: self.y as f32 * scale, + x: self.x * scale, + y: self.y * scale, width: self.width * scale, height: self.height * scale, } diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml new file mode 100644 index 00000000..39154786 --- /dev/null +++ b/examples/custom_quad/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "custom_quad" +version = "0.1.0" +authors = ["Robert Krahn"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs new file mode 100644 index 00000000..6509887c --- /dev/null +++ b/examples/custom_quad/src/main.rs @@ -0,0 +1,160 @@ +//! This example showcases a drawing a quad. +mod quad { + use iced_native::layout::{self, Layout}; + use iced_native::renderer; + use iced_native::widget::{self, Widget}; + use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + + pub struct CustomQuad { + size: f32, + radius: [f32; 4], + border_width: f32, + } + + impl CustomQuad { + pub fn new(size: f32, radius: [f32; 4], border_width: f32) -> Self { + Self { + size, + radius, + border_width, + } + } + } + + impl<Message, Renderer> Widget<Message, Renderer> for CustomQuad + where + Renderer: renderer::Renderer, + { + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + _limits: &layout::Limits, + ) -> layout::Node { + layout::Node::new(Size::new(self.size, self.size)) + } + + fn draw( + &self, + _state: &widget::Tree, + renderer: &mut Renderer, + _theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) { + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: self.radius.into(), + border_width: self.border_width, + border_color: Color::from_rgb(1.0, 0.0, 0.0), + }, + Color::BLACK, + ); + } + } + + impl<'a, Message, Renderer> From<CustomQuad> for Element<'a, Message, Renderer> + where + Renderer: renderer::Renderer, + { + fn from(circle: CustomQuad) -> Self { + Self::new(circle) + } + } +} + +use iced::widget::{column, container, slider, text}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +struct Example { + radius: [f32; 4], + border_width: f32, +} + +#[derive(Debug, Clone, Copy)] +#[allow(clippy::enum_variant_names)] +enum Message { + RadiusTopLeftChanged(f32), + RadiusTopRightChanged(f32), + RadiusBottomRightChanged(f32), + RadiusBottomLeftChanged(f32), + BorderWidthChanged(f32), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self { + radius: [50.0; 4], + border_width: 0.0, + } + } + + fn title(&self) -> String { + String::from("Custom widget - Iced") + } + + fn update(&mut self, message: Message) { + let [tl, tr, br, bl] = self.radius; + match message { + Message::RadiusTopLeftChanged(radius) => { + self.radius = [radius, tr, br, bl]; + } + Message::RadiusTopRightChanged(radius) => { + self.radius = [tl, radius, br, bl]; + } + Message::RadiusBottomRightChanged(radius) => { + self.radius = [tl, tr, radius, bl]; + } + Message::RadiusBottomLeftChanged(radius) => { + self.radius = [tl, tr, br, radius]; + } + Message::BorderWidthChanged(width) => { + self.border_width = width; + } + } + } + + fn view(&self) -> Element<Message> { + let [tl, tr, br, bl] = self.radius; + + let content = column![ + quad::CustomQuad::new(200.0, self.radius, self.border_width), + text(format!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}")), + slider(1.0..=100.0, tl, Message::RadiusTopLeftChanged).step(0.01), + slider(1.0..=100.0, tr, Message::RadiusTopRightChanged).step(0.01), + slider(1.0..=100.0, br, Message::RadiusBottomRightChanged) + .step(0.01), + slider(1.0..=100.0, bl, Message::RadiusBottomLeftChanged) + .step(0.01), + slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged) + .step(0.01), + ] + .padding(20) + .spacing(20) + .max_width(500) + .align_items(Alignment::Center); + + container(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index c37a1a12..f6bb3b1e 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -61,7 +61,7 @@ mod circle { renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border_radius: self.radius, + border_radius: self.radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 2a8b3721..b0f1c96d 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -492,8 +492,10 @@ mod grid { let old_scaling = self.scaling; let scaling = (self.scaling * (1.0 + y / 30.0)) - .max(Self::MIN_SCALING) - .min(Self::MAX_SCALING); + .clamp( + Self::MIN_SCALING, + Self::MAX_SCALING, + ); let translation = if let Some(cursor_to_center) = diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml new file mode 100644 index 00000000..8770acac --- /dev/null +++ b/examples/modal/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "modal" +version = "0.1.0" +authors = ["tarkah <admin@tarkah.dev>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = [] } +iced_native = { path = "../../native" } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs new file mode 100644 index 00000000..5afafd0d --- /dev/null +++ b/examples/modal/src/main.rs @@ -0,0 +1,479 @@ +use iced::widget::{ + self, button, column, container, horizontal_space, row, text, text_input, +}; +use iced::{ + executor, keyboard, subscription, theme, Alignment, Application, Command, + Element, Event, Length, Settings, Subscription, +}; + +use self::modal::Modal; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Default)] +struct App { + show_modal: bool, + email: String, + password: String, +} + +#[derive(Debug, Clone)] +enum Message { + ShowModal, + HideModal, + Email(String), + Password(String), + Submit, + Event(Event), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Theme = iced::Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command<Message>) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Modal - Iced") + } + + fn subscription(&self) -> Subscription<Self::Message> { + subscription::events().map(Message::Event) + } + + fn update(&mut self, message: Message) -> Command<Message> { + match message { + Message::ShowModal => { + self.show_modal = true; + widget::focus_next() + } + Message::HideModal => { + self.hide_modal(); + Command::none() + } + Message::Email(email) => { + self.email = email; + Command::none() + } + Message::Password(password) => { + self.password = password; + Command::none() + } + Message::Submit => { + if !self.email.is_empty() && !self.password.is_empty() { + self.hide_modal(); + } + + Command::none() + } + Message::Event(event) => match event { + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + modifiers, + }) => { + if modifiers.shift() { + widget::focus_previous() + } else { + widget::focus_next() + } + } + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Escape, + .. + }) => { + self.hide_modal(); + Command::none() + } + _ => Command::none(), + }, + } + } + + fn view(&self) -> Element<Message> { + let content = container( + column![ + row![ + text("Top Left"), + horizontal_space(Length::Fill), + text("Top Right") + ] + .align_items(Alignment::Start) + .height(Length::Fill), + container( + button(text("Show Modal")).on_press(Message::ShowModal) + ) + .center_x() + .center_y() + .width(Length::Fill) + .height(Length::Fill), + row![ + text("Bottom Left"), + horizontal_space(Length::Fill), + text("Bottom Right") + ] + .align_items(Alignment::End) + .height(Length::Fill), + ] + .height(Length::Fill), + ) + .padding(10) + .width(Length::Fill) + .height(Length::Fill); + + if self.show_modal { + let modal = container( + column![ + text("Sign Up").size(24), + column![ + column![ + text("Email").size(12), + text_input( + "abc@123.com", + &self.email, + Message::Email + ) + .on_submit(Message::Submit) + .padding(5), + ] + .spacing(5), + column![ + text("Password").size(12), + text_input("", &self.password, Message::Password) + .on_submit(Message::Submit) + .password() + .padding(5), + ] + .spacing(5), + button(text("Submit")).on_press(Message::HideModal), + ] + .spacing(10) + ] + .spacing(20), + ) + .width(Length::Units(300)) + .padding(10) + .style(theme::Container::Box); + + Modal::new(content, modal) + .on_blur(Message::HideModal) + .into() + } else { + content.into() + } + } +} + +impl App { + fn hide_modal(&mut self) { + self.show_modal = false; + self.email.clear(); + self.password.clear(); + } +} + +mod modal { + use iced_native::alignment::Alignment; + use iced_native::widget::{self, Tree}; + use iced_native::{ + event, layout, mouse, overlay, renderer, Clipboard, Color, Element, + Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, + }; + + /// A widget that centers a modal element over some base element + pub struct Modal<'a, Message, Renderer> { + base: Element<'a, Message, Renderer>, + modal: Element<'a, Message, Renderer>, + on_blur: Option<Message>, + } + + impl<'a, Message, Renderer> Modal<'a, Message, Renderer> { + /// Returns a new [`Modal`] + pub fn new( + base: impl Into<Element<'a, Message, Renderer>>, + modal: impl Into<Element<'a, Message, Renderer>>, + ) -> Self { + Self { + base: base.into(), + modal: modal.into(), + on_blur: None, + } + } + + /// Sets the message that will be produces when the background + /// of the [`Modal`] is pressed + pub fn on_blur(self, on_blur: Message) -> Self { + Self { + on_blur: Some(on_blur), + ..self + } + } + } + + impl<'a, Message, Renderer> Widget<Message, Renderer> + for Modal<'a, Message, Renderer> + where + Renderer: iced_native::Renderer, + Message: Clone, + { + fn children(&self) -> Vec<Tree> { + vec![Tree::new(&self.base), Tree::new(&self.modal)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.base, &self.modal]); + } + + fn width(&self) -> Length { + self.base.as_widget().width() + } + + fn height(&self) -> Length { + self.base.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.base.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.base.as_widget_mut().on_event( + &mut state.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &<Renderer as iced_native::Renderer>::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.base.as_widget().draw( + &state.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option<overlay::Element<'b, Message, Renderer>> { + Some(overlay::Element::new( + layout.position(), + Box::new(Overlay { + content: &mut self.modal, + tree: &mut state.children[1], + size: layout.bounds().size(), + on_blur: self.on_blur.clone(), + }), + )) + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.base.as_widget().mouse_interaction( + &state.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn operate( + &self, + state: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation<Message>, + ) { + self.base.as_widget().operate( + &mut state.children[0], + layout, + renderer, + operation, + ); + } + } + + struct Overlay<'a, 'b, Message, Renderer> { + content: &'b mut Element<'a, Message, Renderer>, + tree: &'b mut Tree, + size: Size, + on_blur: Option<Message>, + } + + impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> + for Overlay<'a, 'b, Message, Renderer> + where + Renderer: iced_native::Renderer, + Message: Clone, + { + fn layout( + &self, + renderer: &Renderer, + _bounds: Size, + position: Point, + ) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, self.size) + .width(Length::Fill) + .height(Length::Fill); + + let mut child = self.content.as_widget().layout(renderer, &limits); + child.align(Alignment::Center, Alignment::Center, limits.max()); + + let mut node = layout::Node::with_children(self.size, vec![child]); + node.move_to(position); + + node + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let content_bounds = layout.children().next().unwrap().bounds(); + + if let Some(message) = self.on_blur.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) = &event + { + if !content_bounds.contains(cursor_position) { + shell.publish(message.clone()); + return event::Status::Captured; + } + } + } + + self.content.as_widget_mut().on_event( + self.tree, + event, + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: renderer::BorderRadius::from(0.0), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Color { + a: 0.80, + ..Color::BLACK + }, + ); + + self.content.as_widget().draw( + self.tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor_position, + &layout.bounds(), + ); + } + + fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation<Message>, + ) { + self.content.as_widget().operate( + self.tree, + layout.children().next().unwrap(), + renderer, + operation, + ); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + self.tree, + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + } + + impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> + for Element<'a, Message, Renderer> + where + Renderer: 'a + iced_native::Renderer, + Message: 'a + Clone, + { + fn from(modal: Modal<'a, Message, Renderer>) -> Self { + Element::new(modal) + } + } +} diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml new file mode 100644 index 00000000..112d7cff --- /dev/null +++ b/examples/slider/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "slider" +version = "0.1.0" +authors = ["Casper Rogild Storm<casper@rogildstorm.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/slider/README.md b/examples/slider/README.md new file mode 100644 index 00000000..829d8285 --- /dev/null +++ b/examples/slider/README.md @@ -0,0 +1,14 @@ +## Slider + +A `Slider` is a bar and a handle that selects a single value from a range of values. +There exists both `Slider` and `VerticalSlider` depending on which orientation you need. + +<div align="center"> + <img src="sliders.gif"> +</div> + +You can run it with `cargo run`: + +``` +cargo run --package slider +``` diff --git a/examples/slider/sliders.gif b/examples/slider/sliders.gif Binary files differnew file mode 100644 index 00000000..f906d05a --- /dev/null +++ b/examples/slider/sliders.gif diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs new file mode 100644 index 00000000..6286d625 --- /dev/null +++ b/examples/slider/src/main.rs @@ -0,0 +1,63 @@ +use iced::widget::{column, container, slider, text, vertical_slider}; +use iced::{Element, Length, Sandbox, Settings}; + +pub fn main() -> iced::Result { + Slider::run(Settings::default()) +} + +#[derive(Debug, Clone)] +pub enum Message { + SliderChanged(u8), +} + +pub struct Slider { + slider_value: u8, +} + +impl Sandbox for Slider { + type Message = Message; + + fn new() -> Slider { + Slider { slider_value: 50 } + } + + fn title(&self) -> String { + String::from("Slider - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::SliderChanged(value) => { + self.slider_value = value; + } + } + } + + fn view(&self) -> Element<Message> { + let value = self.slider_value; + + let h_slider = + container(slider(0..=100, value, Message::SliderChanged)) + .width(Length::Units(250)); + + let v_slider = + container(vertical_slider(0..=100, value, Message::SliderChanged)) + .height(Length::Units(200)); + + let text = text(format!("{value}")); + + container( + column![ + container(v_slider).width(Length::Fill).center_x(), + container(h_slider).width(Length::Fill).center_x(), + container(text).width(Length::Fill).center_x(), + ] + .spacing(25), + ) + .height(Length::Fill) + .width(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 27d175da..4dc92416 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,39 +1,76 @@ -use iced::widget::{container, svg}; -use iced::{Element, Length, Sandbox, Settings}; +use iced::theme; +use iced::widget::{checkbox, column, container, svg}; +use iced::{color, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Tiger::run(Settings::default()) } -struct Tiger; +#[derive(Debug, Default)] +struct Tiger { + apply_color_filter: bool, +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + ToggleColorFilter(bool), +} impl Sandbox for Tiger { - type Message = (); + type Message = Message; fn new() -> Self { - Tiger + Tiger::default() } fn title(&self) -> String { String::from("SVG - Iced") } - fn update(&mut self, _message: ()) {} + fn update(&mut self, message: Self::Message) { + match message { + Message::ToggleColorFilter(apply_color_filter) => { + self.apply_color_filter = apply_color_filter; + } + } + } - fn view(&self) -> Element<()> { - let svg = svg(svg::Handle::from_path(format!( + fn view(&self) -> Element<Self::Message> { + let handle = svg::Handle::from_path(format!( "{}/resources/tiger.svg", env!("CARGO_MANIFEST_DIR") - ))) - .width(Length::Fill) - .height(Length::Fill); + )); + + let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( + if self.apply_color_filter { + theme::Svg::custom_fn(|_theme| svg::Appearance { + color: Some(color!(0x0000ff)), + }) + } else { + theme::Svg::Default + }, + ); - container(svg) + let apply_color_filter = checkbox( + "Apply a color filter", + self.apply_color_filter, + Message::ToggleColorFilter, + ); + + container( + column![ + svg, + container(apply_color_filter).width(Length::Fill).center_x() + ] + .spacing(20) .width(Length::Fill) - .height(Length::Fill) - .padding(20) - .center_x() - .center_y() - .into() + .height(Length::Fill), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() } } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 30cf7581..1d4b68a6 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.5/examples -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch +/// [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 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 1977f4b6..f586d24d 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glow" -version = "0.4.1" +version = "0.5.1" 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.6" +version = "0.7" path = "../native" [dependencies.iced_graphics] -version = "0.4" +version = "0.5" path = "../graphics" features = ["font-fallback", "font-icons", "opengl"] diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 1a41d540..416c3b94 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -128,8 +128,14 @@ impl Backend { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); - self.image_pipeline - .draw(gl, scaled, scale_factor, &layer.images); + self.image_pipeline.draw( + gl, + target_height, + scaled, + scale_factor, + &layer.images, + bounds, + ); } if !layer.text.is_empty() { diff --git a/glow/src/image.rs b/glow/src/image.rs index f906cd4c..521a01e7 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -14,6 +14,7 @@ use iced_graphics::image::raster; use iced_graphics::image::vector; use iced_graphics::layer; +use iced_graphics::Rectangle; use iced_graphics::Size; use glow::HasContext; @@ -141,14 +142,17 @@ impl Pipeline { pub fn draw( &mut self, mut gl: &glow::Context, + target_height: u32, transformation: Transformation, _scale_factor: f32, images: &[layer::Image], + layer_bounds: Rectangle<u32>, ) { unsafe { gl.use_program(Some(self.program)); gl.bind_vertex_array(Some(self.vertex_array)); gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + gl.enable(glow::SCISSOR_TEST); } #[cfg(feature = "image")] @@ -168,11 +172,16 @@ impl Pipeline { layer::Image::Raster { handle: _, bounds } => (None, bounds), #[cfg(feature = "svg")] - layer::Image::Vector { handle, bounds } => { + layer::Image::Vector { + handle, + color, + bounds, + } => { let size = [bounds.width, bounds.height]; ( vector_cache.upload( handle, + *color, size, _scale_factor, &mut gl, @@ -183,10 +192,18 @@ impl Pipeline { } #[cfg(not(feature = "svg"))] - layer::Image::Vector { handle: _, bounds } => (None, bounds), + layer::Image::Vector { bounds, .. } => (None, bounds), }; unsafe { + gl.scissor( + layer_bounds.x as i32, + (target_height - (layer_bounds.y + layer_bounds.height)) + as i32, + layer_bounds.width as i32, + layer_bounds.height as i32, + ); + if let Some(storage::Entry { texture, .. }) = entry { gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) } else { @@ -213,6 +230,7 @@ impl Pipeline { gl.bind_buffer(glow::ARRAY_BUFFER, None); gl.bind_vertex_array(None); gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); } } diff --git a/glow/src/lib.rs b/glow/src/lib.rs index e6ca0562..710ac36d 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.5/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/glow/src/quad/compatibility.rs b/glow/src/quad/compatibility.rs index 28a5ea7c..e909162c 100644 --- a/glow/src/quad/compatibility.rs +++ b/glow/src/quad/compatibility.rs @@ -254,7 +254,7 @@ unsafe fn create_buffers( gl.enable_vertex_attrib_array(4); gl.vertex_attrib_pointer_f32( 4, - 1, + 4, glow::FLOAT, false, stride, @@ -268,7 +268,7 @@ unsafe fn create_buffers( glow::FLOAT, false, stride, - 4 * (2 + 2 + 4 + 4 + 1), + 4 * (2 + 2 + 4 + 4 + 4), ); gl.enable_vertex_attrib_array(6); @@ -278,7 +278,7 @@ unsafe fn create_buffers( glow::FLOAT, false, stride, - 4 * (2 + 2 + 4 + 4 + 1 + 1), + 4 * (2 + 2 + 4 + 4 + 4 + 1), ); gl.bind_vertex_array(None); @@ -307,7 +307,7 @@ pub struct Vertex { pub border_color: [f32; 4], /// The border radius of the [`Vertex`]. - pub border_radius: f32, + pub border_radius: [f32; 4], /// The border width of the [`Vertex`]. pub border_width: f32, diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs index 16bec385..89036530 100644 --- a/glow/src/quad/core.rs +++ b/glow/src/quad/core.rs @@ -218,7 +218,7 @@ unsafe fn create_instance_buffer( gl.enable_vertex_attrib_array(4); gl.vertex_attrib_pointer_f32( 4, - 1, + 4, glow::FLOAT, false, stride, @@ -233,7 +233,7 @@ unsafe fn create_instance_buffer( glow::FLOAT, false, stride, - 4 * (2 + 2 + 4 + 4 + 1), + 4 * (2 + 2 + 4 + 4 + 4), ); gl.vertex_attrib_divisor(5, 1); diff --git a/glow/src/shader/compatibility/quad.frag b/glow/src/shader/compatibility/quad.frag index 8ea5693d..bb9d8122 100644 --- a/glow/src/shader/compatibility/quad.frag +++ b/glow/src/shader/compatibility/quad.frag @@ -12,7 +12,7 @@ varying vec4 v_Color; varying vec4 v_BorderColor; varying vec2 v_Pos; varying vec2 v_Scale; -varying float v_BorderRadius; +varying vec4 v_BorderRadius; varying float v_BorderWidth; float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) @@ -33,10 +33,26 @@ float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) return sqrt(distance.x * distance.x + distance.y * distance.y); } +float selectBorderRadius(vec4 radi, vec2 position, vec2 center) +{ + float rx = radi.x; + float ry = radi.y; + rx = position.x > center.x ? radi.y : radi.x; + ry = position.x > center.x ? radi.z : radi.w; + rx = position.y > center.y ? ry : rx; + return rx; +} + void main() { vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); + float border_radius = selectBorderRadius( + v_BorderRadius, + fragCoord, + (v_Pos + v_Scale * 0.5).xy + ); + + float internal_border = max(border_radius - v_BorderWidth, 0.0); float internal_distance = _distance( fragCoord, @@ -57,11 +73,11 @@ void main() { fragCoord, v_Pos, v_Scale, - v_BorderRadius + border_radius ); float radius_alpha = - 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); + 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); } diff --git a/glow/src/shader/compatibility/quad.vert b/glow/src/shader/compatibility/quad.vert index abe70c0e..89931f06 100644 --- a/glow/src/shader/compatibility/quad.vert +++ b/glow/src/shader/compatibility/quad.vert @@ -5,7 +5,7 @@ attribute vec2 i_Pos; attribute vec2 i_Scale; attribute vec4 i_Color; attribute vec4 i_BorderColor; -attribute float i_BorderRadius; +attribute vec4 i_BorderRadius; attribute float i_BorderWidth; attribute vec2 q_Pos; @@ -13,7 +13,7 @@ varying vec4 v_Color; varying vec4 v_BorderColor; varying vec2 v_Pos; varying vec2 v_Scale; -varying float v_BorderRadius; +varying vec4 v_BorderRadius; varying float v_BorderWidth; @@ -21,9 +21,11 @@ void main() { vec2 p_Pos = i_Pos * u_Scale; vec2 p_Scale = i_Scale * u_Scale; - float i_BorderRadius = min( - i_BorderRadius, - min(i_Scale.x, i_Scale.y) / 2.0 + vec4 i_BorderRadius = vec4( + min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) ); mat4 i_Transform = mat4( diff --git a/glow/src/shader/core/quad.frag b/glow/src/shader/core/quad.frag index 57e2e8e7..71147aa5 100644 --- a/glow/src/shader/core/quad.frag +++ b/glow/src/shader/core/quad.frag @@ -17,7 +17,7 @@ in vec4 v_Color; in vec4 v_BorderColor; in vec2 v_Pos; in vec2 v_Scale; -in float v_BorderRadius; +in vec4 v_BorderRadius; in float v_BorderWidth; float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) @@ -38,14 +38,30 @@ float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) return sqrt(distance.x * distance.x + distance.y * distance.y); } +float selectBorderRadius(vec4 radi, vec2 position, vec2 center) +{ + float rx = radi.x; + float ry = radi.y; + rx = position.x > center.x ? radi.y : radi.x; + ry = position.x > center.x ? radi.z : radi.w; + rx = position.y > center.y ? ry : rx; + return rx; +} + void main() { vec4 mixed_color; vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); + float border_radius = selectBorderRadius( + v_BorderRadius, + fragCoord, + (v_Pos + v_Scale * 0.5).xy + ); + // TODO: Remove branching (?) if(v_BorderWidth > 0.0) { - float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); + float internal_border = max(border_radius - v_BorderWidth, 0.0); float internal_distance = fDistance( fragCoord, @@ -69,11 +85,11 @@ void main() { fragCoord, v_Pos, v_Scale, - v_BorderRadius + border_radius ); float radius_alpha = - 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); + 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); } diff --git a/glow/src/shader/core/quad.vert b/glow/src/shader/core/quad.vert index b1fb2365..17c3e641 100644 --- a/glow/src/shader/core/quad.vert +++ b/glow/src/shader/core/quad.vert @@ -5,14 +5,14 @@ in vec2 i_Pos; in vec2 i_Scale; in vec4 i_Color; in vec4 i_BorderColor; -in float i_BorderRadius; +in vec4 i_BorderRadius; in float i_BorderWidth; out vec4 v_Color; out vec4 v_BorderColor; out vec2 v_Pos; out vec2 v_Scale; -out float v_BorderRadius; +out vec4 v_BorderRadius; out float v_BorderWidth; vec2 positions[4] = vec2[]( @@ -27,9 +27,11 @@ void main() { vec2 p_Pos = i_Pos * u_Scale; vec2 p_Scale = i_Scale * u_Scale; - float i_BorderRadius = min( - i_BorderRadius, - min(i_Scale.x, i_Scale.y) / 2.0 + vec4 i_BorderRadius = vec4( + min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), + min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) ); mat4 i_Transform = mat4( diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 730ee6f7..022457b1 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glutin" -version = "0.4.0" +version = "0.5.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A glutin runtime for Iced" @@ -23,15 +23,15 @@ git = "https://github.com/iced-rs/glutin" rev = "da8d291486b4c9bec12487a46c119c4b1d386abf" [dependencies.iced_native] -version = "0.6" +version = "0.7" path = "../native" [dependencies.iced_winit] -version = "0.5" +version = "0.6" path = "../winit" features = ["application"] [dependencies.iced_graphics] -version = "0.4" +version = "0.5" path = "../graphics" features = ["opengl"] diff --git a/glutin/src/application.rs b/glutin/src/application.rs index f474cbba..3e9d11f9 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -200,6 +200,7 @@ async fn run_instance<A, E, C>( let mut cache = user_interface::Cache::default(); let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); + let mut should_exit = false; application::run_command( &application, @@ -209,6 +210,7 @@ async fn run_instance<A, E, C>( init_command, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, context.window(), @@ -271,6 +273,7 @@ async fn run_instance<A, E, C>( &mut renderer, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, &mut messages, @@ -281,8 +284,6 @@ async fn run_instance<A, E, C>( // Update window state.synchronize(&application, context.window()); - let should_exit = application.should_exit(); - user_interface = ManuallyDrop::new(application::build_user_interface( &application, diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index b601f37c..823a05f4 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.4.0" +version = "0.5.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,7 +44,7 @@ version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.6" +version = "0.7" path = "../native" [dependencies.iced_style] diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs index 2098c7b2..1b5b5c35 100644 --- a/graphics/src/image/storage.rs +++ b/graphics/src/image/storage.rs @@ -20,7 +20,7 @@ pub trait Storage { state: &mut Self::State<'_>, ) -> Option<Self::Entry>; - /// Romve a [`Self::Entry`] from the [`Storage`]. + /// Remove a [`Self::Entry`] from the [`Storage`]. fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); } diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 42f4b500..82d77aff 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,5 +1,6 @@ //! Vector image loading and caching use crate::image::Storage; +use crate::Color; use iced_native::svg; use iced_native::Size; @@ -33,11 +34,13 @@ impl Svg { #[derive(Debug)] pub struct Cache<T: Storage> { svgs: HashMap<u64, Svg>, - rasterized: HashMap<(u64, u32, u32), T::Entry>, + rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>, svg_hits: HashSet<u64>, - rasterized_hits: HashSet<(u64, u32, u32)>, + rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, } +type ColorFilter = Option<[u8; 4]>; + impl<T: Storage> Cache<T> { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { @@ -76,6 +79,7 @@ impl<T: Storage> Cache<T> { pub fn upload( &mut self, handle: &svg::Handle, + color: Option<Color>, [width, height]: [f32; 2], scale: f32, state: &mut T::State<'_>, @@ -88,15 +92,18 @@ impl<T: Storage> Cache<T> { (scale * height).ceil() as u32, ); + let color = color.map(Color::into_rgba8); + let key = (id, width, height, color); + // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.contains_key(&(id, width, height)) { + if self.rasterized.contains_key(&key) { let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self.rasterized_hits.insert(key); - return self.rasterized.get(&(id, width, height)); + return self.rasterized.get(&key); } match self.load(handle) { @@ -121,15 +128,26 @@ impl<T: Storage> Cache<T> { img.as_mut(), )?; - let allocation = - storage.upload(width, height, img.data(), state)?; + let mut rgba = img.take(); + + if let Some(color) = color { + rgba.chunks_exact_mut(4).for_each(|rgba| { + if rgba[3] > 0 { + rgba[0] = color[0]; + rgba[1] = color[1]; + rgba[2] = color[2]; + } + }); + } + + let allocation = storage.upload(width, height, &rgba, state)?; log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); - let _ = self.rasterized.insert((id, width, height), allocation); + let _ = self.rasterized_hits.insert(key); + let _ = self.rasterized.insert(key, allocation); - self.rasterized.get(&(id, width, height)) + self.rasterized.get(&key) } Svg::NotFound => None, } diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index fd670f48..1d453caa 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -251,11 +251,16 @@ impl<'a> Layer<'a> { bounds: *bounds + translation, }); } - Primitive::Svg { handle, bounds } => { + Primitive::Svg { + handle, + color, + bounds, + } => { let layer = &mut layers[current_layer]; layer.images.push(Image::Vector { handle: handle.clone(), + color: *color, bounds: *bounds + translation, }); } diff --git a/graphics/src/layer/image.rs b/graphics/src/layer/image.rs index 045ec665..3eff2397 100644 --- a/graphics/src/layer/image.rs +++ b/graphics/src/layer/image.rs @@ -1,4 +1,5 @@ -use crate::Rectangle; +use crate::{Color, Rectangle}; + use iced_native::{image, svg}; /// A raster or vector image. @@ -17,6 +18,9 @@ pub enum Image { /// The handle of a vector image. handle: svg::Handle, + /// The [`Color`] filter + color: Option<Color>, + /// The bounds of the image. bounds: Rectangle, }, diff --git a/graphics/src/layer/quad.rs b/graphics/src/layer/quad.rs index 4def8427..0d8bde9d 100644 --- a/graphics/src/layer/quad.rs +++ b/graphics/src/layer/quad.rs @@ -17,7 +17,7 @@ pub struct Quad { pub border_color: [f32; 4], /// The border radius of the [`Quad`]. - pub border_radius: f32, + pub border_radius: [f32; 4], /// The border width of the [`Quad`]. pub border_width: f32, diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 9759d97a..5a163a2f 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -42,7 +42,7 @@ pub enum Primitive { /// The background of the quad background: Background, /// The border radius of the quad - border_radius: f32, + border_radius: [f32; 4], /// The border width of the quad border_width: f32, /// The border color of the quad @@ -60,6 +60,9 @@ pub enum Primitive { /// The path of the SVG file handle: svg::Handle, + /// The [`Color`] filter + color: Option<Color>, + /// The bounds of the viewport bounds: Rectangle, }, diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 036b398c..aabdf7fc 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -6,7 +6,7 @@ use iced_native::layout; use iced_native::renderer; use iced_native::svg; use iced_native::text::{self, Text}; -use iced_native::{Background, Element, Font, Point, Rectangle, Size}; +use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; @@ -109,7 +109,7 @@ where self.primitives.push(Primitive::Quad { bounds: quad.bounds, background: background.into(), - border_radius: quad.border_radius, + border_radius: quad.border_radius.into(), border_width: quad.border_width, border_color: quad.border_color, }); @@ -200,7 +200,16 @@ where self.backend().viewport_dimensions(handle) } - fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) { - self.draw_primitive(Primitive::Svg { handle, bounds }) + fn draw( + &mut self, + handle: svg::Handle, + color: Option<Color>, + bounds: Rectangle, + ) { + self.draw_primitive(Primitive::Svg { + handle, + color, + bounds, + }) } } diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml index 51459148..1b26e5c9 100644 --- a/lazy/Cargo.toml +++ b/lazy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_lazy" -version = "0.2.0" +version = "0.3.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.6" +version = "0.7" path = "../native" diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 4f1df650..d8f21f8a 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -11,7 +11,7 @@ use iced_native::{ }; use ouroboros::self_referencing; -use std::cell::{Ref, RefCell}; +use std::cell::RefCell; use std::marker::PhantomData; /// A reusable, custom widget that uses The Elm Architecture. @@ -46,6 +46,16 @@ pub trait Component<Message, Renderer> { /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) /// on user interaction. fn view(&self, state: &Self::State) -> Element<'_, Self::Event, Renderer>; + + /// Update the [`Component`] state based on the provided [`Operation`](widget::Operation) + /// + /// By default, it does nothing. + fn operate( + &self, + _state: &mut Self::State, + _operation: &mut dyn widget::Operation<Message>, + ) { + } } /// Turns an implementor of [`Component`] into an [`Element`] that can be @@ -106,6 +116,26 @@ where ); } + fn rebuild_element_with_operation( + &self, + state: &mut S, + operation: &mut dyn widget::Operation<Message>, + ) { + let heads = self.state.borrow_mut().take().unwrap().into_heads(); + + heads.component.operate(state, operation); + + *self.state.borrow_mut() = Some( + StateBuilder { + component: heads.component, + message: PhantomData, + state: PhantomData, + element_builder: |component| Some(component.view(state)), + } + .build(), + ); + } + fn with_element<T>( &self, f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, @@ -234,8 +264,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { + self.rebuild_element_with_operation( + tree.state.downcast_mut(), + operation, + ); + struct MapOperation<'a, B> { operation: &'a mut dyn widget::Operation<B>, } @@ -274,6 +310,7 @@ where element.as_widget().operate( &mut tree.children[0], layout, + renderer, &mut MapOperation { operation }, ); }); @@ -322,25 +359,25 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { let overlay = OverlayBuilder { instance: self, - instance_ref_builder: |instance| instance.state.borrow(), tree, types: PhantomData, overlay_builder: |instance, tree| { - instance - .as_ref() - .unwrap() - .borrow_element() - .as_ref() - .unwrap() - .as_widget() - .overlay(&mut tree.children[0], layout, renderer) + instance.state.get_mut().as_mut().unwrap().with_element_mut( + move |element| { + element.as_mut().unwrap().as_widget_mut().overlay( + &mut tree.children[0], + layout, + renderer, + ) + }, + ) }, } .build(); @@ -362,15 +399,11 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Event, S> { - instance: &'a Instance<'b, Message, Renderer, Event, S>, + instance: &'a mut Instance<'b, Message, Renderer, Event, S>, tree: &'a mut Tree, types: PhantomData<(Message, Event, S)>, - #[borrows(instance)] - #[covariant] - instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>, - - #[borrows(instance_ref, mut tree)] + #[borrows(mut instance, mut tree)] #[covariant] overlay: Option<overlay::Element<'this, Event, Renderer>>, } @@ -514,7 +547,6 @@ where self.overlay = Some( OverlayBuilder { instance: overlay.instance, - instance_ref_builder: |instance| instance.state.borrow(), tree: overlay.tree, types: PhantomData, overlay_builder: |_, _| None, diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index d61cc77e..ec35e8f0 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -130,12 +130,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { self.with_element(|element| { element.as_widget().operate( &mut tree.children[0], layout, + renderer, operation, ); }); @@ -207,7 +209,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -216,12 +218,12 @@ where cached: self, tree: &mut tree.children[0], types: PhantomData, - element_ref_builder: |cached| cached.element.borrow(), - element_builder: |element_ref| { - element_ref.as_ref().unwrap().borrow() - }, - overlay_builder: |element, tree| { - element.as_widget().overlay(tree, layout, renderer) + overlay_builder: |cached, tree| { + Rc::get_mut(cached.element.get_mut().as_mut().unwrap()) + .unwrap() + .get_mut() + .as_widget_mut() + .overlay(tree, layout, renderer) }, } .build(); @@ -237,20 +239,11 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, + cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>, tree: &'a mut Tree, types: PhantomData<(Message, Dependency, View)>, - #[borrows(cached)] - #[covariant] - element_ref: - Ref<'this, Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>, - - #[borrows(element_ref)] - #[covariant] - element: Ref<'this, Element<'static, Message, Renderer>>, - - #[borrows(element, mut tree)] + #[borrows(mut cached, mut tree)] #[covariant] overlay: Option<overlay::Element<'this, Message, Renderer>>, } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 0b7ae6de..945c935a 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -3,8 +3,8 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; -use iced_native::widget::horizontal_space; use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, horizontal_space}; use iced_native::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; @@ -142,6 +142,29 @@ where layout::Node::new(limits.max()) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation<Message>, + ) { + let state = tree.state.downcast_mut::<State>(); + let mut content = self.content.borrow_mut(); + + content.resolve( + &mut state.tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, renderer, layout, element| { + element + .as_widget() + .operate(tree, layout, renderer, operation); + }, + ); + } + fn on_event( &mut self, tree: &mut Tree, @@ -235,18 +258,20 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { + use std::ops::DerefMut; + let state = tree.state.downcast_ref::<State>(); let overlay = OverlayBuilder { content: self.content.borrow_mut(), tree: state.tree.borrow_mut(), types: PhantomData, - overlay_builder: |content, tree| { + overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| { content.update( tree, renderer, @@ -254,16 +279,18 @@ where &self.view, ); + let Content { + element, layout, .. + } = content.deref_mut(); + let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &content.layout, + layout.bounds().position() - Point::ORIGIN, + layout, ); - content.element.as_widget().overlay( - tree, - content_layout, - renderer, - ) + element + .as_widget_mut() + .overlay(tree, content_layout, renderer) }, } .build(); diff --git a/native/Cargo.toml b/native/Cargo.toml index 558909be..bbf92951 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_native" -version = "0.6.1" +version = "0.7.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A renderer-agnostic library for native GUIs" @@ -25,5 +25,5 @@ path = "../futures" features = ["thread-pool"] [dependencies.iced_style] -version = "0.5" +version = "0.5.1" path = "../style" diff --git a/native/src/element.rs b/native/src/element.rs index f941a490..2409b1c9 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -290,6 +290,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<B>, ) { struct MapOperation<'a, B> { @@ -334,8 +335,12 @@ where } } - self.widget - .operate(tree, layout, &mut MapOperation { operation }); + self.widget.operate( + tree, + layout, + renderer, + &mut MapOperation { operation }, + ); } fn on_event( @@ -405,7 +410,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -473,9 +478,12 @@ where &self, state: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { - self.element.widget.operate(state, layout, operation) + self.element + .widget + .operate(state, layout, renderer, operation) } fn on_event( @@ -519,7 +527,7 @@ where bounds: layout.bounds(), border_color: color, border_width: 1.0, - border_radius: 0.0, + border_radius: 0.0.into(), }, Color::TRANSPARENT, ); @@ -560,7 +568,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 6d5f6563..33a452d0 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -51,7 +51,7 @@ impl Limits { } Length::Units(units) => { let new_width = - (units as f32).min(self.max.width).max(self.min.width); + (units as f32).clamp(self.min.width, self.max.width); self.min.width = new_width; self.max.width = new_width; @@ -73,7 +73,7 @@ impl Limits { } Length::Units(units) => { let new_height = - (units as f32).min(self.max.height).max(self.min.height); + (units as f32).clamp(self.min.height, self.max.height); self.min.height = new_height; self.max.height = new_height; @@ -86,16 +86,14 @@ impl Limits { /// Applies a minimum width constraint to the current [`Limits`]. pub fn min_width(mut self, min_width: u32) -> Limits { - self.min.width = - self.min.width.max(min_width as f32).min(self.max.width); + self.min.width = self.min.width.clamp(min_width as f32, self.max.width); self } /// Applies a maximum width constraint to the current [`Limits`]. pub fn max_width(mut self, max_width: u32) -> Limits { - self.max.width = - self.max.width.min(max_width as f32).max(self.min.width); + self.max.width = self.max.width.clamp(self.min.width, max_width as f32); self } @@ -103,7 +101,7 @@ impl Limits { /// Applies a minimum height constraint to the current [`Limits`]. pub fn min_height(mut self, min_height: u32) -> Limits { self.min.height = - self.min.height.max(min_height as f32).min(self.max.height); + self.min.height.clamp(min_height as f32, self.max.height); self } @@ -111,7 +109,7 @@ impl Limits { /// Applies a maximum height constraint to the current [`Limits`]. pub fn max_height(mut self, max_height: u32) -> Limits { self.max.height = - self.max.height.min(max_height as f32).max(self.min.height); + self.max.height.clamp(self.min.height, max_height as f32); self } @@ -157,14 +155,10 @@ impl Limits { /// intrinsic size of some content. pub fn resolve(&self, intrinsic_size: Size) -> Size { Size::new( - intrinsic_size - .width - .min(self.max.width) - .max(self.fill.width), + intrinsic_size.width.clamp(self.fill.width, self.max.width), intrinsic_size .height - .min(self.max.height) - .max(self.fill.height), + .clamp(self.fill.height, self.max.height), ) } } diff --git a/native/src/lib.rs b/native/src/lib.rs index 62b8f372..ce7c010d 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.5/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.5/winit +//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.6/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.6/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.rs b/native/src/overlay.rs index 8c01581f..22f8b6ec 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -44,8 +44,9 @@ where /// Applies a [`widget::Operation`] to the [`Overlay`]. fn operate( - &self, + &mut self, _layout: Layout<'_>, + _renderer: &Renderer, _operation: &mut dyn widget::Operation<Message>, ) { } @@ -93,7 +94,7 @@ where /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. pub fn from_children<'a, Message, Renderer>( - children: &'a [crate::Element<'_, Message, Renderer>], + children: &'a mut [crate::Element<'_, Message, Renderer>], tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -102,11 +103,11 @@ where Renderer: crate::Renderer, { children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child.as_widget().overlay(state, layout, renderer) + child.as_widget_mut().overlay(state, layout, renderer) }) .next() } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 09eee86d..498e9ae3 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -106,11 +106,12 @@ where /// Applies a [`widget::Operation`] to the [`Element`]. pub fn operate( - &self, + &mut self, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { - self.overlay.operate(layout, operation); + self.overlay.operate(layout, renderer, operation); } } @@ -142,8 +143,9 @@ where } fn operate( - &self, + &mut self, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<B>, ) { struct MapOperation<'a, B> { @@ -189,7 +191,7 @@ where } self.content - .operate(layout, &mut MapOperation { operation }); + .operate(layout, renderer, &mut MapOperation { operation }); } fn on_event( diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 2e4f70b5..099b1a97 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -287,7 +287,7 @@ where }, border_color: appearance.border_color, border_width: appearance.border_width, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), }, appearance.background, ); @@ -479,7 +479,7 @@ where bounds, border_color: Color::TRANSPARENT, border_width: 0.0, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), }, appearance.selected_background, ); diff --git a/native/src/renderer.rs b/native/src/renderer.rs index ef64ac36..5e776be6 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -50,7 +50,7 @@ pub struct Quad { pub bounds: Rectangle, /// The border radius of the [`Quad`]. - pub border_radius: f32, + pub border_radius: BorderRadius, /// The border width of the [`Quad`]. pub border_width: f32, @@ -59,6 +59,29 @@ pub struct Quad { pub border_color: Color, } +/// The border radi for the corners of a graphics primitive in the order: +/// top-left, top-right, bottom-right, bottom-left. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct BorderRadius([f32; 4]); + +impl From<f32> for BorderRadius { + fn from(w: f32) -> Self { + Self([w; 4]) + } +} + +impl From<[f32; 4]> for BorderRadius { + fn from(radi: [f32; 4]) -> Self { + Self(radi) + } +} + +impl From<BorderRadius> for [f32; 4] { + fn from(radi: BorderRadius) -> Self { + radi.0 + } +} + /// The styling attributes of a [`Renderer`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { diff --git a/native/src/subscription.rs b/native/src/subscription.rs index d24801d4..c60b1281 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -155,7 +155,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.5/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.6/examples/websocket pub fn unfold<I, T, Fut, Message>( id: I, initial: T, diff --git a/native/src/svg.rs b/native/src/svg.rs index a8e481d2..2168e409 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,5 +1,5 @@ //! Load and draw vector graphics. -use crate::{Hasher, Rectangle, Size}; +use crate::{Color, Hasher, Rectangle, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; @@ -84,6 +84,6 @@ pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. fn dimensions(&self, handle: &Handle) -> Size<u32>; - /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, bounds: Rectangle); + /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. + fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle); } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 7c82878c..2b43829d 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -18,8 +18,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.5/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_wgpu +/// [`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 #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, @@ -190,7 +190,7 @@ where let mut state = State::Updated; let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -226,7 +226,7 @@ where ); manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -395,11 +395,11 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + let base_cursor = if let Some(overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + { let overlay_layout = self .overlay .take() @@ -452,7 +452,7 @@ where overlay .as_ref() .and_then(|layout| { - root.as_widget() + root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( @@ -493,17 +493,24 @@ where self.root.as_widget().operate( &mut self.state, Layout::new(&self.base), + renderer, operation, ); - if let Some(layout) = self.overlay.as_ref() { - if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { - overlay.operate(Layout::new(layout), operation); + if let Some(mut overlay) = self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + if self.overlay.is_none() { + self.overlay = Some(overlay.layout(renderer, self.bounds)); } + + overlay.operate( + Layout::new(self.overlay.as_ref().unwrap()), + renderer, + operation, + ); } } diff --git a/native/src/widget.rs b/native/src/widget.rs index 526c7d00..f714e28a 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -33,6 +33,7 @@ pub mod text_input; pub mod toggler; pub mod tooltip; pub mod tree; +pub mod vertical_slider; mod action; mod id; @@ -79,6 +80,8 @@ pub use toggler::Toggler; pub use tooltip::Tooltip; #[doc(no_inline)] pub use tree::Tree; +#[doc(no_inline)] +pub use vertical_slider::VerticalSlider; pub use action::Action; pub use id::Id; @@ -107,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.5/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry +/// [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 /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu pub trait Widget<Message, Renderer> where Renderer: crate::Renderer, @@ -172,6 +175,7 @@ where &self, _state: &mut Tree, _layout: Layout<'_>, + _renderer: &Renderer, _operation: &mut dyn Operation<Message>, ) { } @@ -208,7 +212,7 @@ where /// Returns the overlay of the [`Widget`], if there is any. fn overlay<'a>( - &'a self, + &'a mut self, _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index fa5da24b..b4276317 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -169,12 +169,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -260,12 +262,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -393,7 +395,7 @@ where y: bounds.y + styling.shadow_offset.y, ..bounds }, - border_radius: styling.border_radius, + border_radius: styling.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -404,7 +406,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: styling.border_radius, + border_radius: styling.border_radius.into(), border_width: styling.border_width, border_color: styling.border_color, }, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 77d639a9..bec5c448 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -236,7 +236,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: custom_style.border_radius, + border_radius: custom_style.border_radius.into(), border_width: custom_style.border_width, border_color: custom_style.border_color, }, diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a8b0f183..f2ef132a 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -147,6 +147,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -155,7 +156,9 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), layout)| { - child.as_widget().operate(state, layout, operation); + child + .as_widget() + .operate(state, layout, renderer, operation); }) }); } @@ -242,12 +245,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 9d3e4d9b..cdf1c859 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -169,12 +169,14 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -248,12 +250,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -321,7 +323,7 @@ pub fn draw_background<Renderer>( renderer.fill_quad( renderer::Quad { bounds, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), border_width: appearance.border_width, border_color: appearance.border_color, }, diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 3bce9e60..5b241f83 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -162,7 +162,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::toggler::StyleSheet, { - widget::Toggler::new(is_checked, label, f) + widget::Toggler::new(label, is_checked, f) } /// Creates a new [`TextInput`]. @@ -198,6 +198,23 @@ where widget::Slider::new(range, value, on_change) } +/// Creates a new [`VerticalSlider`]. +/// +/// [`VerticalSlider`]: widget::VerticalSlider +pub fn vertical_slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive<T>, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> widget::VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: widget::slider::StyleSheet, +{ + widget::VerticalSlider::new(range, value, on_change) +} + /// Creates a new [`PickList`]. /// /// [`PickList`]: widget::PickList @@ -285,6 +302,12 @@ where /// /// [`Svg`]: widget::Svg /// [`Handle`]: widget::svg::Handle -pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg { +pub fn svg<Renderer>( + handle: impl Into<widget::svg::Handle>, +) -> widget::Svg<Renderer> +where + Renderer: crate::svg::Renderer, + Renderer::Theme: widget::svg::StyleSheet, +{ widget::Svg::new(handle) } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 9c83287e..fdbd3216 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -170,8 +170,7 @@ where } else { state.scale / (1.0 + self.scale_step) }) - .max(self.min_scale) - .min(self.max_scale); + .clamp(self.min_scale, self.max_scale); let image_size = image_size( renderer, @@ -251,16 +250,14 @@ where let x = if bounds.width < image_size.width { (state.starting_offset.x - delta.x) - .min(hidden_width) - .max(-hidden_width) + .clamp(-hidden_width, hidden_width) } else { 0.0 }; let y = if bounds.height < image_size.height { (state.starting_offset.y - delta.y) - .min(hidden_height) - .max(-hidden_height) + .clamp(-hidden_height, hidden_height) } else { 0.0 }; @@ -374,8 +371,8 @@ impl State { (image_size.height - bounds.height / 2.0).max(0.0).round(); Vector::new( - self.current_offset.x.min(hidden_width).max(-hidden_width), - self.current_offset.y.min(hidden_height).max(-hidden_height), + self.current_offset.x.clamp(-hidden_width, hidden_width), + self.current_offset.y.clamp(-hidden_height, hidden_height), ) } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8f9065b0..f8dbab74 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.5/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid mod axis; mod configuration; mod content; @@ -294,6 +294,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -302,7 +303,7 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|(((_pane, content), state), layout)| { - content.operate(state, layout, operation); + content.operate(state, layout, renderer, operation); }) }); } @@ -444,13 +445,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { self.contents - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|(((_, pane), tree), layout)| { @@ -630,13 +631,13 @@ pub fn update<'a, Message, T: Draggable>( let position = cursor_position.y - bounds.y - rectangle.y; - (position / rectangle.height).max(0.1).min(0.9) + (position / rectangle.height).clamp(0.1, 0.9) } Axis::Vertical => { let position = cursor_position.x - bounds.x - rectangle.x; - (position / rectangle.width).max(0.1).min(0.9) + (position / rectangle.width).clamp(0.1, 0.9) } }; @@ -877,7 +878,7 @@ pub fn draw<Renderer, T>( height: split_region.height, }, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 5e843cff..c9b0df07 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -187,6 +187,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { let body_layout = if let Some(title_bar) = &self.title_bar { @@ -195,6 +196,7 @@ where title_bar.operate( &mut tree.children[1], children.next().unwrap(), + renderer, operation, ); @@ -206,6 +208,7 @@ where self.body.as_widget().operate( &mut tree.children[0], body_layout, + renderer, operation, ); } @@ -305,12 +308,12 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - if let Some(title_bar) = self.title_bar.as_ref() { + if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); let title_bar_layout = children.next()?; @@ -321,14 +324,14 @@ where match title_bar.overlay(title_bar_state, title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.as_widget().overlay( + None => self.body.as_widget_mut().overlay( body_state, children.next()?, renderer, ), } } else { - self.body.as_widget().overlay( + self.body.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 115f6270..ea0969aa 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -261,6 +261,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn widget::Operation<Message>, ) { let mut children = layout.children(); @@ -282,6 +283,7 @@ where controls.as_widget().operate( &mut tree.children[1], controls_layout, + renderer, operation, ) }; @@ -290,6 +292,7 @@ where self.content.as_widget().operate( &mut tree.children[0], title_layout, + renderer, operation, ) } @@ -395,7 +398,7 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -415,13 +418,13 @@ where let controls_state = states.next().unwrap(); content - .as_widget() + .as_widget_mut() .overlay(title_state, title_layout, renderer) .or_else(move || { - controls.as_ref().and_then(|controls| { + controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; - controls.as_widget().overlay( + controls.as_widget_mut().overlay( controls_state, controls_layout, renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 43ae7ebb..c2853314 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -282,7 +282,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, @@ -600,7 +600,7 @@ pub fn draw<T, Renderer>( bounds, border_color: style.border_color, border_width: style.border_width, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), }, style.background, ); diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index b053d959..7d5d5be5 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -47,7 +47,7 @@ where /// * the current value of the [`ProgressBar`] pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { ProgressBar { - value: value.max(*range.start()).min(*range.end()), + value: value.clamp(*range.start(), *range.end()), range, width: Length::Fill, height: None, @@ -129,7 +129,7 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { ..bounds }, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -143,7 +143,7 @@ where width: active_progress_width, ..bounds }, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 743689c7..b95ccc5b 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -245,7 +245,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: size / 2.0, + border_radius: (size / 2.0).into(), border_width: custom_style.border_width, border_color: custom_style.border_color, }, @@ -261,7 +261,7 @@ where width: bounds.width - dot_size, height: bounds.height - dot_size, }, - border_radius: dot_size / 2.0, + border_radius: (dot_size / 2.0).into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index eda7c2d3..108e98e4 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -134,6 +134,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { operation.container(None, &mut |operation| { @@ -142,7 +143,9 @@ where .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), layout)| { - child.as_widget().operate(state, layout, operation); + child + .as_widget() + .operate(state, layout, renderer, operation); }) }); } @@ -229,12 +232,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index e44d8d99..2dc7b6f0 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -123,7 +123,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border_radius: style.radius, + border_radius: style.radius.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 32ec6eb3..20780f89 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -164,6 +164,7 @@ where &self, tree: &mut Tree, layout: Layout<'_>, + renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { let state = tree.state.downcast_mut::<State>(); @@ -174,6 +175,7 @@ where self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), + renderer, operation, ); }); @@ -276,13 +278,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { self.content - .as_widget() + .as_widget_mut() .overlay( &mut tree.children[0], layout.children().next().unwrap(), @@ -704,7 +706,7 @@ pub fn draw<Renderer>( renderer.fill_quad( renderer::Quad { bounds: scrollbar.bounds, - border_radius: style.border_radius, + border_radius: style.border_radius.into(), border_width: style.border_width, border_color: style.border_color, }, @@ -714,14 +716,13 @@ pub fn draw<Renderer>( ); } - if is_mouse_over - || state.is_scroller_grabbed() - || is_scrollbar_visible + if (is_mouse_over || state.is_scroller_grabbed()) + && is_scrollbar_visible { renderer.fill_quad( renderer::Quad { bounds: scrollbar.scroller.bounds, - border_radius: style.scroller.border_radius, + border_radius: style.scroller.border_radius.into(), border_width: style.scroller.border_width, border_color: style.scroller.border_color, }, @@ -882,8 +883,7 @@ impl State { self.offset = Offset::Absolute( (self.offset.absolute(bounds, content_bounds) - delta_y) - .max(0.0) - .min((content_bounds.height - bounds.height) as f32), + .clamp(0.0, content_bounds.height - bounds.height), ); } @@ -906,7 +906,7 @@ impl State { /// `0` represents scrollbar at the top, while `1` represents scrollbar at /// the bottom. pub fn snap_to(&mut self, percentage: f32) { - self.offset = Offset::Relative(percentage.max(0.0).min(1.0)); + self.offset = Offset::Relative(percentage.clamp(0.0, 1.0)); } /// Unsnaps the current scroll position, if snapped, given the bounds of the diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index fd9160a4..87030a4d 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -380,7 +380,7 @@ pub fn draw<T, R>( width: bounds.width, height: 2.0, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -395,7 +395,7 @@ pub fn draw<T, R>( width: bounds.width, height: 2.0, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -435,7 +435,7 @@ pub fn draw<T, R>( width: handle_width, height: handle_height, }, - border_radius: handle_border_radius, + border_radius: handle_border_radius.into(), border_width: style.handle.border_width, border_color: style.handle.border_color, }, diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 1015ed0a..f83f5acf 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -9,6 +9,7 @@ use crate::{ use std::path::PathBuf; +pub use iced_style::svg::{Appearance, StyleSheet}; pub use svg::Handle; /// A vector graphics image. @@ -17,15 +18,24 @@ pub use svg::Handle; /// /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. -#[derive(Debug, Clone)] -pub struct Svg { +#[allow(missing_debug_implementations)] +pub struct Svg<Renderer> +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, + style: <Renderer::Theme as StyleSheet>::Style, } -impl Svg { +impl<Renderer> Svg<Renderer> +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into<Handle>) -> Self { Svg { @@ -33,22 +43,26 @@ impl Svg { width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, + style: Default::default(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. + #[must_use] pub fn from_path(path: impl Into<PathBuf>) -> Self { Self::new(Handle::from_path(path)) } /// Sets the width of the [`Svg`]. + #[must_use] pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the height of the [`Svg`]. + #[must_use] pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -57,17 +71,29 @@ impl Svg { /// Sets the [`ContentFit`] of the [`Svg`]. /// /// Defaults to [`ContentFit::Contain`] + #[must_use] pub fn content_fit(self, content_fit: ContentFit) -> Self { Self { content_fit, ..self } } + + /// Sets the style variant of this [`Svg`]. + #[must_use] + pub fn style( + mut self, + style: <Renderer::Theme as StyleSheet>::Style, + ) -> Self { + self.style = style; + self + } } -impl<Message, Renderer> Widget<Message, Renderer> for Svg +impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer> where Renderer: svg::Renderer, + Renderer::Theme: iced_style::svg::StyleSheet, { fn width(&self) -> Length { self.width @@ -114,7 +140,7 @@ where &self, _state: &Tree, renderer: &mut Renderer, - _theme: &Renderer::Theme, + theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, @@ -138,7 +164,13 @@ where ..bounds }; - renderer.draw(self.handle.clone(), drawing_bounds + offset) + let appearance = theme.appearance(&self.style); + + renderer.draw( + self.handle.clone(), + appearance.color, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width @@ -146,16 +178,18 @@ where { renderer.with_layer(bounds, render); } else { - render(renderer) + render(renderer); } } } -impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From<Svg<Renderer>> + for Element<'a, Message, Renderer> where - Renderer: svg::Renderer, + Renderer: svg::Renderer + 'a, + Renderer::Theme: iced_style::svg::StyleSheet, { - fn from(icon: Svg) -> Element<'a, Message, Renderer> { + fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> { Element::new(icon) } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 14e7e1b7..8b4514e3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -228,6 +228,7 @@ where &self, tree: &mut Tree, _layout: Layout<'_>, + _renderer: &Renderer, operation: &mut dyn Operation<Message>, ) { let state = tree.state.downcast_mut::<State>(); @@ -453,9 +454,17 @@ where ) } else { None - }; + } + .unwrap_or(0); - state.cursor.move_to(position.unwrap_or(0)); + if state.keyboard_modifiers.shift() { + state.cursor.select_range( + state.cursor.start(value), + position, + ); + } else { + state.cursor.move_to(position); + } state.is_dragging = true; } click::Kind::Double => { @@ -801,7 +810,7 @@ pub fn draw<Renderer>( renderer.fill_quad( renderer::Quad { bounds, - border_radius: appearance.border_radius, + border_radius: appearance.border_radius.into(), border_width: appearance.border_width, border_color: appearance.border_color, }, @@ -833,7 +842,7 @@ pub fn draw<Renderer>( width: 1.0, height: text_bounds.height, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -877,7 +886,7 @@ pub fn draw<Renderer>( width, height: text_bounds.height, }, - border_radius: 0.0, + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 99a56ea8..f0a944a3 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -24,9 +24,9 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// TogglerToggled(bool), /// } /// -/// let is_active = true; +/// let is_toggled = true; /// -/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); +/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] pub struct Toggler<'a, Message, Renderer> @@ -34,7 +34,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - is_active: bool, + is_toggled: bool, on_toggle: Box<dyn Fn(bool) -> Message + 'a>, label: Option<String>, width: Length, @@ -63,15 +63,15 @@ where /// will receive the new state of the [`Toggler`] and must produce a /// `Message`. pub fn new<F>( - is_active: bool, label: impl Into<Option<String>>, + is_toggled: bool, f: F, ) -> Self where F: 'a + Fn(bool) -> Message, { Toggler { - is_active, + is_toggled, on_toggle: Box::new(f), label: label.into(), width: Length::Fill, @@ -193,7 +193,7 @@ where let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - shell.publish((self.on_toggle)(!self.is_active)); + shell.publish((self.on_toggle)(!self.is_toggled)); event::Status::Captured } else { @@ -260,13 +260,13 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(&self.style, self.is_active) + theme.hovered(&self.style, self.is_toggled) } else { - theme.active(&self.style, self.is_active) + theme.active(&self.style, self.is_toggled) }; - let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; - let space = SPACE_RATIO * bounds.height as f32; + let border_radius = bounds.height / BORDER_RADIUS_RATIO; + let space = SPACE_RATIO * bounds.height; let toggler_background_bounds = Rectangle { x: bounds.x + space, @@ -278,7 +278,7 @@ where renderer.fill_quad( renderer::Quad { bounds: toggler_background_bounds, - border_radius, + border_radius: border_radius.into(), border_width: 1.0, border_color: style .background_border @@ -289,7 +289,7 @@ where let toggler_foreground_bounds = Rectangle { x: bounds.x - + if self.is_active { + + if self.is_toggled { bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) } else { 2.0 * space @@ -302,7 +302,7 @@ where renderer.fill_quad( renderer::Quad { bounds: toggler_foreground_bounds, - border_radius, + border_radius: border_radius.into(), border_width: 1.0, border_color: style .foreground_border diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 9347a886..084dc269 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -221,12 +221,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs new file mode 100644 index 00000000..28e8405c --- /dev/null +++ b/native/src/widget/vertical_slider.rs @@ -0,0 +1,470 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`VerticalSlider`] has some local [`State`]. +use std::ops::RangeInclusive; + +pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; + +use crate::event::{self, Event}; +use crate::widget::tree::{self, Tree}; +use crate::{ + layout, mouse, renderer, touch, Background, Clipboard, Color, Element, + Layout, Length, Point, Rectangle, Shell, Size, Widget, +}; + +/// An vertical bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`VerticalSlider`] will try to fill the vertical space of its container. +/// +/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults +/// to 1 unit. +/// +/// # Example +/// ``` +/// # use iced_native::widget::vertical_slider; +/// # use iced_native::renderer::Null; +/// # +/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; +/// # +/// #[derive(Clone)] +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let value = 50.0; +/// +/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); +/// ``` +#[allow(missing_debug_implementations)] +pub struct VerticalSlider<'a, T, Message, Renderer> +where + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + range: RangeInclusive<T>, + step: T, + value: T, + on_change: Box<dyn Fn(T) -> Message + 'a>, + on_release: Option<Message>, + width: u16, + height: Length, + style: <Renderer::Theme as StyleSheet>::Style, +} + +impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From<u8> + std::cmp::PartialOrd, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + /// The default width of a [`VerticalSlider`]. + pub const DEFAULT_WIDTH: u16 = 22; + + /// Creates a new [`VerticalSlider`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`VerticalSlider`] + /// * a function that will be called when the [`VerticalSlider`] is dragged. + /// It receives the new value of the [`VerticalSlider`] and must produce a + /// `Message`. + pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self + where + F: 'a + Fn(T) -> Message, + { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + + VerticalSlider { + value, + range, + step: T::from(1), + on_change: Box::new(on_change), + on_release: None, + width: Self::DEFAULT_WIDTH, + height: Length::Fill, + style: Default::default(), + } + } + + /// Sets the release message of the [`VerticalSlider`]. + /// This is called when the mouse is released from the slider. + /// + /// Typically, the user's interaction with the slider is finished when this message is produced. + /// This is useful if you need to spawn a long-running task from the slider's result, where + /// the default on_change message could create too many events. + pub fn on_release(mut self, on_release: Message) -> Self { + self.on_release = Some(on_release); + self + } + + /// Sets the width of the [`VerticalSlider`]. + pub fn width(mut self, width: u16) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`VerticalSlider`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the style of the [`VerticalSlider`]. + pub fn style( + mut self, + style: impl Into<<Renderer::Theme as StyleSheet>::Style>, + ) -> Self { + self.style = style.into(); + self + } + + /// Sets the step size of the [`VerticalSlider`]. + pub fn step(mut self, step: T) -> Self { + self.step = step; + self + } +} + +impl<'a, T, Message, Renderer> Widget<Message, Renderer> + for VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + Into<f64> + num_traits::FromPrimitive, + Message: Clone, + Renderer: crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::<State>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = + limits.width(Length::Units(self.width)).height(self.height); + + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + update( + event, + layout, + cursor_position, + shell, + tree.state.downcast_mut::<State>(), + &mut self.value, + &self.range, + self.step, + self.on_change.as_ref(), + &self.on_release, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + draw( + renderer, + layout, + cursor_position, + tree.state.downcast_ref::<State>(), + self.value, + &self.range, + theme, + &self.style, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + mouse_interaction( + layout, + cursor_position, + tree.state.downcast_ref::<State>(), + ) + } +} + +impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>> + for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, + Message: 'a + Clone, + Renderer: 'a + crate::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from( + slider: VerticalSlider<'a, T, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(slider) + } +} + +/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`] +/// accordingly. +pub fn update<Message, T>( + event: Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + state: &mut State, + value: &mut T, + range: &RangeInclusive<T>, + step: T, + on_change: &dyn Fn(T) -> Message, + on_release: &Option<Message>, +) -> event::Status +where + T: Copy + Into<f64> + num_traits::FromPrimitive, + Message: Clone, +{ + let is_dragging = state.is_dragging; + + let mut change = || { + let bounds = layout.bounds(); + let new_value = if cursor_position.y >= bounds.y + bounds.height { + *range.start() + } else if cursor_position.y <= bounds.y { + *range.end() + } else { + let step = step.into(); + let start = (*range.start()).into(); + let end = (*range.end()).into(); + + let percent = 1.0 + - f64::from(cursor_position.y - bounds.y) + / f64::from(bounds.height); + + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + if let Some(value) = T::from_f64(value) { + value + } else { + return; + } + }; + + if ((*value).into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((on_change)(new_value)); + + *value = new_value; + } + }; + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if layout.bounds().contains(cursor_position) { + change(); + state.is_dragging = true; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if is_dragging { + if let Some(on_release) = on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + change(); + + return event::Status::Captured; + } + } + _ => {} + } + + event::Status::Ignored +} + +/// Draws a [`VerticalSlider`]. +pub fn draw<T, R>( + renderer: &mut R, + layout: Layout<'_>, + cursor_position: Point, + state: &State, + value: T, + range: &RangeInclusive<T>, + style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, + style: &<R::Theme as StyleSheet>::Style, +) where + T: Into<f64> + Copy, + R: crate::Renderer, + R::Theme: StyleSheet, +{ + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let style = if state.is_dragging { + style_sheet.dragging(style) + } else if is_mouse_over { + style_sheet.hovered(style) + } else { + style_sheet.active(style) + }; + + let rail_x = bounds.x + (bounds.width / 2.0).round(); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - 1.0, + y: bounds.y, + width: 2.0, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + style.rail_colors.0, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x + 1.0, + y: bounds.y, + width: 2.0, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(style.rail_colors.1), + ); + + let (handle_width, handle_height, handle_border_radius) = match style + .handle + .shape + { + HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), bounds.width, border_radius), + }; + + let value = value.into() as f32; + let (range_start, range_end) = { + let (start, end) = range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) + }; + + let handle_offset = if range_start >= range_end { + 0.0 + } else { + bounds.height * (value - range_end) / (range_start - range_end) + - handle_width / 2.0 + }; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - (handle_height / 2.0), + y: bounds.y + handle_offset.round(), + width: handle_height, + height: handle_width, + }, + border_radius: handle_border_radius.into(), + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }, + style.handle.color, + ); +} + +/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`]. +pub fn mouse_interaction( + layout: Layout<'_>, + cursor_position: Point, + state: &State, +) -> mouse::Interaction { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + if state.is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + } +} + +/// The local state of a [`VerticalSlider`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_dragging: bool, +} + +impl State { + /// Creates a new [`State`]. + pub fn new() -> State { + State::default() + } +} diff --git a/native/src/window.rs b/native/src/window.rs index f910b8f2..1b97e655 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -2,7 +2,9 @@ mod action; mod event; mod mode; +mod user_attention; pub use action::Action; pub use event::Event; pub use mode::Mode; +pub use user_attention::UserAttention; diff --git a/native/src/window/action.rs b/native/src/window/action.rs index 009dcc27..f0fe845d 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,10 +1,12 @@ -use crate::window::Mode; +use crate::window::{Mode, UserAttention}; use iced_futures::MaybeSend; use std::fmt; /// An operation to be performed on some window. pub enum Action<T> { + /// Closes the current window and exits the application. + Close, /// Moves the window with the left mouse button until the button is /// released. /// @@ -33,10 +35,29 @@ pub enum Action<T> { }, /// Set the [`Mode`] of the window. SetMode(Mode), - /// Sets the window to maximized or back - ToggleMaximize, /// Fetch the current [`Mode`] of the window. FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), + /// Sets the window to maximized or back + ToggleMaximize, + /// Toggles whether window has decorations + /// ## Platform-specific + /// - **X11:** Not implemented. + /// - **Web:** Unsupported. + ToggleDecorations, + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see [`UserAttentionType`] for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. + RequestUserAttention(Option<UserAttention>), } impl<T> Action<T> { @@ -49,14 +70,19 @@ impl<T> Action<T> { T: 'static, { match self { + Self::Close => Action::Close, Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, Self::Maximize(bool) => Action::Maximize(bool), Self::Minimize(bool) => Action::Minimize(bool), Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), - Self::ToggleMaximize => Action::ToggleMaximize, Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), + Self::ToggleMaximize => Action::ToggleMaximize, + Self::ToggleDecorations => Action::ToggleDecorations, + Self::RequestUserAttention(attention_type) => { + Action::RequestUserAttention(attention_type) + } } } } @@ -64,6 +90,7 @@ impl<T> Action<T> { impl<T> fmt::Debug for Action<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Close => write!(f, "Action::Close"), Self::Drag => write!(f, "Action::Drag"), Self::Resize { width, height } => write!( f, @@ -76,8 +103,12 @@ impl<T> fmt::Debug for Action<T> { write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) } Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), - Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::FetchMode(_) => write!(f, "Action::FetchMode"), + Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), + Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), + Self::RequestUserAttention(_) => { + write!(f, "Action::RequestUserAttention") + } } } } diff --git a/native/src/window/user_attention.rs b/native/src/window/user_attention.rs new file mode 100644 index 00000000..b03dfeef --- /dev/null +++ b/native/src/window/user_attention.rs @@ -0,0 +1,21 @@ +/// The type of user attention to request. +/// +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. +/// +/// [`Critical`]: Self::Critical +/// [`Informational`]: Self::Informational +#[derive(Debug, Clone, Copy)] +pub enum UserAttention { + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} diff --git a/src/application.rs b/src/application.rs index 0f514f66..f2b7c955 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.5/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.5/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.5/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.5/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.5/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.5/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.5/examples/todos +/// [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 /// [`Sandbox`]: crate::Sandbox /// [`Canvas`]: crate::widget::Canvas /// [PokéAPI]: https://pokeapi.co/ @@ -270,8 +270,4 @@ where fn scale_factor(&self) -> f64 { self.0.scale_factor() } - - fn should_exit(&self) -> bool { - self.0.should_exit() - } } @@ -26,11 +26,11 @@ //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md //! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/0.4/master/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.5/wgpu -//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.5/winit +//! [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 //! [`dodrio`]: https://github.com/fitzgen/dodrio //! [web runtime]: https://github.com/iced-rs/iced_web -//! [examples]: https://github.com/iced-rs/iced/tree/0.5/examples +//! [examples]: https://github.com/iced-rs/iced/tree/0.6/examples //! [repository]: https://github.com/iced-rs/iced //! //! # Overview diff --git a/src/sandbox.rs b/src/sandbox.rs index dc69b6bb..47bad831 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.5/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool -/// [`counter`]: https://github.com/iced-rs/iced/tree/0.5/examples/counter -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry -/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid -/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.5/examples/progress_bar -/// [`styling`]: https://github.com/iced-rs/iced/tree/0.5/examples/styling -/// [`svg`]: https://github.com/iced-rs/iced/tree/0.5/examples/svg -/// [`tour`]: https://github.com/iced-rs/iced/tree/0.5/examples/tour +/// [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 /// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu /// [`Svg` widget]: crate::widget::Svg /// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg /// diff --git a/src/widget.rs b/src/widget.rs index acc9da9a..d2d4a1b8 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.5/examples/pane_grid + //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid pub use iced_native::widget::pane_grid::{ Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent, Split, State, StyleSheet, @@ -141,6 +141,7 @@ pub mod tooltip { pub use iced_native::widget::progress_bar; pub use iced_native::widget::rule; pub use iced_native::widget::slider; +pub use iced_native::widget::vertical_slider; pub use iced_native::widget::Space; pub use button::Button; @@ -157,6 +158,7 @@ pub use text::Text; pub use text_input::TextInput; pub use toggler::Toggler; pub use tooltip::Tooltip; +pub use vertical_slider::VerticalSlider; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] @@ -194,7 +196,7 @@ pub use iced_graphics::widget::qr_code; pub mod svg { //! Display vector graphics in your application. pub use iced_native::svg::Handle; - pub use iced_native::widget::Svg; + pub use iced_native::widget::svg::{Appearance, StyleSheet, Svg}; } #[cfg(feature = "canvas")] diff --git a/style/Cargo.toml b/style/Cargo.toml index 559b4462..9f7d904a 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_style" -version = "0.5.0" +version = "0.5.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The default set of styles of Iced" diff --git a/style/src/lib.rs b/style/src/lib.rs index 3242602c..59eb1eb8 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -32,6 +32,7 @@ pub mod radio; pub mod rule; pub mod scrollable; pub mod slider; +pub mod svg; pub mod text; pub mod text_input; pub mod theme; diff --git a/style/src/svg.rs b/style/src/svg.rs new file mode 100644 index 00000000..9378c1a7 --- /dev/null +++ b/style/src/svg.rs @@ -0,0 +1,23 @@ +//! Change the appearance of a svg. + +use iced_core::Color; + +/// The appearance of an SVG. +#[derive(Debug, Default, Clone, Copy)] +pub struct Appearance { + /// The [`Color`] filter of an SVG. + /// + /// Useful for coloring a symbolic icon. + /// + /// `None` keeps the original color. + pub color: Option<Color>, +} + +/// The stylesheet of a svg. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default; + + /// Produces the [`Appearance`] of the svg. + fn appearance(&self, style: &Self::Style) -> Appearance; +} diff --git a/style/src/theme.rs b/style/src/theme.rs index bd0ae28e..a766b279 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -16,6 +16,7 @@ use crate::radio; use crate::rule; use crate::scrollable; use crate::slider; +use crate::svg; use crate::text; use crate::text_input; use crate::toggler; @@ -559,7 +560,7 @@ impl pick_list::StyleSheet for Theme { border_color: palette.primary.strong.color, } } - PickList::Custom(custom, _) => custom.active(self), + PickList::Custom(custom, _) => custom.hovered(self), } } } @@ -823,6 +824,44 @@ impl rule::StyleSheet for fn(&Theme) -> rule::Appearance { } } +/** + * Svg + */ +#[derive(Default)] +pub enum Svg { + /// No filtering to the rendered SVG. + #[default] + Default, + /// A custom style. + Custom(Box<dyn svg::StyleSheet<Style = Theme>>), +} + +impl Svg { + /// Creates a custom [`Svg`] style. + pub fn custom_fn(f: fn(&Theme) -> svg::Appearance) -> Self { + Self::Custom(Box::new(f)) + } +} + +impl svg::StyleSheet for Theme { + type Style = Svg; + + fn appearance(&self, style: &Self::Style) -> svg::Appearance { + match style { + Svg::Default => Default::default(), + Svg::Custom(custom) => custom.appearance(self), + } + } +} + +impl svg::StyleSheet for fn(&Theme) -> svg::Appearance { + type Style = Theme; + + fn appearance(&self, style: &Self::Style) -> svg::Appearance { + (self)(style) + } +} + /// The style of a scrollable. #[derive(Default)] pub enum Scrollable { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b1fb0dd1..a40d9967 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.6.1" +version = "0.7.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.6" +version = "0.7" path = "../native" [dependencies.iced_graphics] -version = "0.4" +version = "0.5" path = "../graphics" features = ["font-fallback", "font-icons"] diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs index 18be03dd..88289b98 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -80,7 +80,7 @@ impl<T: ShaderType + WriteInto> Buffer<T> { pub fn push(&mut self, value: &T) { //this write operation on the cpu buffer will adjust for uniform alignment requirements let offset = self.cpu.write(value); - self.offsets.push(offset as u32); + self.offsets.push(offset); } /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index d06815bb..390bad90 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -318,11 +318,16 @@ impl Pipeline { layer::Image::Raster { .. } => {} #[cfg(feature = "svg")] - layer::Image::Vector { handle, bounds } => { + layer::Image::Vector { + handle, + color, + bounds, + } => { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = vector_cache.upload( handle, + *color, size, _scale, &mut (device, encoder), diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 74152945..e4a38005 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.5/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/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/wgpu/src/quad.rs b/wgpu/src/quad.rs index a117df64..027a34be 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -91,7 +91,7 @@ impl Pipeline { 2 => Float32x2, 3 => Float32x4, 4 => Float32x4, - 5 => Float32, + 5 => Float32x4, 6 => Float32, ), }, diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 73edd97c..cf4f7e4d 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -11,7 +11,7 @@ struct VertexInput { @location(2) scale: vec2<f32>, @location(3) color: vec4<f32>, @location(4) border_color: vec4<f32>, - @location(5) border_radius: f32, + @location(5) border_radius: vec4<f32>, @location(6) border_width: f32, } @@ -21,7 +21,7 @@ struct VertexOutput { @location(1) border_color: vec4<f32>, @location(2) pos: vec2<f32>, @location(3) scale: vec2<f32>, - @location(4) border_radius: f32, + @location(4) border_radius: vec4<f32>, @location(5) border_width: f32, } @@ -32,9 +32,12 @@ fn vs_main(input: VertexInput) -> VertexOutput { var pos: vec2<f32> = input.pos * globals.scale; var scale: vec2<f32> = input.scale * globals.scale; - var border_radius: f32 = min( - input.border_radius, - min(input.scale.x, input.scale.y) / 2.0 + var min_border_radius = min(input.scale.x, input.scale.y) * 0.5; + var border_radius: vec4<f32> = vec4<f32>( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) ); var transform: mat4x4<f32> = mat4x4<f32>( @@ -76,6 +79,18 @@ fn distance_alg( return sqrt(dist.x * dist.x + dist.y * dist.y); } +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 { + var rx = radi.x; + var ry = radi.y; + rx = select(radi.x, radi.y, position.x > center.x); + ry = select(radi.w, radi.z, position.x > center.x); + rx = select(rx, ry, position.y > center.y); + return rx; +} + @fragment fn fs_main( @@ -83,14 +98,17 @@ fn fs_main( ) -> @location(0) vec4<f32> { var mixed_color: vec4<f32> = input.color; + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (input.pos + input.scale * 0.5).xy + ); + if (input.border_width > 0.0) { - var internal_border: f32 = max( - input.border_radius - input.border_width, - 0.0 - ); + var internal_border: f32 = max(border_radius - input.border_width, 0.0); var internal_distance: f32 = distance_alg( - vec2<f32>(input.position.x, input.position.y), + input.position.xy, input.pos + vec2<f32>(input.border_width, input.border_width), input.scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0), internal_border @@ -109,13 +127,14 @@ fn fs_main( vec2<f32>(input.position.x, input.position.y), input.pos, input.scale, - input.border_radius + border_radius ); var radius_alpha: f32 = 1.0 - smoothstep( - max(input.border_radius - 0.5, 0.0), - input.border_radius + 0.5, - dist); + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist + ); return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index b33b488a..061154b6 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -331,11 +331,7 @@ impl Pipeline { wgpu::IndexFormat::Uint32, ); - render_pass.draw_indexed( - 0..(self.index_strides[index] as u32), - 0, - 0..1, - ); + render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); } } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index a3ac3ddd..ebbadb12 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.5.1" +version = "0.6.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A winit runtime for Iced" @@ -26,11 +26,11 @@ git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" [dependencies.iced_native] -version = "0.6" +version = "0.7" path = "../native" [dependencies.iced_graphics] -version = "0.4" +version = "0.5" path = "../graphics" [dependencies.iced_futures] diff --git a/winit/src/application.rs b/winit/src/application.rs index 1706d2e9..7092e124 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -93,13 +93,6 @@ where fn scale_factor(&self) -> f64 { 1.0 } - - /// Returns whether the [`Application`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -255,6 +248,7 @@ async fn run_instance<A, E, C>( let mut clipboard = Clipboard::connect(&window); let mut cache = user_interface::Cache::default(); let mut surface = compositor.create_surface(&window); + let mut should_exit = false; let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); @@ -275,6 +269,7 @@ async fn run_instance<A, E, C>( init_command, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, &window, @@ -336,6 +331,7 @@ async fn run_instance<A, E, C>( &mut renderer, &mut runtime, &mut clipboard, + &mut should_exit, &mut proxy, &mut debug, &mut messages, @@ -346,8 +342,6 @@ async fn run_instance<A, E, C>( // Update window state.synchronize(&application, &window); - let should_exit = application.should_exit(); - user_interface = ManuallyDrop::new(build_user_interface( &application, cache, @@ -555,6 +549,7 @@ pub fn update<A: Application, E: Executor>( renderer: &mut A::Renderer, runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, + should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, debug: &mut Debug, messages: &mut Vec<A::Message>, @@ -578,6 +573,7 @@ pub fn update<A: Application, E: Executor>( command, runtime, clipboard, + should_exit, proxy, debug, window, @@ -598,6 +594,7 @@ pub fn run_command<A, E>( command: Command<A::Message>, runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, + should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, debug: &mut Debug, window: &winit::window::Window, @@ -629,6 +626,9 @@ pub fn run_command<A, E>( } }, command::Action::Window(action) => match action { + window::Action::Close => { + *should_exit = true; + } window::Action::Drag => { let _res = window.drag_window(); } @@ -657,9 +657,6 @@ pub fn run_command<A, E>( mode, )); } - window::Action::ToggleMaximize => { - window.set_maximized(!window.is_maximized()) - } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) @@ -671,6 +668,16 @@ pub fn run_command<A, E>( .send_event(tag(mode)) .expect("Send message to event loop"); } + window::Action::ToggleMaximize => { + window.set_maximized(!window.is_maximized()) + } + window::Action::ToggleDecorations => { + window.set_decorations(!window.is_decorated()) + } + window::Action::RequestUserAttention(user_attention) => window + .request_user_attention( + user_attention.map(conversion::user_attention), + ), }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 0707aed5..1418e346 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.5/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native use crate::keyboard; use crate::mouse; use crate::touch; @@ -218,7 +218,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.5/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native pub fn mouse_interaction( interaction: mouse::Interaction, ) -> winit::window::CursorIcon { @@ -242,7 +242,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.5/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { match mouse_button { winit::event::MouseButton::Left => mouse::Button::Left, @@ -258,7 +258,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.5/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native pub fn modifiers( modifiers: winit::event::ModifiersState, ) -> keyboard::Modifiers { @@ -285,7 +285,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.5/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native pub fn touch_event( touch: winit::event::Touch, scale_factor: f64, @@ -316,7 +316,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.5/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native pub fn key_code( virtual_keycode: winit::event::VirtualKeyCode, ) -> keyboard::KeyCode { @@ -493,6 +493,22 @@ pub fn key_code( } } +/// Converts some [`UserAttention`] into it's `winit` counterpart. +/// +/// [`UserAttention`]: window::UserAttention +pub fn user_attention( + user_attention: window::UserAttention, +) -> winit::window::UserAttentionType { + match user_attention { + window::UserAttention::Critical => { + winit::window::UserAttentionType::Critical + } + window::UserAttention::Informational => { + winit::window::UserAttentionType::Informational + } + } +} + // As defined in: http://www.unicode.org/faq/private_use.html pub(crate) fn is_private_use_character(c: char) -> bool { matches!( diff --git a/winit/src/lib.rs b/winit/src/lib.rs index bb3a3d5b..b8ed492d 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.5/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native //! [`winit`]: https://github.com/rust-windowing/winit //! [`conversion`]: crate::conversion #![doc( diff --git a/winit/src/window.rs b/winit/src/window.rs index 1e704c5b..89db3262 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,12 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Event, Mode}; +pub use window::{Event, Mode, UserAttention}; + +/// Closes the current window and exits the application. +pub fn close<Message>() -> Command<Message> { + Command::single(command::Action::Window(window::Action::Close)) +} /// Begins dragging the window while the left mouse button is held. pub fn drag<Message>() -> Command<Message> { |