use crate::event::{Key, NamedKey, Point, Rect};
use crate::types::{Modifiers, WidgetId};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Switch {
pub id: WidgetId,
#[serde(default)]
pub value: bool,
#[serde(default)]
pub disabled: bool,
#[serde(default)]
pub focused: bool,
}
impl Switch {
pub fn new(id: WidgetId) -> Self {
Self {
id,
value: false,
disabled: false,
focused: false,
}
}
pub fn layout(&self, bounds: Rect) -> SwitchLayout {
SwitchLayout { bounds }
}
pub fn handle_key(&self, key: &Key, _modifiers: &Modifiers) -> Option<SwitchEvent> {
if self.disabled || !self.focused {
return None;
}
match key {
Key::Named(NamedKey::Enter) | Key::Char(' ') => Some(SwitchEvent::Toggled {
id: self.id.clone(),
value: !self.value,
}),
_ => None,
}
}
pub fn click(&self) -> Option<SwitchEvent> {
if self.disabled {
return None;
}
Some(SwitchEvent::Toggled {
id: self.id.clone(),
value: !self.value,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SwitchLayout {
pub bounds: Rect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SwitchHit {
Body,
Outside,
}
impl SwitchLayout {
pub fn hit_test(&self, p: Point) -> SwitchHit {
if self.bounds.contains(p) {
SwitchHit::Body
} else {
SwitchHit::Outside
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SwitchEvent {
Toggled { id: WidgetId, value: bool },
}
#[cfg(test)]
mod tests {
use super::*;
fn switch() -> Switch {
Switch::new(WidgetId::new("settings:wrap"))
}
#[test]
fn new_defaults_to_off_enabled_and_unfocused() {
let s = switch();
assert!(!s.value);
assert!(!s.disabled);
assert!(!s.focused);
}
#[test]
fn hit_test_inside_and_outside() {
let s = switch();
let layout = s.layout(Rect::new(2.0, 2.0, 6.0, 1.0));
assert_eq!(layout.hit_test(Point::new(3.0, 2.0)), SwitchHit::Body);
assert_eq!(layout.hit_test(Point::new(0.0, 0.0)), SwitchHit::Outside);
}
#[test]
fn handle_key_toggles_on_enter_when_focused_and_enabled() {
let mut s = switch();
s.focused = true;
let event = s.handle_key(&Key::Named(NamedKey::Enter), &Modifiers::default());
assert_eq!(
event,
Some(SwitchEvent::Toggled {
id: s.id.clone(),
value: true
})
);
}
#[test]
fn handle_key_toggles_on_space() {
let mut s = switch();
s.focused = true;
s.value = true;
let event = s.handle_key(&Key::Char(' '), &Modifiers::default());
assert_eq!(
event,
Some(SwitchEvent::Toggled {
id: s.id.clone(),
value: false
})
);
}
#[test]
fn handle_key_ignores_unrelated_keys() {
let mut s = switch();
s.focused = true;
assert_eq!(
s.handle_key(&Key::Named(NamedKey::Tab), &Modifiers::default()),
None
);
}
#[test]
fn handle_key_is_inert_when_not_focused() {
let s = switch();
assert_eq!(
s.handle_key(&Key::Named(NamedKey::Enter), &Modifiers::default()),
None
);
}
#[test]
fn handle_key_is_inert_when_disabled() {
let mut s = switch();
s.focused = true;
s.disabled = true;
assert_eq!(
s.handle_key(&Key::Named(NamedKey::Enter), &Modifiers::default()),
None
);
}
#[test]
fn click_toggles_without_requiring_focus() {
let s = switch();
assert_eq!(
s.click(),
Some(SwitchEvent::Toggled {
id: s.id.clone(),
value: true
})
);
}
#[test]
fn click_is_inert_when_disabled() {
let mut s = switch();
s.disabled = true;
assert_eq!(s.click(), None);
}
#[test]
fn serde_round_trip() {
let mut s = switch();
s.value = true;
s.focused = true;
let json = serde_json::to_string(&s).unwrap();
let back: Switch = serde_json::from_str(&json).unwrap();
assert_eq!(s, back);
}
#[test]
fn serde_defaults_value_disabled_focused_to_false_when_omitted() {
let json = r#"{"id":"settings:wrap"}"#;
let s: Switch = serde_json::from_str(json).unwrap();
assert!(!s.value);
assert!(!s.disabled);
assert!(!s.focused);
}
}