Skip to content

Commit

Permalink
Create switch widget
Browse files Browse the repository at this point in the history
  • Loading branch information
giannissc committed Nov 22, 2023
1 parent f26bd59 commit d4bda72
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod core;
mod linear_layout;
mod piet_scene_helpers;
mod raw_event;
mod switch;
//mod scroll_view;
mod text;
#[allow(clippy::module_inception)]
Expand All @@ -33,5 +34,6 @@ 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 text::TextWidget;
pub use widget::{AnyWidget, Widget};
165 changes: 165 additions & 0 deletions src/widget/switch.rs
Original file line number Diff line number Diff line change
@@ -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_dragging: 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_dragging: 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_dragging {
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_dragging = 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_dragging = 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_dragging {
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_dragging || 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 d4bda72

Please sign in to comment.