use crate::render::{Bounds, RenderContext};
use super::{
ClickButton, Command, Msg, StateColors, Tooltip, Widget, WidgetStyle, draw_icon_pill,
glyph_label, measure_text_pill,
};
const LOCK_GLYPH: &str = "\u{f023}";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hypridle {
active: bool,
}
impl Hypridle {
pub fn new(active: bool) -> Self {
Self { active }
}
pub fn active(self) -> bool {
self.active
}
}
pub struct HypridleWidget {
bounds: Bounds,
state: Option<Hypridle>,
style: WidgetStyle,
}
impl HypridleWidget {
pub fn new(bounds: Bounds) -> Self {
Self {
bounds,
state: None,
style: WidgetStyle::default(),
}
}
pub fn with_style(mut self, style: WidgetStyle) -> Self {
self.style = style;
self
}
fn display_text(&self) -> String {
self.state
.map(|_| glyph_label(self.style.glyph(LOCK_GLYPH), ""))
.unwrap_or_default()
}
fn colors(&self) -> StateColors {
StateColors {
background: self.style.background,
foreground: if self.state.is_some_and(Hypridle::active) {
self.style.accent
} else {
self.style.foreground
},
border: self.style.border,
}
}
fn contains(&self, px: u32, py: u32) -> bool {
px >= self.bounds.x
&& px < self.bounds.x + self.bounds.width
&& py >= self.bounds.y
&& py < self.bounds.y + self.bounds.height
}
}
impl Widget for HypridleWidget {
fn update(&mut self, msg: &Msg) -> bool {
let Msg::Hypridle(next) = msg else {
return false;
};
if self.state == Some(*next) {
return false;
}
self.state = Some(*next);
true
}
fn draw(&self, ctx: &mut RenderContext) {
draw_icon_pill(
ctx,
&self.style,
self.bounds,
&self.display_text(),
self.colors(),
);
}
fn measure(&self, ctx: &mut RenderContext, _height: u32) -> u32 {
measure_text_pill(ctx, &self.style, &self.display_text())
}
fn bounds(&self) -> Bounds {
self.bounds
}
fn set_bounds(&mut self, bounds: Bounds) {
self.bounds = bounds;
}
fn on_click(&self, px: u32, py: u32, button: ClickButton) -> Option<Command> {
if button != ClickButton::Left || !self.contains(px, py) {
return None;
}
Some(Command::SetHypridle(!self.state?.active()))
}
fn tooltip_at(&self, px: u32, py: u32) -> Option<Tooltip> {
if !self.contains(px, py) {
return None;
}
let status = if self.state?.active() {
"active"
} else {
"inactive"
};
Some(Tooltip {
text: format!("Hypridle {status}"),
bounds: self.bounds,
})
}
}
#[cfg(test)]
mod tests {
use crate::render::{Bounds, RenderContext};
use crate::widget::{ClickButton, Command, Msg, Widget};
use super::{Hypridle, HypridleWidget};
#[test]
fn state_changes_redraw_and_identical_readings_do_not() {
let mut widget = HypridleWidget::new(Bounds::new(0, 0, 32, 32));
assert!(widget.update(&Msg::Hypridle(Hypridle::new(true))));
assert!(!widget.update(&Msg::Hypridle(Hypridle::new(true))));
assert!(widget.update(&Msg::Hypridle(Hypridle::new(false))));
}
#[test]
fn left_click_requests_the_opposite_state() {
let mut widget = HypridleWidget::new(Bounds::new(10, 0, 32, 32));
widget.update(&Msg::Hypridle(Hypridle::new(true)));
assert_eq!(
widget.on_click(20, 10, ClickButton::Left),
Some(Command::SetHypridle(false))
);
widget.update(&Msg::Hypridle(Hypridle::new(false)));
assert_eq!(
widget.on_click(20, 10, ClickButton::Left),
Some(Command::SetHypridle(true))
);
assert_eq!(widget.on_click(20, 10, ClickButton::Right), None);
}
#[test]
fn widget_is_hidden_and_inert_before_the_first_reading() {
let widget = HypridleWidget::new(Bounds::new(0, 0, 32, 32));
let mut ctx = RenderContext::new(64, 32);
assert_eq!(widget.measure(&mut ctx, 32), 0);
assert_eq!(widget.on_click(10, 10, ClickButton::Left), None);
}
#[test]
fn tooltip_reports_the_current_state() {
let mut widget = HypridleWidget::new(Bounds::new(0, 0, 32, 32));
widget.update(&Msg::Hypridle(Hypridle::new(true)));
assert_eq!(
widget.tooltip_at(10, 10).map(|tooltip| tooltip.text),
Some("Hypridle active".to_string())
);
widget.update(&Msg::Hypridle(Hypridle::new(false)));
assert_eq!(
widget.tooltip_at(10, 10).map(|tooltip| tooltip.text),
Some("Hypridle inactive".to_string())
);
}
#[test]
fn unrelated_messages_are_ignored() {
let mut widget = HypridleWidget::new(Bounds::new(0, 0, 32, 32));
assert!(!widget.update(&Msg::tick_now()));
}
}