From 7b35b7869d17d6faa9e84bbe5e0eafea34791ef7 Mon Sep 17 00:00:00 2001 From: Ioannis Skottis Date: Thu, 24 Aug 2023 22:28:33 +0100 Subject: [PATCH 01/12] Add vscode workspace to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 67291a435..f46608e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock .vscode .cspell +*.code-workspace \ No newline at end of file From 45239102385d752b89e8b35327d9990791d85896 Mon Sep 17 00:00:00 2001 From: Ioannis Skottis Date: Thu, 24 Aug 2023 19:04:46 +0100 Subject: [PATCH 02/12] Add fill function --- src/widget/piet_scene_helpers.rs | 36 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/widget/piet_scene_helpers.rs b/src/widget/piet_scene_helpers.rs index 64884e270..f7b8ba6de 100644 --- a/src/widget/piet_scene_helpers.rs +++ b/src/widget/piet_scene_helpers.rs @@ -1,5 +1,5 @@ use vello::kurbo::{self, Affine, Rect, Shape}; -use vello::peniko::{BrushRef, ColorStopsSource, Fill, Gradient, Stroke}; +use vello::peniko::{BrushRef, Color, ColorStopsSource, Fill, Gradient, Stroke}; use vello::SceneBuilder; #[derive(Debug, Clone, Copy)] @@ -8,21 +8,6 @@ pub struct UnitPoint { v: f64, } -pub fn stroke<'b>( - builder: &mut SceneBuilder, - path: &impl Shape, - brush: impl Into>, - stroke_width: f64, -) { - builder.stroke( - &Stroke::new(stroke_width as f32), - Affine::IDENTITY, - brush, - None, - path, - ) -} - // Note: copied from piet #[allow(unused)] impl UnitPoint { @@ -62,6 +47,21 @@ impl UnitPoint { } } +pub fn stroke<'b>( + builder: &mut SceneBuilder, + path: &impl Shape, + brush: impl Into>, + stroke_width: f64, +) { + builder.stroke( + &Stroke::new(stroke_width as f32), + Affine::IDENTITY, + brush, + None, + path, + ) +} + pub fn fill_lin_gradient( builder: &mut SceneBuilder, path: &impl Shape, @@ -73,3 +73,7 @@ pub fn fill_lin_gradient( let brush = Gradient::new_linear(start.resolve(rect), end.resolve(rect)).with_stops(stops); builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, path); } + +pub fn fill_color(builder: &mut SceneBuilder, path: &impl Shape, color: Color) { + builder.fill(Fill::NonZero, Affine::IDENTITY, color, None, path) +} From 4b73324d69b7523b96a13812e5359a21c4d23fb0 Mon Sep 17 00:00:00 2001 From: Ioannis Skottis Date: Thu, 24 Aug 2023 19:05:23 +0100 Subject: [PATCH 03/12] Port Switch Widget --- src/view/mod.rs | 4 +- src/view/switch.rs | 74 +++++++++++++++++++ src/widget/mod.rs | 4 +- src/widget/switch.rs | 165 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/view/switch.rs create mode 100644 src/widget/switch.rs diff --git a/src/view/mod.rs b/src/view/mod.rs index 958f6acc2..22a5bdd5e 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -14,10 +14,11 @@ // mod async_list; mod button; +mod switch; +// mod text; // mod layout_observer; // mod list; // mod scroll_view; -// mod text; // mod use_state; mod linear_layout; mod list; @@ -29,4 +30,5 @@ pub use xilem_core::{Id, IdPath, VecSplice}; pub use button::button; pub use linear_layout::{h_stack, v_stack, LinearLayout}; pub use list::{list, List}; +pub use switch::switch; pub use view::{Adapt, AdaptState, Cx, Memoize, View, ViewMarker, ViewSequence}; diff --git a/src/view/switch.rs b/src/view/switch.rs new file mode 100644 index 000000000..2c0e40eb7 --- /dev/null +++ b/src/view/switch.rs @@ -0,0 +1,74 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::any::Any; + +use crate::view::ViewMarker; +use crate::{view::Id, widget::ChangeFlags, MessageResult}; + +use super::{Cx, View}; + +pub struct Switch { + is_on: bool, +} + +pub fn switch(is_on: bool) -> Switch { + Switch::new(is_on) +} + +impl Switch { + pub fn new(is_on: bool) -> Self { + Switch { is_on } + } +} + +impl ViewMarker for Switch {} + +impl View for Switch { + type State = (); + + type Element = crate::widget::Switch; + + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { + let (id, element) = + cx.with_new_id(|cx| crate::widget::Switch::new(cx.id_path(), self.is_on)); + (id, (), element) + } + + fn rebuild( + &self, + _cx: &mut Cx, + prev: &Self, + _id: &mut Id, + _state: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + if prev.is_on != self.is_on { + element.set_is_on(self.is_on) + } else { + ChangeFlags::default() + } + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + _message: Box, + app_state: &mut bool, + ) -> MessageResult { + *app_state = !*app_state; + MessageResult::Nop + } +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 2ec7572b0..b7cb01bdb 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -16,13 +16,14 @@ mod box_constraints; mod button; mod contexts; mod core; +mod switch; +// mod text; //mod layout_observer; //mod list; mod linear_layout; mod piet_scene_helpers; mod raw_event; //mod scroll_view; -//mod text; #[allow(clippy::module_inception)] mod widget; @@ -33,4 +34,5 @@ pub use button::Button; pub use contexts::{AccessCx, CxState, EventCx, LayoutCx, LifeCycleCx, PaintCx, UpdateCx}; pub use linear_layout::LinearLayout; pub use raw_event::{Event, LifeCycle, MouseEvent, ViewContext}; +pub use switch::Switch; pub use widget::{AnyWidget, Widget}; diff --git a/src/widget/switch.rs b/src/widget/switch.rs new file mode 100644 index 000000000..50c6699e0 --- /dev/null +++ b/src/widget/switch.rs @@ -0,0 +1,165 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use glazier::kurbo::Circle; +use vello::{ + kurbo::{Point, Size}, + peniko::Color, + SceneBuilder, +}; + +use crate::{IdPath, Message}; + +use super::{ + contexts::LifeCycleCx, + piet_scene_helpers::{fill_color, stroke}, + AccessCx, BoxConstraints, ChangeFlags, Event, EventCx, LayoutCx, LifeCycle, PaintCx, UpdateCx, + Widget, +}; + +pub struct Switch { + id_path: IdPath, + is_on: bool, + is_moved: bool, + knob_position: Point, +} + +impl Switch { + pub fn new(id_path: &IdPath, is_on: bool) -> Switch { + Switch { + id_path: id_path.clone(), + is_on, + is_moved: false, + knob_position: if is_on { + Point::new(ON_POS, KNOB_DIAMETER / 2. + SWITCH_PADDING) + } else { + Point::new(OFF_POS, KNOB_DIAMETER / 2. + SWITCH_PADDING) + }, + } + } + + pub fn set_is_on(&mut self, is_on: bool) -> ChangeFlags { + self.is_on = is_on; + ChangeFlags::PAINT + } +} + +// See druid's button for info. +const KNOB_DIAMETER: f64 = 20.0; +const SWITCH_PADDING: f64 = 3.0; +const SWITCH_WIDTH: f64 = 2.0 * KNOB_DIAMETER + 2.0 * SWITCH_PADDING; +const SWITCH_HEIGHT: f64 = KNOB_DIAMETER + 2.0 * SWITCH_PADDING; +const ON_POS: f64 = SWITCH_WIDTH - KNOB_DIAMETER / 2.0 - SWITCH_PADDING; +const OFF_POS: f64 = KNOB_DIAMETER / 2.0 + SWITCH_PADDING; + +impl Widget for Switch { + fn event(&mut self, cx: &mut EventCx, event: &Event) { + match event { + Event::MouseDown(_) => { + cx.set_active(true); + cx.request_paint(); + } + Event::MouseUp(_) => { + if self.is_moved { + if self.is_on != (self.knob_position.x > SWITCH_WIDTH / 2.0) { + cx.add_message(Message::new(self.id_path.clone(), ())) + } + } else if cx.is_active() { + cx.add_message(Message::new(self.id_path.clone(), ())); + } + // Reset Flags + cx.set_active(false); + self.is_moved = false; + + // Request repaint + cx.request_paint(); + } + Event::MouseMove(mouse) => { + if cx.is_active() { + self.knob_position.x = mouse.pos.x.clamp(OFF_POS, ON_POS); + self.is_moved = true; + println!("Mouse Move{:?}", self.knob_position); + } + cx.request_paint(); + } + Event::TargetedAccessibilityAction(request) => { + if request.action == accesskit::Action::Default + && cx.is_accesskit_target(request.target) + { + cx.add_message(Message::new(self.id_path.clone(), ())); + } + } + _ => (), + }; + } + + fn lifecycle(&mut self, cx: &mut LifeCycleCx, event: &LifeCycle) { + if let LifeCycle::HotChanged(_) = event { + cx.request_paint(); + } + } + + fn update(&mut self, cx: &mut UpdateCx) { + cx.request_layout(); + } + + fn layout(&mut self, _cx: &mut LayoutCx, _bc: &BoxConstraints) -> Size { + Size::new(SWITCH_WIDTH, SWITCH_HEIGHT) + } + + fn accessibility(&mut self, cx: &mut AccessCx) { + let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Switch); + builder.set_default_action_verb(accesskit::DefaultActionVerb::Click); + cx.push_node(builder); + } + + fn paint(&mut self, cx: &mut PaintCx, builder: &mut SceneBuilder) { + // Change the position of of the knob based on its state + // If the knob is currently being dragged with the mouse use the position that was set in MouseMove + if !self.is_moved { + self.knob_position.x = if self.is_on { ON_POS } else { OFF_POS } + } + + // Paint the Swith background + // The on/off states have different colors + // The transition between the two color is controlled by the knob position and calculated using the opacity + let opacity = (self.knob_position.x - OFF_POS) / (ON_POS - OFF_POS); + + let background_on_state = Color::SPRING_GREEN.with_alpha_factor(opacity as f32); + let background_off_state = Color::WHITE_SMOKE.with_alpha_factor(1.0 - opacity as f32); + + let background_rect = cx.size().to_rect().to_rounded_rect(SWITCH_HEIGHT / 2.); + + fill_color(builder, &background_rect, background_off_state); + fill_color(builder, &background_rect, background_on_state); + + // Paint the Switch knob + println!("Paint: {:?}", self.knob_position); + let knob_color = if self.is_moved || cx.is_hot() { + Color::SLATE_GRAY + } else { + Color::LIGHT_SLATE_GRAY + }; + let knob_border_color = Color::DIM_GRAY; + let mut knob_size = KNOB_DIAMETER / 2.0; + + if cx.is_active() { + knob_size += 1.0; + } + + let knob_circle = Circle::new(self.knob_position, knob_size); + fill_color(builder, &knob_circle, knob_color); + stroke(builder, &knob_circle, knob_border_color, 2.0); + } +} From 6275db82c73f14dce5284d7bdfb1507cdbb747f8 Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 11:27:53 +0100 Subject: [PATCH 04/12] Rework Swtich so that it's genertic on T --- examples/hello.rs | 36 +++++++++++++++++++++++++----------- src/view/switch.rs | 28 +++++++++++++++++----------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index f1c78fe88..1de432ab6 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,34 +1,43 @@ -use xilem::view::{button, h_stack, v_stack}; +use xilem::view::{button, h_stack, switch, v_stack}; use xilem::{view::View, App, AppLauncher}; -fn app_logic(data: &mut i32) -> impl View { +fn app_logic(data: &mut AppData) -> impl View { // here's some logic, deriving state for the view from our state - let label = if *data == 1 { + let count = data.count; + let label = if count == 1 { "clicked 1 time".to_string() } else { - format!("clicked {data} times") + format!("clicked {count} times") }; // The actual UI Code starts here v_stack(( - button(label, |data| { + button(label, |data: &mut AppData| { println!("clicked"); - *data += 1; + data.count += 1; }), h_stack(( - button("decrease", |data| { + button("decrease", |data: &mut AppData| { println!("clicked decrease"); - *data -= 1; + data.count -= 1; }), - button("reset", |data| { + button("reset", |data: &mut AppData| { println!("clicked reset"); - *data = 0; + data.count = 0; + }), + switch(data.is_on, |data: &mut AppData, value: bool| { + data.is_on = value }), )), )) .with_spacing(20.0) } +struct AppData { + count: i32, + is_on: bool, +} + fn main() { /* let app = Application::new().unwrap(); @@ -40,6 +49,11 @@ fn main() { window_handle.show(); app.run(None); */ - let app = App::new(0, app_logic); + let data = AppData { + count: 0, + is_on: false, + }; + + let app = App::new(data, app_logic); AppLauncher::new(app).run() } diff --git a/src/view/switch.rs b/src/view/switch.rs index 2c0e40eb7..e56492ada 100644 --- a/src/view/switch.rs +++ b/src/view/switch.rs @@ -19,23 +19,30 @@ use crate::{view::Id, widget::ChangeFlags, MessageResult}; use super::{Cx, View}; -pub struct Switch { +pub struct Switch { is_on: bool, + callback: Box A + Send>, } -pub fn switch(is_on: bool) -> Switch { - Switch::new(is_on) +pub fn switch( + is_on: bool, + clicked: impl Fn(&mut T, bool) -> A + Send + 'static, +) -> Switch { + Switch::new(is_on, clicked) } -impl Switch { - pub fn new(is_on: bool) -> Self { - Switch { is_on } +impl Switch { + pub fn new(is_on: bool, clicked: impl Fn(&mut T, bool) -> A + Send + 'static) -> Self { + Switch { + is_on, + callback: Box::new(clicked), + } } } -impl ViewMarker for Switch {} +impl ViewMarker for Switch {} -impl View for Switch { +impl View for Switch { type State = (); type Element = crate::widget::Switch; @@ -66,9 +73,8 @@ impl View for Switch { _id_path: &[Id], _state: &mut Self::State, _message: Box, - app_state: &mut bool, + app_state: &mut T, ) -> MessageResult { - *app_state = !*app_state; - MessageResult::Nop + MessageResult::Action((self.callback)(app_state, !self.is_on)) } } From 17d0bd1801e782c01b6dec6f9926d1f668b5ac7e Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 11:27:53 +0100 Subject: [PATCH 05/12] Rework Swtich so that it's genertic on T --- src/view/switch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/switch.rs b/src/view/switch.rs index e56492ada..0dab26ad2 100644 --- a/src/view/switch.rs +++ b/src/view/switch.rs @@ -14,6 +14,7 @@ use std::any::Any; +use crate::app; use crate::view::ViewMarker; use crate::{view::Id, widget::ChangeFlags, MessageResult}; From 52af847302e04c50ee0db39a0db07afab7e68a6f Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 11:28:01 +0100 Subject: [PATCH 06/12] Add swtich to example --- examples/hello.rs | 1 + src/view/switch.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hello.rs b/examples/hello.rs index 1de432ab6..793f37d7c 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -28,6 +28,7 @@ fn app_logic(data: &mut AppData) -> impl View { switch(data.is_on, |data: &mut AppData, value: bool| { data.is_on = value }), + switch(data, |data: &mut AppData| &mut data.is_on), )), )) .with_spacing(20.0) diff --git a/src/view/switch.rs b/src/view/switch.rs index 0dab26ad2..e56492ada 100644 --- a/src/view/switch.rs +++ b/src/view/switch.rs @@ -14,7 +14,6 @@ use std::any::Any; -use crate::app; use crate::view::ViewMarker; use crate::{view::Id, widget::ChangeFlags, MessageResult}; From 6aee95495e85aab2e02902b3c4456b056397b78f Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 15:00:31 +0100 Subject: [PATCH 07/12] Progress Bar Widget --- src/widget/mod.rs | 2 + src/widget/progress_bar.rs | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/widget/progress_bar.rs diff --git a/src/widget/mod.rs b/src/widget/mod.rs index b7cb01bdb..6bea836b0 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -16,6 +16,7 @@ mod box_constraints; mod button; mod contexts; mod core; +mod progress_bar; mod switch; // mod text; //mod layout_observer; @@ -33,6 +34,7 @@ pub use box_constraints::BoxConstraints; pub use button::Button; pub use contexts::{AccessCx, CxState, EventCx, LayoutCx, LifeCycleCx, PaintCx, UpdateCx}; pub use linear_layout::LinearLayout; +pub use progress_bar::ProgressBar; pub use raw_event::{Event, LifeCycle, MouseEvent, ViewContext}; pub use switch::Switch; pub use widget::{AnyWidget, Widget}; diff --git a/src/widget/progress_bar.rs b/src/widget/progress_bar.rs new file mode 100644 index 000000000..110a540d9 --- /dev/null +++ b/src/widget/progress_bar.rs @@ -0,0 +1,87 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use vello::{ + kurbo::{Rect, Size}, + peniko::Color, + SceneBuilder, +}; + +use super::{ + contexts::LifeCycleCx, + piet_scene_helpers::{fill_color, stroke}, + AccessCx, BoxConstraints, ChangeFlags, Event, EventCx, LayoutCx, LifeCycle, PaintCx, UpdateCx, + Widget, +}; + +/// A progress bar, displaying a numeric progress value. +/// +/// This type impls `Widget`, expecting a float in the range `0.0..1.0`. +pub struct ProgressBar { + value: f64, +} + +impl ProgressBar { + pub fn new(value: f64) -> ProgressBar { + ProgressBar { + value: value.clamp(0., 1.), + } + } + + pub fn set_value(&mut self, value: f64) -> ChangeFlags { + self.value = value.clamp(0., 1.); + ChangeFlags::PAINT + } +} + +// See druid's button for info. +const HEIGHT: f64 = 20.0; +const WIDTH: f64 = 200.0; +const STROKE: f64 = 2.0; + +impl Widget for ProgressBar { + fn event(&mut self, _cx: &mut EventCx, _event: &Event) {} + + fn lifecycle(&mut self, _cx: &mut LifeCycleCx, _event: &LifeCycle) {} + + fn update(&mut self, cx: &mut UpdateCx) { + cx.request_paint(); + } + + fn layout(&mut self, _cx: &mut LayoutCx, bc: &BoxConstraints) -> Size { + bc.constrain(Size::new(WIDTH, HEIGHT)) + } + + fn accessibility(&mut self, cx: &mut AccessCx) { + let mut builder = accesskit::NodeBuilder::new(accesskit::Role::ProgressIndicator); + builder.set_default_action_verb(accesskit::DefaultActionVerb::Click); + cx.push_node(builder); + } + + fn paint(&mut self, cx: &mut PaintCx, builder: &mut SceneBuilder) { + let background_color = Color::WHITE_SMOKE; + let bar_color = Color::SPRING_GREEN; + let border_color = Color::DIM_GRAY; + + let progress_background_rect = cx.size().to_rect().to_rounded_rect(HEIGHT / 2.); + fill_color(builder, &progress_background_rect, background_color); + stroke(builder, &progress_background_rect, border_color, STROKE); + + let progress_bar_rect = + Rect::from_origin_size((0., 0.), Size::new(self.value * cx.size().width, HEIGHT)) + .inset(-STROKE / 2.) + .to_rounded_rect(HEIGHT / 2.); + fill_color(builder, &progress_bar_rect, bar_color); + } +} From e2b8045bfcba3f1243c4d33e507aaf58bb0037ca Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 15:00:38 +0100 Subject: [PATCH 08/12] Progress Bar View --- src/view/mod.rs | 2 ++ src/view/progress_bar.rs | 77 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/view/progress_bar.rs diff --git a/src/view/mod.rs b/src/view/mod.rs index 22a5bdd5e..a4e21c711 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -14,6 +14,7 @@ // mod async_list; mod button; +mod progress_bar; mod switch; // mod text; // mod layout_observer; @@ -30,5 +31,6 @@ pub use xilem_core::{Id, IdPath, VecSplice}; pub use button::button; pub use linear_layout::{h_stack, v_stack, LinearLayout}; pub use list::{list, List}; +pub use progress_bar::progress; pub use switch::switch; pub use view::{Adapt, AdaptState, Cx, Memoize, View, ViewMarker, ViewSequence}; diff --git a/src/view/progress_bar.rs b/src/view/progress_bar.rs new file mode 100644 index 000000000..250335701 --- /dev/null +++ b/src/view/progress_bar.rs @@ -0,0 +1,77 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::any::Any; +use std::marker::PhantomData; + +use crate::view::ViewMarker; +use crate::{view::Id, widget::ChangeFlags, MessageResult}; + +use super::{Cx, View}; + +pub struct ProgressBar { + value: f64, + data: PhantomData, +} + +pub fn progress(value: f64) -> ProgressBar { + ProgressBar::new(value) +} + +impl ProgressBar { + pub fn new(value: f64) -> Self { + ProgressBar { + value: value.clamp(0.0, 1.0), + data: PhantomData, + } + } +} + +impl ViewMarker for ProgressBar {} + +impl View for ProgressBar { + type State = (); + + type Element = crate::widget::ProgressBar; + + fn build(&self, cx: &mut Cx) -> (crate::view::Id, Self::State, Self::Element) { + let (id, element) = cx.with_new_id(|_cx| crate::widget::ProgressBar::new(self.value)); + (id, (), element) + } + + fn rebuild( + &self, + _cx: &mut Cx, + prev: &Self, + _id: &mut Id, + _state: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + if prev.value != self.value { + element.set_value(self.value) + } else { + ChangeFlags::default() + } + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + _message: Box, + _app_state: &mut T, + ) -> MessageResult { + MessageResult::Nop + } +} From ac3b27fe111472e5671ff40a66c980e4de87d454 Mon Sep 17 00:00:00 2001 From: John Skottis Date: Mon, 28 Aug 2023 15:00:43 +0100 Subject: [PATCH 09/12] Progress Bar Example --- examples/hello.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index 793f37d7c..070097900 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,4 +1,4 @@ -use xilem::view::{button, h_stack, switch, v_stack}; +use xilem::view::{button, h_stack, progress, switch, v_stack}; use xilem::{view::View, App, AppLauncher}; fn app_logic(data: &mut AppData) -> impl View { @@ -9,6 +9,7 @@ fn app_logic(data: &mut AppData) -> impl View { } else { format!("clicked {count} times") }; + let progress_val = data.count as f64 / 10.0; // The actual UI Code starts here v_stack(( @@ -28,8 +29,8 @@ fn app_logic(data: &mut AppData) -> impl View { switch(data.is_on, |data: &mut AppData, value: bool| { data.is_on = value }), - switch(data, |data: &mut AppData| &mut data.is_on), )), + progress(progress_val), )) .with_spacing(20.0) } From 4ebabdefdc4a4253c91d61074f42dcb5e57136f5 Mon Sep 17 00:00:00 2001 From: John Skottis Date: Thu, 31 Aug 2023 15:28:22 +0100 Subject: [PATCH 10/12] Fix rendering of progressbar when the width is zero --- src/widget/progress_bar.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/widget/progress_bar.rs b/src/widget/progress_bar.rs index 110a540d9..fa2ee2217 100644 --- a/src/widget/progress_bar.rs +++ b/src/widget/progress_bar.rs @@ -78,10 +78,15 @@ impl Widget for ProgressBar { fill_color(builder, &progress_background_rect, background_color); stroke(builder, &progress_background_rect, border_color, STROKE); - let progress_bar_rect = - Rect::from_origin_size((0., 0.), Size::new(self.value * cx.size().width, HEIGHT)) - .inset(-STROKE / 2.) - .to_rounded_rect(HEIGHT / 2.); - fill_color(builder, &progress_bar_rect, bar_color); + println!("{:?}", self.value * cx.size()); + let progress_width = self.value * cx.size().width; + if progress_width != 0. { + let progress_bar_rect = + Rect::from_origin_size((0., 0.), Size::new(progress_width, HEIGHT)) + .inset(-STROKE / 2.) + .to_rounded_rect(HEIGHT / 2.); + fill_color(builder, &progress_bar_rect, bar_color); + } + } } From 6d213a7ece9e011e88b8038a9ea47be16d11c680 Mon Sep 17 00:00:00 2001 From: John Skottis Date: Sun, 3 Sep 2023 09:23:00 +0100 Subject: [PATCH 11/12] Imrpove accessibility --- src/widget/progress_bar.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widget/progress_bar.rs b/src/widget/progress_bar.rs index fa2ee2217..515bdd071 100644 --- a/src/widget/progress_bar.rs +++ b/src/widget/progress_bar.rs @@ -65,7 +65,9 @@ impl Widget for ProgressBar { fn accessibility(&mut self, cx: &mut AccessCx) { let mut builder = accesskit::NodeBuilder::new(accesskit::Role::ProgressIndicator); - builder.set_default_action_verb(accesskit::DefaultActionVerb::Click); + builder.set_numeric_value(self.value * 100.); + builder.set_max_numeric_value(100.); + builder.set_min_numeric_value(0.); cx.push_node(builder); } From 5b2ead8cf618486baf64f388524ff88973cb48a6 Mon Sep 17 00:00:00 2001 From: John Skottis Date: Sun, 3 Sep 2023 09:23:12 +0100 Subject: [PATCH 12/12] Return stale message --- src/view/progress_bar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/progress_bar.rs b/src/view/progress_bar.rs index 250335701..c29acc84e 100644 --- a/src/view/progress_bar.rs +++ b/src/view/progress_bar.rs @@ -72,6 +72,6 @@ impl View for ProgressBar { _message: Box, _app_state: &mut T, ) -> MessageResult { - MessageResult::Nop + MessageResult::Stale(()) } }