From 852d59752e209d4f3cdc38e164f5eeb31e164700 Mon Sep 17 00:00:00 2001 From: Tom Pridham Date: Sun, 12 Apr 2020 00:51:17 -0600 Subject: add some accessibility features to web widgets --- web/src/widget/checkbox.rs | 20 +++++++++++++++++--- web/src/widget/image.rs | 16 +++++++++++++++- web/src/widget/radio.rs | 31 +++++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 5ebc26c8..10a46661 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -28,6 +28,7 @@ pub struct Checkbox { is_checked: bool, on_toggle: Rc Message>, label: String, + id: String, width: Length, style: Box, } @@ -51,6 +52,7 @@ impl Checkbox { is_checked, on_toggle: Rc::new(f), label: label.into(), + id: Default::default(), width: Length::Shrink, style: Default::default(), } @@ -71,6 +73,14 @@ impl Checkbox { self.style = style.into(); self } + + /// Sets the id of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn id(mut self, id: impl Into) -> Self { + self.id = id.into(); + self + } } impl Widget for Checkbox @@ -85,7 +95,10 @@ where ) -> dodrio::Node<'b> { use dodrio::builder::*; - let checkbox_label = bumpalo::format!(in bump, "{}", self.label); + let checkbox_label = + bumpalo::format!(in bump, "{}", self.label).into_bump_str(); + let checkbox_id = + bumpalo::format!(in bump, "{}", self.id).into_bump_str(); let event_bus = bus.clone(); let on_toggle = self.on_toggle.clone(); @@ -96,6 +109,7 @@ where let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); label(bump) + .attr("for", checkbox_id) .attr( "class", bumpalo::format!(in bump, "{} {}", row_class, spacing_class) @@ -110,6 +124,7 @@ where // TODO: Checkbox styling input(bump) .attr("type", "checkbox") + .attr("id", checkbox_id) .bool_attr("checked", self.is_checked) .on("click", move |_root, vdom, _event| { let msg = on_toggle(!is_checked); @@ -118,8 +133,7 @@ where vdom.schedule_render(); }) .finish(), - span(bump).children(vec![ - text(checkbox_label.into_bump_str())]).finish(), + text(checkbox_label), ]) .finish() } diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index 029ab352..a20bebea 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -22,6 +22,9 @@ pub struct Image { /// The image path pub handle: Handle, + /// The alt text of the image + pub alt: String, + /// The width of the image pub width: Length, @@ -36,6 +39,7 @@ impl Image { pub fn new>(handle: T) -> Self { Image { handle: handle.into(), + alt: Default::default(), width: Length::Shrink, height: Length::Shrink, } @@ -56,6 +60,14 @@ impl Image { self.height = height; self } + + /// Sets the alt text of the [`Image`]. + /// + /// [`Image`]: struct.Image.html + pub fn alt(mut self, alt: impl Into) -> Self { + self.alt = alt.into(); + self + } } impl Widget for Image { @@ -70,8 +82,10 @@ impl Widget for Image { let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() { Data::Path(path) => path.to_str().unwrap_or("") }); + let alt = bumpalo::format!(in bump, "{}", self.alt).into_bump_str(); - let mut image = img(bump).attr("src", src.into_bump_str()); + let mut image = + img(bump).attr("src", src.into_bump_str()).attr("alt", alt); match self.width { Length::Shrink => {} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index 520b24cd..acbac3b6 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -35,6 +35,8 @@ pub struct Radio { is_selected: bool, on_click: Message, label: String, + id: String, + name: String, style: Box, } @@ -63,6 +65,8 @@ impl Radio { is_selected: Some(value) == selected, on_click: f(value), label: label.into(), + id: Default::default(), + name: Default::default(), style: Default::default(), } } @@ -74,6 +78,22 @@ impl Radio { self.style = style.into(); self } + + /// Sets the name attribute of the [`Radio`] button. + /// + /// [`Radio`]: struct.Radio.html + pub fn name(mut self, name: impl Into) -> Self { + self.name = name.into(); + self + } + + /// Sets the id of the [`Radio`] button. + /// + /// [`Radio`]: struct.Radio.html + pub fn id(mut self, id: impl Into) -> Self { + self.id = id.into(); + self + } } impl Widget for Radio @@ -88,7 +108,11 @@ where ) -> dodrio::Node<'b> { use dodrio::builder::*; - let radio_label = bumpalo::format!(in bump, "{}", self.label); + let radio_label = + bumpalo::format!(in bump, "{}", self.label).into_bump_str(); + let radio_name = + bumpalo::format!(in bump, "{}", self.name).into_bump_str(); + let radio_id = bumpalo::format!(in bump, "{}", self.id).into_bump_str(); let event_bus = bus.clone(); let on_click = self.on_click.clone(); @@ -96,16 +120,19 @@ where // TODO: Complete styling label(bump) .attr("style", "display: block; font-size: 20px") + .attr("for", radio_id) .children(vec![ input(bump) .attr("type", "radio") + .attr("id", radio_id) + .attr("name", radio_name) .attr("style", "margin-right: 10px") .bool_attr("checked", self.is_selected) .on("click", move |_root, _vdom, _event| { event_bus.publish(on_click.clone()); }) .finish(), - text(radio_label.into_bump_str()), + text(radio_label), ]) .finish() } -- cgit From 6a2c73d0e0ea710f2b6acfaad2b6a6551f807352 Mon Sep 17 00:00:00 2001 From: bansheerubber Date: Fri, 5 Jun 2020 08:58:34 -0700 Subject: sketch of move_cursor_to commands --- native/src/widget/text_input.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e3a5355b..9e7d504a 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -683,6 +683,22 @@ impl State { pub fn cursor(&self) -> Cursor { self.cursor } + + /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. + /// + /// [`Cursor`]: struct.Cursor.html + /// [`TextInput`]: struct.TextInput.html + pub fn move_cursor_to_front(&mut self) { + self.cursor.move_to(0); + } + + /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text. + /// + /// [`Cursor`]: struct.Cursor.html + /// [`TextInput`]: struct.TextInput.html + pub fn move_cursor_to_end(&mut self) { + self.cursor.move_to(5000); + } } // TODO: Reduce allocations -- cgit From 0d119aa73197d545f12d95e5a2549a68d3067de9 Mon Sep 17 00:00:00 2001 From: bansheerubber Date: Fri, 5 Jun 2020 09:08:36 -0700 Subject: added value to move_cursor_to_end --- native/src/widget/text_input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 9e7d504a..0dbcc3d6 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -696,8 +696,8 @@ impl State { /// /// [`Cursor`]: struct.Cursor.html /// [`TextInput`]: struct.TextInput.html - pub fn move_cursor_to_end(&mut self) { - self.cursor.move_to(5000); + pub fn move_cursor_to_end(&mut self, value: &String) { + self.cursor.move_to(value.len()); } } -- cgit From 98cf9c455a201fec3069f415ce2ddf5e88def242 Mon Sep 17 00:00:00 2001 From: bansheerubber Date: Fri, 5 Jun 2020 09:19:46 -0700 Subject: added move_cursor_to --- native/src/widget/text_input.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 0dbcc3d6..c821247f 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -699,6 +699,14 @@ impl State { pub fn move_cursor_to_end(&mut self, value: &String) { self.cursor.move_to(value.len()); } + + /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. + /// + /// [`Cursor`]: struct.Cursor.html + /// [`TextInput`]: struct.TextInput.html + pub fn move_cursor_to(&mut self, position: usize) { + self.cursor.move_to(position); + } } // TODO: Reduce allocations -- cgit From 19c07da86f6481244577f30262d250df25f43c39 Mon Sep 17 00:00:00 2001 From: bansheerubber Date: Fri, 5 Jun 2020 09:57:18 -0700 Subject: fixed formatting --- native/src/widget/text_input.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c821247f..9d6afffb 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -685,7 +685,7 @@ impl State { } /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. - /// + /// /// [`Cursor`]: struct.Cursor.html /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_to_front(&mut self) { @@ -693,7 +693,7 @@ impl State { } /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text. - /// + /// /// [`Cursor`]: struct.Cursor.html /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_to_end(&mut self, value: &String) { @@ -701,7 +701,7 @@ impl State { } /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. - /// + /// /// [`Cursor`]: struct.Cursor.html /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_to(&mut self, position: usize) { -- cgit From 5260b3072ac0426489a16ab435dc9d533c5ed081 Mon Sep 17 00:00:00 2001 From: bansheerubber Date: Mon, 8 Jun 2020 10:00:25 -0700 Subject: implemented hecrj's suggestion --- native/src/widget/text_input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 9d6afffb..24085606 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -696,8 +696,8 @@ impl State { /// /// [`Cursor`]: struct.Cursor.html /// [`TextInput`]: struct.TextInput.html - pub fn move_cursor_to_end(&mut self, value: &String) { - self.cursor.move_to(value.len()); + pub fn move_cursor_to_end(&mut self) { + self.cursor.move_to(usize::MAX); } /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. -- cgit From 8b93c9cb6a623d8f936e7e7135a2598e3ce4e4ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 Jun 2020 19:41:33 +0200 Subject: Clarify `leeway` meaning in `PaneGrid` --- native/src/widget/pane_grid.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index f8788932..2d21a968 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -211,6 +211,10 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// The `leeway` describes the amount of space around a split that can be /// used to grab it. /// + /// The grabbable area of a split will have a length of `spacing + leeway`, + /// properly centered. In other words, a length of + /// `(spacing + leeway) / 2.0` on either side of the split line. + /// /// [`PaneGrid`]: struct.PaneGrid.html pub fn on_resize(mut self, leeway: u16, f: F) -> Self where -- cgit From 49dbf2c14658cb5f2aafdbb75d826d8ba8fedc31 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Jun 2020 15:45:57 +0200 Subject: Request a redraw only on relevant events --- examples/integration/src/main.rs | 21 ++++++++++++--------- glutin/src/application.rs | 4 ++++ native/src/program/state.rs | 11 +++++++---- winit/src/application.rs | 4 ++++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index db8b4366..4c4b6d84 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -119,16 +119,19 @@ pub fn main() { } } Event::MainEventsCleared => { - // We update iced - let _ = state.update( - None, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + None, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); - // and request a redraw - window.request_redraw(); + // and request a redraw + window.request_redraw(); + } } Event::RedrawRequested(_) => { if resized { diff --git a/glutin/src/application.rs b/glutin/src/application.rs index c777a13b..4f36114c 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -95,6 +95,10 @@ pub fn run( event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { + if state.is_queue_empty() { + return; + } + let command = runtime.enter(|| { state.update( clipboard.as_ref().map(|c| c as _), diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 8716d8b9..bb428198 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -88,6 +88,13 @@ where self.queued_messages.push(message); } + /// Returns whether the event queue of the [`State`] is empty or not. + /// + /// [`State`]: struct.State.html + pub fn is_queue_empty(&self) -> bool { + self.queued_events.is_empty() && self.queued_messages.is_empty() + } + /// Processes all the queued events and messages, rebuilding and redrawing /// the widgets of the linked [`Program`] if necessary. /// @@ -102,10 +109,6 @@ where renderer: &mut P::Renderer, debug: &mut Debug, ) -> Option> { - if self.queued_events.is_empty() && self.queued_messages.is_empty() { - return None; - } - let mut user_interface = build_user_interface( &mut self.program, self.cache.take().unwrap(), diff --git a/winit/src/application.rs b/winit/src/application.rs index 73ac72b2..a5d00407 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -150,6 +150,10 @@ pub fn run( event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { + if state.is_queue_empty() { + return; + } + let command = runtime.enter(|| { state.update( clipboard.as_ref().map(|c| c as _), -- cgit From c3643eaf6d90dc8b60c0b762200bf1f8097cdfe9 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Tue, 2 Jun 2020 20:34:13 +0200 Subject: Add `step` member to slider widgets Both the native and the web slider now have a member `step` to control the least possible change of the slider's value. It defaults to 1.0 for all sliders and can be adjusted with the step method. --- examples/color_palette/src/main.rs | 12 ++++++++--- examples/integration/src/controls.rs | 39 ++++++++++++++++++++++-------------- examples/tour/src/main.rs | 27 ++++++++++++++++--------- native/src/widget/slider.rs | 20 ++++++++++++++---- web/src/widget/slider.rs | 16 +++++++++++++-- 5 files changed, 81 insertions(+), 33 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index cec6ac79..9f39fe56 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -288,9 +288,15 @@ impl ColorPicker { .spacing(10) .align_items(Align::Center) .push(Text::new(C::LABEL).width(Length::Units(50))) - .push(Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3))) - .push(Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3))) - .push(Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v))) + .push( + Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3)).step(0.01), + ) + .push( + Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3)).step(0.01), + ) + .push( + Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v)).step(0.01), + ) .push( Text::new(color.to_string()) .width(Length::Units(185)) diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index e6e74995..824f9f53 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -48,24 +48,33 @@ impl Program for Controls { let sliders = Row::new() .width(Length::Units(500)) .spacing(20) - .push(Slider::new(r, 0.0..=1.0, background_color.r, move |r| { - Message::BackgroundColorChanged(Color { - r, - ..background_color + .push( + Slider::new(r, 0.0..=1.0, background_color.r, move |r| { + Message::BackgroundColorChanged(Color { + r, + ..background_color + }) }) - })) - .push(Slider::new(g, 0.0..=1.0, background_color.g, move |g| { - Message::BackgroundColorChanged(Color { - g, - ..background_color + .step(0.01), + ) + .push( + Slider::new(g, 0.0..=1.0, background_color.g, move |g| { + Message::BackgroundColorChanged(Color { + g, + ..background_color + }) }) - })) - .push(Slider::new(b, 0.0..=1.0, background_color.b, move |b| { - Message::BackgroundColorChanged(Color { - b, - ..background_color + .step(0.01), + ) + .push( + Slider::new(b, 0.0..=1.0, background_color.b, move |b| { + Message::BackgroundColorChanged(Color { + b, + ..background_color + }) }) - })); + .step(0.01), + ); Row::new() .width(Length::Fill) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index c9678b9d..43627cc3 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -500,15 +500,24 @@ impl<'a> Step { .push( Row::new() .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), + .push( + Slider::new(red, 0.0..=1.0, color.r, move |r| { + StepMessage::TextColorChanged(Color { r, ..color }) + }) + .step(0.01), + ) + .push( + Slider::new(green, 0.0..=1.0, color.g, move |g| { + StepMessage::TextColorChanged(Color { g, ..color }) + }) + .step(0.01), + ) + .push( + Slider::new(blue, 0.0..=1.0, color.b, move |b| { + StepMessage::TextColorChanged(Color { b, ..color }) + }) + .step(0.01), + ), ); Self::container("Text") diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 753a49fe..8670628c 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -16,6 +16,8 @@ use std::{hash::Hash, ops::RangeInclusive}; /// /// A [`Slider`] will try to fill the horizontal space of its container. /// +/// The step size defaults to 1.0. +/// /// [`Slider`]: struct.Slider.html /// /// # Example @@ -38,6 +40,7 @@ use std::{hash::Hash, ops::RangeInclusive}; pub struct Slider<'a, Message, Renderer: self::Renderer> { state: &'a mut State, range: RangeInclusive, + step: f32, value: f32, on_change: Box Message>, on_release: Option, @@ -71,6 +74,7 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { state, value: value.max(*range.start()).min(*range.end()), range, + step: 1.0, on_change: Box::new(on_change), on_release: None, width: Length::Fill, @@ -106,6 +110,14 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { self.style = style.into(); self } + + /// Sets the step size of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn step(mut self, step: f32) -> Self { + self.step = step; + self + } } /// The local state of a [`Slider`]. @@ -164,16 +176,16 @@ where ) { let mut change = || { let bounds = layout.bounds(); - if cursor_position.x <= bounds.x { messages.push((self.on_change)(*self.range.start())); } else if cursor_position.x >= bounds.x + bounds.width { messages.push((self.on_change)(*self.range.end())); } else { let percent = (cursor_position.x - bounds.x) / bounds.width; - let value = (self.range.end() - self.range.start()) * percent - + self.range.start(); - + let steps = (percent * (self.range.end() - self.range.start()) + / self.step) + .round(); + let value = steps * self.step + self.range.start(); messages.push((self.on_change)(value)); } }; diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 5aa6439e..60d69798 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -16,6 +16,8 @@ use std::{ops::RangeInclusive, rc::Rc}; /// /// A [`Slider`] will try to fill the horizontal space of its container. /// +/// The step size defaults to 1.0. +/// /// [`Slider`]: struct.Slider.html /// /// # Example @@ -37,6 +39,7 @@ use std::{ops::RangeInclusive, rc::Rc}; pub struct Slider<'a, Message> { _state: &'a mut State, range: RangeInclusive, + step: f32, value: f32, on_change: Rc Message>>, width: Length, @@ -69,6 +72,7 @@ impl<'a, Message> Slider<'a, Message> { _state: state, value: value.max(*range.start()).min(*range.end()), range, + step: 1.0, on_change: Rc::new(Box::new(on_change)), width: Length::Fill, style: Default::default(), @@ -90,6 +94,14 @@ impl<'a, Message> Slider<'a, Message> { self.style = style.into(); self } + + /// Sets the step size of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn step(mut self, step: f32) -> Self { + self.step = step; + self + } } impl<'a, Message> Widget for Slider<'a, Message> @@ -110,15 +122,15 @@ where let min = bumpalo::format!(in bump, "{}", start); let max = bumpalo::format!(in bump, "{}", end); let value = bumpalo::format!(in bump, "{}", self.value); + let step = bumpalo::format!(in bump, "{}", self.step); let on_change = self.on_change.clone(); let event_bus = bus.clone(); - // TODO: Make `step` configurable // TODO: Styling input(bump) .attr("type", "range") - .attr("step", "0.01") + .attr("step", step.into_bump_str()) .attr("min", min.into_bump_str()) .attr("max", max.into_bump_str()) .attr("value", value.into_bump_str()) -- cgit From 7b6f1baa69d86b6b2146bb6d61bf0cd470441db1 Mon Sep 17 00:00:00 2001 From: Vanille-N Date: Thu, 11 Jun 2020 14:58:47 +0200 Subject: Calculated sweep_angle in call to lyon::geom::Arc was actually end_angle Adresses `Arc end_angle should be renamed to span_angle #400` --- graphics/src/widget/canvas/path/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs index 6511fa52..e0e52845 100644 --- a/graphics/src/widget/canvas/path/builder.rs +++ b/graphics/src/widget/canvas/path/builder.rs @@ -84,7 +84,7 @@ impl Builder { radii: math::Vector::new(arc.radii.x, arc.radii.y), x_rotation: math::Angle::radians(arc.rotation), start_angle: math::Angle::radians(arc.start_angle), - sweep_angle: math::Angle::radians(arc.end_angle), + sweep_angle: math::Angle::radians(arc.end_angle - arc.start_angle), }; let _ = self.raw.move_to(arc.sample(0.0)); -- cgit From 4c0286e8acdf0792a9680f6f8212a534a51e3da0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 12 Jun 2020 22:12:15 +0200 Subject: Add `background_color` to `Application` and `Sandbox` --- glow/src/window/compositor.rs | 8 +++++--- glutin/src/application.rs | 5 +++++ graphics/src/lib.rs | 4 ++-- graphics/src/window/compositor.rs | 3 ++- graphics/src/window/gl_compositor.rs | 3 ++- src/application.rs | 18 +++++++++++++++++- src/sandbox.rs | 18 +++++++++++++++++- style/src/lib.rs | 2 ++ wgpu/src/lib.rs | 2 +- wgpu/src/window/compositor.rs | 17 +++++++++++------ winit/src/application.rs | 18 +++++++++++++++++- 11 files changed, 81 insertions(+), 17 deletions(-) diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 2f504ff7..3ad10b59 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -1,4 +1,4 @@ -use crate::{Backend, Renderer, Settings, Viewport}; +use crate::{Backend, Color, Renderer, Settings, Viewport}; use core::ffi::c_void; use glow::HasContext; @@ -21,8 +21,6 @@ impl iced_graphics::window::GLCompositor for Compositor { ) -> (Self, Self::Renderer) { let gl = glow::Context::from_loader_function(loader_function); - gl.clear_color(1.0, 1.0, 1.0, 1.0); - // Enable auto-conversion from/to sRGB gl.enable(glow::FRAMEBUFFER_SRGB); @@ -60,12 +58,16 @@ impl iced_graphics::window::GLCompositor for Compositor { &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, + color: Color, output: &::Output, overlay: &[T], ) -> mouse::Interaction { let gl = &self.gl; + let [r, g, b, a] = color.into_linear(); + unsafe { + gl.clear_color(r, g, b, a); gl.clear(glow::COLOR_BUFFER_BIT); } diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 4f36114c..63d41573 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -47,6 +47,7 @@ pub fn run( let mut title = application.title(); let mut mode = application.mode(); + let mut background_color = application.background_color(); let context = { let builder = settings.window.into_builder( @@ -138,6 +139,9 @@ pub fn run( mode = new_mode; } + + // Update background color + background_color = program.background_color(); } context.window().request_redraw(); @@ -164,6 +168,7 @@ pub fn run( let new_mouse_interaction = compositor.draw( &mut renderer, &viewport, + background_color, state.primitive(), &debug.overlay(), ); diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index b6dda132..77b96655 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -35,6 +35,6 @@ pub use transformation::Transformation; pub use viewport::Viewport; pub use iced_native::{ - Background, Font, HorizontalAlignment, Point, Rectangle, Size, Vector, - VerticalAlignment, + Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, + Vector, VerticalAlignment, }; diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index d5920c95..aa625f43 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -1,4 +1,4 @@ -use crate::Viewport; +use crate::{Color, Viewport}; use iced_native::mouse; use raw_window_handle::HasRawWindowHandle; @@ -49,6 +49,7 @@ pub trait Compositor: Sized { renderer: &mut Self::Renderer, swap_chain: &mut Self::SwapChain, viewport: &Viewport, + background_color: Color, output: &::Output, overlay: &[T], ) -> mouse::Interaction; diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index 542213b5..2ba39d6e 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -1,4 +1,4 @@ -use crate::{Size, Viewport}; +use crate::{Color, Size, Viewport}; use iced_native::mouse; use core::ffi::c_void; @@ -61,6 +61,7 @@ pub trait GLCompositor: Sized { &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, + background_color: Color, output: &::Output, overlay: &[T], ) -> mouse::Interaction; diff --git a/src/application.rs b/src/application.rs index 19cab7da..2de67eb0 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,6 @@ -use crate::{window, Command, Element, Executor, Settings, Subscription}; +use crate::{ + window, Color, Command, Element, Executor, Settings, Subscription, +}; /// An interactive cross-platform application. /// @@ -174,6 +176,16 @@ pub trait Application: Sized { window::Mode::Windowed } + /// Returns the background color of the [`Application`]. + /// + /// By default, it returns [`Color::WHITE`]. + /// + /// [`Application`]: trait.Application.html + /// [`Color::WHITE`]: struct.Color.html#const.WHITE + fn background_color(&self) -> Color { + Color::WHITE + } + /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -256,6 +268,10 @@ where fn subscription(&self) -> Subscription { self.0.subscription() } + + fn background_color(&self) -> Color { + self.0.background_color() + } } #[cfg(target_arch = "wasm32")] diff --git a/src/sandbox.rs b/src/sandbox.rs index c6fa45d0..729d9103 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,4 +1,6 @@ -use crate::{executor, Application, Command, Element, Settings, Subscription}; +use crate::{ + executor, Application, Color, Command, Element, Settings, Subscription, +}; /// A sandboxed [`Application`]. /// @@ -124,6 +126,16 @@ pub trait Sandbox { /// [`Sandbox`]: trait.Sandbox.html fn view(&mut self) -> Element<'_, Self::Message>; + /// Returns the background color of the [`Sandbox`]. + /// + /// By default, it returns [`Color::WHITE`]. + /// + /// [`Application`]: trait.Application.html + /// [`Color::WHITE`]: struct.Color.html#const.WHITE + fn background_color(&self) -> Color { + Color::WHITE + } + /// Runs the [`Sandbox`]. /// /// On native platforms, this method will take control of the current thread @@ -169,4 +181,8 @@ where fn view(&mut self) -> Element<'_, T::Message> { T::view(self) } + + fn background_color(&self) -> Color { + T::background_color(self) + } } diff --git a/style/src/lib.rs b/style/src/lib.rs index 2c5977b5..72d83aec 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -2,6 +2,8 @@ //! //! It contains a set of styles and stylesheets for most of the built-in //! widgets. +pub use iced_core::{Background, Color}; + pub mod button; pub mod checkbox; pub mod container; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index e67221c7..e51a225c 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -36,7 +36,7 @@ mod backend; mod quad; mod text; -pub use iced_graphics::{Antialiasing, Defaults, Primitive, Viewport}; +pub use iced_graphics::{Antialiasing, Color, Defaults, Primitive, Viewport}; pub use wgpu; pub use backend::Backend; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 8345679a..5bdd34bc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,4 +1,4 @@ -use crate::{Backend, Renderer, Settings}; +use crate::{Backend, Color, Renderer, Settings}; use iced_graphics::Viewport; use iced_native::{futures, mouse}; @@ -103,6 +103,7 @@ impl iced_graphics::window::Compositor for Compositor { renderer: &mut Self::Renderer, swap_chain: &mut Self::SwapChain, viewport: &Viewport, + background_color: Color, output: &::Output, overlay: &[T], ) -> mouse::Interaction { @@ -118,11 +119,15 @@ impl iced_graphics::window::Compositor for Compositor { resolve_target: None, load_op: wgpu::LoadOp::Clear, store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, + clear_color: { + let [r, g, b, a] = background_color.into_linear(); + + wgpu::Color { + r: f64::from(r), + g: f64::from(g), + b: f64::from(b), + a: f64::from(a), + } }, }], depth_stencil_attachment: None, diff --git a/winit/src/application.rs b/winit/src/application.rs index a5d00407..ceca5645 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,6 +1,6 @@ //! Create interactive, native cross-platform applications. use crate::{ - conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy, + conversion, mouse, Clipboard, Color, Command, Debug, Executor, Mode, Proxy, Runtime, Settings, Size, Subscription, }; use iced_graphics::window; @@ -73,6 +73,17 @@ pub trait Application: Program { fn mode(&self) -> Mode { Mode::Windowed } + + /// Returns the background [`Color`] of the [`Application`]. + /// + /// By default, it returns [`Color::WHITE`]. + /// + /// [`Color`]: struct.Color.html + /// [`Application`]: trait.Application.html + /// [`Color::WHITE`]: struct.Color.html#const.WHITE + fn background_color(&self) -> Color { + Color::WHITE + } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -112,6 +123,7 @@ pub fn run( let mut title = application.title(); let mut mode = application.mode(); + let mut background_color = application.background_color(); let window = settings .window @@ -193,6 +205,9 @@ pub fn run( mode = new_mode; } + + // Update background color + background_color = program.background_color(); } window.request_redraw(); @@ -219,6 +234,7 @@ pub fn run( &mut renderer, &mut swap_chain, &viewport, + background_color, state.primitive(), &debug.overlay(), ); -- cgit From 0b819de3e22837dc456d028f51bf492891d6c3a5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:17:41 +0200 Subject: Make `Slider` value type generic --- glow/src/widget/slider.rs | 2 +- graphics/src/widget/slider.rs | 4 +-- native/Cargo.toml | 1 + native/src/widget/slider.rs | 78 +++++++++++++++++++++++++++++-------------- wgpu/src/widget/slider.rs | 2 +- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs index cf036829..3a8c2595 100644 --- a/glow/src/widget/slider.rs +++ b/glow/src/widget/slider.rs @@ -13,4 +13,4 @@ pub use iced_native::slider::State; /// values. /// /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. -pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>; +pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>; diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index b00cde9a..da8b5a86 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -16,8 +16,8 @@ pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; /// values. /// /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. -pub type Slider<'a, Message, Backend> = - iced_native::Slider<'a, Message, Renderer>; +pub type Slider<'a, T, Message, Backend> = + iced_native::Slider<'a, T, Message, Renderer>; const HANDLE_HEIGHT: f32 = 22.0; diff --git a/native/Cargo.toml b/native/Cargo.toml index 75b4a56b..13052a93 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -13,6 +13,7 @@ debug = [] [dependencies] twox-hash = "1.5" unicode-segmentation = "1.6" +num-traits = "0.2" [dependencies.iced_core] version = "0.2" diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 8670628c..94f743e8 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -24,7 +24,7 @@ use std::{hash::Hash, ops::RangeInclusive}; /// ``` /// # use iced_native::{slider, renderer::Null}; /// # -/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>; +/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>; /// pub enum Message { /// SliderChanged(f32), /// } @@ -37,18 +37,22 @@ use std::{hash::Hash, ops::RangeInclusive}; /// /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) #[allow(missing_debug_implementations)] -pub struct Slider<'a, Message, Renderer: self::Renderer> { +pub struct Slider<'a, T, Message, Renderer: self::Renderer> { state: &'a mut State, - range: RangeInclusive, - step: f32, - value: f32, - on_change: Box Message>, + range: RangeInclusive, + step: T, + value: T, + on_change: Box Message>, on_release: Option, width: Length, style: Renderer::Style, } -impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { +impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> +where + T: Copy + From + std::cmp::PartialOrd, + Renderer: self::Renderer, +{ /// Creates a new [`Slider`]. /// /// It expects: @@ -63,18 +67,30 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { /// [`State`]: struct.State.html pub fn new( state: &'a mut State, - range: RangeInclusive, - value: f32, + range: RangeInclusive, + value: T, on_change: F, ) -> Self where - F: 'static + Fn(f32) -> Message, + F: 'static + Fn(T) -> Message, { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + Slider { state, - value: value.max(*range.start()).min(*range.end()), + value, range, - step: 1.0, + step: T::from(1), on_change: Box::new(on_change), on_release: None, width: Length::Fill, @@ -114,7 +130,7 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> { /// Sets the step size of the [`Slider`]. /// /// [`Slider`]: struct.Slider.html - pub fn step(mut self, step: f32) -> Self { + pub fn step(mut self, step: T) -> Self { self.step = step; self } @@ -137,9 +153,10 @@ impl State { } } -impl<'a, Message, Renderer> Widget - for Slider<'a, Message, Renderer> +impl<'a, T, Message, Renderer> Widget + for Slider<'a, T, Message, Renderer> where + T: Copy + Into + num_traits::FromPrimitive, Renderer: self::Renderer, Message: Clone, { @@ -181,12 +198,19 @@ where } else if cursor_position.x >= bounds.x + bounds.width { messages.push((self.on_change)(*self.range.end())); } else { - let percent = (cursor_position.x - bounds.x) / bounds.width; - let steps = (percent * (self.range.end() - self.range.start()) - / self.step) - .round(); - let value = steps * self.step + self.range.start(); - messages.push((self.on_change)(value)); + let step: f64 = self.step.into(); + let start: f64 = (*self.range.start()).into(); + let end: f64 = (*self.range.end()).into(); + + let percent = f64::from(cursor_position.x - bounds.x) + / f64::from(bounds.width); + + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + if let Some(value) = T::from_f64(value) { + messages.push((self.on_change)(value)); + } } }; @@ -224,11 +248,14 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { + let start = *self.range.start(); + let end = *self.range.end(); + renderer.draw( layout.bounds(), cursor_position, - self.range.clone(), - self.value, + start.into() as f32..=end.into() as f32, + self.value.into() as f32, self.state.is_dragging, &self.style, ) @@ -281,14 +308,15 @@ pub trait Renderer: crate::Renderer { ) -> Self::Output; } -impl<'a, Message, Renderer> From> +impl<'a, T, Message, Renderer> From> for Element<'a, Message, Renderer> where + T: 'a + Copy + Into + num_traits::FromPrimitive, Renderer: 'a + self::Renderer, Message: 'a + Clone, { fn from( - slider: Slider<'a, Message, Renderer>, + slider: Slider<'a, T, Message, Renderer>, ) -> Element<'a, Message, Renderer> { Element::new(slider) } diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs index cf036829..3a8c2595 100644 --- a/wgpu/src/widget/slider.rs +++ b/wgpu/src/widget/slider.rs @@ -13,4 +13,4 @@ pub use iced_native::slider::State; /// values. /// /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. -pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>; +pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>; -- cgit From c71d83fe0eb2b4cefe27690a922771b8e9e05435 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:34:23 +0200 Subject: Remove unnecessary type annotations in `Slider` --- native/src/widget/slider.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 94f743e8..0b7f424c 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -198,9 +198,9 @@ where } else if cursor_position.x >= bounds.x + bounds.width { messages.push((self.on_change)(*self.range.end())); } else { - let step: f64 = self.step.into(); - let start: f64 = (*self.range.start()).into(); - let end: f64 = (*self.range.end()).into(); + let step = self.step.into(); + let start = (*self.range.start()).into(); + let end = (*self.range.end()).into(); let percent = f64::from(cursor_position.x - bounds.x) / f64::from(bounds.width); -- cgit From 4aed3ede921f84db5e01e1d8fc53b9ef56a74ac4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:34:39 +0200 Subject: Use generic `Slider` in `tour` example --- examples/tour/src/main.rs | 77 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 43627cc3..4f8a4b32 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -190,7 +190,7 @@ enum Step { Welcome, Slider { state: slider::State, - value: u16, + value: u8, }, RowsAndColumns { layout: Layout, @@ -222,13 +222,13 @@ enum Step { #[derive(Debug, Clone)] pub enum StepMessage { - SliderChanged(f32), + SliderChanged(u8), LayoutChanged(Layout), - SpacingChanged(f32), - TextSizeChanged(f32), + SpacingChanged(u16), + TextSizeChanged(u16), TextColorChanged(Color), LanguageSelected(Language), - ImageWidthChanged(f32), + ImageWidthChanged(u16), InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), @@ -249,12 +249,12 @@ impl<'a> Step { } StepMessage::SliderChanged(new_value) => { if let Step::Slider { value, .. } = self { - *value = new_value.round() as u16; + *value = new_value; } } StepMessage::TextSizeChanged(new_size) => { if let Step::Text { size, .. } = self { - *size = new_size.round() as u16; + *size = new_size; } } StepMessage::TextColorChanged(new_color) => { @@ -269,12 +269,12 @@ impl<'a> Step { } StepMessage::SpacingChanged(new_spacing) => { if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing.round() as u16; + *spacing = new_spacing; } } StepMessage::ImageWidthChanged(new_width) => { if let Step::Image { width, .. } = self { - *width = new_width.round() as u16; + *width = new_width; } } StepMessage::InputChanged(new_value) => { @@ -384,7 +384,7 @@ impl<'a> Step { fn slider( state: &'a mut slider::State, - value: u16, + value: u8, ) -> Column<'a, StepMessage> { Self::container("Slider") .push(Text::new( @@ -397,8 +397,8 @@ impl<'a> Step { )) .push(Slider::new( state, - 0.0..=100.0, - value as f32, + 0..=100, + value, StepMessage::SliderChanged, )) .push( @@ -444,8 +444,8 @@ impl<'a> Step { .spacing(10) .push(Slider::new( spacing_slider, - 0.0..=80.0, - spacing as f32, + 0..=80, + spacing, StepMessage::SpacingChanged, )) .push( @@ -486,39 +486,25 @@ impl<'a> Step { ) .push(Slider::new( size_slider, - 10.0..=70.0, - size as f32, + 10..=70, + size, StepMessage::TextSizeChanged, )); let [red, green, blue] = color_sliders; + + let color_sliders = Row::new() + .spacing(10) + .push(color_slider(red, color.r, move |r| Color { r, ..color })) + .push(color_slider(green, color.g, move |g| Color { g, ..color })) + .push(color_slider(blue, color.b, move |b| Color { b, ..color })); + let color_section = Column::new() .padding(20) .spacing(20) .push(Text::new("And its color:")) .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push( - Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - }) - .step(0.01), - ) - .push( - Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - }) - .step(0.01), - ) - .push( - Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - }) - .step(0.01), - ), - ); + .push(color_sliders); Self::container("Text") .push(Text::new( @@ -568,8 +554,8 @@ impl<'a> Step { .push(ferris(width)) .push(Slider::new( slider, - 100.0..=500.0, - width as f32, + 100..=500, + width, StepMessage::ImageWidthChanged, )) .push( @@ -715,6 +701,17 @@ fn button<'a, Message>( .min_width(100) } +fn color_slider( + state: &mut slider::State, + component: f32, + update: impl Fn(f32) -> Color + 'static, +) -> Slider { + Slider::new(state, 0.0..=1.0, f64::from(component), move |c| { + StepMessage::TextColorChanged(update(c as f32)) + }) + .step(0.01) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Language { Rust, -- cgit From 7bc7b603219b6c810e480ef5c7f8c4f7a9bf7cb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:36:10 +0200 Subject: Mention generic range in `Slider` documentation --- native/src/widget/slider.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 0b7f424c..70f2b6ac 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -16,7 +16,8 @@ use std::{hash::Hash, ops::RangeInclusive}; /// /// A [`Slider`] will try to fill the horizontal space of its container. /// -/// The step size defaults to 1.0. +/// The [`Slider`] range of numeric values is generic and its step size defaults +/// to 1 unit. /// /// [`Slider`]: struct.Slider.html /// -- cgit From 3cc32abc7ca3eed0ef7decfb60a3b02292dbe49f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:39:31 +0200 Subject: Reduce slider `step` in `progress_bar` example --- examples/progress_bar/src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs index 43b09928..51b56eda 100644 --- a/examples/progress_bar/src/main.rs +++ b/examples/progress_bar/src/main.rs @@ -36,12 +36,15 @@ impl Sandbox for Progress { Column::new() .padding(20) .push(ProgressBar::new(0.0..=100.0, self.value)) - .push(Slider::new( - &mut self.progress_bar_slider, - 0.0..=100.0, - self.value, - Message::SliderChanged, - )) + .push( + Slider::new( + &mut self.progress_bar_slider, + 0.0..=100.0, + self.value, + Message::SliderChanged, + ) + .step(0.01), + ) .into() } } -- cgit From d7d2e0a8aaa130113a9f2ee07a1bd946ddf97215 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:50:21 +0200 Subject: Increase precision in `color_palette` example --- examples/color_palette/src/main.rs | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 9f39fe56..3186deff 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -269,7 +269,7 @@ struct ColorPicker { trait ColorSpace: Sized { const LABEL: &'static str; - const COMPONENT_RANGES: [RangeInclusive; 3]; + const COMPONENT_RANGES: [RangeInclusive; 3]; fn new(a: f32, b: f32, c: f32) -> Self; @@ -284,19 +284,25 @@ impl ColorPicker { let [s1, s2, s3] = &mut self.sliders; let [cr1, cr2, cr3] = C::COMPONENT_RANGES; + fn slider( + state: &mut slider::State, + range: RangeInclusive, + component: f32, + update: impl Fn(f32) -> C + 'static, + ) -> Slider { + Slider::new(state, range, f64::from(component), move |v| { + update(v as f32) + }) + .step(0.01) + } + Row::new() .spacing(10) .align_items(Align::Center) .push(Text::new(C::LABEL).width(Length::Units(50))) - .push( - Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3)).step(0.01), - ) - .push( - Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3)).step(0.01), - ) - .push( - Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v)).step(0.01), - ) + .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3))) + .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3))) + .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v))) .push( Text::new(color.to_string()) .width(Length::Units(185)) @@ -308,7 +314,7 @@ impl ColorPicker { impl ColorSpace for Color { const LABEL: &'static str = "RGB"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=1.0, 0.0..=1.0, 0.0..=1.0]; fn new(r: f32, g: f32, b: f32) -> Self { @@ -331,7 +337,7 @@ impl ColorSpace for Color { impl ColorSpace for palette::Hsl { const LABEL: &'static str = "HSL"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, saturation: f32, lightness: f32) -> Self { @@ -362,7 +368,7 @@ impl ColorSpace for palette::Hsl { impl ColorSpace for palette::Hsv { const LABEL: &'static str = "HSV"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, saturation: f32, value: f32) -> Self { @@ -385,7 +391,7 @@ impl ColorSpace for palette::Hsv { impl ColorSpace for palette::Hwb { const LABEL: &'static str = "HWB"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, whiteness: f32, blackness: f32) -> Self { @@ -416,7 +422,7 @@ impl ColorSpace for palette::Hwb { impl ColorSpace for palette::Lab { const LABEL: &'static str = "Lab"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=100.0, -128.0..=127.0, -128.0..=127.0]; fn new(l: f32, a: f32, b: f32) -> Self { @@ -434,7 +440,7 @@ impl ColorSpace for palette::Lab { impl ColorSpace for palette::Lch { const LABEL: &'static str = "Lch"; - const COMPONENT_RANGES: [RangeInclusive; 3] = + const COMPONENT_RANGES: [RangeInclusive; 3] = [0.0..=100.0, 0.0..=128.0, 0.0..=360.0]; fn new(l: f32, chroma: f32, hue: f32) -> Self { -- cgit From 6b4a4655c1e533c789308b180f4d3596e62b0179 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 14:59:09 +0200 Subject: Make `Slider` value generic in `iced_web` --- web/Cargo.toml | 1 + web/src/widget/slider.rs | 61 +++++++++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index 12d3865e..88c3102f 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -19,6 +19,7 @@ dodrio = "0.1.0" wasm-bindgen = "0.2.51" wasm-bindgen-futures = "0.4" url = "2.0" +num-traits = "0.2" [dependencies.iced_core] version = "0.2" diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 60d69798..afeedb2d 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -36,17 +36,20 @@ use std::{ops::RangeInclusive, rc::Rc}; /// /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) #[allow(missing_debug_implementations)] -pub struct Slider<'a, Message> { +pub struct Slider<'a, T, Message> { _state: &'a mut State, - range: RangeInclusive, - step: f32, - value: f32, - on_change: Rc Message>>, + range: RangeInclusive, + step: T, + value: T, + on_change: Rc Message>>, width: Length, style: Box, } -impl<'a, Message> Slider<'a, Message> { +impl<'a, T, Message> Slider<'a, T, Message> +where + T: Copy + From + std::cmp::PartialOrd, +{ /// Creates a new [`Slider`]. /// /// It expects: @@ -61,18 +64,30 @@ impl<'a, Message> Slider<'a, Message> { /// [`State`]: struct.State.html pub fn new( state: &'a mut State, - range: RangeInclusive, - value: f32, + range: RangeInclusive, + value: T, on_change: F, ) -> Self where - F: 'static + Fn(f32) -> Message, + F: 'static + Fn(T) -> Message, { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + Slider { _state: state, - value: value.max(*range.start()).min(*range.end()), + value, range, - step: 1.0, + step: T::from(1), on_change: Rc::new(Box::new(on_change)), width: Length::Fill, style: Default::default(), @@ -98,14 +113,15 @@ impl<'a, Message> Slider<'a, Message> { /// Sets the step size of the [`Slider`]. /// /// [`Slider`]: struct.Slider.html - pub fn step(mut self, step: f32) -> Self { + pub fn step(mut self, step: T) -> Self { self.step = step; self } } -impl<'a, Message> Widget for Slider<'a, Message> +impl<'a, T, Message> Widget for Slider<'a, T, Message> where + T: 'static + Copy + Into + num_traits::FromPrimitive, Message: 'static, { fn node<'b>( @@ -119,10 +135,10 @@ where let (start, end) = self.range.clone().into_inner(); - let min = bumpalo::format!(in bump, "{}", start); - let max = bumpalo::format!(in bump, "{}", end); - let value = bumpalo::format!(in bump, "{}", self.value); - let step = bumpalo::format!(in bump, "{}", self.step); + let min = bumpalo::format!(in bump, "{}", start.into()); + let max = bumpalo::format!(in bump, "{}", end.into()); + let value = bumpalo::format!(in bump, "{}", self.value.into()); + let step = bumpalo::format!(in bump, "{}", self.step.into()); let on_change = self.on_change.clone(); let event_bus = bus.clone(); @@ -143,19 +159,22 @@ where Some(slider) => slider, }; - if let Ok(value) = slider.value().parse::() { - event_bus.publish(on_change(value)); + if let Ok(value) = slider.value().parse::() { + if let Some(value) = T::from_f64(value) { + event_bus.publish(on_change(value)); + } } }) .finish() } } -impl<'a, Message> From> for Element<'a, Message> +impl<'a, T, Message> From> for Element<'a, Message> where + T: 'static + Copy + Into + num_traits::FromPrimitive, Message: 'static, { - fn from(slider: Slider<'a, Message>) -> Element<'a, Message> { + fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> { Element::new(slider) } } -- cgit From f5e16312bfa02eac13ce46aa97831c554151e2f8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 13 Jun 2020 15:04:49 +0200 Subject: Update `Slider` docs in `iced_web` --- web/src/widget/slider.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index afeedb2d..a0d9df00 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -16,7 +16,8 @@ use std::{ops::RangeInclusive, rc::Rc}; /// /// A [`Slider`] will try to fill the horizontal space of its container. /// -/// The step size defaults to 1.0. +/// The [`Slider`] range of numeric values is generic and its step size defaults +/// to 1 unit. /// /// [`Slider`]: struct.Slider.html /// -- cgit From b3c192a2e478e9f2a101aecb417e316ed6900a80 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 Jun 2020 00:08:28 +0200 Subject: Make default text size configurable in `Settings` --- glow/src/backend.rs | 6 ++++++ glow/src/settings.rs | 6 ++++++ graphics/src/backend.rs | 3 +++ graphics/src/widget/text.rs | 4 +++- native/src/renderer/null.rs | 4 +++- native/src/widget/checkbox.rs | 10 +++++----- native/src/widget/radio.rs | 10 +++++----- native/src/widget/text.rs | 8 ++++---- src/application.rs | 1 + src/settings.rs | 27 ++++++++++++++++++++++++--- wgpu/src/backend.rs | 8 ++++++++ wgpu/src/settings.rs | 6 ++++++ 12 files changed, 74 insertions(+), 19 deletions(-) diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 882dd605..8b5b4f9c 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -18,6 +18,7 @@ pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, + default_text_size: u16, } impl Backend { @@ -33,6 +34,7 @@ impl Backend { quad_pipeline, text_pipeline, triangle_pipeline, + default_text_size: settings.default_text_size, } } @@ -192,6 +194,10 @@ impl backend::Text for Backend { const ICON_FONT: Font = font::ICONS; const CHECKMARK_ICON: char = font::CHECKMARK_ICON; + fn default_size(&self) -> u16 { + self.default_text_size + } + fn measure( &self, contents: &str, diff --git a/glow/src/settings.rs b/glow/src/settings.rs index dce30029..c2c605ef 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -11,6 +11,11 @@ pub struct Settings { /// If `None` is provided, a default system font will be chosen. pub default_font: Option<&'static [u8]>, + /// The default size of text. + /// + /// By default, it will be set to 20. + pub default_text_size: u16, + /// The antialiasing strategy that will be used for triangle primitives. pub antialiasing: Option, } @@ -19,6 +24,7 @@ impl Default for Settings { fn default() -> Settings { Settings { default_font: None, + default_text_size: 20, antialiasing: None, } } diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 83510311..b73c636e 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -25,6 +25,9 @@ pub trait Text { /// [`ICON_FONT`]: #associatedconst.ICON_FONt const CHECKMARK_ICON: char; + /// Returns the default size of text. + fn default_size(&self) -> u16; + /// Measures the text contents with the given size and font, /// returning the size of a laid out paragraph that fits in the provided /// bounds. diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs index 327f8e29..7e22e680 100644 --- a/graphics/src/widget/text.rs +++ b/graphics/src/widget/text.rs @@ -20,7 +20,9 @@ where { type Font = Font; - const DEFAULT_SIZE: u16 = 20; + fn default_size(&self) -> u16 { + self.backend().default_size() + } fn measure( &self, diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index b8695b9c..b8b0b996 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -49,7 +49,9 @@ impl row::Renderer for Null { impl text::Renderer for Null { type Font = Font; - const DEFAULT_SIZE: u16 = 20; + fn default_size(&self) -> u16 { + 20 + } fn measure( &self, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 5fb13290..44962288 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -32,7 +32,7 @@ pub struct Checkbox { width: Length, size: u16, spacing: u16, - text_size: u16, + text_size: Option, style: Renderer::Style, } @@ -60,7 +60,7 @@ impl width: Length::Shrink, size: ::DEFAULT_SIZE, spacing: Renderer::DEFAULT_SPACING, - text_size: ::DEFAULT_SIZE, + text_size: None, style: Renderer::Style::default(), } } @@ -93,7 +93,7 @@ impl /// /// [`Checkbox`]: struct.Checkbox.html pub fn text_size(mut self, text_size: u16) -> Self { - self.text_size = text_size; + self.text_size = Some(text_size); self } @@ -136,7 +136,7 @@ where .push( Text::new(&self.label) .width(self.width) - .size(self.text_size), + .size(self.text_size.unwrap_or(renderer.default_size())), ) .layout(renderer, limits) } @@ -181,7 +181,7 @@ where defaults, label_layout.bounds(), &self.label, - self.text_size, + self.text_size.unwrap_or(renderer.default_size()), Default::default(), None, HorizontalAlignment::Left, diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index d07a9012..5b8d00e9 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -41,7 +41,7 @@ pub struct Radio { width: Length, size: u16, spacing: u16, - text_size: u16, + text_size: Option, style: Renderer::Style, } @@ -75,7 +75,7 @@ impl width: Length::Shrink, size: ::DEFAULT_SIZE, spacing: Renderer::DEFAULT_SPACING, //15 - text_size: ::DEFAULT_SIZE, + text_size: None, style: Renderer::Style::default(), } } @@ -108,7 +108,7 @@ impl /// /// [`Radio`]: struct.Radio.html pub fn text_size(mut self, text_size: u16) -> Self { - self.text_size = text_size; + self.text_size = Some(text_size); self } @@ -151,7 +151,7 @@ where .push( Text::new(&self.label) .width(self.width) - .size(self.text_size), + .size(self.text_size.unwrap_or(renderer.default_size())), ) .layout(renderer, limits) } @@ -194,7 +194,7 @@ where defaults, label_layout.bounds(), &self.label, - self.text_size, + self.text_size.unwrap_or(renderer.default_size()), Default::default(), None, HorizontalAlignment::Left, diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 0b05b67d..48a69e34 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -131,7 +131,7 @@ where ) -> layout::Node { let limits = limits.width(self.width).height(self.height); - let size = self.size.unwrap_or(Renderer::DEFAULT_SIZE); + let size = self.size.unwrap_or(renderer.default_size()); let bounds = limits.max(); @@ -154,7 +154,7 @@ where defaults, layout.bounds(), &self.content, - self.size.unwrap_or(Renderer::DEFAULT_SIZE), + self.size.unwrap_or(renderer.default_size()), self.font, self.color, self.horizontal_alignment, @@ -187,10 +187,10 @@ pub trait Renderer: crate::Renderer { /// [`Text`]: struct.Text.html type Font: Default + Copy; - /// The default size of [`Text`]. + /// Returns the default size of [`Text`]. /// /// [`Text`]: struct.Text.html - const DEFAULT_SIZE: u16; + fn default_size(&self) -> u16; /// Measures the [`Text`] in the given bounds and returns the minimum /// boundaries that can fit the contents. diff --git a/src/application.rs b/src/application.rs index 2de67eb0..b9b71645 100644 --- a/src/application.rs +++ b/src/application.rs @@ -202,6 +202,7 @@ pub trait Application: Sized { { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, + default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { Some(crate::renderer::settings::Antialiasing::MSAAx4) } else { diff --git a/src/settings.rs b/src/settings.rs index 01ad0ee0..933829bd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@ use crate::window; /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Settings { /// The window settings. /// @@ -22,6 +22,11 @@ pub struct Settings { // TODO: Add `name` for web compatibility pub default_font: Option<&'static [u8]>, + /// The text size that will be used by default. + /// + /// The default value is 20. + pub default_text_size: u16, + /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// @@ -39,12 +44,28 @@ impl Settings { /// /// [`Application`]: ../trait.Application.html pub fn with_flags(flags: Flags) -> Self { + let default_settings = Settings::<()>::default(); + Self { flags, - // not using ..Default::default() struct update syntax since it is more permissive to - // allow initializing with flags without trait bound on Default + antialiasing: default_settings.antialiasing, + default_font: default_settings.default_font, + default_text_size: default_settings.default_text_size, + window: default_settings.window, + } + } +} + +impl Default for Settings +where + Flags: Default, +{ + fn default() -> Self { + Self { + flags: Default::default(), antialiasing: Default::default(), default_font: Default::default(), + default_text_size: 20, window: Default::default(), } } diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9ed4438b..a25f42f7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -24,6 +24,8 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + + default_text_size: u16, } impl Backend { @@ -50,6 +52,8 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + + default_text_size: settings.default_text_size, } } @@ -245,6 +249,10 @@ impl backend::Text for Backend { const ICON_FONT: Font = font::ICONS; const CHECKMARK_ICON: char = font::CHECKMARK_ICON; + fn default_size(&self) -> u16 { + self.default_text_size + } + fn measure( &self, contents: &str, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 4655e64f..bc146c4c 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -16,6 +16,11 @@ pub struct Settings { /// If `None` is provided, a default system font will be chosen. pub default_font: Option<&'static [u8]>, + /// The default size of text. + /// + /// By default, it will be set to 20. + pub default_text_size: u16, + /// The antialiasing strategy that will be used for triangle primitives. pub antialiasing: Option, } @@ -25,6 +30,7 @@ impl Default for Settings { Settings { format: wgpu::TextureFormat::Bgra8UnormSrgb, default_font: None, + default_text_size: 20, antialiasing: None, } } -- cgit From c9696ca687446d78de374a828183de0a5e4bace3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 Jun 2020 19:17:05 +0200 Subject: Add `scale_factor` to `Application` and `Sandbox` --- glutin/src/application.rs | 18 +++++++++++++++++- src/application.rs | 19 +++++++++++++++++++ src/sandbox.rs | 21 ++++++++++++++++++++- winit/src/application.rs | 40 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 63d41573..bcdd9e33 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -48,6 +48,7 @@ pub fn run( let mut title = application.title(); let mut mode = application.mode(); let mut background_color = application.background_color(); + let mut scale_factor = application.scale_factor(); let context = { let builder = settings.window.into_builder( @@ -75,7 +76,7 @@ pub fn run( 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(), + context.window().scale_factor() * scale_factor, ); let mut resized = false; @@ -142,6 +143,20 @@ pub fn run( // Update background color background_color = program.background_color(); + + // Update scale factor + let new_scale_factor = program.scale_factor(); + + if scale_factor != new_scale_factor { + let size = context.window().inner_size(); + + viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + context.window().scale_factor() * new_scale_factor, + ); + + scale_factor = new_scale_factor; + } } context.window().request_redraw(); @@ -195,6 +210,7 @@ pub fn run( application::handle_window_event( &window_event, context.window(), + scale_factor, control_flow, &mut modifiers, &mut viewport, diff --git a/src/application.rs b/src/application.rs index 2de67eb0..92d9cbed 100644 --- a/src/application.rs +++ b/src/application.rs @@ -186,6 +186,21 @@ pub trait Application: Sized { Color::WHITE } + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + /// + /// [`Application`]: trait.Application.html + fn scale_factor(&self) -> f64 { + 1.0 + } + /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread @@ -272,6 +287,10 @@ where fn background_color(&self) -> Color { self.0.background_color() } + + fn scale_factor(&self) -> f64 { + self.0.scale_factor() + } } #[cfg(target_arch = "wasm32")] diff --git a/src/sandbox.rs b/src/sandbox.rs index 729d9103..6a73eab0 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -130,12 +130,27 @@ pub trait Sandbox { /// /// By default, it returns [`Color::WHITE`]. /// - /// [`Application`]: trait.Application.html + /// [`Sandbox`]: trait.Sandbox.html /// [`Color::WHITE`]: struct.Color.html#const.WHITE fn background_color(&self) -> Color { Color::WHITE } + /// Returns the scale factor of the [`Sandbox`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn scale_factor(&self) -> f64 { + 1.0 + } + /// Runs the [`Sandbox`]. /// /// On native platforms, this method will take control of the current thread @@ -185,4 +200,8 @@ where fn background_color(&self) -> Color { T::background_color(self) } + + fn scale_factor(&self) -> f64 { + T::scale_factor(self) + } } diff --git a/winit/src/application.rs b/winit/src/application.rs index ceca5645..b512aace 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -84,6 +84,21 @@ pub trait Application: Program { fn background_color(&self) -> Color { Color::WHITE } + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + /// + /// [`Application`]: trait.Application.html + fn scale_factor(&self) -> f64 { + 1.0 + } } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -124,6 +139,7 @@ pub fn run( let mut title = application.title(); let mut mode = application.mode(); let mut background_color = application.background_color(); + let mut scale_factor = application.scale_factor(); let window = settings .window @@ -138,7 +154,7 @@ pub fn run( let physical_size = window.inner_size(); let mut viewport = Viewport::with_physical_size( Size::new(physical_size.width, physical_size.height), - window.scale_factor(), + window.scale_factor() * scale_factor, ); let mut resized = false; @@ -208,6 +224,20 @@ pub fn run( // Update background color background_color = program.background_color(); + + // Update scale factor + let new_scale_factor = program.scale_factor(); + + if scale_factor != new_scale_factor { + let size = window.inner_size(); + + viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor() * new_scale_factor, + ); + + scale_factor = new_scale_factor; + } } window.request_redraw(); @@ -259,6 +289,7 @@ pub fn run( handle_window_event( &window_event, &window, + scale_factor, control_flow, &mut modifiers, &mut viewport, @@ -286,6 +317,7 @@ pub fn run( pub fn handle_window_event( event: &winit::event::WindowEvent<'_>, window: &winit::window::Window, + scale_factor: f64, control_flow: &mut winit::event_loop::ControlFlow, modifiers: &mut winit::event::ModifiersState, viewport: &mut Viewport, @@ -298,8 +330,10 @@ pub fn handle_window_event( WindowEvent::Resized(new_size) => { let size = Size::new(new_size.width, new_size.height); - *viewport = - Viewport::with_physical_size(size, window.scale_factor()); + *viewport = Viewport::with_physical_size( + size, + window.scale_factor() * scale_factor, + ); *resized = true; } WindowEvent::CloseRequested => { -- cgit From bbdf558bd7eb3abbf69c922b34075360cd5f12c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Jun 2020 06:12:06 +0200 Subject: Relayout when `Application::scale_factor` changes --- glutin/src/application.rs | 13 +++++++++++++ winit/src/application.rs | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index bcdd9e33..93877734 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -155,6 +155,19 @@ pub fn run( context.window().scale_factor() * new_scale_factor, ); + // We relayout the UI with the new logical size. + // The queue is empty, therefore this will never produce + // a `Command`. + // + // TODO: Properly queue `WindowResized` and `CursorMoved` + // events. + let _ = state.update( + clipboard.as_ref().map(|c| c as _), + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + scale_factor = new_scale_factor; } } diff --git a/winit/src/application.rs b/winit/src/application.rs index b512aace..cb1bbf1e 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -236,6 +236,19 @@ pub fn run( window.scale_factor() * new_scale_factor, ); + // We relayout the UI with the new logical size. + // The queue is empty, therefore this will never produce + // a `Command`. + // + // TODO: Properly queue `WindowResized` and `CursorMoved` + // events. + let _ = state.update( + clipboard.as_ref().map(|c| c as _), + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + scale_factor = new_scale_factor; } } -- cgit From f30a666dc81fdc85d225dc83f1a33e32d5dccbd2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Jun 2020 06:44:34 +0200 Subject: Decouple `cursor_position` from `Cache` Instead, we ask explicitly for it in the different `update` and `draw` methods. This way, the runtime can derive the logical position of the cursor from the source of truth. --- examples/integration/src/main.rs | 15 ++++++++++--- glutin/src/application.rs | 18 ++++++++++++---- native/src/program/state.rs | 13 +++++++----- native/src/user_interface.rs | 46 +++++++++++++++++++++++----------------- winit/src/application.rs | 22 +++++++++++++++---- winit/src/conversion.rs | 12 ++++++++++- 6 files changed, 90 insertions(+), 36 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 4c4b6d84..33d4c361 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -5,9 +5,10 @@ use controls::Controls; use scene::Scene; use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{futures, program, winit, Debug, Size}; +use iced_winit::{conversion, futures, program, winit, Debug, Size}; use winit::{ + dpi::PhysicalPosition, event::{Event, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, }; @@ -24,6 +25,7 @@ pub fn main() { Size::new(physical_size.width, physical_size.height), window.scale_factor(), ); + let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); let mut modifiers = ModifiersState::default(); // Initialize wgpu @@ -79,6 +81,7 @@ pub fn main() { let mut state = program::State::new( controls, viewport.logical_size(), + conversion::cursor_position(cursor_position, viewport.scale_factor()), &mut renderer, &mut debug, ); @@ -91,6 +94,9 @@ pub fn main() { match event { Event::WindowEvent { event, .. } => { match event { + WindowEvent::CursorMoved { position, .. } => { + cursor_position = position; + } WindowEvent::ModifiersChanged(new_modifiers) => { modifiers = new_modifiers; } @@ -105,7 +111,6 @@ pub fn main() { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } - _ => {} } @@ -123,8 +128,12 @@ pub fn main() { if !state.is_queue_empty() { // We update iced let _ = state.update( - None, viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + None, &mut renderer, &mut debug, ); diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 93877734..3be9b65f 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -70,6 +70,7 @@ pub fn run( }; 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(); @@ -90,6 +91,7 @@ pub fn run( let mut state = program::State::new( application, viewport.logical_size(), + conversion::cursor_position(cursor_position, viewport.scale_factor()), &mut renderer, &mut debug, ); @@ -103,8 +105,12 @@ pub fn run( let command = runtime.enter(|| { state.update( - clipboard.as_ref().map(|c| c as _), viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + clipboard.as_ref().map(|c| c as _), &mut renderer, &mut debug, ) @@ -159,11 +165,14 @@ pub fn run( // The queue is empty, therefore this will never produce // a `Command`. // - // TODO: Properly queue `WindowResized` and `CursorMoved` - // events. + // TODO: Properly queue `WindowResized` let _ = state.update( - clipboard.as_ref().map(|c| c as _), viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + clipboard.as_ref().map(|c| c as _), &mut renderer, &mut debug, ); @@ -225,6 +234,7 @@ pub fn run( context.window(), scale_factor, control_flow, + &mut cursor_position, &mut modifiers, &mut viewport, &mut resized, diff --git a/native/src/program/state.rs b/native/src/program/state.rs index bb428198..fdc42e8b 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,5 +1,5 @@ use crate::{ - Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size, + Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size, UserInterface, }; @@ -31,6 +31,7 @@ where pub fn new( mut program: P, bounds: Size, + cursor_position: Point, renderer: &mut P::Renderer, debug: &mut Debug, ) -> Self { @@ -43,7 +44,7 @@ where ); debug.draw_started(); - let primitive = user_interface.draw(renderer); + let primitive = user_interface.draw(renderer, cursor_position); debug.draw_finished(); let cache = Some(user_interface.into_cache()); @@ -104,8 +105,9 @@ where /// [`Program`]: trait.Program.html pub fn update( &mut self, - clipboard: Option<&dyn Clipboard>, bounds: Size, + cursor_position: Point, + clipboard: Option<&dyn Clipboard>, renderer: &mut P::Renderer, debug: &mut Debug, ) -> Option> { @@ -120,6 +122,7 @@ where debug.event_processing_started(); let mut messages = user_interface.update( self.queued_events.drain(..), + cursor_position, clipboard, renderer, ); @@ -128,7 +131,7 @@ where if messages.is_empty() { debug.draw_started(); - self.primitive = user_interface.draw(renderer); + self.primitive = user_interface.draw(renderer, cursor_position); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); @@ -159,7 +162,7 @@ where ); debug.draw_started(); - self.primitive = user_interface.draw(renderer); + self.primitive = user_interface.draw(renderer, cursor_position); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index e963b601..b9646043 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,4 +1,4 @@ -use crate::{layout, mouse, Clipboard, Element, Event, Layout, Point, Size}; +use crate::{layout, Clipboard, Element, Event, Layout, Point, Size}; use std::hash::Hasher; @@ -23,7 +23,6 @@ pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, layout: layout::Node, bounds: Size, - cursor_position: Point, } impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> @@ -115,7 +114,6 @@ where root, layout, bounds, - cursor_position: cache.cursor_position, } } @@ -132,7 +130,7 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{UserInterface, Cache, Size}; + /// use iced_native::{UserInterface, Cache, Size, Point}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -154,6 +152,7 @@ where /// let mut cache = Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); + /// let mut cursor_position = Point::default(); /// /// // Initialize our event storage /// let mut events = Vec::new(); @@ -169,7 +168,12 @@ where /// ); /// /// // Update the user interface - /// let messages = user_interface.update(events.drain(..), None, &renderer); + /// let messages = user_interface.update( + /// events.drain(..), + /// cursor_position, + /// None, + /// &renderer, + /// ); /// /// cache = user_interface.into_cache(); /// @@ -182,20 +186,17 @@ where pub fn update( &mut self, events: impl IntoIterator, + cursor_position: Point, clipboard: Option<&dyn Clipboard>, renderer: &Renderer, ) -> Vec { let mut messages = Vec::new(); for event in events { - if let Event::Mouse(mouse::Event::CursorMoved { x, y }) = event { - self.cursor_position = Point::new(x, y); - } - self.root.widget.on_event( event, Layout::new(&self.layout), - self.cursor_position, + cursor_position, &mut messages, renderer, clipboard, @@ -219,7 +220,7 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::{UserInterface, Cache, Size}; + /// use iced_native::{UserInterface, Cache, Size, Point}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -241,6 +242,7 @@ where /// let mut cache = Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); + /// let mut cursor_position = Point::default(); /// let mut events = Vec::new(); /// /// loop { @@ -253,10 +255,15 @@ where /// &mut renderer, /// ); /// - /// let messages = user_interface.update(events.drain(..), None, &renderer); + /// let messages = user_interface.update( + /// events.drain(..), + /// cursor_position, + /// None, + /// &renderer, + /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer); + /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position); /// /// cache = user_interface.into_cache(); /// @@ -268,12 +275,16 @@ where /// // Flush rendering operations... /// } /// ``` - pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output { + pub fn draw( + &self, + renderer: &mut Renderer, + cursor_position: Point, + ) -> Renderer::Output { self.root.widget.draw( renderer, &Renderer::Defaults::default(), Layout::new(&self.layout), - self.cursor_position, + cursor_position, ) } @@ -287,7 +298,6 @@ where hash: self.hash, layout: self.layout, bounds: self.bounds, - cursor_position: self.cursor_position, } } } @@ -300,7 +310,6 @@ pub struct Cache { hash: u64, layout: layout::Node, bounds: Size, - cursor_position: Point, } impl Cache { @@ -316,7 +325,6 @@ impl Cache { hash: 0, layout: layout::Node::new(Size::new(0.0, 0.0)), bounds: Size::ZERO, - cursor_position: Point::new(-1.0, -1.0), } } } @@ -329,7 +337,7 @@ impl Default for Cache { impl PartialEq for Cache { fn eq(&self, other: &Cache) -> bool { - self.hash == other.hash && self.cursor_position == other.cursor_position + self.hash == other.hash } } diff --git a/winit/src/application.rs b/winit/src/application.rs index cb1bbf1e..5b93c8af 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -148,6 +148,7 @@ pub fn run( .expect("Open window"); let clipboard = Clipboard::new(&window); + 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(); @@ -171,6 +172,7 @@ pub fn run( let mut state = program::State::new( application, viewport.logical_size(), + conversion::cursor_position(cursor_position, viewport.scale_factor()), &mut renderer, &mut debug, ); @@ -184,8 +186,12 @@ pub fn run( let command = runtime.enter(|| { state.update( - clipboard.as_ref().map(|c| c as _), viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + clipboard.as_ref().map(|c| c as _), &mut renderer, &mut debug, ) @@ -240,11 +246,14 @@ pub fn run( // The queue is empty, therefore this will never produce // a `Command`. // - // TODO: Properly queue `WindowResized` and `CursorMoved` - // events. + // TODO: Properly queue `WindowResized` let _ = state.update( - clipboard.as_ref().map(|c| c as _), viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + clipboard.as_ref().map(|c| c as _), &mut renderer, &mut debug, ); @@ -304,6 +313,7 @@ pub fn run( &window, scale_factor, control_flow, + &mut cursor_position, &mut modifiers, &mut viewport, &mut resized, @@ -332,6 +342,7 @@ pub fn handle_window_event( window: &winit::window::Window, scale_factor: f64, control_flow: &mut winit::event_loop::ControlFlow, + cursor_position: &mut winit::dpi::PhysicalPosition, modifiers: &mut winit::event::ModifiersState, viewport: &mut Viewport, resized: &mut bool, @@ -352,6 +363,9 @@ pub fn handle_window_event( WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } + WindowEvent::CursorMoved { position, .. } => { + *cursor_position = *position; + } WindowEvent::ModifiersChanged(new_modifiers) => { *modifiers = *new_modifiers; } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b887db6e..80727bd8 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -4,7 +4,7 @@ //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::{ keyboard::{self, KeyCode, ModifiersState}, - mouse, window, Event, Mode, + mouse, window, Event, Mode, Point, }; /// Converts a winit window event into an iced event. @@ -174,6 +174,16 @@ pub fn modifiers_state( } } +/// Converts a physical cursor position to a logical `Point`. +pub fn cursor_position( + position: winit::dpi::PhysicalPosition, + scale_factor: f64, +) -> Point { + let logical_position = position.to_logical(scale_factor); + + Point::new(logical_position.x, logical_position.y) +} + /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit -- cgit From ab53df8e9d1843fa89e954544c0505255d63bc24 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Jun 2020 21:11:13 +0200 Subject: Recreate uniforms `BindGroup` when necessary --- wgpu/src/triangle.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index fe2388a3..2744c67a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -16,6 +16,7 @@ const INDEX_BUFFER_SIZE: usize = 10_000; pub(crate) struct Pipeline { pipeline: wgpu::RenderPipeline, blit: Option, + constants_layout: wgpu::BindGroupLayout, constants: wgpu::BindGroup, uniforms_buffer: Buffer, vertex_buffer: Buffer, @@ -50,8 +51,10 @@ impl Buffer { } } - pub fn ensure_capacity(&mut self, device: &wgpu::Device, size: usize) { - if self.size < size { + pub fn expand(&mut self, device: &wgpu::Device, size: usize) -> bool { + let needs_resize = self.size < size; + + if needs_resize { self.raw = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: (std::mem::size_of::() * size) as u64, @@ -60,6 +63,8 @@ impl Buffer { self.size = size; } + + needs_resize } } @@ -69,7 +74,7 @@ impl Pipeline { format: wgpu::TextureFormat, antialiasing: Option, ) -> Pipeline { - let constant_layout = + let constants_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, bindings: &[wgpu::BindGroupLayoutEntry { @@ -88,7 +93,7 @@ impl Pipeline { let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, - layout: &constant_layout, + layout: &constants_layout, bindings: &[wgpu::Binding { binding: 0, resource: wgpu::BindingResource::Buffer { @@ -100,7 +105,7 @@ impl Pipeline { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - bind_group_layouts: &[&constant_layout], + bind_group_layouts: &[&constants_layout], }); let vs = include_bytes!("shader/triangle.vert.spv"); @@ -180,6 +185,7 @@ impl Pipeline { Pipeline { pipeline, blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + constants_layout, constants: constant_bind_group, uniforms_buffer: constants_buffer, vertex_buffer: Buffer::new( @@ -220,9 +226,25 @@ impl Pipeline { // Then we ensure the current buffers are big enough, resizing if // necessary - self.uniforms_buffer.ensure_capacity(device, meshes.len()); - self.vertex_buffer.ensure_capacity(device, total_vertices); - self.index_buffer.ensure_capacity(device, total_indices); + let _ = self.vertex_buffer.expand(device, total_vertices); + let _ = self.index_buffer.expand(device, total_indices); + + // If the uniforms buffer is resized, then we need to recreate its + // bind group. + if self.uniforms_buffer.expand(device, meshes.len()) { + self.constants = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.constants_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &self.uniforms_buffer.raw, + range: 0..std::mem::size_of::() as u64, + }, + }], + }); + } let mut uniforms: Vec = Vec::with_capacity(meshes.len()); let mut offsets: Vec<( -- cgit From 65a4dca0d965ca963428231173ca5fb9c672ab52 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 25 Jun 2020 00:32:41 +0200 Subject: Add `min_size` and `max_size` to `window::Settings` --- src/settings.rs | 7 +------ src/window/settings.rs | 24 +++++++++++++++++++++++- winit/src/settings.rs | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 933829bd..d7ff4cab 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -75,12 +75,7 @@ where impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { - window: iced_winit::settings::Window { - size: settings.window.size, - resizable: settings.window.resizable, - decorations: settings.window.decorations, - platform_specific: Default::default(), - }, + window: settings.window.into(), flags: settings.flags, } } diff --git a/src/window/settings.rs b/src/window/settings.rs index a31d2af2..eb997899 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,9 +1,15 @@ /// The window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Settings { - /// The size of the window. + /// The initial size of the window. pub size: (u32, u32), + /// The minimum size of the window. + pub min_size: Option<(u32, u32)>, + + /// The maximum size of the window. + pub max_size: Option<(u32, u32)>, + /// Whether the window should be resizable or not. pub resizable: bool, @@ -15,8 +21,24 @@ impl Default for Settings { fn default() -> Settings { Settings { size: (1024, 768), + min_size: None, + max_size: None, resizable: true, decorations: true, } } } + +#[cfg(not(target_arch = "wasm32"))] +impl From for iced_winit::settings::Window { + fn from(settings: Settings) -> Self { + Self { + size: settings.size, + min_size: settings.min_size, + max_size: settings.max_size, + resizable: settings.resizable, + decorations: settings.decorations, + platform_specific: Default::default(), + } + } +} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 37cb832f..6799f23f 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -33,6 +33,12 @@ pub struct Window { /// The size of the window. pub size: (u32, u32), + /// The minimum size of the window. + pub min_size: Option<(u32, u32)>, + + /// The maximum size of the window. + pub max_size: Option<(u32, u32)>, + /// Whether the window should be resizable or not. pub resizable: bool, @@ -62,6 +68,16 @@ impl Window { .with_decorations(self.decorations) .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); + if let Some((width, height)) = self.min_size { + window_builder = window_builder + .with_min_inner_size(winit::dpi::LogicalSize { width, height }); + } + + if let Some((width, height)) = self.max_size { + window_builder = window_builder + .with_max_inner_size(winit::dpi::LogicalSize { width, height }); + } + #[cfg(target_os = "windows")] { use winit::platform::windows::WindowBuilderExtWindows; @@ -79,6 +95,8 @@ impl Default for Window { fn default() -> Window { Window { size: (1024, 768), + min_size: None, + max_size: None, resizable: true, decorations: true, platform_specific: Default::default(), -- cgit From b5d842f877145c78f5d595a87cc1927bb6f5b86a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 27 Jun 2020 03:40:04 +0200 Subject: Show idle cursor when hovering a disabled `Button` --- graphics/src/widget/button.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index aeb862d5..ecabc868 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -103,7 +103,7 @@ where } else { content }, - if is_mouse_over { + if is_mouse_over && !is_disabled { mouse::Interaction::Pointer } else { mouse::Interaction::default() -- cgit From 1bc69e7a8a8ea59efc14fd765889895662c9ba46 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 28 Jun 2020 21:38:03 +0200 Subject: Expose `defaults` module in `iced_graphics` Fixes #429 --- graphics/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 77b96655..38d8dffa 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,6 @@ #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; -mod defaults; mod primitive; mod renderer; mod transformation; @@ -17,6 +16,7 @@ mod viewport; mod widget; pub mod backend; +pub mod defaults; pub mod font; pub mod layer; pub mod triangle; -- cgit From cb530ccf2f0ba82e49be317a14eb61025777a24e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 00:32:55 +0200 Subject: Rename `regions` and `splits` in `pane_grid::Node` --- native/src/widget/pane_grid.rs | 8 ++++---- native/src/widget/pane_grid/node.rs | 4 ++-- native/src/widget/pane_grid/state.rs | 14 ++++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 2d21a968..de59795c 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -288,7 +288,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { if let Some((split, _)) = self.state.picked_split() { let bounds = layout.bounds(); - let splits = self.state.splits( + let splits = self.state.split_regions( f32::from(self.spacing), Size::new(bounds.width, bounds.height), ); @@ -410,7 +410,7 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); - let regions = self.state.regions(f32::from(self.spacing), size); + let regions = self.state.pane_regions(f32::from(self.spacing), size); let children = self .elements @@ -453,7 +453,7 @@ where cursor_position.y - bounds.y, ); - let splits = self.state.splits( + let splits = self.state.split_regions( f32::from(self.spacing), Size::new(bounds.width, bounds.height), ); @@ -590,7 +590,7 @@ where let splits = self .state - .splits(f32::from(self.spacing), bounds.size()); + .split_regions(f32::from(self.spacing), bounds.size()); hovered_split( splits.iter(), diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index b13c5e26..823caab1 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -48,7 +48,7 @@ impl Node { /// /// [`Pane`]: struct.Pane.html /// [`Node`]: enum.Node.html - pub fn regions( + pub fn pane_regions( &self, spacing: f32, size: Size, @@ -75,7 +75,7 @@ impl Node { /// /// [`Split`]: struct.Split.html /// [`Node`]: enum.Node.html - pub fn splits( + pub fn split_regions( &self, spacing: f32, size: Size, diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 4b13fb8e..57fdbc55 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -154,8 +154,10 @@ impl State { /// [`Pane`]: struct.Pane.html /// [`State::active`]: struct.State.html#method.active pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { - let regions = - self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); + let regions = self + .internal + .layout + .pane_regions(0.0, Size::new(4096.0, 4096.0)); let current_region = regions.get(pane)?; @@ -362,20 +364,20 @@ impl Internal { } } - pub fn regions( + pub fn pane_regions( &self, spacing: f32, size: Size, ) -> HashMap { - self.layout.regions(spacing, size) + self.layout.pane_regions(spacing, size) } - pub fn splits( + pub fn split_regions( &self, spacing: f32, size: Size, ) -> HashMap { - self.layout.splits(spacing, size) + self.layout.split_regions(spacing, size) } pub fn focus(&mut self, pane: &Pane) { -- cgit From 23f753e59978352292950e706d023ad02a3199bc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 00:58:14 +0200 Subject: Introduce `splits` method in `pane_grid::Node` --- native/src/widget/pane_grid/node.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 823caab1..cbfd8a43 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -43,6 +43,30 @@ pub enum Node { } impl Node { + /// Returns an iterator over each [`Split`] in this [`Node`]. + /// + /// [`Split`]: struct.Split.html + /// [`Node`]: enum.Node.html + pub fn splits(&self) -> impl Iterator { + let mut unvisited_nodes = vec![self]; + + std::iter::from_fn(move || { + while let Some(node) = unvisited_nodes.pop() { + match node { + Node::Split { id, a, b, .. } => { + unvisited_nodes.push(a); + unvisited_nodes.push(b); + + return Some(id); + } + _ => {} + } + } + + None + }) + } + /// Returns the rectangular region for each [`Pane`] in the [`Node`] given /// the spacing between panes and the total available space. /// -- cgit From cee8400663ac9a3ed5e56863021aabdbc4596198 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 01:01:08 +0200 Subject: Unfocus `Pane` in `pane_grid` on click outbounds --- native/src/widget/pane_grid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index de59795c..d5ce7fbb 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -273,8 +273,6 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.state.focus(pane); } } - } else { - self.state.unfocus(); } } @@ -482,6 +480,8 @@ where ); } } + } else { + self.state.unfocus(); } } mouse::Event::ButtonReleased(mouse::Button::Left) => { -- cgit From e50c61f7ff8c7bd559afb66025eaded78ca423bb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 02:53:15 +0200 Subject: Add `unfocus` method to `pane_grid::State` --- native/src/widget/pane_grid/state.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 57fdbc55..a4cfb6f6 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -193,6 +193,13 @@ impl State { 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. /// -- cgit From 78cb805fac6e6f6d5679b24e222271a2c2ed221e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 07:36:13 +0200 Subject: Add `ModifiersChanged` to `keyboard::Event` --- core/src/keyboard/event.rs | 3 +++ winit/src/conversion.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs index bc8437a8..d142c3bc 100644 --- a/core/src/keyboard/event.rs +++ b/core/src/keyboard/event.rs @@ -28,4 +28,7 @@ pub enum Event { /// A unicode character was received. CharacterReceived(char), + + /// The keyboard modifiers have changed. + ModifiersChanged(ModifiersState), } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 80727bd8..cfa76e88 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -92,6 +92,9 @@ pub fn window_event( } } })), + WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( + keyboard::Event::ModifiersChanged(modifiers_state(*new_modifiers)), + )), WindowEvent::HoveredFile(path) => { Some(Event::Window(window::Event::FileHovered(path.clone()))) } -- cgit From e8aeb86698ae3f4ef23621d67685243d62158036 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Jun 2020 07:38:04 +0200 Subject: Use `keyboard::ModifiersChanged` in `PaneGrid` --- native/src/widget/pane_grid.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 2d21a968..f89c3588 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -539,10 +539,8 @@ where } } } - - *self.pressed_modifiers = modifiers; } - keyboard::Event::KeyReleased { modifiers, .. } => { + keyboard::Event::ModifiersChanged(modifiers) => { *self.pressed_modifiers = modifiers; } _ => {} -- cgit From 9a037a23e9b32d9dbe7086a54d777b5f0550a660 Mon Sep 17 00:00:00 2001 From: Francesco Pasa Date: Sat, 11 Apr 2020 15:16:10 +0200 Subject: Add support for setting window icon This adds a new property from Settings::window::iconand a Icon struct which can be converted to winit::window::Icon. It also adds code to display this icon in Application::run. Due to the fact that the Icon struct is non copyable, I also had to remove the Copy trait from all Settings, both in `iced` and `iced_winit`. --- src/settings.rs | 2 +- src/window/settings.rs | 7 ++++++- winit/src/lib.rs | 1 + winit/src/settings.rs | 39 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index d7ff4cab..ec0e56b6 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@ use crate::window; /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Settings { /// The window settings. /// diff --git a/src/window/settings.rs b/src/window/settings.rs index eb997899..961e8d66 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,5 +1,5 @@ /// The window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Settings { /// The initial size of the window. pub size: (u32, u32), @@ -15,6 +15,9 @@ pub struct Settings { /// Whether the window should have a border, a title bar, etc. or not. pub decorations: bool, + + /// The window icon, which is also usually used in the taskbar + pub icon: Option, } impl Default for Settings { @@ -25,6 +28,7 @@ impl Default for Settings { max_size: None, resizable: true, decorations: true, + icon: None, } } } @@ -38,6 +42,7 @@ impl From for iced_winit::settings::Window { max_size: settings.max_size, resizable: settings.resizable, decorations: settings.decorations, + icon: settings.icon, platform_specific: Default::default(), } } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index bdab3ed7..4e5dc547 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -38,5 +38,6 @@ pub use clipboard::Clipboard; pub use mode::Mode; pub use proxy::Proxy; pub use settings::Settings; +pub use settings::Icon; pub use iced_graphics::Viewport; diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 6799f23f..8cfadf02 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -14,7 +14,7 @@ use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; /// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct Settings { /// The [`Window`] settings /// @@ -28,7 +28,7 @@ pub struct Settings { } /// The window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Window { /// The size of the window. pub size: (u32, u32), @@ -45,6 +45,9 @@ pub struct Window { /// Whether the window should have a border, a title bar, etc. pub decorations: bool, + /// The window icon, which is also usually used in the taskbar + pub icon: Option, + /// Platform specific settings. pub platform_specific: platform::PlatformSpecific, } @@ -66,6 +69,7 @@ impl Window { .with_inner_size(winit::dpi::LogicalSize { width, height }) .with_resizable(self.resizable) .with_decorations(self.decorations) + .with_window_icon(self.icon.map(Icon::into)) .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); if let Some((width, height)) = self.min_size { @@ -99,7 +103,38 @@ impl Default for Window { max_size: None, resizable: true, decorations: true, + icon: None, platform_specific: Default::default(), } } } + +/// An Icon +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Icon { + rgba: Vec, + width: u32, + height: u32, +} + +impl Icon { + /// + pub fn new(rgba: &[u8], width: u32, height: u32) -> Self { + Self { + rgba: rgba.to_vec(), + width, + height, + } + } +} + +impl Into for Icon { + fn into(self) -> winit::window::Icon { + winit::window::Icon::from_rgba( + self.rgba.to_vec(), + self.width, + self.height, + ) + .unwrap() + } +} -- cgit From a0cc7e4e43f16de4c19607f913b7f587c61aa5ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Jul 2020 06:09:39 +0200 Subject: Move `Icon` to `iced` crate and introduce `Error` --- src/settings.rs | 2 +- src/window.rs | 3 ++ src/window/icon.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ src/window/settings.rs | 10 ++-- winit/src/lib.rs | 1 - winit/src/settings.rs | 38 ++------------ 6 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 src/window/icon.rs diff --git a/src/settings.rs b/src/settings.rs index ec0e56b6..40b1b1ea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@ use crate::window; /// The settings of an application. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Settings { /// The window settings. /// diff --git a/src/window.rs b/src/window.rs index 54ea2a02..a2883b62 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,5 +2,8 @@ mod mode; mod settings; +pub mod icon; + +pub use icon::Icon; pub use mode::Mode; pub use settings::Settings; diff --git a/src/window/icon.rs b/src/window/icon.rs new file mode 100644 index 00000000..15e0312d --- /dev/null +++ b/src/window/icon.rs @@ -0,0 +1,132 @@ +//! Attach an icon to the window of your application. +use std::fmt; +use std::io; + +/// The icon of a window. +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, Clone)] +pub struct Icon(iced_winit::winit::window::Icon); + +/// The icon of a window. +#[cfg(target_arch = "wasm32")] +#[derive(Debug, Clone)] +pub struct Icon; + +impl Icon { + /// Creates an icon from 32bpp RGBA data. + #[cfg(not(target_arch = "wasm32"))] + pub fn from_rgba( + rgba: Vec, + width: u32, + height: u32, + ) -> Result { + let raw = + iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?; + + Ok(Icon(raw)) + } + + /// Creates an icon from 32bpp RGBA data. + #[cfg(target_arch = "wasm32")] + pub fn from_rgba( + _rgba: Vec, + _width: u32, + _height: u32, + ) -> Result { + Ok(Icon) + } +} + +/// An error produced when using `Icon::from_rgba` with invalid arguments. +#[derive(Debug)] +pub enum Error { + /// The provided RGBA data isn't divisble by 4. + /// + /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels. + InvalidData { + /// The length of the provided RGBA data. + byte_count: usize, + }, + + /// The number of RGBA pixels does not match the provided dimensions. + DimensionsMismatch { + /// The provided width. + width: u32, + /// The provided height. + height: u32, + /// The amount of pixels of the provided RGBA data. + pixel_count: usize, + }, + + /// The underlying OS failed to create the icon. + OsError(io::Error), +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + fn from(error: iced_winit::winit::window::BadIcon) -> Self { + use iced_winit::winit::window::BadIcon; + + match error { + BadIcon::ByteCountNotDivisibleBy4 { byte_count } => { + Error::InvalidData { byte_count } + } + BadIcon::DimensionsVsPixelCount { + width, + height, + pixel_count, + .. + } => Error::DimensionsMismatch { + width, + height, + pixel_count, + }, + BadIcon::OsError(os_error) => Error::OsError(os_error), + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for iced_winit::winit::window::Icon { + fn from(icon: Icon) -> Self { + icon.0 + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidData { byte_count } => { + write!(f, + "The provided RGBA data (with length {:?}) isn't divisble by \ + 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ + pixels.", + byte_count, + ) + } + Error::DimensionsMismatch { + width, + height, + pixel_count, + } => { + write!(f, + "The number of RGBA pixels ({:?}) does not match the provided \ + dimensions ({:?}x{:?}).", + width, height, pixel_count, + ) + } + Error::OsError(e) => write!( + f, + "The underlying OS failed to create the window \ + icon: {:?}", + e + ), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self) + } +} diff --git a/src/window/settings.rs b/src/window/settings.rs index 961e8d66..2046f2d9 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,5 +1,7 @@ +use crate::window::Icon; + /// The window settings of an application. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Settings { /// The initial size of the window. pub size: (u32, u32), @@ -16,8 +18,8 @@ pub struct Settings { /// Whether the window should have a border, a title bar, etc. or not. pub decorations: bool, - /// The window icon, which is also usually used in the taskbar - pub icon: Option, + /// The icon of the window. + pub icon: Option, } impl Default for Settings { @@ -42,7 +44,7 @@ impl From for iced_winit::settings::Window { max_size: settings.max_size, resizable: settings.resizable, decorations: settings.decorations, - icon: settings.icon, + icon: settings.icon.map(Icon::into), platform_specific: Default::default(), } } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 4e5dc547..bdab3ed7 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -38,6 +38,5 @@ pub use clipboard::Clipboard; pub use mode::Mode; pub use proxy::Proxy; pub use settings::Settings; -pub use settings::Icon; pub use iced_graphics::Viewport; diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 8cfadf02..4155bf7d 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -14,7 +14,7 @@ use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; /// The settings of an application. -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, Default)] pub struct Settings { /// The [`Window`] settings /// @@ -28,7 +28,7 @@ pub struct Settings { } /// The window settings of an application. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Window { /// The size of the window. pub size: (u32, u32), @@ -46,7 +46,7 @@ pub struct Window { pub decorations: bool, /// The window icon, which is also usually used in the taskbar - pub icon: Option, + pub icon: Option, /// Platform specific settings. pub platform_specific: platform::PlatformSpecific, @@ -69,7 +69,7 @@ impl Window { .with_inner_size(winit::dpi::LogicalSize { width, height }) .with_resizable(self.resizable) .with_decorations(self.decorations) - .with_window_icon(self.icon.map(Icon::into)) + .with_window_icon(self.icon) .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); if let Some((width, height)) = self.min_size { @@ -108,33 +108,3 @@ impl Default for Window { } } } - -/// An Icon -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Icon { - rgba: Vec, - width: u32, - height: u32, -} - -impl Icon { - /// - pub fn new(rgba: &[u8], width: u32, height: u32) -> Self { - Self { - rgba: rgba.to_vec(), - width, - height, - } - } -} - -impl Into for Icon { - fn into(self) -> winit::window::Icon { - winit::window::Icon::from_rgba( - self.rgba.to_vec(), - self.width, - self.height, - ) - .unwrap() - } -} -- cgit From ffd195cdb543dfbff42327c516d6082cb2df51ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Jul 2020 06:52:13 +0200 Subject: Fix empty `id` and `name` attributes in `iced_web` --- web/src/widget/checkbox.rs | 22 +++++++++++++--------- web/src/widget/radio.rs | 38 ++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 10a46661..4d0c7c17 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -28,7 +28,7 @@ pub struct Checkbox { is_checked: bool, on_toggle: Rc Message>, label: String, - id: String, + id: Option, width: Length, style: Box, } @@ -52,7 +52,7 @@ impl Checkbox { is_checked, on_toggle: Rc::new(f), label: label.into(), - id: Default::default(), + id: None, width: Length::Shrink, style: Default::default(), } @@ -78,7 +78,7 @@ impl Checkbox { /// /// [`Checkbox`]: struct.Checkbox.html pub fn id(mut self, id: impl Into) -> Self { - self.id = id.into(); + self.id = Some(id.into()); self } } @@ -97,8 +97,6 @@ where let checkbox_label = bumpalo::format!(in bump, "{}", self.label).into_bump_str(); - let checkbox_id = - bumpalo::format!(in bump, "{}", self.id).into_bump_str(); let event_bus = bus.clone(); let on_toggle = self.on_toggle.clone(); @@ -108,8 +106,15 @@ where let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); - label(bump) - .attr("for", checkbox_id) + let (label, input) = if let Some(id) = &self.id { + let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + + (label(bump).attr("for", id), input(bump).attr("id", id)) + } else { + (label(bump), input(bump)) + }; + + label .attr( "class", bumpalo::format!(in bump, "{} {}", row_class, spacing_class) @@ -122,9 +127,8 @@ where ) .children(vec![ // TODO: Checkbox styling - input(bump) + input .attr("type", "checkbox") - .attr("id", checkbox_id) .bool_attr("checked", self.is_checked) .on("click", move |_root, vdom, _event| { let msg = on_toggle(!is_checked); diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index acbac3b6..fae67cd8 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -35,8 +35,8 @@ pub struct Radio { is_selected: bool, on_click: Message, label: String, - id: String, - name: String, + id: Option, + name: Option, style: Box, } @@ -65,8 +65,8 @@ impl Radio { is_selected: Some(value) == selected, on_click: f(value), label: label.into(), - id: Default::default(), - name: Default::default(), + id: None, + name: None, style: Default::default(), } } @@ -83,7 +83,7 @@ impl Radio { /// /// [`Radio`]: struct.Radio.html pub fn name(mut self, name: impl Into) -> Self { - self.name = name.into(); + self.name = Some(name.into()); self } @@ -91,7 +91,7 @@ impl Radio { /// /// [`Radio`]: struct.Radio.html pub fn id(mut self, id: impl Into) -> Self { - self.id = id.into(); + self.id = Some(id.into()); self } } @@ -110,22 +110,32 @@ where let radio_label = bumpalo::format!(in bump, "{}", self.label).into_bump_str(); - let radio_name = - bumpalo::format!(in bump, "{}", self.name).into_bump_str(); - let radio_id = bumpalo::format!(in bump, "{}", self.id).into_bump_str(); let event_bus = bus.clone(); let on_click = self.on_click.clone(); + let (label, input) = if let Some(id) = &self.id { + let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + + (label(bump).attr("for", id), input(bump).attr("id", id)) + } else { + (label(bump), input(bump)) + }; + + let input = if let Some(name) = &self.name { + let name = bumpalo::format!(in bump, "{}", name).into_bump_str(); + + dodrio::builder::input(bump).attr("name", name) + } else { + input + }; + // TODO: Complete styling - label(bump) + label .attr("style", "display: block; font-size: 20px") - .attr("for", radio_id) .children(vec![ - input(bump) + input .attr("type", "radio") - .attr("id", radio_id) - .attr("name", radio_name) .attr("style", "margin-right: 10px") .bool_attr("checked", self.is_selected) .on("click", move |_root, _vdom, _event| { -- cgit From d873c37e31fb052fb376caada6780137e176a6e7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Jul 2020 07:19:51 +0200 Subject: Update `dodrio` dependency in `iced_web` --- web/Cargo.toml | 4 ++-- web/src/lib.rs | 19 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index 88c3102f..e03d2b63 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -15,8 +15,8 @@ categories = ["web-programming"] maintenance = { status = "actively-developed" } [dependencies] -dodrio = "0.1.0" -wasm-bindgen = "0.2.51" +dodrio = "0.2" +wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" url = "2.0" num-traits = "0.2" diff --git a/web/src/lib.rs b/web/src/lib.rs index 53b54b7e..b7970c56 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -238,28 +238,25 @@ struct Instance { bus: Bus, } -impl dodrio::Render for Instance +impl<'a, A> dodrio::Render<'a> for Instance where A: Application, { - fn render<'a, 'bump>( - &'a self, - bump: &'bump bumpalo::Bump, - ) -> dodrio::Node<'bump> - where - 'a: 'bump, - { + fn render( + &self, + context: &mut dodrio::RenderContext<'a>, + ) -> dodrio::Node<'a> { use dodrio::builder::*; let mut ui = self.application.borrow_mut(); let element = ui.view(); let mut css = Css::new(); - let node = element.widget.node(bump, &self.bus, &mut css); + let node = element.widget.node(context.bump, &self.bus, &mut css); - div(bump) + div(context.bump) .attr("style", "width: 100%; height: 100%") - .children(vec![css.node(bump), node]) + .children(vec![css.node(context.bump), node]) .finish() } } -- cgit From 75464ad89422884e0718eb0429586a9d77f61c71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Jul 2020 07:36:42 +0200 Subject: Use `String::from_str_in` in `iced_web` --- web/src/widget/button.rs | 14 ++++++++---- web/src/widget/checkbox.rs | 5 ++-- web/src/widget/image.rs | 17 +++++++++----- web/src/widget/radio.rs | 7 +++--- web/src/widget/text.rs | 10 ++++++-- web/src/widget/text_input.rs | 54 ++++++++++++++++++++++++++------------------ 6 files changed, 67 insertions(+), 40 deletions(-) diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 3a5afe60..a4bcc33d 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -154,16 +154,20 @@ where }, }; + let class = { + use dodrio::bumpalo::collections::String; + + String::from_str_in(&padding_class, bump).into_bump_str() + }; + let mut node = button(bump) - .attr( - "class", - bumpalo::format!(in bump, "{}", padding_class).into_bump_str(), - ) + .attr("class", class) .attr( "style", bumpalo::format!( in bump, - "background: {}; border-radius: {}px; width:{}; min-width: {}; color: {}", + "background: {}; border-radius: {}px; width:{}; \ + min-width: {}; color: {}", background, style.border_radius, css::length(self.width), diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 4d0c7c17..21801e39 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -94,9 +94,10 @@ where style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; + use dodrio::bumpalo::collections::String; let checkbox_label = - bumpalo::format!(in bump, "{}", self.label).into_bump_str(); + String::from_str_in(&self.label, bump).into_bump_str(); let event_bus = bus.clone(); let on_toggle = self.on_toggle.clone(); @@ -107,7 +108,7 @@ where let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); let (label, input) = if let Some(id) = &self.id { - let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + let id = String::from_str_in(id, bump).into_bump_str(); (label(bump).attr("for", id), input(bump).attr("id", id)) } else { diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index a20bebea..a595c29a 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -78,14 +78,19 @@ impl Widget for Image { _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; + use dodrio::bumpalo::collections::String; - let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() { - Data::Path(path) => path.to_str().unwrap_or("") - }); - let alt = bumpalo::format!(in bump, "{}", self.alt).into_bump_str(); + let src = String::from_str_in( + match self.handle.data.as_ref() { + Data::Path(path) => path.to_str().unwrap_or(""), + }, + bump, + ) + .into_bump_str(); - let mut image = - img(bump).attr("src", src.into_bump_str()).attr("alt", alt); + let alt = String::from_str_in(&self.alt, bump).into_bump_str(); + + let mut image = img(bump).attr("src", src).attr("alt", alt); match self.width { Length::Shrink => {} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index fae67cd8..c9d0a00e 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -107,15 +107,16 @@ where _style_sheet: &mut Css<'b>, ) -> dodrio::Node<'b> { use dodrio::builder::*; + use dodrio::bumpalo::collections::String; let radio_label = - bumpalo::format!(in bump, "{}", self.label).into_bump_str(); + String::from_str_in(&self.label, bump).into_bump_str(); let event_bus = bus.clone(); let on_click = self.on_click.clone(); let (label, input) = if let Some(id) = &self.id { - let id = bumpalo::format!(in bump, "{}", id).into_bump_str(); + let id = String::from_str_in(id, bump).into_bump_str(); (label(bump).attr("for", id), input(bump).attr("id", id)) } else { @@ -123,7 +124,7 @@ where }; let input = if let Some(name) = &self.name { - let name = bumpalo::format!(in bump, "{}", name).into_bump_str(); + let name = String::from_str_in(name, bump).into_bump_str(); dodrio::builder::input(bump).attr("name", name) } else { diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index 3ec565a8..2f7308ee 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -116,7 +116,12 @@ impl<'a, Message> Widget for Text { ) -> dodrio::Node<'b> { use dodrio::builder::*; - let content = bumpalo::format!(in bump, "{}", self.content); + let content = { + use dodrio::bumpalo::collections::String; + + String::from_str_in(&self.content, bump) + }; + let color = self .color .map(css::color) @@ -133,7 +138,8 @@ impl<'a, Message> Widget for Text { let style = bumpalo::format!( in bump, - "width: {}; height: {}; font-size: {}px; color: {}; text-align: {}; font-family: {}", + "width: {}; height: {}; font-size: {}px; color: {}; \ + text-align: {}; font-family: {}", width, height, self.size.unwrap_or(20), diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index 3fa458bd..0049a553 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -151,8 +151,26 @@ where use dodrio::builder::*; use wasm_bindgen::JsCast; - let padding_class = - style_sheet.insert(bump, css::Rule::Padding(self.padding)); + let class = { + use dodrio::bumpalo::collections::String; + + let padding_class = + style_sheet.insert(bump, css::Rule::Padding(self.padding)); + + String::from_str_in(&padding_class, bump).into_bump_str() + }; + + let placeholder = { + use dodrio::bumpalo::collections::String; + + String::from_str_in(&self.placeholder, bump).into_bump_str() + }; + + let value = { + use dodrio::bumpalo::collections::String; + + String::from_str_in(&self.value, bump).into_bump_str() + }; let on_change = self.on_change.clone(); let on_submit = self.on_submit.clone(); @@ -161,15 +179,14 @@ where let style = self.style_sheet.active(); input(bump) - .attr( - "class", - bumpalo::format!(in bump, "{}", padding_class).into_bump_str(), - ) + .attr("class", class) .attr( "style", bumpalo::format!( in bump, - "width: {}; max-width: {}; font-size: {}px; background: {}; border-width: {}px; border-color: {}; border-radius: {}px; color: {}", + "width: {}; max-width: {}; font-size: {}px; \ + background: {}; border-width: {}px; border-color: {}; \ + border-radius: {}px; color: {}", css::length(self.width), css::max_length(self.max_width), self.size.unwrap_or(20), @@ -181,19 +198,9 @@ where ) .into_bump_str(), ) - .attr( - "placeholder", - bumpalo::format!(in bump, "{}", self.placeholder) - .into_bump_str(), - ) - .attr( - "value", - bumpalo::format!(in bump, "{}", self.value).into_bump_str(), - ) - .attr( - "type", - bumpalo::format!(in bump, "{}", if self.is_secure { "password" } else { "text" }).into_bump_str(), - ) + .attr("placeholder", placeholder) + .attr("value", value) + .attr("type", if self.is_secure { "password" } else { "text" }) .on("input", move |_root, _vdom, event| { let text_input = match event.target().and_then(|t| { t.dyn_into::().ok() @@ -206,10 +213,13 @@ where }) .on("keypress", move |_root, _vdom, event| { if let Some(on_submit) = on_submit.clone() { - let event = event.unchecked_into::(); + let event = + event.unchecked_into::(); match event.key_code() { - 13 => { submit_event_bus.publish(on_submit); } + 13 => { + submit_event_bus.publish(on_submit); + } _ => {} } } -- cgit From 946bbd26835fef21b770c1ae56921fb4d8512287 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Jul 2020 03:12:18 +0200 Subject: Truncate `Debug` messages after 100 characters --- native/src/debug/basic.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index 5338d0d9..8a712038 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -174,9 +174,13 @@ impl Debug { lines.push(key_value("Render:", self.render_durations.average())); lines.push(key_value("Message count:", self.message_count)); lines.push(String::from("Last messages:")); - lines.extend( - self.last_messages.iter().map(|msg| format!(" {}", msg)), - ); + lines.extend(self.last_messages.iter().map(|msg| { + if msg.len() <= 100 { + format!(" {}", msg) + } else { + format!(" {:.100}...", msg) + } + })); lines } -- cgit From dfeb3db003d724a1c980329dab9cbfae55b7f589 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 Jul 2020 23:58:15 +0200 Subject: Use `default_font_size` for `TextInput` widget --- graphics/src/widget/text_input.rs | 6 ------ native/src/renderer/null.rs | 5 ----- native/src/widget/text_input.rs | 22 +++++++--------------- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index f13f6606..575d67f5 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -27,14 +27,8 @@ impl text_input::Renderer for Renderer where B: Backend + backend::Text, { - type Font = Font; type Style = Box; - fn default_size(&self) -> u16 { - // TODO: Make this configurable - 20 - } - fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { let backend = self.backend(); diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index b8b0b996..5fd3627b 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -105,13 +105,8 @@ impl scrollable::Renderer for Null { } impl text_input::Renderer for Null { - type Font = Font; type Style = (); - fn default_size(&self) -> u16 { - 20 - } - fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 { 0.0 } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 24085606..3f415101 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -17,8 +17,8 @@ use editor::Editor; use crate::{ keyboard, layout, mouse::{self, click}, - Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, - Widget, + text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, }; use std::u32; @@ -486,7 +486,8 @@ where let text_bounds = layout.children().next().unwrap().bounds(); if self.is_secure { - renderer.draw( + self::Renderer::draw( + renderer, bounds, text_bounds, cursor_position, @@ -498,7 +499,8 @@ where &self.style, ) } else { - renderer.draw( + self::Renderer::draw( + renderer, bounds, text_bounds, cursor_position, @@ -531,20 +533,10 @@ where /// /// [`TextInput`]: struct.TextInput.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + Sized { - /// The font type used for [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - type Font: Default + Copy; - +pub trait Renderer: text::Renderer + Sized { /// The style supported by this renderer. type Style: Default; - /// Returns the default size of the text of the [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - fn default_size(&self) -> u16; - /// Returns the width of the value of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html -- cgit From 5c4f5ae5ecb36703a95cafb2cd58692529c9466d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Jul 2020 10:20:55 +0200 Subject: Export `Canvas` if `glow_canvas` feature is enabled --- src/widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget.rs b/src/widget.rs index 3e4d4788..007bd531 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -50,7 +50,7 @@ mod platform { text_input::TextInput, }; - #[cfg(feature = "canvas")] + #[cfg(any(feature = "canvas", feature = "glow_canvas"))] #[doc(no_inline)] pub use canvas::Canvas; } -- cgit