diff options
102 files changed, 2314 insertions, 1312 deletions
@@ -21,13 +21,17 @@ image = ["iced_wgpu/image"] svg = ["iced_wgpu/svg"] # Enables the `Canvas` widget canvas = ["iced_wgpu/canvas"] -# Enables using system fonts. +# Enables the `QRCode` widget +qr_code = ["iced_wgpu/qr_code"] +# Enables using system fonts default_system_font = ["iced_wgpu/default_system_font"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` glow = ["iced_glow", "iced_glutin"] # Enables the `Canvas` widget for `iced_glow` glow_canvas = ["iced_glow/canvas"] -# Enables using system fonts for `iced_glow`. +# Enables the `QRCode` widget for `iced_glow` +glow_qr_code = ["iced_glow/qr_code"] +# Enables using system fonts for `iced_glow` glow_default_system_font = ["iced_glow/default_system_font"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] @@ -69,6 +73,7 @@ members = [ "examples/pick_list", "examples/pokedex", "examples/progress_bar", + "examples/qr_code", "examples/scrollable", "examples/solar_system", "examples/stopwatch", diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 4d24266f..254013c3 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -15,6 +15,26 @@ pub struct ModifiersState { } impl ModifiersState { + /// Returns true if the current [`ModifiersState`] has a "command key" + /// pressed. + /// + /// The "command key" is the main modifier key used to issue commands in the + /// current platform. Specifically: + /// + /// - It is the `logo` or command key (⌘) on macOS + /// - It is the `control` key on other platforms + /// + /// [`ModifiersState`]: struct.ModifiersState.html + pub fn is_command_pressed(self) -> bool { + #[cfg(target_os = "macos")] + let is_pressed = self.logo; + + #[cfg(not(target_os = "macos"))] + let is_pressed = self.control; + + is_pressed + } + /// Returns true if the current [`ModifiersState`] has at least the same /// modifiers enabled as the given value, and false otherwise. /// diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index cb5247aa..97832e01 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -69,7 +69,8 @@ impl Sandbox for Example { mod bezier { use iced::{ - canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, + canvas::event::{self, Event}, + canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke}, mouse, Element, Length, Point, Rectangle, }; @@ -109,41 +110,51 @@ mod bezier { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Curve> { - let cursor_position = cursor.position_in(&bounds)?; + ) -> (event::Status, Option<Curve>) { + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; match event { - Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: cursor_position, - }); - None - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) + Event::Mouse(mouse_event) => { + let message = match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: cursor_position, + }); + + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: cursor_position, + }) + } } } - } - _ => None, - }, - _ => None, + _ => None, + }; + + (event::Status::Captured, message) + } + _ => (event::Status::Ignored, None), } } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index a0003d65..36f468c7 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -16,11 +16,11 @@ mod circle { }; pub struct Circle { - radius: u16, + radius: f32, } impl Circle { - pub fn new(radius: u16) -> Self { + pub fn new(radius: f32) -> Self { Self { radius } } } @@ -42,16 +42,13 @@ mod circle { _renderer: &Renderer<B>, _limits: &layout::Limits, ) -> layout::Node { - layout::Node::new(Size::new( - f32::from(self.radius) * 2.0, - f32::from(self.radius) * 2.0, - )) + layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0)) } fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash; - self.radius.hash(state); + self.radius.to_bits().hash(state); } fn draw( @@ -67,7 +64,7 @@ mod circle { bounds: layout.bounds(), background: Background::Color(Color::BLACK), border_radius: self.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, mouse::Interaction::default(), @@ -96,7 +93,7 @@ pub fn main() -> iced::Result { } struct Example { - radius: u16, + radius: f32, slider: slider::State, } @@ -110,7 +107,7 @@ impl Sandbox for Example { fn new() -> Self { Example { - radius: 50, + radius: 50.0, slider: slider::State::new(), } } @@ -122,7 +119,7 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { Message::RadiusChanged(radius) => { - self.radius = radius.round() as u16; + self.radius = radius; } } } @@ -134,13 +131,16 @@ impl Sandbox for Example { .max_width(500) .align_items(Align::Center) .push(Circle::new(self.radius)) - .push(Text::new(format!("Radius: {}", self.radius.to_string()))) - .push(Slider::new( - &mut self.slider, - 1.0..=100.0, - f32::from(self.radius), - Message::RadiusChanged, - )); + .push(Text::new(format!("Radius: {:.2}", self.radius))) + .push( + Slider::new( + &mut self.slider, + 1.0..=100.0, + self.radius, + Message::RadiusChanged, + ) + .step(0.01), + ); Container::new(content) .width(Length::Fill) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 3f087f88..e18bd6e0 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -153,9 +153,8 @@ impl Application for GameOfLife { mod grid { use crate::Preset; use iced::{ - canvas::{ - self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, - }, + canvas::event::{self, Event}, + canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text}, mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; @@ -328,12 +327,18 @@ mod grid { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Message> { + ) -> (event::Status, Option<Message>) { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { self.interaction = Interaction::None; } - let cursor_position = cursor.position_in(&bounds)?; + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; + let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -345,28 +350,32 @@ mod grid { match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(button) => match button { - mouse::Button::Left => { - self.interaction = if is_populated { - Interaction::Erasing - } else { - Interaction::Drawing - }; - - populate.or(unpopulate) - } - mouse::Button::Right => { - self.interaction = Interaction::Panning { - translation: self.translation, - start: cursor_position, - }; + mouse::Event::ButtonPressed(button) => { + let message = match button { + mouse::Button::Left => { + self.interaction = if is_populated { + Interaction::Erasing + } else { + Interaction::Drawing + }; + + populate.or(unpopulate) + } + mouse::Button::Right => { + self.interaction = Interaction::Panning { + translation: self.translation, + start: cursor_position, + }; - None - } - _ => None, - }, + None + } + _ => None, + }; + + (event::Status::Captured, message) + } mouse::Event::CursorMoved { .. } => { - match self.interaction { + let message = match self.interaction { Interaction::Drawing => populate, Interaction::Erasing => unpopulate, Interaction::Panning { translation, start } => { @@ -380,7 +389,14 @@ mod grid { None } _ => None, - } + }; + + let event_status = match self.interaction { + Interaction::None => event::Status::Ignored, + _ => event::Status::Captured, + }; + + (event_status, message) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -413,12 +429,12 @@ mod grid { self.grid_cache.clear(); } - None + (event::Status::Captured, None) } }, - _ => None, + _ => (event::Status::Ignored, None), }, - _ => None, + _ => (event::Status::Ignored, None), } } diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index 308ce43c..6605826f 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -44,7 +44,7 @@ impl button::StyleSheet for Button { fn active(&self) -> button::Style { button::Style { background: Some(Background::Color(ACTIVE)), - border_radius: 3, + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -60,7 +60,7 @@ impl button::StyleSheet for Button { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -73,7 +73,7 @@ impl button::StyleSheet for Clear { fn active(&self) -> button::Style { button::Style { background: Some(Background::Color(DESTRUCTIVE)), - border_radius: 3, + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -92,7 +92,7 @@ impl button::StyleSheet for Clear { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -106,9 +106,9 @@ impl slider::StyleSheet for Slider { slider::Style { rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9 }, + shape: slider::HandleShape::Circle { radius: 9.0 }, color: ACTIVE, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -146,7 +146,7 @@ impl pick_list::StyleSheet for PickList { pick_list::Menu { text_color: Color::WHITE, background: BACKGROUND.into(), - border_width: 1, + border_width: 1.0, border_color: Color { a: 0.7, ..Color::BLACK @@ -164,12 +164,12 @@ impl pick_list::StyleSheet for PickList { pick_list::Style { text_color: Color::WHITE, background: BACKGROUND.into(), - border_width: 1, + border_width: 1.0, border_color: Color { a: 0.6, ..Color::BLACK }, - border_radius: 2, + border_radius: 2.0, icon_size: 0.5, } } diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index afc2c791..4515502f 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } iced_wgpu = { path = "../../wgpu" } -env_logger = "0.7" +env_logger = "0.8" diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac..e489f210 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c4946645..3c3256cf 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,9 @@ use iced::{ - button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, - Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, - Settings, Text, + button, executor, keyboard, pane_grid, scrollable, Align, Application, + Button, Column, Command, Container, Element, HorizontalAlignment, Length, + PaneGrid, Scrollable, Settings, Subscription, Text, }; +use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -11,6 +12,7 @@ pub fn main() -> iced::Result { struct Example { panes: pane_grid::State<Content>, panes_created: usize, + focus: Option<pane_grid::Pane>, } #[derive(Debug, Clone, Copy)] @@ -18,59 +20,77 @@ enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), CloseFocused, } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Executor = executor::Default; + type Flags = (); - fn new() -> Self { + fn new(_flags: ()) -> (Self, Command<Message>) { let (panes, _) = pane_grid::State::new(Content::new(0)); - Example { - panes, - panes_created: 1, - } + ( + Example { + panes, + panes_created: 1, + focus: None, + }, + Command::none(), + ) } fn title(&self) -> String { String::from("Pane grid - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Split(axis, pane) => { - let _ = self.panes.split( + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } Message::SplitFocused(axis) => { - if let Some(pane) = self.panes.active() { - let _ = self.panes.split( + if let Some(pane) = self.focus { + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } } Message::FocusAdjacent(direction) => { - if let Some(pane) = self.panes.active() { + if let Some(pane) = self.focus { if let Some(adjacent) = self.panes.adjacent(&pane, direction) { - self.panes.focus(&adjacent); + self.focus = Some(adjacent); } } } + Message::Clicked(pane) => { + self.focus = Some(pane); + } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.panes.resize(&split, ratio); } @@ -82,37 +102,60 @@ impl Sandbox for Example { } Message::Dragged(_) => {} Message::Close(pane) => { - let _ = self.panes.close(&pane); + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } Message::CloseFocused => { - if let Some(pane) = self.panes.active() { - let _ = self.panes.close(&pane); + if let Some(pane) = self.focus { + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } } } + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + subscription::events_with(|event, status| { + if let event::Status::Captured = status { + return None; + } + + match event { + Event::Keyboard(keyboard::Event::KeyPressed { + modifiers, + key_code, + }) if modifiers.is_command_pressed() => handle_hotkey(key_code), + _ => None, + } + }) } fn view(&mut self) -> Element<Message> { + let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = - PaneGrid::new(&mut self.panes, |pane, content, focus| { - let is_focused = focus.is_some(); - let title_bar = - pane_grid::TitleBar::new(format!("Pane {}", content.id)) - .padding(10) - .style(style::TitleBar { is_focused }); - - pane_grid::Content::new(content.view(pane, total_panes)) - .title_bar(title_bar) - .style(style::Pane { is_focused }) - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_drag(Message::Dragged) - .on_resize(10, Message::Resized) - .on_key_press(handle_hotkey); + let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { + let is_focused = focus == Some(pane); + + let title_bar = + pane_grid::TitleBar::new(format!("Pane {}", content.id)) + .padding(10) + .style(style::TitleBar { is_focused }); + + pane_grid::Content::new(content.view(pane, total_panes)) + .title_bar(title_bar) + .style(style::Pane { is_focused }) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_click(Message::Clicked) + .on_drag(Message::Dragged) + .on_resize(10, Message::Resized); Container::new(pane_grid) .width(Length::Fill) @@ -122,11 +165,11 @@ impl Sandbox for Example { } } -fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> { +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; - let direction = match event.key_code { + let direction = match key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), @@ -134,7 +177,7 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> { _ => None, }; - match event.key_code { + match key_code { KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::W => Some(Message::CloseFocused), @@ -275,7 +318,7 @@ mod style { fn style(&self) -> container::Style { container::Style { background: Some(Background::Color(SURFACE)), - border_width: 2, + border_width: 2.0, border_color: if self.is_focused { Color::BLACK } else { @@ -303,7 +346,7 @@ mod style { button::Style { text_color, background: background.map(Background::Color), - border_radius: 5, + border_radius: 5.0, shadow_offset: Vector::new(0.0, 0.0), ..button::Style::default() } diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 30674fa0..187e5dee 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -251,7 +251,7 @@ mod style { background: Some(Background::Color(match self { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), })), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::WHITE, ..button::Style::default() diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml new file mode 100644 index 00000000..7f2d4e42 --- /dev/null +++ b/examples/qr_code/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "qr_code" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["qr_code"] } diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md new file mode 100644 index 00000000..2dd89c26 --- /dev/null +++ b/examples/qr_code/README.md @@ -0,0 +1,18 @@ +## QR Code Generator + +A basic QR code generator that showcases the `QRCode` widget. + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <a href="https://gfycat.com/heavyexhaustedaracari"> + <img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif"> + </a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package qr_code +``` + +[`main`]: src/main.rs diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs new file mode 100644 index 00000000..37b4855d --- /dev/null +++ b/examples/qr_code/src/main.rs @@ -0,0 +1,81 @@ +use iced::qr_code::{self, QRCode}; +use iced::text_input::{self, TextInput}; +use iced::{ + Align, Column, Container, Element, Length, Sandbox, Settings, Text, +}; + +pub fn main() -> iced::Result { + QRGenerator::run(Settings::default()) +} + +#[derive(Default)] +struct QRGenerator { + data: String, + input: text_input::State, + qr_code: Option<qr_code::State>, +} + +#[derive(Debug, Clone)] +enum Message { + DataChanged(String), +} + +impl Sandbox for QRGenerator { + type Message = Message; + + fn new() -> Self { + QRGenerator { + qr_code: qr_code::State::new("").ok(), + ..Self::default() + } + } + + fn title(&self) -> String { + String::from("QR Code Generator - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::DataChanged(mut data) => { + data.truncate(100); + + self.qr_code = qr_code::State::new(&data).ok(); + self.data = data; + } + } + } + + fn view(&mut self) -> Element<Message> { + let title = Text::new("QR Code Generator") + .size(70) + .color([0.5, 0.5, 0.5]); + + let input = TextInput::new( + &mut self.input, + "Type the data of your QR code here...", + &self.data, + Message::DataChanged, + ) + .size(30) + .padding(15); + + let mut content = Column::new() + .width(Length::Units(700)) + .spacing(20) + .align_items(Align::Center) + .push(title) + .push(input); + + if let Some(qr_code) = self.qr_code.as_mut() { + content = content.push(QRCode::new(qr_code).cell_size(10)); + } + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs index 24d711ac..ae449141 100644 --- a/examples/scrollable/src/style.rs +++ b/examples/scrollable/src/style.rs @@ -114,7 +114,7 @@ mod dark { radio::Style { background: SURFACE.into(), dot_color: ACTIVE, - border_width: 1, + border_width: 1.0, border_color: ACTIVE, } } @@ -137,13 +137,13 @@ mod dark { ..SCROLLBAR } .into(), - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, scroller: scrollable::Scroller { color: Color { a: 0.7, ..SCROLLER }, - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -182,7 +182,7 @@ mod dark { rule::Style { color: SURFACE, width: 2, - radius: 1, + radius: 1.0, fill_mode: rule::FillMode::Percent(30.0), } } diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 5a69aa9a..983cf3e6 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -161,7 +161,7 @@ mod style { Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), })), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::WHITE, ..button::Style::default() diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index ef302e61..8975fd9a 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -249,7 +249,7 @@ mod style { fn active(&self) -> button::Style { button::Style { background: Color::from_rgb(0.11, 0.42, 0.87).into(), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() @@ -315,7 +315,7 @@ mod style { radio::Style { background: SURFACE.into(), dot_color: ACTIVE, - border_width: 1, + border_width: 1.0, border_color: ACTIVE, } } @@ -334,15 +334,15 @@ mod style { fn active(&self) -> text_input::Style { text_input::Style { background: SURFACE.into(), - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } fn focused(&self) -> text_input::Style { text_input::Style { - border_width: 1, + border_width: 1.0, border_color: ACCENT, ..self.active() } @@ -350,7 +350,7 @@ mod style { fn hovered(&self) -> text_input::Style { text_input::Style { - border_width: 1, + border_width: 1.0, border_color: Color { a: 0.3, ..ACCENT }, ..self.focused() } @@ -375,7 +375,7 @@ mod style { fn active(&self) -> button::Style { button::Style { background: ACTIVE.into(), - border_radius: 3, + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -391,7 +391,7 @@ mod style { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -404,13 +404,13 @@ mod style { fn active(&self) -> scrollable::Scrollbar { scrollable::Scrollbar { background: SURFACE.into(), - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, scroller: scrollable::Scroller { color: ACTIVE, - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -449,9 +449,9 @@ mod style { slider::Style { rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9 }, + shape: slider::HandleShape::Circle { radius: 9.0 }, color: ACTIVE, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -489,7 +489,7 @@ mod style { progress_bar::Style { background: SURFACE.into(), bar: ACTIVE.into(), - border_radius: 10, + border_radius: 10.0, } } } @@ -502,8 +502,8 @@ mod style { background: if is_checked { ACTIVE } else { SURFACE } .into(), checkmark_color: Color::WHITE, - border_radius: 2, - border_width: 1, + border_radius: 2.0, + border_width: 1.0, border_color: ACTIVE, } } @@ -527,7 +527,7 @@ mod style { rule::Style { color: SURFACE, width: 2, - radius: 1, + radius: 1.0, fill_mode: rule::FillMode::Padded(15), } } diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index b236cc0d..c8926c33 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -12,7 +12,7 @@ serde_json = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" -directories = "2.0" +directories-next = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features = ["Window", "Storage"] } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 7a546815..ccee2703 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -499,7 +499,7 @@ enum SaveError { impl SavedState { fn path() -> std::path::PathBuf { let mut path = if let Some(project_dirs) = - directories::ProjectDirs::from("rs", "Iced", "Todos") + directories_next::ProjectDirs::from("rs", "Iced", "Todos") { project_dirs.data_dir().into() } else { @@ -611,7 +611,7 @@ mod style { background: Some(Background::Color( Color::from_rgb(0.2, 0.2, 0.7), )), - border_radius: 10, + border_radius: 10.0, text_color: Color::WHITE, ..button::Style::default() } @@ -627,7 +627,7 @@ mod style { background: Some(Background::Color(Color::from_rgb( 0.8, 0.2, 0.2, ))), - border_radius: 5, + border_radius: 5.0, text_color: Color::WHITE, shadow_offset: Vector::new(1.0, 1.0), ..button::Style::default() diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 96749e90..bc7fac11 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["image", "debug"] } -env_logger = "0.7" +env_logger = "0.8" diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 560d67e2..e8755d39 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -769,7 +769,7 @@ mod style { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), })), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 11ca80e2..0178f9f7 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -9,17 +9,18 @@ repository = "https://github.com/hecrj/iced" [features] canvas = ["iced_graphics/canvas"] +qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] # Not supported yet! image = [] svg = [] [dependencies] -glow = "0.5" -glow_glyph = "0.3" +glow = "0.6" +glow_glyph = "0.4" glyph_brush = "0.7" -euclid = "0.20" -bytemuck = "1.2" +euclid = "0.22" +bytemuck = "1.4" log = "0.4" [dependencies.iced_native] diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert index d37b5c8d..82417856 100644 --- a/glow/src/shader/quad.vert +++ b/glow/src/shader/quad.vert @@ -29,6 +29,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 + ); + mat4 i_Transform = mat4( vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 0e33909d..b5c84c56 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -52,6 +52,14 @@ pub mod canvas; #[doc(no_inline)] pub use canvas::Canvas; +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + pub use iced_native::{Image, Space}; /// A container that distributes its contents vertically. diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs index 3c47b562..f594473f 100644 --- a/glow/src/widget/pane_grid.rs +++ b/glow/src/widget/pane_grid.rs @@ -11,8 +11,8 @@ use crate::Renderer; pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node, - Pane, ResizeEvent, Split, State, + Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, + State, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/glow/src/widget/qr_code.rs b/glow/src/widget/qr_code.rs new file mode 100644 index 00000000..7b1c2408 --- /dev/null +++ b/glow/src/widget/qr_code.rs @@ -0,0 +1,2 @@ +//! Encode and display information in a QR code. +pub use iced_graphics::qr_code::*; diff --git a/glutin/src/application.rs b/glutin/src/application.rs index fe6ad99d..e593f0ae 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,13 +1,17 @@ //! Create interactive, native cross-platform applications. -use crate::{mouse, Error, Executor, Runtime, Size}; +use crate::{mouse, Error, Executor, Runtime}; + +pub use iced_winit::Application; + use iced_graphics::window; -use iced_graphics::Viewport; use iced_winit::application; use iced_winit::conversion; -use iced_winit::{Clipboard, Debug, Proxy, Settings}; +use iced_winit::futures; +use iced_winit::futures::channel::mpsc; +use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings}; -pub use iced_winit::Application; -pub use iced_winit::{program, Program}; +use glutin::window::Window; +use std::mem::ManuallyDrop; /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. @@ -22,11 +26,10 @@ where E: Executor + 'static, C: window::GLCompositor<Renderer = A::Renderer> + 'static, { - use glutin::{ - event, - event_loop::{ControlFlow, EventLoop}, - ContextBuilder, - }; + use futures::task; + use futures::Future; + use glutin::event_loop::EventLoop; + use glutin::ContextBuilder; let mut debug = Debug::new(); debug.startup_started(); @@ -39,22 +42,21 @@ where Runtime::new(executor, proxy) }; - let flags = settings.flags; - let (application, init_command) = runtime.enter(|| A::new(flags)); - runtime.spawn(init_command); + let (application, init_command) = { + let flags = settings.flags; + + runtime.enter(|| A::new(flags)) + }; let subscription = application.subscription(); - runtime.track(subscription); - let mut title = application.title(); - let mut mode = application.mode(); - let mut background_color = application.background_color(); - let mut scale_factor = application.scale_factor(); + runtime.spawn(init_command); + runtime.track(subscription); let context = { let builder = settings.window.into_builder( - &title, - mode, + &application.title(), + application.mode(), event_loop.primary_monitor(), ); @@ -79,189 +81,219 @@ where } }; - let clipboard = Clipboard::new(&context.window()); - let mut cursor_position = glutin::dpi::PhysicalPosition::new(-1.0, -1.0); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = glutin::event::ModifiersState::default(); - - let physical_size = context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - context.window().scale_factor() * scale_factor, - ); - let mut resized = false; - #[allow(unsafe_code)] - let (mut compositor, mut renderer) = unsafe { + let (compositor, renderer) = unsafe { C::new(compositor_settings, |address| { context.get_proc_address(address) })? }; - let mut state = program::State::new( - application, - viewport.logical_size(), - conversion::cursor_position(cursor_position, viewport.scale_factor()), - &mut renderer, - &mut debug, - ); - debug.startup_finished(); + let (mut sender, receiver) = mpsc::unbounded(); - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if state.is_queue_empty() { - return; - } + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + context, + runtime, + debug, + receiver, + )); - let command = runtime.enter(|| { - state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - &mut debug, - ) - }); + let mut context = task::Context::from_waker(task::noop_waker_ref()); - // If the application was updated - if let Some(command) = command { - runtime.spawn(command); + event_loop.run(move |event, _, control_flow| { + use glutin::event_loop::ControlFlow; - let program = state.program(); + if let ControlFlow::Exit = control_flow { + return; + } - // Update subscriptions - let subscription = program.subscription(); - runtime.track(subscription); + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); - // Update window title - let new_title = program.title(); + let poll = instance.as_mut().poll(&mut context); - if title != new_title { - context.window().set_title(&new_title); + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} - title = new_title; - } +async fn run_instance<A, E, C>( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>, + mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor<Renderer = A::Renderer> + 'static, +{ + use glutin::event; + use iced_winit::futures::stream::StreamExt; + + let clipboard = Clipboard::new(context.window()); + + let mut state = application::State::new(&application, context.window()); + let mut viewport_version = state.viewport_version(); + let mut user_interface = + ManuallyDrop::new(application::build_user_interface( + &mut application, + Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + )); + + let mut primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + let mut mouse_interaction = mouse::Interaction::default(); - // Update window mode - let new_mode = program.mode(); + let mut events = Vec::new(); + let mut messages = Vec::new(); - if mode != new_mode { - context.window().set_fullscreen(conversion::fullscreen( - context.window().current_monitor(), - new_mode, - )); + debug.startup_finished(); - mode = new_mode; + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && messages.is_empty() { + continue; } - // Update background color - background_color = program.background_color(); + debug.event_processing_started(); - // Update scale factor - let new_scale_factor = program.scale_factor(); + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); - if scale_factor != new_scale_factor { - let size = context.window().inner_size(); + debug.event_processing_finished(); - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - context.window().scale_factor() * new_scale_factor, - ); + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } - // We relayout the UI with the new logical size. - // The queue is empty, therefore this will never produce - // a `Command`. - // - // TODO: Properly queue `WindowResized` - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), - &mut renderer, + if !messages.is_empty() { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); + + // Update application + application::update( + &mut application, + &mut runtime, &mut debug, + &mut messages, ); - scale_factor = new_scale_factor; + // Update window + state.synchronize(&application, context.window()); + + user_interface = + ManuallyDrop::new(application::build_user_interface( + &mut application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); } + + debug.draw_started(); + primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + + context.window().request_redraw(); + } + event::Event::UserEvent(message) => { + messages.push(message); } + event::Event::RedrawRequested(_) => { + debug.render_started(); + let current_viewport_version = state.viewport_version(); + + if viewport_version != current_viewport_version { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); + + debug.layout_started(); + user_interface = ManuallyDrop::new( + ManuallyDrop::into_inner(user_interface) + .relayout(logical_size, &mut renderer), + ); + debug.layout_finished(); - context.window().request_redraw(); - } - event::Event::UserEvent(message) => { - state.queue_message(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); - if resized { - let physical_size = viewport.physical_size(); + context.resize(glutin::dpi::PhysicalSize::new( + physical_size.width, + physical_size.height, + )); - context.resize(glutin::dpi::PhysicalSize::new( - physical_size.width, - physical_size.height, - )); + compositor.resize_viewport(physical_size); - compositor.resize_viewport(physical_size); + viewport_version = current_viewport_version; + } - resized = false; - } + let new_mouse_interaction = compositor.draw( + &mut renderer, + state.viewport(), + state.background_color(), + &primitive, + &debug.overlay(), + ); - let new_mouse_interaction = compositor.draw( - &mut renderer, - &viewport, - background_color, - state.primitive(), - &debug.overlay(), - ); + context.swap_buffers().expect("Swap buffers"); - context.swap_buffers().expect("Swap buffers"); + debug.render_finished(); - debug.render_finished(); + if new_mouse_interaction != mouse_interaction { + context.window().set_cursor_icon( + conversion::mouse_interaction(new_mouse_interaction), + ); - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); + mouse_interaction = new_mouse_interaction; + } - mouse_interaction = new_mouse_interaction; + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. } + event::Event::WindowEvent { + event: window_event, + .. + } => { + if application::requests_exit(&window_event, state.modifiers()) + { + break; + } - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - application::handle_window_event( - &window_event, - context.window(), - scale_factor, - control_flow, - &mut cursor_position, - &mut modifiers, - &mut viewport, - &mut resized, - &mut debug, - ); - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - state.queue_event(event.clone()); - runtime.broadcast(event); + state.update(context.window(), &window_event, &mut debug); + + if let Some(event) = conversion::window_event( + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events.push(event); + } } + _ => {} } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index dec24c59..4ce50e5a 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -6,17 +6,21 @@ edition = "2018" [features] canvas = ["lyon"] +qr_code = ["qrcode", "canvas"] font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] [dependencies] -bytemuck = "1.2" -glam = "0.9" +glam = "0.10" raw-window-handle = "0.3" thiserror = "1.0" +[dependencies.bytemuck] +version = "1.4" +features = ["derive"] + [dependencies.iced_native] version = "0.2" path = "../native" @@ -26,11 +30,15 @@ version = "0.1" path = "../style" [dependencies.lyon] -version = "0.15" +version = "0.16" +optional = true + +[dependencies.qrcode] +version = "0.12" optional = true [dependencies.font-kit] -version = "0.6" +version = "0.8" optional = true [package.metadata.docs.rs] diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 6aca738e..038c93ff 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -156,8 +156,8 @@ impl<'a> Layer<'a> { color: match background { Background::Color(color) => color.into_linear(), }, - border_radius: *border_radius as f32, - border_width: *border_width as f32, + border_radius: *border_radius, + border_width: *border_width, border_color: border_color.into_linear(), }); } diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index f42c5e3c..ffe998c5 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -29,7 +29,7 @@ where background: style.background, border_color: style.border_color, border_width: style.border_width, - border_radius: 0, + border_radius: 0.0, }, primitives, ], @@ -80,8 +80,8 @@ where bounds, background: style.selected_background, border_color: Color::TRANSPARENT, - border_width: 0, - border_radius: 0, + border_width: 0.0, + border_radius: 0.0, }); } diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 95dbf7dd..30263bd4 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -40,9 +40,9 @@ pub enum Primitive { /// The background of the quad background: Background, /// The border radius of the quad - border_radius: u16, + border_radius: f32, /// The border width of the quad - border_width: u16, + border_width: f32, /// The border color of the quad border_color: Color, }, diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index a880e22a..ae5038db 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -119,8 +119,8 @@ fn explain_layout( primitives.push(Primitive::Quad { bounds: layout.bounds(), background: Background::Color(Color::TRANSPARENT), - border_radius: 0, - border_width: 1, + border_radius: 0.0, + border_width: 1.0, border_color: [0.6, 0.6, 0.6, 0.5].into(), }); diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index ce879ffc..eb1494b1 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,4 +1,5 @@ //! Draw geometry using meshes of triangles. +use bytemuck::{Pod, Zeroable}; /// A set of [`Vertex2D`] and indices representing a list of triangles. /// @@ -16,7 +17,7 @@ pub struct Mesh2D { } /// A two-dimensional vertex with some color in __linear__ RGBA. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] #[repr(C)] pub struct Vertex2D { /// The vertex position @@ -24,9 +25,3 @@ pub struct Vertex2D { /// The vertex color in __linear__ RGBA. pub color: [f32; 4], } - -#[allow(unsafe_code)] -unsafe impl bytemuck::Zeroable for Vertex2D {} - -#[allow(unsafe_code)] -unsafe impl bytemuck::Pod for Vertex2D {} diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 66122e6d..bc7c5807 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,7 +1,7 @@ use crate::{Size, Transformation}; /// A viewing region for displaying computer graphics. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Viewport { physical_size: Size<u32>, logical_size: Size<f32>, diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index f87b558a..159ca91b 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -63,3 +63,11 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index a1afc940..87581175 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -66,7 +66,7 @@ where ); ( - if styling.background.is_some() || styling.border_width > 0 { + if styling.background.is_some() || styling.border_width > 0.0 { let background = Primitive::Quad { bounds, background: styling @@ -93,7 +93,7 @@ where [0.0, 0.0, 0.0, 0.5].into(), ), border_radius: styling.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 73778d16..ae0d87a4 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -7,18 +7,20 @@ //! [`Canvas`]: struct.Canvas.html //! [`Frame`]: struct.Frame.html use crate::{Backend, Defaults, Primitive, Renderer}; +use iced_native::layout; +use iced_native::mouse; use iced_native::{ - layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, - Rectangle, Size, Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; use std::hash::Hash; use std::marker::PhantomData; +pub mod event; pub mod path; mod cache; mod cursor; -mod event; mod fill; mod frame; mod geometry; @@ -166,7 +168,7 @@ where messages: &mut Vec<Message>, _renderer: &Renderer<B>, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { @@ -182,12 +184,17 @@ where let cursor = Cursor::from_window_position(cursor_position); if let Some(canvas_event) = canvas_event { - if let Some(message) = - self.program.update(canvas_event, bounds, cursor) - { + let (event_status, message) = + self.program.update(canvas_event, bounds, cursor); + + if let Some(message) = message { messages.push(message); } + + return event_status; } + + event::Status::Ignored } fn draw( diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs index 0e66f0ff..ede2fd73 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/graphics/src/widget/canvas/event.rs @@ -1,6 +1,9 @@ +//! Handle events of a canvas. use iced_native::keyboard; use iced_native::mouse; +pub use iced_native::event::Status; + /// A [`Canvas`] event. /// /// [`Canvas`]: struct.Event.html diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index b5c6a2b1..21e9ec28 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -276,7 +276,7 @@ impl Frame { .transforms .current .raw - .pre_rotate(lyon::math::Angle::radians(-angle)); + .pre_rotate(lyon::math::Angle::radians(angle)); self.transforms.current.is_identity = false; } diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index 725d9d72..e8f43380 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,4 +1,5 @@ -use crate::canvas::{Cursor, Event, Geometry}; +use crate::canvas::event::{self, Event}; +use crate::canvas::{Cursor, Geometry}; use iced_native::{mouse, Rectangle}; /// The state and logic of a [`Canvas`]. @@ -27,8 +28,8 @@ pub trait Program<Message> { _event: Event, _bounds: Rectangle, _cursor: Cursor, - ) -> Option<Message> { - None + ) -> (event::Status, Option<Message>) { + (event::Status::Ignored, None) } /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -67,7 +68,7 @@ where event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Message> { + ) -> (event::Status, Option<Message>) { T::update(self, event, bounds, cursor) } diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs index 4854f589..aae3e1d8 100644 --- a/graphics/src/widget/container.rs +++ b/graphics/src/widget/container.rs @@ -62,7 +62,7 @@ pub(crate) fn background( bounds: Rectangle, style: &container::Style, ) -> Option<Primitive> { - if style.background.is_some() || style.border_width > 0 { + if style.background.is_some() || style.border_width > 0.0 { Some(Primitive::Quad { bounds, background: style diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 5b0eb391..72a380e4 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -20,8 +20,8 @@ use iced_native::{ }; pub use iced_native::pane_grid::{ - Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent, - Pane, ResizeEvent, Split, State, TitleBar, + Axis, Configuration, Content, Direction, DragEvent, Pane, ResizeEvent, + Split, State, TitleBar, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs index 48acb3c1..c1801a41 100644 --- a/graphics/src/widget/progress_bar.rs +++ b/graphics/src/widget/progress_bar.rs @@ -43,7 +43,7 @@ where bounds: Rectangle { ..bounds }, background: style.background, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }], }; @@ -57,7 +57,7 @@ where }, background: style.bar, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs new file mode 100644 index 00000000..b3a01dd7 --- /dev/null +++ b/graphics/src/widget/qr_code.rs @@ -0,0 +1,305 @@ +//! Encode and display information in a QR code. +use crate::canvas; +use crate::{Backend, Defaults, Primitive, Renderer, Vector}; + +use iced_native::{ + layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, +}; +use thiserror::Error; + +const DEFAULT_CELL_SIZE: u16 = 4; +const QUIET_ZONE: usize = 2; + +/// A type of matrix barcode consisting of squares arranged in a grid which +/// can be read by an imaging device, such as a camera. +#[derive(Debug)] +pub struct QRCode<'a> { + state: &'a State, + dark: Color, + light: Color, + cell_size: u16, +} + +impl<'a> QRCode<'a> { + /// Creates a new [`QRCode`] with the provided [`State`]. + pub fn new(state: &'a State) -> Self { + Self { + cell_size: DEFAULT_CELL_SIZE, + dark: Color::BLACK, + light: Color::WHITE, + state, + } + } + + /// Sets both the dark and light [`Color`]s of the [`QRCode`]. + pub fn color(mut self, dark: Color, light: Color) -> Self { + self.dark = dark; + self.light = light; + self + } + + /// Sets the size of the squares of the grid cell of the [`QRCode`]. + pub fn cell_size(mut self, cell_size: u16) -> Self { + self.cell_size = cell_size; + self + } +} + +impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a> +where + B: Backend, +{ + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer<B>, + _limits: &layout::Limits, + ) -> layout::Node { + let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 + * f32::from(self.cell_size); + + layout::Node::new(Size::new( + f32::from(side_length), + f32::from(side_length), + )) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.state.contents.hash(state); + } + + fn draw( + &self, + _renderer: &mut Renderer<B>, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) -> (Primitive, mouse::Interaction) { + let bounds = layout.bounds(); + let side_length = self.state.width + 2 * QUIET_ZONE; + + // Reuse cache if possible + let geometry = self.state.cache.draw(bounds.size(), |frame| { + // Scale units to cell size + frame.scale(f32::from(self.cell_size)); + + // Draw background + frame.fill_rectangle( + Point::ORIGIN, + Size::new(side_length as f32, side_length as f32), + self.light, + ); + + // Avoid drawing on the quiet zone + frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32)); + + // Draw contents + self.state + .contents + .iter() + .enumerate() + .filter(|(_, value)| **value == qrcode::Color::Dark) + .for_each(|(index, _)| { + let row = index / self.state.width; + let column = index % self.state.width; + + frame.fill_rectangle( + Point::new(column as f32, row as f32), + Size::UNIT, + self.dark, + ); + }); + }); + + ( + Primitive::Translate { + translation: Vector::new(bounds.x, bounds.y), + content: Box::new(geometry.into_primitive()), + }, + mouse::Interaction::default(), + ) + } +} + +impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a> +where + B: Backend, +{ + fn into(self) -> Element<'a, Message, Renderer<B>> { + Element::new(self) + } +} + +/// The state of a [`QRCode`]. +/// +/// It stores the data that will be displayed. +#[derive(Debug)] +pub struct State { + contents: Vec<qrcode::Color>, + width: usize, + cache: canvas::Cache, +} + +impl State { + /// Creates a new [`State`] with the provided data. + /// + /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest + /// size to display the data. + pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> { + let encoded = qrcode::QrCode::new(data)?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`ErrorCorrection`]. + pub fn with_error_correction( + data: impl AsRef<[u8]>, + error_correction: ErrorCorrection, + ) -> Result<Self, Error> { + let encoded = qrcode::QrCode::with_error_correction_level( + data, + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`Version`] and + /// [`ErrorCorrection`]. + pub fn with_version( + data: impl AsRef<[u8]>, + version: Version, + error_correction: ErrorCorrection, + ) -> Result<Self, Error> { + let encoded = qrcode::QrCode::with_version( + data, + version.into(), + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + fn build(encoded: qrcode::QrCode) -> Self { + let width = encoded.width(); + let contents = encoded.into_colors(); + + Self { + contents, + width, + cache: canvas::Cache::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The size of a [`QRCode`]. +/// +/// The higher the version the larger the grid of cells, and therefore the more +/// information the [`QRCode`] can carry. +pub enum Version { + /// A normal QR code version. It should be between 1 and 40. + Normal(u8), + + /// A micro QR code version. It should be between 1 and 4. + Micro(u8), +} + +impl From<Version> for qrcode::Version { + fn from(version: Version) -> Self { + match version { + Version::Normal(v) => qrcode::Version::Normal(i16::from(v)), + Version::Micro(v) => qrcode::Version::Micro(i16::from(v)), + } + } +} + +/// The error correction level. +/// +/// It controls the amount of data that can be damaged while still being able +/// to recover the original information. +/// +/// A higher error correction level allows for more corrupted data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorCorrection { + /// Low error correction. 7% of the data can be restored. + Low, + /// Medium error correction. 15% of the data can be restored. + Medium, + /// Quartile error correction. 25% of the data can be restored. + Quartile, + /// High error correction. 30% of the data can be restored. + High, +} + +impl From<ErrorCorrection> for qrcode::EcLevel { + fn from(ec_level: ErrorCorrection) -> Self { + match ec_level { + ErrorCorrection::Low => qrcode::EcLevel::L, + ErrorCorrection::Medium => qrcode::EcLevel::M, + ErrorCorrection::Quartile => qrcode::EcLevel::Q, + ErrorCorrection::High => qrcode::EcLevel::H, + } + } +} + +/// An error that occurred when building a [`State`] for a [`QRCode`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] +pub enum Error { + /// The data is too long to encode in a QR code for the chosen [`Version`]. + #[error( + "The data is too long to encode in a QR code for the chosen version" + )] + DataTooLong, + + /// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid. + #[error( + "The chosen version and error correction level combination is invalid." + )] + InvalidVersion, + + /// One or more characters in the provided data are not supported by the + /// chosen [`Version`]. + #[error( + "One or more characters in the provided data are not supported by the \ + chosen version" + )] + UnsupportedCharacterSet, + + /// The chosen ECI designator is invalid. A valid designator should be + /// between 0 and 999999. + #[error( + "The chosen ECI designator is invalid. A valid designator should be \ + between 0 and 999999." + )] + InvalidEciDesignator, + + /// A character that does not belong to the character set was found. + #[error("A character that does not belong to the character set was found")] + InvalidCharacter, +} + +impl From<qrcode::types::QrError> for Error { + fn from(error: qrcode::types::QrError) -> Self { + use qrcode::types::QrError; + + match error { + QrError::DataTooLong => Error::DataTooLong, + QrError::InvalidVersion => Error::InvalidVersion, + QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet, + QrError::InvalidEciDesignator => Error::InvalidEciDesignator, + QrError::InvalidCharacter => Error::InvalidCharacter, + } + } +} diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs index da41ac47..fd3d8145 100644 --- a/graphics/src/widget/radio.rs +++ b/graphics/src/widget/radio.rs @@ -42,7 +42,7 @@ where let radio = Primitive::Quad { bounds, background: style.background, - border_radius: (size / 2.0) as u16, + border_radius: size / 2.0, border_width: style.border_width, border_color: style.border_color, }; @@ -58,8 +58,8 @@ where height: bounds.height - dot_size, }, background: Background::Color(style.dot_color), - border_radius: (dot_size / 2.0) as u16, - border_width: 0, + border_radius: dot_size / 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/rule.rs b/graphics/src/widget/rule.rs index a7a5d0e7..835ebed8 100644 --- a/graphics/src/widget/rule.rs +++ b/graphics/src/widget/rule.rs @@ -43,7 +43,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } else { @@ -63,7 +63,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } }; diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index fed79c18..57065ba2 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -103,7 +103,7 @@ where }; let is_scrollbar_visible = - style.background.is_some() || style.border_width > 0; + style.background.is_some() || style.border_width > 0.0; let scroller = if is_mouse_over || state.is_scroller_grabbed() diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index 99f0a098..051e18b8 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -57,8 +57,8 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.0), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, Primitive::Quad { @@ -69,8 +69,8 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.1), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, ); @@ -82,7 +82,7 @@ where .shape { HandleShape::Circle { radius } => { - (f32::from(radius * 2), f32::from(radius * 2), radius) + (radius * 2.0, radius * 2.0, radius) } HandleShape::Rectangle { width, diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 575d67f5..55eb34e4 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -149,8 +149,8 @@ where background: Background::Color( style_sheet.value_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, offset, @@ -193,8 +193,8 @@ where background: Background::Color( style_sheet.selection_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, if end == right { diff --git a/native/src/element.rs b/native/src/element.rs index 10e1b5fb..9703a7db 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,7 +1,8 @@ +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Clipboard, Color, Event, Hasher, Layout, Length, Point, Rectangle, Widget, + Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget, }; /// A generic [`Widget`]. @@ -240,7 +241,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.widget.on_event( event, layout, @@ -248,7 +249,7 @@ where messages, renderer, clipboard, - ); + ) } /// Draws the [`Element`] and its children using the given [`Layout`]. @@ -335,10 +336,10 @@ where messages: &mut Vec<B>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut original_messages = Vec::new(); - self.widget.on_event( + let status = self.widget.on_event( event, layout, cursor_position, @@ -350,6 +351,8 @@ where original_messages .drain(..) .for_each(|message| messages.push((self.mapper)(message))); + + status } fn draw( @@ -423,7 +426,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.element.widget.on_event( event, layout, diff --git a/native/src/event.rs b/native/src/event.rs index 606a71d6..9c079151 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,3 +1,4 @@ +//! Handle events of a user interface. use crate::{keyboard, mouse, window}; /// A user interface event. @@ -6,7 +7,7 @@ use crate::{keyboard, mouse, window}; /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/hecrj/iced/issues -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum Event { /// A keyboard event Keyboard(keyboard::Event), @@ -17,3 +18,41 @@ pub enum Event { /// A window event Window(window::Event), } + +/// The status of an [`Event`] after being processed. +/// +/// [`Event`]: enum.Event.html +/// [`UserInterface`]: ../struct.UserInterface.html +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Event`] was **NOT** handled by any widget. + /// + /// [`Event`]: enum.Event.html + Ignored, + + /// The [`Event`] was handled and processed by a widget. + /// + /// [`Event`]: enum.Event.html + Captured, +} + +impl Status { + /// Merges two [`Status`] into one. + /// + /// `Captured` takes precedence over `Ignored`: + /// + /// ``` + /// use iced_native::event::Status; + /// + /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); + /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); + /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured); + /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured); + /// ``` + pub fn merge(self, b: Self) -> Self { + match self { + Status::Ignored => b, + Status::Captured => Status::Captured, + } + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs index 067e3c0a..d1252eaf 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -35,6 +35,7 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] +pub mod event; pub mod keyboard; pub mod layout; pub mod mouse; @@ -47,7 +48,6 @@ pub mod window; mod clipboard; mod element; -mod event; mod hasher; mod runtime; mod user_interface; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 7c3bec32..56d055d3 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -6,7 +6,9 @@ pub mod menu; pub use element::Element; pub use menu::Menu; -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; +use crate::event::{self, Event}; +use crate::layout; +use crate::{Clipboard, Hasher, Layout, Point, Size}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay<Message, Renderer> @@ -79,6 +81,7 @@ where _messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + event::Status::Ignored } } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index e1fd9b88..3f346695 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,6 +1,8 @@ pub use crate::Overlay; -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; +use crate::event::{self, Event}; +use crate::layout; +use crate::{Clipboard, Hasher, Layout, Point, Size, Vector}; /// A generic [`Overlay`]. /// @@ -67,7 +69,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.overlay.on_event( event, layout, @@ -136,10 +138,10 @@ where messages: &mut Vec<B>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut original_messages = Vec::new(); - self.content.on_event( + let event_status = self.content.on_event( event, layout, cursor_position, @@ -151,6 +153,8 @@ where original_messages .drain(..) .for_each(|message| messages.push((self.mapper)(message))); + + event_status } fn draw( diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 4b392a8e..d99b5940 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -1,8 +1,14 @@ //! Build and show dropdown menus. +use crate::container; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::scrollable; +use crate::text; use crate::{ - container, layout, mouse, overlay, scrollable, text, Clipboard, Container, - Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, - Vector, Widget, + Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle, + Scrollable, Size, Vector, Widget, }; /// A list of selectable options. @@ -235,7 +241,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.container.on_event( event.clone(), layout, @@ -243,7 +249,7 @@ where messages, renderer, clipboard, - ); + ) } fn draw( @@ -336,7 +342,7 @@ where _messages: &mut Vec<Message>, renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let bounds = layout.bounds(); @@ -364,6 +370,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 95e6b60c..76283e30 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -120,14 +120,17 @@ where ); debug.event_processing_started(); - let mut messages = user_interface.update( + let mut messages = Vec::new(); + + let _ = user_interface.update( &self.queued_events, cursor_position, clipboard, renderer, + &mut messages, ); - messages.extend(self.queued_messages.drain(..)); + messages.extend(self.queued_messages.drain(..)); self.queued_events.clear(); debug.event_processing_finished(); diff --git a/native/src/runtime.rs b/native/src/runtime.rs index 9fa031f4..bd814a0b 100644 --- a/native/src/runtime.rs +++ b/native/src/runtime.rs @@ -1,5 +1,6 @@ //! Run commands and subscriptions. -use crate::{Event, Hasher}; +use crate::event::{self, Event}; +use crate::Hasher; /// A native runtime with a generic executor and receiver of results. /// @@ -8,5 +9,10 @@ use crate::{Event, Hasher}; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: ../struct.Subscription.html -pub type Runtime<Executor, Receiver, Message> = - iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>; +pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime< + Hasher, + (Event, event::Status), + Executor, + Receiver, + Message, +>; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 0d002c6c..3cc04188 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,5 +1,6 @@ //! Listen to external events in your application. -use crate::{Event, Hasher}; +use crate::event::{self, Event}; +use crate::Hasher; use iced_futures::futures::stream::BoxStream; /// A request to listen to external events. @@ -15,19 +16,21 @@ use iced_futures::futures::stream::BoxStream; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>; +pub type Subscription<T> = + iced_futures::Subscription<Hasher, (Event, event::Status), T>; /// A stream of runtime events. /// /// It is the input of a [`Subscription`] in the native runtime. /// /// [`Subscription`]: type.Subscription.html -pub type EventStream = BoxStream<'static, Event>; +pub type EventStream = BoxStream<'static, (Event, event::Status)>; /// A native [`Subscription`] tracker. /// /// [`Subscription`]: type.Subscription.html -pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>; +pub type Tracker = + iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>; pub use iced_futures::subscription::Recipe; @@ -37,11 +40,36 @@ use events::Events; /// Returns a [`Subscription`] to all the runtime events. /// -/// This subscription will notify your application of any [`Event`] handled by -/// the runtime. +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. /// /// [`Subscription`]: type.Subscription.html /// [`Event`]: ../enum.Event.html pub fn events() -> Subscription<Event> { - Subscription::from_recipe(Events) + Subscription::from_recipe(Events { + f: |event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }, + }) +} + +/// Returns a [`Subscription`] that filters all the runtime events with the +/// provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +/// +/// [`Subscription`]: type.Subscription.html +/// [`Event`]: ../enum.Event.html +pub fn events_with<Message>( + f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where + Message: 'static + Send, +{ + Subscription::from_recipe(Events { f }) } diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs index ceae467d..f689f3af 100644 --- a/native/src/subscription/events.rs +++ b/native/src/subscription/events.rs @@ -1,18 +1,26 @@ -use crate::{ - subscription::{EventStream, Recipe}, - Event, Hasher, -}; +use crate::event::{self, Event}; +use crate::subscription::{EventStream, Recipe}; +use crate::Hasher; +use iced_futures::futures::future; +use iced_futures::futures::StreamExt; use iced_futures::BoxStream; -pub struct Events; +pub struct Events<Message> { + pub(super) f: fn(Event, event::Status) -> Option<Message>, +} -impl Recipe<Hasher, Event> for Events { - type Output = Event; +impl<Message> Recipe<Hasher, (Event, event::Status)> for Events<Message> +where + Message: 'static + Send, +{ + type Output = Message; fn hash(&self, state: &mut Hasher) { use std::hash::Hash; - std::any::TypeId::of::<Self>().hash(state); + struct Marker; + std::any::TypeId::of::<Marker>().hash(state); + self.f.hash(state); } fn stream( @@ -20,5 +28,9 @@ impl Recipe<Hasher, Event> for Events { event_stream: EventStream, ) -> BoxStream<Self::Output> { event_stream + .filter_map(move |(event, status)| { + future::ready((self.f)(event, status)) + }) + .boxed() } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 59d91f42..31bb6b99 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,6 +1,7 @@ +use crate::event::{self, Event}; use crate::layout; use crate::overlay; -use crate::{Clipboard, Element, Event, Layout, Point, Rectangle, Size}; +use crate::{Clipboard, Element, Layout, Point, Rectangle, Size}; use std::hash::Hasher; @@ -169,9 +170,10 @@ where /// /// // Initialize our event storage /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); /// /// loop { - /// // Process system events... + /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), @@ -181,17 +183,18 @@ where /// ); /// /// // Update the user interface - /// let messages = user_interface.update( + /// let event_statuses = user_interface.update( /// &events, /// cursor_position, /// None, /// &renderer, + /// &mut messages /// ); /// /// cache = user_interface.into_cache(); /// /// // Process the produced messages - /// for message in messages { + /// for message in messages.drain(..) { /// counter.update(message); /// } /// } @@ -202,10 +205,9 @@ where cursor_position: Point, clipboard: Option<&dyn Clipboard>, renderer: &Renderer, - ) -> Vec<Message> { - let mut messages = Vec::new(); - - let base_cursor = if let Some(mut overlay) = + messages: &mut Vec<Message>, + ) -> Vec<event::Status> { + let (base_cursor, overlay_statuses) = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { let layer = Self::overlay_layer( @@ -215,16 +217,20 @@ where renderer, ); - for event in events { - overlay.on_event( - event.clone(), - Layout::new(&layer.layout), - cursor_position, - &mut messages, - renderer, - clipboard, - ); - } + let event_statuses = events + .iter() + .cloned() + .map(|event| { + overlay.on_event( + event, + Layout::new(&layer.layout), + cursor_position, + messages, + renderer, + clipboard, + ) + }) + .collect(); let base_cursor = if layer.layout.bounds().contains(cursor_position) { @@ -236,23 +242,28 @@ where self.overlay = Some(layer); - base_cursor + (base_cursor, event_statuses) } else { - cursor_position + (cursor_position, vec![event::Status::Ignored; events.len()]) }; - for event in events { - self.root.widget.on_event( - event.clone(), - Layout::new(&self.base.layout), - base_cursor, - &mut messages, - renderer, - clipboard, - ); - } + events + .iter() + .cloned() + .zip(overlay_statuses.into_iter()) + .map(|(event, overlay_status)| { + let event_status = self.root.widget.on_event( + event, + Layout::new(&self.base.layout), + base_cursor, + messages, + renderer, + clipboard, + ); - messages + event_status.merge(overlay_status) + }) + .collect() } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -293,9 +304,10 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); /// /// loop { - /// // Process system events... + /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), @@ -304,11 +316,13 @@ where /// &mut renderer, /// ); /// - /// let messages = user_interface.update( + /// // Update the user interface + /// let event_statuses = user_interface.update( /// &events, /// cursor_position, /// None, /// &renderer, + /// &mut messages /// ); /// /// // Draw the user interface @@ -316,7 +330,7 @@ where /// /// cache = user_interface.into_cache(); /// - /// for message in messages { + /// for message in messages.drain(..) { /// counter.update(message); /// } /// @@ -388,6 +402,23 @@ where } } + /// Relayouts and returns a new [`UserInterface`] using the provided + /// bounds. + /// + /// [`UserInterface`]: struct.UserInterface.html + pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { + Self::build( + self.root, + bounds, + Cache { + base: self.base, + overlay: self.overlay, + bounds: self.bounds, + }, + renderer, + ) + } + /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the /// process. /// diff --git a/native/src/widget.rs b/native/src/widget.rs index 8687ce6f..d3ffe9c2 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -73,9 +73,10 @@ pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; -use crate::{ - layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point, Rectangle, -}; +use crate::event::{self, Event}; +use crate::layout; +use crate::overlay; +use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle}; /// A component that displays information and allows interaction. /// @@ -182,7 +183,8 @@ where _messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + event::Status::Ignored } /// Returns the overlay of the [`Element`], if there is any. diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 995ba7bc..466f6ac5 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -4,9 +4,11 @@ //! //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; use crate::{ - layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::hash::Hash; @@ -184,31 +186,38 @@ where messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if self.on_press.is_some() { let bounds = layout.bounds(); - self.state.is_pressed = bounds.contains(cursor_position); + if bounds.contains(cursor_position) { + self.state.is_pressed = true; + + return event::Status::Captured; + } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); - let is_clicked = self.state.is_pressed - && bounds.contains(cursor_position); + if self.state.is_pressed { + self.state.is_pressed = false; - self.state.is_pressed = false; + if bounds.contains(cursor_position) { + messages.push(on_press); + } - if is_clicked { - messages.push(on_press); + return event::Status::Captured; } } } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index e389427e..42e52aef 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -1,10 +1,14 @@ //! Show toggle controls using checkboxes. use std::hash::Hash; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::row; +use crate::text; use crate::{ - layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, - HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, - VerticalAlignment, Widget, + Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, + Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; /// A box that can be checked. @@ -161,17 +165,21 @@ where messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { messages.push((self.on_toggle)(!self.is_checked)); + + return event::Status::Captured; } } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e874ad42..42a9e734 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,11 +1,11 @@ //! Distribute content vertically. use std::hash::Hash; +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::u32; @@ -162,9 +162,11 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { + ) -> event::Status { + self.children + .iter_mut() + .zip(layout.children()) + .map(|(child, layout)| { child.widget.on_event( event.clone(), layout, @@ -173,8 +175,8 @@ where renderer, clipboard, ) - }, - ); + }) + .fold(event::Status::Ignored, event::Status::merge) } fn draw( diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 5b04d699..419060db 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -1,9 +1,11 @@ //! Decorate content and apply alignment. use std::hash::Hash; +use crate::event::{self, Event}; +use crate::layout; +use crate::overlay; use crate::{ - layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::u32; @@ -174,7 +176,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.content.widget.on_event( event, layout.children().next().unwrap(), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 276bfae3..acb43276 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -25,12 +25,19 @@ pub use direction::Direction; pub use node::Node; pub use pane::Pane; pub use split::Split; -pub use state::{Focus, State}; +pub use state::State; pub use title_bar::TitleBar; +use crate::container; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::row; +use crate::text; use crate::{ - container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element, - Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -73,7 +80,7 @@ use crate::{ /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// /// let pane_grid = -/// PaneGrid::new(&mut state, |pane, state, focus| { +/// PaneGrid::new(&mut state, |pane, state| { /// pane_grid::Content::new(match state { /// PaneState::SomePane => Text::new("This is some pane"), /// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"), @@ -92,10 +99,9 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> { width: Length, height: Length, spacing: u16, - modifier_keys: keyboard::ModifiersState, + on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, - on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -112,31 +118,13 @@ where /// [`Pane`]: struct.Pane.html pub fn new<T>( state: &'a mut State<T>, - view: impl Fn( - Pane, - &'a mut T, - Option<Focus>, - ) -> Content<'a, Message, Renderer>, + view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>, ) -> Self { let elements = { - let action = state.internal.action(); - let current_focus = action.focus(); - state .panes .iter_mut() - .map(move |(pane, pane_state)| { - let focus = match current_focus { - Some((focused_pane, focus)) - if *pane == focused_pane => - { - Some(focus) - } - _ => None, - }; - - (*pane, view(*pane, pane_state, focus)) - }) + .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) .collect() }; @@ -146,13 +134,9 @@ where width: Length::Fill, height: Length::Fill, spacing: 0, - modifier_keys: keyboard::ModifiersState { - control: true, - ..Default::default() - }, + on_click: None, on_drag: None, on_resize: None, - on_key_press: None, } } @@ -180,18 +164,16 @@ where self } - /// Sets the modifier keys of the [`PaneGrid`]. - /// - /// The modifier keys will need to be pressed to trigger key events. - /// - /// The default modifier key is `Ctrl`. + /// Sets the message that will be produced when a [`Pane`] of the + /// [`PaneGrid`] is clicked. /// + /// [`Pane`]: struct.Pane.html /// [`PaneGrid`]: struct.PaneGrid.html - pub fn modifier_keys( - mut self, - modifier_keys: keyboard::ModifiersState, - ) -> Self { - self.modifier_keys = modifier_keys; + pub fn on_click<F>(mut self, f: F) -> Self + where + F: 'a + Fn(Pane) -> Message, + { + self.on_click = Some(Box::new(f)); self } @@ -225,31 +207,6 @@ where self.on_resize = Some((leeway, Box::new(f))); self } - - /// Captures hotkey interactions with the [`PaneGrid`], using the provided - /// function to produce messages. - /// - /// The function will be called when: - /// - a [`Pane`] is focused - /// - a key is pressed - /// - all the modifier keys are pressed - /// - /// If the function returns `None`, the key press event will be discarded - /// without producing any message. - /// - /// This method is particularly useful to implement hotkey interactions. - /// For instance, you can use it to enable splitting, swapping, or resizing - /// panes by pressing combinations of keys. - /// - /// [`PaneGrid`]: struct.PaneGrid.html - /// [`Pane`]: struct.Pane.html - pub fn on_key_press<F>(mut self, f: F) -> Self - where - F: 'a + Fn(KeyPressEvent) -> Option<Message>, - { - self.on_key_press = Some(Box::new(f)); - self - } } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -268,24 +225,20 @@ where ); if let Some(((pane, content), layout)) = clicked_region.next() { - match &self.on_drag { - Some(on_drag) => { - if content.can_be_picked_at(layout, cursor_position) { - let pane_position = layout.position(); + if let Some(on_click) = &self.on_click { + messages.push(on_click(*pane)); + } - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); + if let Some(on_drag) = &self.on_drag { + if content.can_be_picked_at(layout, cursor_position) { + let pane_position = layout.position(); - self.state.pick_pane(pane, origin); + let origin = cursor_position + - Vector::new(pane_position.x, pane_position.y); - messages - .push(on_drag(DragEvent::Picked { pane: *pane })); - } else { - self.state.focus(pane); - } - } - None => { - self.state.focus(pane); + self.state.pick_pane(pane, origin); + + messages.push(on_drag(DragEvent::Picked { pane: *pane })); } } } @@ -296,7 +249,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, - ) { + ) -> event::Status { if let Some((_, on_resize)) = &self.on_resize { if let Some((split, _)) = self.state.picked_split() { let bounds = layout.bounds(); @@ -323,9 +276,13 @@ where }; messages.push(on_resize(ResizeEvent { split, ratio })); + + return event::Status::Captured; } } } + + event::Status::Ignored } } @@ -390,18 +347,6 @@ pub struct ResizeEvent { pub ratio: f32, } -/// An event produced during a key press interaction of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: struct.PaneGrid.html -#[derive(Debug, Clone, Copy)] -pub struct KeyPressEvent { - /// The key that was pressed. - pub key_code: keyboard::KeyCode, - - /// The state of the modifier keys when the key was pressed. - pub modifiers: keyboard::ModifiersState, -} - impl<'a, Message, Renderer> Widget<Message, Renderer> for PaneGrid<'a, Message, Renderer> where @@ -452,13 +397,17 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + let mut event_status = event::Status::Ignored; + match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { let bounds = layout.bounds(); if bounds.contains(cursor_position) { + event_status = event::Status::Captured; + match self.on_resize { Some((leeway, _)) => { let relative_cursor = Point::new( @@ -495,17 +444,10 @@ where ); } } - } else { - // TODO: Encode cursor availability in the type system - if cursor_position.x > 0.0 && cursor_position.y > 0.0 { - self.state.unfocus(); - } } } mouse::Event::ButtonReleased(mouse::Button::Left) => { if let Some((pane, _)) = self.state.picked_pane() { - self.state.focus(&pane); - if let Some(on_drag) = &self.on_drag { let mut dropped_region = self .elements @@ -527,58 +469,42 @@ where messages.push(on_drag(event)); } + + self.state.idle(); + + event_status = event::Status::Captured; } else if self.state.picked_split().is_some() { - self.state.drop_split(); + self.state.idle(); + + event_status = event::Status::Captured; } } mouse::Event::CursorMoved { .. } => { - self.trigger_resize(layout, cursor_position, messages); + event_status = + self.trigger_resize(layout, cursor_position, messages); } _ => {} }, - Event::Keyboard(keyboard_event) => { - match keyboard_event { - keyboard::Event::KeyPressed { - modifiers, - key_code, - } => { - if let Some(on_key_press) = &self.on_key_press { - // TODO: Discard when event is captured - if let Some(_) = self.state.active_pane() { - if modifiers.matches(self.modifier_keys) { - if let Some(message) = - on_key_press(KeyPressEvent { - key_code, - modifiers, - }) - { - messages.push(message); - } - } - } - } - } - _ => {} - } - } _ => {} } if self.state.picked_pane().is_none() { - { - self.elements.iter_mut().zip(layout.children()).for_each( - |((_, pane), layout)| { - pane.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - }, - ); - } + self.elements + .iter_mut() + .zip(layout.children()) + .map(|((_, pane), layout)| { + pane.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + }) + .fold(event_status, event::Status::merge) + } else { + event::Status::Captured } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 1d339b75..2dac7060 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,8 +1,9 @@ use crate::container; +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::pane_grid::{self, TitleBar}; -use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; /// The content of a [`Pane`]. /// @@ -41,9 +42,9 @@ where self } - /// Sets the style of the [`TitleBar`]. + /// Sets the style of the [`Content`]. /// - /// [`TitleBar`]: struct.TitleBar.html + /// [`Content`]: struct.Content.html pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { self.style = style.into(); self @@ -154,11 +155,13 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + let mut event_status = event::Status::Ignored; + let body_layout = if let Some(title_bar) = &mut self.title_bar { let mut children = layout.children(); - title_bar.on_event( + event_status = title_bar.on_event( event.clone(), children.next().unwrap(), cursor_position, @@ -172,7 +175,7 @@ where layout }; - self.body.on_event( + let body_status = self.body.on_event( event, body_layout, cursor_position, @@ -180,6 +183,8 @@ where renderer, clipboard, ); + + event_status.merge(body_status) } pub(crate) fn hash_layout(&self, state: &mut Hasher) { diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index fb59c846..7a51781e 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -26,22 +26,6 @@ pub struct State<T> { pub(super) internal: Internal, } -/// The current focus of a [`Pane`]. -/// -/// [`Pane`]: struct.Pane.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Focus { - /// The [`Pane`] is just focused. - /// - /// [`Pane`]: struct.Pane.html - Idle, - - /// The [`Pane`] is being dragged. - /// - /// [`Pane`]: struct.Pane.html - Dragging, -} - impl<T> State<T> { /// Creates a new [`State`], initializing the first pane with the provided /// state. @@ -72,7 +56,7 @@ impl<T> State<T> { internal: Internal { layout, last_id, - action: Action::Idle { focus: None }, + action: Action::Idle, }, } } @@ -122,45 +106,10 @@ impl<T> State<T> { &self.internal.layout } - /// Returns the focused [`Pane`] of the [`State`], if there is one. - /// - /// [`Pane`]: struct.Pane.html - /// [`State`]: struct.State.html - pub fn focused(&self) -> Option<Pane> { - self.internal.focused_pane() - } - - /// Returns the active [`Pane`] of the [`State`], if there is one. - /// - /// A [`Pane`] is active if it is focused and is __not__ being dragged. - /// - /// [`Pane`]: struct.Pane.html - /// [`State`]: struct.State.html - pub fn active(&self) -> Option<Pane> { - self.internal.active_pane() - } - /// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// direction, if there is one. /// - /// ## Example - /// You can combine this with [`State::active`] to find the pane that is - /// adjacent to the current active one, and then swap them. For instance: - /// - /// ``` - /// # use iced_native::pane_grid; - /// # - /// # let (mut state, _) = pane_grid::State::new(()); - /// # - /// if let Some(active) = state.active() { - /// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) { - /// state.swap(&active, &adjacent); - /// } - /// } - /// ``` - /// /// [`Pane`]: struct.Pane.html - /// [`State::active`]: struct.State.html#method.active pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> { let regions = self .internal @@ -194,20 +143,6 @@ impl<T> State<T> { Some(*pane) } - /// Focuses the given [`Pane`]. - /// - /// [`Pane`]: struct.Pane.html - pub fn focus(&mut self, pane: &Pane) { - self.internal.focus(pane); - } - - /// Unfocuses the current focused [`Pane`]. - /// - /// [`Pane`]: struct.Pane.html - pub fn unfocus(&mut self) { - self.internal.unfocus(); - } - /// Splits the given [`Pane`] into two in the given [`Axis`] and /// initializing the new [`Pane`] with the provided internal state. /// @@ -236,7 +171,6 @@ impl<T> State<T> { node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); - self.focus(&new_pane); Some((new_pane, new_split)) } @@ -277,13 +211,13 @@ impl<T> State<T> { let _ = self.internal.layout.resize(split, ratio); } - /// Closes the given [`Pane`] and returns its internal state, if it exists. + /// Closes the given [`Pane`] and returns its internal state and its closest + /// sibling, if it exists. /// /// [`Pane`]: struct.Pane.html - pub fn close(&mut self, pane: &Pane) -> Option<T> { + pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { if let Some(sibling) = self.internal.layout.remove(pane) { - self.focus(&sibling); - self.panes.remove(pane) + self.panes.remove(pane).map(|state| (state, sibling)) } else { None } @@ -329,52 +263,12 @@ pub struct Internal { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { - Idle { - focus: Option<Pane>, - }, - Dragging { - pane: Pane, - origin: Point, - focus: Option<Pane>, - }, - Resizing { - split: Split, - axis: Axis, - focus: Option<Pane>, - }, -} - -impl Action { - pub fn focus(&self) -> Option<(Pane, Focus)> { - match self { - Action::Idle { focus } | Action::Resizing { focus, .. } => { - focus.map(|pane| (pane, Focus::Idle)) - } - Action::Dragging { pane, .. } => Some((*pane, Focus::Dragging)), - } - } + Idle, + Dragging { pane: Pane, origin: Point }, + Resizing { split: Split, axis: Axis }, } impl Internal { - pub fn action(&self) -> Action { - self.action - } - - pub fn focused_pane(&self) -> Option<Pane> { - match self.action { - Action::Idle { focus } => focus, - Action::Dragging { focus, .. } => focus, - Action::Resizing { focus, .. } => focus, - } - } - - pub fn active_pane(&self) -> Option<Pane> { - match self.action { - Action::Idle { focus } => focus, - _ => None, - } - } - pub fn picked_pane(&self) -> Option<(Pane, Point)> { match self.action { Action::Dragging { pane, origin, .. } => Some((pane, origin)), @@ -405,17 +299,10 @@ impl Internal { self.layout.split_regions(spacing, size) } - pub fn focus(&mut self, pane: &Pane) { - self.action = Action::Idle { focus: Some(*pane) }; - } - pub fn pick_pane(&mut self, pane: &Pane, origin: Point) { - let focus = self.focused_pane(); - self.action = Action::Dragging { pane: *pane, origin, - focus, }; } @@ -426,26 +313,14 @@ impl Internal { return; } - let focus = self.action.focus().map(|(pane, _)| pane); - self.action = Action::Resizing { split: *split, axis, - focus, }; } - pub fn drop_split(&mut self) { - match self.action { - Action::Resizing { focus, .. } => { - self.action = Action::Idle { focus }; - } - _ => {} - } - } - - pub fn unfocus(&mut self) { - self.action = Action::Idle { focus: None }; + pub fn idle(&mut self) { + self.action = Action::Idle; } pub fn hash_layout(&self, hasher: &mut Hasher) { diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 9dfb9ae4..f8ff43eb 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,8 +1,7 @@ +use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; -use crate::{ - Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size, -}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; /// The title bar of a [`Pane`]. /// @@ -245,7 +244,7 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { if let Some(controls) = &mut self.controls { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -261,7 +260,9 @@ where messages, renderer, clipboard, - ); + ) + } else { + event::Status::Ignored } } } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index e086e367..113197f7 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -1,9 +1,13 @@ //! Display a dropdown list of selectable values. +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::overlay::menu::{self, Menu}; +use crate::scrollable; +use crate::text; use crate::{ - layout, mouse, overlay, - overlay::menu::{self, Menu}, - scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::borrow::Cow; @@ -223,13 +227,15 @@ where messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if *self.is_open { + let event_status = if *self.is_open { // TODO: Encode cursor availability in the type system *self.is_open = cursor_position.x < 0.0 || cursor_position.y < 0.0; + + event::Status::Captured } else if layout.bounds().contains(cursor_position) { let selected = self.selected.as_ref(); @@ -238,15 +244,23 @@ where .options .iter() .position(|option| Some(option) == selected); - } + + event::Status::Captured + } else { + event::Status::Ignored + }; if let Some(last_selection) = self.last_selection.take() { messages.push((self.on_selected)(last_selection)); *self.is_open = false; + + event::Status::Captured + } else { + event_status } } - _ => {} + _ => event::Status::Ignored, } } diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 06d3f846..781fffb1 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,8 +1,12 @@ //! Create choices using radio buttons. +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::row; +use crate::text; use crate::{ - layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, - HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, - VerticalAlignment, Widget, + Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, + Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; use std::hash::Hash; @@ -166,15 +170,19 @@ where messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); + + return event::Status::Captured; } } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index bc8a3df1..6b09d0c8 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,9 +1,9 @@ //! Distribute content horizontally. +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::hash::Hash; @@ -162,9 +162,11 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { + ) -> event::Status { + self.children + .iter_mut() + .zip(layout.children()) + .map(|(child, layout)| { child.widget.on_event( event.clone(), layout, @@ -173,8 +175,8 @@ where renderer, clipboard, ) - }, - ); + }) + .fold(event::Status::Ignored, event::Status::merge) } fn draw( diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 60ec2d7d..92671ddd 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,12 @@ //! Navigate an endless amount of content with a scrollbar. +use crate::column; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; use crate::{ - column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event, - Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Align, Clipboard, Column, Element, Hasher, Layout, Length, Point, + Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -184,14 +189,56 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); - // TODO: Event capture. Nested scrollables should capture scroll events. + let offset = self.state.offset(bounds, content_bounds); + let scrollbar = renderer.scrollbar( + bounds, + content_bounds, + offset, + self.scrollbar_width, + self.scrollbar_margin, + self.scroller_width, + ); + let is_mouse_over_scrollbar = scrollbar + .as_ref() + .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) + .unwrap_or(false); + + let event_status = { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { + Point::new( + cursor_position.x, + cursor_position.y + + self.state.offset(bounds, content_bounds) as f32, + ) + } else { + // TODO: Make `cursor_position` an `Option<Point>` so we can encode + // cursor availability. + // This will probably happen naturally once we add multi-window + // support. + Point::new(cursor_position.x, -1.0) + }; + + self.content.on_event( + event.clone(), + content, + cursor_position, + messages, + renderer, + clipboard, + ) + }; + + if let event::Status::Captured = event_status { + return event::Status::Captured; + } + if is_mouse_over { match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { @@ -204,31 +251,21 @@ where self.state.scroll(y, bounds, content_bounds); } } + + return event::Status::Captured; } _ => {} } } - let offset = self.state.offset(bounds, content_bounds); - let scrollbar = renderer.scrollbar( - bounds, - content_bounds, - offset, - self.scrollbar_width, - self.scrollbar_margin, - self.scroller_width, - ); - let is_mouse_over_scrollbar = scrollbar - .as_ref() - .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) - .unwrap_or(false); - if self.state.is_scroller_grabbed() { match event { Event::Mouse(mouse::Event::ButtonReleased( mouse::Button::Left, )) => { self.state.scroller_grabbed_at = None; + + return event::Status::Captured; } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let (Some(scrollbar), Some(scroller_grabbed_at)) = @@ -242,6 +279,8 @@ where bounds, content_bounds, ); + + return event::Status::Captured; } } _ => {} @@ -266,6 +305,8 @@ where self.state.scroller_grabbed_at = Some(scroller_grabbed_at); + + return event::Status::Captured; } } } @@ -273,28 +314,7 @@ where } } - let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { - Point::new( - cursor_position.x, - cursor_position.y - + self.state.offset(bounds, content_bounds) as f32, - ) - } else { - // TODO: Make `cursor_position` an `Option<Point>` so we can encode - // cursor availability. - // This will probably happen naturally once we add multi-window - // support. - Point::new(cursor_position.x, -1.0) - }; - - self.content.on_event( - event, - content, - cursor_position, - messages, - renderer, - clipboard, - ) + event::Status::Ignored } fn draw( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index d6e366aa..4e38fb86 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,9 +4,11 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; use crate::{ - layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::{hash::Hash, ops::RangeInclusive}; @@ -202,7 +204,7 @@ where messages: &mut Vec<Message>, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut change = || { let bounds = layout.bounds(); if cursor_position.x <= bounds.x { @@ -232,6 +234,8 @@ where if layout.bounds().contains(cursor_position) { change(); self.state.is_dragging = true; + + return event::Status::Captured; } } mouse::Event::ButtonReleased(mouse::Button::Left) => { @@ -240,17 +244,23 @@ where messages.push(on_release); } self.state.is_dragging = false; + + return event::Status::Captured; } } mouse::Event::CursorMoved { .. } => { if self.state.is_dragging { change(); + + return event::Status::Captured; } } _ => {} }, _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 64182f2d..c067de77 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -14,11 +14,13 @@ pub use value::Value; use editor::Editor; +use crate::event::{self, Event}; +use crate::keyboard; +use crate::layout; +use crate::mouse::{self, click}; +use crate::text; use crate::{ - keyboard, layout, - mouse::{self, click}, - text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::u32; @@ -163,6 +165,7 @@ where /// Sets the style of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html + /// [`State`]: struct.State.html pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { self.style = style.into(); self @@ -171,11 +174,63 @@ where /// Returns the current [`State`] of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html + /// [`State`]: struct.State.html pub fn state(&self) -> &State { self.state } } +impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its + /// [`Value`] if provided. + /// + /// [`TextInput`]: struct.TextInput.html + /// [`Renderer`]: trait.Render.html + /// [`Value`]: struct.Value.html + pub fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + value: Option<&Value>, + ) -> Renderer::Output { + let value = value.unwrap_or(&self.value); + let bounds = layout.bounds(); + let text_bounds = layout.children().next().unwrap().bounds(); + + if self.is_secure { + self::Renderer::draw( + renderer, + bounds, + text_bounds, + cursor_position, + self.font, + self.size.unwrap_or(renderer.default_size()), + &self.placeholder, + &value.secure(), + &self.state, + &self.style, + ) + } else { + self::Renderer::draw( + renderer, + bounds, + text_bounds, + cursor_position, + self.font, + self.size.unwrap_or(renderer.default_size()), + &self.placeholder, + value, + &self.state, + &self.style, + ) + } + } +} + impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message, Renderer> where @@ -218,11 +273,13 @@ where messages: &mut Vec<Message>, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let is_clicked = layout.bounds().contains(cursor_position); + self.state.is_focused = is_clicked; + if is_clicked { let text_layout = layout.children().next().unwrap(); let target = cursor_position.x - text_layout.bounds().x; @@ -254,6 +311,8 @@ where } else { self.state.cursor.move_to(0); } + + self.state.is_dragging = true; } click::Kind::Double => { if self.is_secure { @@ -273,17 +332,19 @@ where self.value.next_end_of_word(position), ); } + + self.state.is_dragging = false; } click::Kind::Triple => { self.state.cursor.select_all(&self.value); + self.state.is_dragging = false; } } self.state.last_click = Some(click); - } - self.state.is_dragging = is_clicked; - self.state.is_focused = is_clicked; + return event::Status::Captured; + } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { self.state.is_dragging = false; @@ -314,6 +375,8 @@ where position, ); } + + return event::Status::Captured; } } Event::Keyboard(keyboard::Event::CharacterReceived(c)) @@ -328,167 +391,203 @@ where let message = (self.on_change)(editor.contents()); messages.push(message); + + return event::Status::Captured; } Event::Keyboard(keyboard::Event::KeyPressed { key_code, modifiers, - }) if self.state.is_focused => match key_code { - keyboard::KeyCode::Enter => { - if let Some(on_submit) = self.on_submit.clone() { - messages.push(on_submit); - } - } - keyboard::KeyCode::Backspace => { - if platform::is_jump_modifier_pressed(modifiers) - && self.state.cursor.selection(&self.value).is_none() - { - if self.is_secure { - let cursor_pos = self.state.cursor.end(&self.value); - self.state.cursor.select_range(0, cursor_pos); - } else { - self.state.cursor.select_left_by_words(&self.value); + }) if self.state.is_focused => { + match key_code { + keyboard::KeyCode::Enter => { + if let Some(on_submit) = self.on_submit.clone() { + messages.push(on_submit); } } + keyboard::KeyCode::Backspace => { + if platform::is_jump_modifier_pressed(modifiers) + && self + .state + .cursor + .selection(&self.value) + .is_none() + { + if self.is_secure { + let cursor_pos = + self.state.cursor.end(&self.value); + self.state.cursor.select_range(0, cursor_pos); + } else { + self.state + .cursor + .select_left_by_words(&self.value); + } + } - let mut editor = - Editor::new(&mut self.value, &mut self.state.cursor); + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, + ); - editor.backspace(); + editor.backspace(); - let message = (self.on_change)(editor.contents()); - messages.push(message); - } - keyboard::KeyCode::Delete => { - if platform::is_jump_modifier_pressed(modifiers) - && self.state.cursor.selection(&self.value).is_none() - { - if self.is_secure { - let cursor_pos = self.state.cursor.end(&self.value); - self.state - .cursor - .select_range(cursor_pos, self.value.len()); - } else { - self.state + let message = (self.on_change)(editor.contents()); + messages.push(message); + } + keyboard::KeyCode::Delete => { + if platform::is_jump_modifier_pressed(modifiers) + && self + .state .cursor - .select_right_by_words(&self.value); + .selection(&self.value) + .is_none() + { + if self.is_secure { + let cursor_pos = + self.state.cursor.end(&self.value); + self.state + .cursor + .select_range(cursor_pos, self.value.len()); + } else { + self.state + .cursor + .select_right_by_words(&self.value); + } } - } - let mut editor = - Editor::new(&mut self.value, &mut self.state.cursor); + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, + ); - editor.delete(); + editor.delete(); - let message = (self.on_change)(editor.contents()); - messages.push(message); - } - keyboard::KeyCode::Left => { - if platform::is_jump_modifier_pressed(modifiers) - && !self.is_secure - { - if modifiers.shift { - self.state.cursor.select_left_by_words(&self.value); + let message = (self.on_change)(editor.contents()); + messages.push(message); + } + keyboard::KeyCode::Left => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift { + self.state + .cursor + .select_left_by_words(&self.value); + } else { + self.state + .cursor + .move_left_by_words(&self.value); + } + } else if modifiers.shift { + self.state.cursor.select_left(&self.value) } else { - self.state.cursor.move_left_by_words(&self.value); + self.state.cursor.move_left(&self.value); } - } else if modifiers.shift { - self.state.cursor.select_left(&self.value) - } else { - self.state.cursor.move_left(&self.value); } - } - keyboard::KeyCode::Right => { - if platform::is_jump_modifier_pressed(modifiers) - && !self.is_secure - { - if modifiers.shift { - self.state - .cursor - .select_right_by_words(&self.value); + keyboard::KeyCode::Right => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift { + self.state + .cursor + .select_right_by_words(&self.value); + } else { + self.state + .cursor + .move_right_by_words(&self.value); + } + } else if modifiers.shift { + self.state.cursor.select_right(&self.value) } else { - self.state.cursor.move_right_by_words(&self.value); + self.state.cursor.move_right(&self.value); } - } else if modifiers.shift { - self.state.cursor.select_right(&self.value) - } else { - self.state.cursor.move_right(&self.value); } - } - keyboard::KeyCode::Home => { - if modifiers.shift { - self.state.cursor.select_range( - self.state.cursor.start(&self.value), - 0, - ); - } else { - self.state.cursor.move_to(0); - } - } - keyboard::KeyCode::End => { - if modifiers.shift { - self.state.cursor.select_range( - self.state.cursor.start(&self.value), - self.value.len(), - ); - } else { - self.state.cursor.move_to(self.value.len()); + keyboard::KeyCode::Home => { + if modifiers.shift { + self.state.cursor.select_range( + self.state.cursor.start(&self.value), + 0, + ); + } else { + self.state.cursor.move_to(0); + } } - } - keyboard::KeyCode::V => { - if platform::is_copy_paste_modifier_pressed(modifiers) { - if let Some(clipboard) = clipboard { - let content = match self.state.is_pasting.take() { - Some(content) => content, - None => { - let content: String = clipboard - .content() - .unwrap_or(String::new()) - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = Editor::new( - &mut self.value, - &mut self.state.cursor, + keyboard::KeyCode::End => { + if modifiers.shift { + self.state.cursor.select_range( + self.state.cursor.start(&self.value), + self.value.len(), ); + } else { + self.state.cursor.move_to(self.value.len()); + } + } + keyboard::KeyCode::V => { + if platform::is_copy_paste_modifier_pressed(modifiers) { + if let Some(clipboard) = clipboard { + let content = match self.state.is_pasting.take() + { + Some(content) => content, + None => { + let content: String = clipboard + .content() + .unwrap_or(String::new()) + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + } + }; + + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, + ); - editor.paste(content.clone()); + editor.paste(content.clone()); - let message = (self.on_change)(editor.contents()); - messages.push(message); + let message = + (self.on_change)(editor.contents()); + messages.push(message); - self.state.is_pasting = Some(content); + self.state.is_pasting = Some(content); + } + } else { + self.state.is_pasting = None; } - } else { - self.state.is_pasting = None; } - } - keyboard::KeyCode::A => { - if platform::is_copy_paste_modifier_pressed(modifiers) { - self.state.cursor.select_all(&self.value); + keyboard::KeyCode::A => { + if platform::is_copy_paste_modifier_pressed(modifiers) { + self.state.cursor.select_all(&self.value); + } } + keyboard::KeyCode::Escape => { + self.state.is_focused = false; + self.state.is_dragging = false; + self.state.is_pasting = None; + } + _ => {} } - keyboard::KeyCode::Escape => { - self.state.is_focused = false; - self.state.is_dragging = false; - self.state.is_pasting = None; - } - _ => {} - }, + + return event::Status::Captured; + } Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. - }) => match key_code { - keyboard::KeyCode::V => { - self.state.is_pasting = None; + }) if self.state.is_focused => { + match key_code { + keyboard::KeyCode::V => { + self.state.is_pasting = None; + } + _ => {} } - _ => {} - }, + + return event::Status::Captured; + } _ => {} } + + event::Status::Ignored } fn draw( @@ -499,36 +598,7 @@ where cursor_position: Point, _viewport: &Rectangle, ) -> Renderer::Output { - let bounds = layout.bounds(); - let text_bounds = layout.children().next().unwrap().bounds(); - - if self.is_secure { - self::Renderer::draw( - renderer, - bounds, - text_bounds, - cursor_position, - self.font, - self.size.unwrap_or(renderer.default_size()), - &self.placeholder, - &self.value.secure(), - &self.state, - &self.style, - ) - } else { - self::Renderer::draw( - renderer, - bounds, - text_bounds, - cursor_position, - self.font, - self.size.unwrap_or(renderer.default_size()), - &self.placeholder, - &self.value, - &self.state, - &self.style, - ) - } + self.draw(renderer, layout, cursor_position, None) } fn hash_layout(&self, state: &mut Hasher) { @@ -693,6 +763,20 @@ impl State { self.cursor } + /// Focuses the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn focus(&mut self) { + self.is_focused = true; + } + + /// Unfocuses the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn unfocus(&mut self) { + self.is_focused = false; + } + /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. /// /// [`Cursor`]: struct.Cursor.html diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs index 1e9ba45b..8df74e0c 100644 --- a/native/src/widget/text_input/value.rs +++ b/native/src/widget/text_input/value.rs @@ -21,6 +21,15 @@ impl Value { Self { graphemes } } + /// Returns whether the [`Value`] is empty or not. + /// + /// A [`Value`] is empty when it contains no graphemes. + /// + /// [`Value`]: struct.Value.html + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + /// Returns the total amount of graphemes in the [`Value`]. /// /// [`Value`]: struct.Value.html diff --git a/src/widget.rs b/src/widget.rs index e8fff9cc..fdef89d6 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,6 +30,13 @@ mod platform { )] pub use crate::renderer::widget::canvas; + #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) + )] + pub use crate::renderer::widget::qr_code; + #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. @@ -53,6 +60,10 @@ mod platform { #[cfg(any(feature = "canvas", feature = "glow_canvas"))] #[doc(no_inline)] pub use canvas::Canvas; + + #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] + #[doc(no_inline)] + pub use qr_code::QRCode; } #[cfg(target_arch = "wasm32")] diff --git a/style/src/button.rs b/style/src/button.rs index 1e3844f9..43d27216 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -6,8 +6,8 @@ use iced_core::{Background, Color, Vector}; pub struct Style { pub shadow_offset: Vector, pub background: Option<Background>, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, pub text_color: Color, } @@ -17,8 +17,8 @@ impl std::default::Default for Style { Self { shadow_offset: Vector::default(), background: None, - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, text_color: Color::BLACK, } @@ -72,8 +72,8 @@ impl StyleSheet for Default { Style { shadow_offset: Vector::new(0.0, 0.0), background: Some(Background::Color([0.87, 0.87, 0.87].into())), - border_radius: 2, - border_width: 1, + border_radius: 2.0, + border_width: 1.0, border_color: [0.7, 0.7, 0.7].into(), text_color: Color::BLACK, } diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index 3c645f15..1c5f2460 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -6,8 +6,8 @@ use iced_core::{Background, Color}; pub struct Style { pub background: Background, pub checkmark_color: Color, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, } @@ -25,8 +25,8 @@ impl StyleSheet for Default { Style { background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), checkmark_color: Color::from_rgb(0.3, 0.3, 0.3), - border_radius: 5, - border_width: 1, + border_radius: 5.0, + border_width: 1.0, border_color: Color::from_rgb(0.6, 0.6, 0.6), } } diff --git a/style/src/container.rs b/style/src/container.rs index d2247342..1ce6a7ca 100644 --- a/style/src/container.rs +++ b/style/src/container.rs @@ -6,8 +6,8 @@ use iced_core::{Background, Color}; pub struct Style { pub text_color: Option<Color>, pub background: Option<Background>, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, } @@ -16,8 +16,8 @@ impl std::default::Default for Style { Self { text_color: None, background: None, - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } @@ -36,8 +36,8 @@ impl StyleSheet for Default { Style { text_color: None, background: None, - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } diff --git a/style/src/menu.rs b/style/src/menu.rs index e8321dc7..90985b8f 100644 --- a/style/src/menu.rs +++ b/style/src/menu.rs @@ -5,7 +5,7 @@ use iced_core::{Background, Color}; pub struct Style { pub text_color: Color, pub background: Background, - pub border_width: u16, + pub border_width: f32, pub border_color: Color, pub selected_text_color: Color, pub selected_background: Background, @@ -16,7 +16,7 @@ impl std::default::Default for Style { Self { text_color: Color::BLACK, background: Background::Color([0.87, 0.87, 0.87].into()), - border_width: 1, + border_width: 1.0, border_color: [0.7, 0.7, 0.7].into(), selected_text_color: Color::WHITE, selected_background: Background::Color([0.4, 0.4, 1.0].into()), diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs index fbd431c0..a757ba98 100644 --- a/style/src/pick_list.rs +++ b/style/src/pick_list.rs @@ -6,8 +6,8 @@ use iced_core::{Background, Color}; pub struct Style { pub text_color: Color, pub background: Background, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, pub icon_size: f32, } @@ -17,8 +17,8 @@ impl std::default::Default for Style { Self { text_color: Color::BLACK, background: Background::Color([0.87, 0.87, 0.87].into()), - border_radius: 0, - border_width: 1, + border_radius: 0.0, + border_width: 1.0, border_color: [0.7, 0.7, 0.7].into(), icon_size: 0.7, } diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs index 73503fa8..36be63f9 100644 --- a/style/src/progress_bar.rs +++ b/style/src/progress_bar.rs @@ -6,7 +6,7 @@ use iced_core::{Background, Color}; pub struct Style { pub background: Background, pub bar: Background, - pub border_radius: u16, + pub border_radius: f32, } /// A set of rules that dictate the style of a progress bar. @@ -21,7 +21,7 @@ impl StyleSheet for Default { Style { background: Background::Color(Color::from_rgb(0.6, 0.6, 0.6)), bar: Background::Color(Color::from_rgb(0.3, 0.9, 0.3)), - border_radius: 5, + border_radius: 5.0, } } } diff --git a/style/src/radio.rs b/style/src/radio.rs index 1f0689b9..83310e05 100644 --- a/style/src/radio.rs +++ b/style/src/radio.rs @@ -6,7 +6,7 @@ use iced_core::{Background, Color}; pub struct Style { pub background: Background, pub dot_color: Color, - pub border_width: u16, + pub border_width: f32, pub border_color: Color, } @@ -24,7 +24,7 @@ impl StyleSheet for Default { Style { background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)), dot_color: Color::from_rgb(0.3, 0.3, 0.3), - border_width: 1, + border_width: 1.0, border_color: Color::from_rgb(0.6, 0.6, 0.6), } } diff --git a/style/src/rule.rs b/style/src/rule.rs index 6ba54e33..c809ae2f 100644 --- a/style/src/rule.rs +++ b/style/src/rule.rs @@ -74,7 +74,7 @@ pub struct Style { /// The width (thickness) of the rule line. pub width: u16, /// The radius of the line corners. - pub radius: u16, + pub radius: f32, /// The [`FillMode`] of the rule. /// /// [`FillMode`]: enum.FillMode.html @@ -94,7 +94,7 @@ impl StyleSheet for Default { Style { color: [0.6, 0.6, 0.6, 0.51].into(), width: 1, - radius: 0, + radius: 0.0, fill_mode: FillMode::Percent(90.0), } } diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs index 690c14a2..65da9803 100644 --- a/style/src/scrollable.rs +++ b/style/src/scrollable.rs @@ -5,8 +5,8 @@ use iced_core::{Background, Color}; #[derive(Debug, Clone, Copy)] pub struct Scrollbar { pub background: Option<Background>, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, pub scroller: Scroller, } @@ -15,8 +15,8 @@ pub struct Scrollbar { #[derive(Debug, Clone, Copy)] pub struct Scroller { pub color: Color, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, } @@ -40,13 +40,13 @@ impl StyleSheet for Default { fn active(&self) -> Scrollbar { Scrollbar { background: None, - border_radius: 5, - border_width: 0, + border_radius: 5.0, + border_width: 0.0, border_color: Color::TRANSPARENT, scroller: Scroller { color: [0.0, 0.0, 0.0, 0.7].into(), - border_radius: 5, - border_width: 0, + border_radius: 5.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } diff --git a/style/src/slider.rs b/style/src/slider.rs index 776e180c..9148fcbe 100644 --- a/style/src/slider.rs +++ b/style/src/slider.rs @@ -13,15 +13,15 @@ pub struct Style { pub struct Handle { pub shape: HandleShape, pub color: Color, - pub border_width: u16, + pub border_width: f32, pub border_color: Color, } /// The shape of the handle of a slider. #[derive(Debug, Clone, Copy)] pub enum HandleShape { - Circle { radius: u16 }, - Rectangle { width: u16, border_radius: u16 }, + Circle { radius: f32 }, + Rectangle { width: u16, border_radius: f32 }, } /// A set of rules that dictate the style of a slider. @@ -45,11 +45,11 @@ impl StyleSheet for Default { handle: Handle { shape: HandleShape::Rectangle { width: 8, - border_radius: 4, + border_radius: 4.0, }, color: Color::from_rgb(0.95, 0.95, 0.95), border_color: Color::from_rgb(0.6, 0.6, 0.6), - border_width: 1, + border_width: 1.0, }, } } diff --git a/style/src/text_input.rs b/style/src/text_input.rs index 1cb72364..19acea65 100644 --- a/style/src/text_input.rs +++ b/style/src/text_input.rs @@ -5,8 +5,8 @@ use iced_core::{Background, Color}; #[derive(Debug, Clone, Copy)] pub struct Style { pub background: Background, - pub border_radius: u16, - pub border_width: u16, + pub border_radius: f32, + pub border_width: f32, pub border_color: Color, } @@ -14,8 +14,8 @@ impl std::default::Default for Style { fn default() -> Self { Self { background: Background::Color(Color::WHITE), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } @@ -47,8 +47,8 @@ impl StyleSheet for Default { fn active(&self) -> Style { Style { background: Background::Color(Color::WHITE), - border_radius: 5, - border_width: 1, + border_radius: 5.0, + border_width: 1.0, border_color: Color::from_rgb(0.7, 0.7, 0.7), } } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 05088bbd..4d9f9ada 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -10,19 +10,22 @@ repository = "https://github.com/hecrj/iced" [features] svg = ["resvg"] canvas = ["iced_graphics/canvas"] +qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] [dependencies] wgpu = "0.6" wgpu_glyph = "0.10" glyph_brush = "0.7" -zerocopy = "0.3" -bytemuck = "1.2" raw-window-handle = "0.3" log = "0.4" -guillotiere = "0.5" +guillotiere = "0.6" futures = "0.3" +[dependencies.bytemuck] +version = "1.4" +features = ["derive"] + [dependencies.iced_native] version = "0.2" path = "../native" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 891dba1e..c256ca7e 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -13,7 +13,8 @@ use iced_graphics::layer; use iced_native::Rectangle; use std::cell::RefCell; use std::mem; -use zerocopy::AsBytes; + +use bytemuck::{Pod, Zeroable}; #[cfg(feature = "image")] use iced_native::image; @@ -219,14 +220,14 @@ impl Pipeline { let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image vertex buffer"), - contents: QUAD_VERTS.as_bytes(), + contents: bytemuck::cast_slice(&QUAD_VERTS), usage: wgpu::BufferUsage::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image index buffer"), - contents: QUAD_INDICES.as_bytes(), + contents: bytemuck::cast_slice(&QUAD_INDICES), usage: wgpu::BufferUsage::INDEX, }); @@ -385,12 +386,9 @@ impl Pipeline { device, ); - uniforms_buffer.copy_from_slice( - Uniforms { - transform: transformation.into(), - } - .as_bytes(), - ); + uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms { + transform: transformation.into(), + })); } let mut i = 0; @@ -411,8 +409,9 @@ impl Pipeline { device, ); - instances_buffer - .copy_from_slice(instances[i..i + amount].as_bytes()); + instances_buffer.copy_from_slice(bytemuck::cast_slice( + &instances[i..i + amount], + )); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -463,7 +462,7 @@ impl Pipeline { } #[repr(C)] -#[derive(Clone, Copy, AsBytes)] +#[derive(Clone, Copy, Zeroable, Pod)] pub struct Vertex { _position: [f32; 2], } @@ -486,7 +485,7 @@ const QUAD_VERTS: [Vertex; 4] = [ ]; #[repr(C)] -#[derive(Debug, Clone, Copy, AsBytes)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Instance { _position: [f32; 2], _size: [f32; 2], @@ -500,7 +499,7 @@ impl Instance { } #[repr(C)] -#[derive(Debug, Clone, Copy, AsBytes)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], } diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 4f69df8c..25607dab 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -43,14 +43,14 @@ impl Cache { let memory = match handle.data() { image::Data::Path(path) => { if let Ok(image) = ::image::open(path) { - Memory::Host(image.to_bgra()) + Memory::Host(image.to_bgra8()) } else { Memory::NotFound } } image::Data::Bytes(bytes) => { if let Ok(image) = ::image::load_from_memory(&bytes) { - Memory::Host(image.to_bgra()) + Memory::Host(image.to_bgra8()) } else { Memory::Invalid } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 7648aa7e..95df2e99 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -2,8 +2,6 @@ use crate::image::atlas::{self, Atlas}; use iced_native::svg; use std::collections::{HashMap, HashSet}; -use zerocopy::AsBytes; - pub enum Svg { Loaded(resvg::usvg::Tree), NotFound, @@ -119,7 +117,7 @@ impl Cache { let allocation = texture_atlas.upload( width, height, - canvas.get_data().as_bytes(), + bytemuck::cast_slice(canvas.get_data()), device, encoder, )?; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index d54f2577..24d20cfa 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -2,9 +2,9 @@ use crate::Transformation; use iced_graphics::layer; use iced_native::Rectangle; +use bytemuck::{Pod, Zeroable}; use std::mem; use wgpu::util::DeviceExt; -use zerocopy::AsBytes; #[derive(Debug)] pub struct Pipeline { @@ -156,14 +156,14 @@ impl Pipeline { let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad vertex buffer"), - contents: QUAD_VERTS.as_bytes(), + contents: bytemuck::cast_slice(&QUAD_VERTS), usage: wgpu::BufferUsage::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad index buffer"), - contents: QUAD_INDICES.as_bytes(), + contents: bytemuck::cast_slice(&QUAD_INDICES), usage: wgpu::BufferUsage::INDEX, }); @@ -207,7 +207,7 @@ impl Pipeline { device, ); - constants_buffer.copy_from_slice(uniforms.as_bytes()); + constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); } let mut i = 0; @@ -271,7 +271,7 @@ impl Pipeline { } #[repr(C)] -#[derive(Clone, Copy, AsBytes)] +#[derive(Clone, Copy, Zeroable, Pod)] pub struct Vertex { _position: [f32; 2], } @@ -296,7 +296,7 @@ const QUAD_VERTS: [Vertex; 4] = [ const MAX_INSTANCES: usize = 100_000; #[repr(C)] -#[derive(Debug, Clone, Copy, AsBytes)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], scale: f32, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index bc146c4c..07a180bb 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -11,6 +11,11 @@ pub struct Settings { /// [`Renderer`]: ../struct.Renderer.html pub format: wgpu::TextureFormat, + /// The present mode of the [`Renderer`]. + /// + /// [`Renderer`]: ../struct.Renderer.html + pub present_mode: wgpu::PresentMode, + /// The bytes of the font that will be used by default. /// /// If `None` is provided, a default system font will be chosen. @@ -29,6 +34,7 @@ impl Default for Settings { fn default() -> Settings { Settings { format: wgpu::TextureFormat::Bgra8UnormSrgb, + present_mode: wgpu::PresentMode::Mailbox, default_font: None, default_text_size: 20, antialiasing: None, diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 1d9a4fd2..09a278b1 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -24,6 +24,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 + ); + mat4 i_Transform = mat4( vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv Binary files differindex 7059b51b..fa71ba1e 100644 --- a/wgpu/src/shader/quad.vert.spv +++ b/wgpu/src/shader/quad.vert.spv diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 53ce454b..61a771d8 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,8 +1,9 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; use iced_graphics::layer; + +use bytemuck::{Pod, Zeroable}; use std::mem; -use zerocopy::AsBytes; pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; @@ -322,7 +323,7 @@ impl Pipeline { } } - let uniforms = uniforms.as_bytes(); + let uniforms = bytemuck::cast_slice(&uniforms); if let Some(uniforms_size) = wgpu::BufferSize::new(uniforms.len() as u64) @@ -409,7 +410,7 @@ impl Pipeline { } #[repr(C)] -#[derive(Debug, Clone, Copy, AsBytes)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], // We need to align this to 256 bytes to please `wgpu`... diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 1dae26f5..177ae1b6 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -52,6 +52,14 @@ pub mod canvas; #[doc(no_inline)] pub use canvas::Canvas; +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + pub use iced_native::Space; /// A container that distributes its contents vertically. diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs index 3c47b562..f594473f 100644 --- a/wgpu/src/widget/pane_grid.rs +++ b/wgpu/src/widget/pane_grid.rs @@ -11,8 +11,8 @@ use crate::Renderer; pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node, - Pane, ResizeEvent, Split, State, + Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, + State, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/wgpu/src/widget/qr_code.rs b/wgpu/src/widget/qr_code.rs new file mode 100644 index 00000000..7b1c2408 --- /dev/null +++ b/wgpu/src/widget/qr_code.rs @@ -0,0 +1,2 @@ +//! Encode and display information in a QR code. +pub use iced_graphics::qr_code::*; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 79ffacdd..baa94d4e 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -111,9 +111,9 @@ impl iced_graphics::window::Compositor for Compositor { &wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: self.settings.format, + present_mode: self.settings.present_mode, width, height, - present_mode: wgpu::PresentMode::Mailbox, }, ) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 12f92053..ded60366 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,13 +1,22 @@ //! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + use crate::conversion; use crate::mouse; use crate::{ Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime, Settings, Size, Subscription, }; + +use iced_futures::futures; +use iced_futures::futures::channel::mpsc; use iced_graphics::window; -use iced_graphics::Viewport; -use iced_native::program::{self, Program}; +use iced_native::program::Program; +use iced_native::{Cache, UserInterface}; + +use std::mem::ManuallyDrop; /// An interactive, native cross-platform application. /// @@ -116,279 +125,273 @@ where E: Executor + 'static, C: window::Compositor<Renderer = A::Renderer> + 'static, { - use winit::{ - event, - event_loop::{ControlFlow, EventLoop}, - }; + use futures::task; + use futures::Future; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); + let (compositor, renderer) = C::new(compositor_settings)?; + let event_loop = EventLoop::with_user_event(); + let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; Runtime::new(executor, proxy) }; - let flags = settings.flags; - let (application, init_command) = runtime.enter(|| A::new(flags)); - runtime.spawn(init_command); + let (application, init_command) = { + let flags = settings.flags; + + runtime.enter(|| A::new(flags)) + }; let subscription = application.subscription(); - runtime.track(subscription); - let mut title = application.title(); - let mut mode = application.mode(); - let mut background_color = application.background_color(); - let mut scale_factor = application.scale_factor(); + runtime.spawn(init_command); + runtime.track(subscription); let window = settings .window - .into_builder(&title, mode, event_loop.primary_monitor()) + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) .build(&event_loop) .map_err(Error::WindowCreationFailed)?; - let clipboard = Clipboard::new(&window); - // TODO: Encode cursor availability in the type-system - let mut cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = winit::event::ModifiersState::default(); + let (mut sender, receiver) = mpsc::unbounded(); - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor() * scale_factor, - ); - let mut resized = false; + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + window, + runtime, + debug, + receiver, + )); - let (mut compositor, mut renderer) = C::new(compositor_settings)?; + let mut context = task::Context::from_waker(task::noop_waker_ref()); - let surface = compositor.create_surface(&window); + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; - let mut swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + if let ControlFlow::Exit = control_flow { + return; + } - let mut state = program::State::new( - application, - viewport.logical_size(), - conversion::cursor_position(cursor_position, viewport.scale_factor()), - &mut renderer, - &mut debug, - ); - debug.startup_finished(); + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if state.is_queue_empty() { - return; - } + let poll = instance.as_mut().poll(&mut context); - let command = runtime.enter(|| { - state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - &mut debug, - ) - }); + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} - // If the application was updated - if let Some(command) = command { - runtime.spawn(command); +async fn run_instance<A, E, C>( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + window: winit::window::Window, + mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor<Renderer = A::Renderer> + 'static, +{ + use iced_futures::futures::stream::StreamExt; + use winit::event; - let program = state.program(); + let surface = compositor.create_surface(&window); + let clipboard = Clipboard::new(&window); - // Update subscriptions - let subscription = program.subscription(); - runtime.track(subscription); + let mut state = State::new(&application, &window); + let mut viewport_version = state.viewport_version(); + let mut swap_chain = { + let physical_size = state.physical_size(); - // Update window title - let new_title = program.title(); + compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ) + }; - if title != new_title { - window.set_title(&new_title); + let mut user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + )); - title = new_title; - } + let mut primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + let mut mouse_interaction = mouse::Interaction::default(); - // Update window mode - let new_mode = program.mode(); + let mut events = Vec::new(); + let mut messages = Vec::new(); - if mode != new_mode { - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - new_mode, - )); + debug.startup_finished(); - mode = new_mode; + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && messages.is_empty() { + continue; } - // Update background color - background_color = program.background_color(); + debug.event_processing_started(); - // Update scale factor - let new_scale_factor = program.scale_factor(); + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); - if scale_factor != new_scale_factor { - let size = window.inner_size(); + debug.event_processing_finished(); - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor() * new_scale_factor, - ); + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } - // We relayout the UI with the new logical size. - // The queue is empty, therefore this will never produce - // a `Command`. - // - // TODO: Properly queue `WindowResized` - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), - &mut renderer, + if !messages.is_empty() { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); + + // Update application + update( + &mut application, + &mut runtime, &mut debug, + &mut messages, ); - scale_factor = new_scale_factor; + // Update window + state.synchronize(&application, &window); + + user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); } + + debug.draw_started(); + primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + + window.request_redraw(); + } + event::Event::UserEvent(message) => { + messages.push(message); } + event::Event::RedrawRequested(_) => { + debug.render_started(); + let current_viewport_version = state.viewport_version(); + + if viewport_version != current_viewport_version { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); + + debug.layout_started(); + user_interface = ManuallyDrop::new( + ManuallyDrop::into_inner(user_interface) + .relayout(logical_size, &mut renderer), + ); + debug.layout_finished(); - window.request_redraw(); - } - event::Event::UserEvent(message) => { - state.queue_message(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); - if resized { - let physical_size = viewport.physical_size(); + swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); - swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + viewport_version = current_viewport_version; + } - resized = false; - } + let new_mouse_interaction = compositor.draw( + &mut renderer, + &mut swap_chain, + state.viewport(), + state.background_color(), + &primitive, + &debug.overlay(), + ); - let new_mouse_interaction = compositor.draw( - &mut renderer, - &mut swap_chain, - &viewport, - background_color, - state.primitive(), - &debug.overlay(), - ); + debug.render_finished(); - debug.render_finished(); + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); + mouse_interaction = new_mouse_interaction; + } - mouse_interaction = new_mouse_interaction; + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. } + event::Event::WindowEvent { + event: window_event, + .. + } => { + if requests_exit(&window_event, state.modifiers()) { + break; + } - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - handle_window_event( - &window_event, - &window, - scale_factor, - control_flow, - &mut cursor_position, - &mut modifiers, - &mut viewport, - &mut resized, - &mut debug, - ); - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - state.queue_event(event.clone()); - runtime.broadcast(event); + state.update(&window, &window_event, &mut debug); + + if let Some(event) = conversion::window_event( + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events.push(event); + } } + _ => {} } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } -/// Handles a `WindowEvent` and mutates the provided control flow, keyboard -/// modifiers, viewport, and resized flag accordingly. -pub fn handle_window_event( +/// Returns true if the provided event should cause an [`Application`] to +/// exit. +/// +/// [`Application`]: trait.Application.html +pub fn requests_exit( event: &winit::event::WindowEvent<'_>, - window: &winit::window::Window, - scale_factor: f64, - control_flow: &mut winit::event_loop::ControlFlow, - cursor_position: &mut winit::dpi::PhysicalPosition<f64>, - modifiers: &mut winit::event::ModifiersState, - viewport: &mut Viewport, - resized: &mut bool, - _debug: &mut Debug, -) { - use winit::{event::WindowEvent, event_loop::ControlFlow}; + _modifiers: winit::event::ModifiersState, +) -> bool { + use winit::event::WindowEvent; match event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - *viewport = Viewport::with_physical_size( - size, - window.scale_factor() * scale_factor, - ); - *resized = true; - } - WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - new_inner_size, - } => { - let size = Size::new(new_inner_size.width, new_inner_size.height); - - *viewport = Viewport::with_physical_size( - size, - new_scale_factor * scale_factor, - ); - *resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::CursorMoved { position, .. } => { - *cursor_position = *position; - } - WindowEvent::CursorLeft { .. } => { - // TODO: Encode cursor availability in the type-system - *cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); - } - WindowEvent::ModifiersChanged(new_modifiers) => { - *modifiers = *new_modifiers; - } + WindowEvent::CloseRequested => true, #[cfg(target_os = "macos")] WindowEvent::KeyboardInput { input: @@ -398,19 +401,57 @@ pub fn handle_window_event( .. }, .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::F12), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => _debug.toggle(), - _ => {} + } if _modifiers.logo() => true, + _ => false, + } +} + +/// Builds a [`UserInterface`] for the provided [`Application`], logging +/// [`Debug`] information accordingly. +/// +/// [`UserInterface`]: struct.UserInterface.html +/// [`Application`]: trait.Application.html +/// [`Debug`]: struct.Debug.html +pub fn build_user_interface<'a, A: Application>( + application: &'a mut A, + cache: Cache, + renderer: &mut A::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface<'a, A::Message, A::Renderer> { + debug.view_started(); + let view = application.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} + +/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// resulting [`Command`], and tracking its [`Subscription`]. +/// +/// [`Application`]: trait.Application.html +/// [`Command`]: struct.Command.html +/// [`Subscription`]: struct.Subscription.html +pub fn update<A: Application, E: Executor>( + application: &mut A, + runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, + debug: &mut Debug, + messages: &mut Vec<A::Message>, +) { + for message in messages.drain(..) { + debug.log_message(&message); + + debug.update_started(); + let command = runtime.enter(|| application.update(message)); + debug.update_finished(); + + runtime.spawn(command); } + + let subscription = application.subscription(); + runtime.track(subscription); } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 00000000..4c0bfd34 --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,235 @@ +use crate::conversion; +use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; + +use std::marker::PhantomData; +use winit::event::WindowEvent; +use winit::window::Window; + +/// The state of a windowed [`Application`]. +/// +/// [`Application`]: ../trait.Application.html +#[derive(Debug, Clone)] +pub struct State<A: Application> { + title: String, + mode: Mode, + background_color: Color, + scale_factor: f64, + viewport: Viewport, + viewport_version: usize, + cursor_position: winit::dpi::PhysicalPosition<f64>, + modifiers: winit::event::ModifiersState, + application: PhantomData<A>, +} + +impl<A: Application> State<A> { + /// Creates a new [`State`] for the provided [`Application`] and window. + /// + /// [`State`]: struct.State.html + /// [`Application`]: ../trait.Application.html + pub fn new(application: &A, window: &Window) -> Self { + let title = application.title(); + let mode = application.mode(); + let background_color = application.background_color(); + let scale_factor = application.scale_factor(); + + let viewport = { + let physical_size = window.inner_size(); + + Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor() * scale_factor, + ) + }; + + Self { + title, + mode, + background_color, + scale_factor, + viewport, + viewport_version: 0, + // TODO: Encode cursor availability in the type-system + cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), + modifiers: winit::event::ModifiersState::default(), + application: PhantomData, + } + } + + /// Returns the current background [`Color`] of the [`State`]. + /// + /// [`Color`]: ../struct.Color.html + /// [`State`]: struct.State.html + pub fn background_color(&self) -> Color { + self.background_color + } + + /// Returns the current [`Viewport`] of the [`State`]. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + /// Returns the version of the [`Viewport`] of the [`State`]. + /// + /// The version is incremented every time the [`Viewport`] changes. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn viewport_version(&self) -> usize { + self.viewport_version + } + + /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. + /// + /// [`Size`]: ../struct.Size.html + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn physical_size(&self) -> Size<u32> { + self.viewport.physical_size() + } + + /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. + /// + /// [`Size`]: ../struct.Size.html + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn logical_size(&self) -> Size<f32> { + self.viewport.logical_size() + } + + /// Returns the current scale factor of the [`Viewport`] of the [`State`]. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn scale_factor(&self) -> f64 { + self.viewport.scale_factor() + } + + /// Returns the current cursor position of the [`State`]. + /// + /// [`State`]: struct.State.html + pub fn cursor_position(&self) -> Point { + conversion::cursor_position( + self.cursor_position, + self.viewport.scale_factor(), + ) + } + + /// Returns the current keyboard modifiers of the [`State`]. + /// + /// [`State`]: struct.State.html + pub fn modifiers(&self) -> winit::event::ModifiersState { + self.modifiers + } + + /// Processes the provided window event and updates the [`State`] + /// accordingly. + /// + /// [`State`]: struct.State.html + pub fn update( + &mut self, + window: &Window, + event: &WindowEvent<'_>, + _debug: &mut Debug, + ) { + match event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + self.viewport = Viewport::with_physical_size( + size, + window.scale_factor() * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::ScaleFactorChanged { + scale_factor: new_scale_factor, + new_inner_size, + } => { + let size = + Size::new(new_inner_size.width, new_inner_size.height); + + self.viewport = Viewport::with_physical_size( + size, + new_scale_factor * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = *position; + } + WindowEvent::CursorLeft { .. } => { + // TODO: Encode cursor availability in the type-system + self.cursor_position = + winit::dpi::PhysicalPosition::new(-1.0, -1.0); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = *new_modifiers; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::F12), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => _debug.toggle(), + _ => {} + } + } + + /// Synchronizes the [`State`] with its [`Application`] and its respective + /// window. + /// + /// Normally an [`Application`] should be synchronized with its [`State`] + /// and window after calling [`Application::update`]. + /// + /// [`State`]: struct.State.html + /// [`Application`]: ../trait.Application.html + /// [`Application::update`]: ../trait.Application.html#tymethod.update + pub fn synchronize(&mut self, application: &A, window: &Window) { + // Update window title + let new_title = application.title(); + + if self.title != new_title { + window.set_title(&new_title); + + self.title = new_title; + } + + // Update window mode + let new_mode = application.mode(); + + if self.mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, + )); + + self.mode = new_mode; + } + + // Update background color + self.background_color = application.background_color(); + + // Update scale factor + let new_scale_factor = application.scale_factor(); + + if self.scale_factor != new_scale_factor { + let size = window.inner_size(); + + self.viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor() * new_scale_factor, + ); + + self.scale_factor = new_scale_factor; + } + } +} |