use std::any::Any;
use std::sync::RwLock;
use super::{Element, ViewLimits, ViewStretch};
use super::context::{BasicContext, Context};
use crate::support::point::Point;
use crate::support::color::Color;
use crate::support::theme::get_theme;
use crate::view::{MouseButton, MouseButtonKind, CursorTracking};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SwitchState {
#[default]
Normal,
Hover,
Pressed,
Disabled,
}
pub type SwitchCallback = Box<dyn Fn(bool) + Send + Sync>;
pub struct SlideSwitch {
on: RwLock<bool>,
state: RwLock<SwitchState>,
track_on_color: Color,
track_off_color: Color,
thumb_color: Color,
width: f32,
height: f32,
enabled: bool,
on_change: Option<SwitchCallback>,
animation_progress: RwLock<f32>,
}
impl SlideSwitch {
pub fn new() -> Self {
let theme = get_theme();
Self {
on: RwLock::new(false),
state: RwLock::new(SwitchState::Normal),
track_on_color: theme.indicator_bright_color,
track_off_color: theme.frame_color,
thumb_color: Color::new(1.0, 1.0, 1.0, 1.0),
width: 44.0,
height: 24.0,
enabled: true,
on_change: None,
animation_progress: RwLock::new(0.0),
}
}
pub fn on(self, on: bool) -> Self {
*self.on.write().unwrap() = on;
*self.animation_progress.write().unwrap() = if on { 1.0 } else { 0.0 };
self
}
pub fn track_on_color(mut self, color: Color) -> Self {
self.track_on_color = color;
self
}
pub fn track_off_color(mut self, color: Color) -> Self {
self.track_off_color = color;
self
}
pub fn thumb_color(mut self, color: Color) -> Self {
self.thumb_color = color;
self
}
pub fn size(mut self, width: f32, height: f32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn on_change<F: Fn(bool) + Send + Sync + 'static>(mut self, callback: F) -> Self {
self.on_change = Some(Box::new(callback));
self
}
pub fn is_on(&self) -> bool {
*self.on.read().unwrap()
}
pub fn set_on(&self, on: bool) {
*self.on.write().unwrap() = on;
*self.animation_progress.write().unwrap() = if on { 1.0 } else { 0.0 };
}
pub fn toggle(&self) {
let mut on = self.on.write().unwrap();
*on = !*on;
*self.animation_progress.write().unwrap() = if *on { 1.0 } else { 0.0 };
}
fn draw_track(&self, ctx: &Context) {
let mut canvas = ctx.canvas.borrow_mut();
let state = *self.state.read().unwrap();
let progress = *self.animation_progress.read().unwrap();
let track_color = Color::new(
self.track_off_color.red + (self.track_on_color.red - self.track_off_color.red) * progress,
self.track_off_color.green + (self.track_on_color.green - self.track_off_color.green) * progress,
self.track_off_color.blue + (self.track_on_color.blue - self.track_off_color.blue) * progress,
self.track_off_color.alpha + (self.track_on_color.alpha - self.track_off_color.alpha) * progress,
);
let color = match state {
SwitchState::Normal => track_color,
SwitchState::Hover => track_color.level(1.1),
SwitchState::Pressed => track_color.level(0.9),
SwitchState::Disabled => track_color.with_alpha(0.5),
};
let corner_radius = self.height / 2.0;
canvas.fill_style(color);
canvas.fill_round_rect(ctx.bounds, corner_radius);
}
fn draw_thumb(&self, ctx: &Context) {
let mut canvas = ctx.canvas.borrow_mut();
let state = *self.state.read().unwrap();
let progress = *self.animation_progress.read().unwrap();
let thumb_radius = (self.height - 4.0) / 2.0;
let thumb_padding = 2.0;
let left_x = ctx.bounds.left + thumb_padding + thumb_radius;
let right_x = ctx.bounds.right - thumb_padding - thumb_radius;
let thumb_x = left_x + progress * (right_x - left_x);
let thumb_y = ctx.bounds.center().y;
let color = match state {
SwitchState::Normal => self.thumb_color,
SwitchState::Hover => self.thumb_color.level(0.95),
SwitchState::Pressed => self.thumb_color.level(0.9),
SwitchState::Disabled => self.thumb_color.with_alpha(0.7),
};
canvas.fill_style(color);
canvas.begin_path();
canvas.add_circle(crate::support::circle::Circle::new(
Point::new(thumb_x, thumb_y),
thumb_radius,
));
canvas.fill();
if state != SwitchState::Disabled {
canvas.stroke_style(Color::new(0.0, 0.0, 0.0, 0.1));
canvas.line_width(1.0);
canvas.begin_path();
canvas.add_circle(crate::support::circle::Circle::new(
Point::new(thumb_x, thumb_y),
thumb_radius,
));
canvas.stroke();
}
}
}
impl Default for SlideSwitch {
fn default() -> Self {
Self::new()
}
}
impl Element for SlideSwitch {
fn limits(&self, _ctx: &BasicContext) -> ViewLimits {
ViewLimits::fixed(self.width, self.height)
}
fn stretch(&self) -> ViewStretch {
ViewStretch::new(0.0, 0.0)
}
fn draw(&self, ctx: &Context) {
self.draw_track(ctx);
self.draw_thumb(ctx);
}
fn hit_test(&self, ctx: &Context, p: Point, _leaf: bool, _control: bool) -> Option<&dyn Element> {
if ctx.bounds.contains(p) && self.enabled {
Some(self)
} else {
None
}
}
fn wants_control(&self) -> bool {
self.enabled
}
fn handle_click(&self, ctx: &Context, btn: MouseButton) -> bool {
if !self.enabled || btn.button != MouseButtonKind::Left {
return false;
}
let mut state = self.state.write().unwrap();
if btn.down {
*state = SwitchState::Pressed;
} else {
if *state == SwitchState::Pressed && ctx.bounds.contains(btn.pos) {
drop(state);
self.toggle();
if let Some(ref callback) = self.on_change {
callback(self.is_on());
}
let mut state = self.state.write().unwrap();
*state = SwitchState::Hover;
} else {
*state = if ctx.bounds.contains(btn.pos) {
SwitchState::Hover
} else {
SwitchState::Normal
};
}
}
true
}
fn cursor(&mut self, _ctx: &Context, _p: Point, status: CursorTracking) -> bool {
if !self.enabled {
return false;
}
let mut state = self.state.write().unwrap();
if *state == SwitchState::Pressed {
return true;
}
match status {
CursorTracking::Entering | CursorTracking::Hovering => {
*state = SwitchState::Hover;
}
CursorTracking::Leaving => {
*state = SwitchState::Normal;
}
}
true
}
fn enable(&mut self, state: bool) {
self.enabled = state;
let mut switch_state = self.state.write().unwrap();
if !state {
*switch_state = SwitchState::Disabled;
} else if *switch_state == SwitchState::Disabled {
*switch_state = SwitchState::Normal;
}
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub fn slide_switch() -> SlideSwitch {
SlideSwitch::new()
}
pub fn slide_switch_on(on: bool) -> SlideSwitch {
SlideSwitch::new().on(on)
}