summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml9
-rw-r--r--core/src/keyboard/modifiers_state.rs20
-rw-r--r--examples/bezier_tool/src/main.rs75
-rw-r--r--examples/custom_widget/src/main.rs36
-rw-r--r--examples/game_of_life/src/main.rs74
-rw-r--r--examples/game_of_life/src/style.rs18
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/pane_grid/Cargo.toml3
-rw-r--r--examples/pane_grid/src/main.rs125
-rw-r--r--examples/pokedex/src/main.rs2
-rw-r--r--examples/qr_code/Cargo.toml9
-rw-r--r--examples/qr_code/README.md18
-rw-r--r--examples/qr_code/src/main.rs81
-rw-r--r--examples/scrollable/src/style.rs12
-rw-r--r--examples/stopwatch/src/main.rs2
-rw-r--r--examples/styling/src/main.rs36
-rw-r--r--examples/todos/Cargo.toml2
-rw-r--r--examples/todos/src/main.rs6
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs2
-rw-r--r--glow/Cargo.toml9
-rw-r--r--glow/src/shader/quad.vert5
-rw-r--r--glow/src/widget.rs8
-rw-r--r--glow/src/widget/pane_grid.rs4
-rw-r--r--glow/src/widget/qr_code.rs2
-rw-r--r--glutin/src/application.rs358
-rw-r--r--graphics/Cargo.toml16
-rw-r--r--graphics/src/layer.rs4
-rw-r--r--graphics/src/overlay/menu.rs6
-rw-r--r--graphics/src/primitive.rs4
-rw-r--r--graphics/src/renderer.rs4
-rw-r--r--graphics/src/triangle.rs9
-rw-r--r--graphics/src/viewport.rs2
-rw-r--r--graphics/src/widget.rs8
-rw-r--r--graphics/src/widget/button.rs4
-rw-r--r--graphics/src/widget/canvas.rs21
-rw-r--r--graphics/src/widget/canvas/event.rs3
-rw-r--r--graphics/src/widget/canvas/frame.rs2
-rw-r--r--graphics/src/widget/canvas/program.rs9
-rw-r--r--graphics/src/widget/container.rs2
-rw-r--r--graphics/src/widget/pane_grid.rs4
-rw-r--r--graphics/src/widget/progress_bar.rs4
-rw-r--r--graphics/src/widget/qr_code.rs305
-rw-r--r--graphics/src/widget/radio.rs6
-rw-r--r--graphics/src/widget/rule.rs4
-rw-r--r--graphics/src/widget/scrollable.rs2
-rw-r--r--graphics/src/widget/slider.rs10
-rw-r--r--graphics/src/widget/text_input.rs8
-rw-r--r--native/src/element.rs15
-rw-r--r--native/src/event.rs41
-rw-r--r--native/src/lib.rs2
-rw-r--r--native/src/overlay.rs7
-rw-r--r--native/src/overlay/element.rs12
-rw-r--r--native/src/overlay/menu.rs20
-rw-r--r--native/src/program/state.rs7
-rw-r--r--native/src/runtime.rs12
-rw-r--r--native/src/subscription.rs42
-rw-r--r--native/src/subscription/events.rs28
-rw-r--r--native/src/user_interface.rs99
-rw-r--r--native/src/widget.rs10
-rw-r--r--native/src/widget/button.rs27
-rw-r--r--native/src/widget/checkbox.rs16
-rw-r--r--native/src/widget/column.rs16
-rw-r--r--native/src/widget/container.rs8
-rw-r--r--native/src/widget/pane_grid.rs212
-rw-r--r--native/src/widget/pane_grid/content.rs17
-rw-r--r--native/src/widget/pane_grid/state.rs145
-rw-r--r--native/src/widget/pane_grid/title_bar.rs11
-rw-r--r--native/src/widget/pick_list.rs30
-rw-r--r--native/src/widget/radio.rs16
-rw-r--r--native/src/widget/row.rs16
-rw-r--r--native/src/widget/scrollable.rs100
-rw-r--r--native/src/widget/slider.rs16
-rw-r--r--native/src/widget/text_input.rs414
-rw-r--r--native/src/widget/text_input/value.rs9
-rw-r--r--src/widget.rs11
-rw-r--r--style/src/button.rs12
-rw-r--r--style/src/checkbox.rs8
-rw-r--r--style/src/container.rs12
-rw-r--r--style/src/menu.rs4
-rw-r--r--style/src/pick_list.rs8
-rw-r--r--style/src/progress_bar.rs4
-rw-r--r--style/src/radio.rs4
-rw-r--r--style/src/rule.rs4
-rw-r--r--style/src/scrollable.rs16
-rw-r--r--style/src/slider.rs10
-rw-r--r--style/src/text_input.rs12
-rw-r--r--wgpu/Cargo.toml9
-rw-r--r--wgpu/src/image.rs27
-rw-r--r--wgpu/src/image/raster.rs4
-rw-r--r--wgpu/src/image/vector.rs4
-rw-r--r--wgpu/src/quad.rs12
-rw-r--r--wgpu/src/settings.rs6
-rw-r--r--wgpu/src/shader/quad.vert5
-rw-r--r--wgpu/src/shader/quad.vert.spvbin3372 -> 3604 bytes
-rw-r--r--wgpu/src/triangle.rs7
-rw-r--r--wgpu/src/widget.rs8
-rw-r--r--wgpu/src/widget/pane_grid.rs4
-rw-r--r--wgpu/src/widget/qr_code.rs2
-rw-r--r--wgpu/src/window/compositor.rs2
-rw-r--r--winit/src/application.rs491
-rw-r--r--winit/src/application/state.rs235
102 files changed, 2314 insertions, 1312 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 90ac75bd..a0433732 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
index 7059b51b..fa71ba1e 100644
--- a/wgpu/src/shader/quad.vert.spv
+++ b/wgpu/src/shader/quad.vert.spv
Binary files differ
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;
+ }
+ }
+}