Skip to content

Commit

Permalink
Port Switch Widget
Browse files Browse the repository at this point in the history
  • Loading branch information
giannissc committed Aug 24, 2023
1 parent ecfdf03 commit 2775987
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
74 changes: 74 additions & 0 deletions src/view/switch.rs
Original file line number Diff line number Diff line change
@@ -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<A> View<bool, A> 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<dyn Any>,
app_state: &mut bool,
) -> MessageResult<A> {
*app_state = !*app_state;
MessageResult::Nop
}
}
4 changes: 3 additions & 1 deletion src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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};
167 changes: 167 additions & 0 deletions src/widget/switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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_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);
}
}

0 comments on commit 2775987

Please sign in to comment.