use std::cell::RefCell;
use crate::event::{MouseButton, Point, Rect, UiEvent};
use crate::primitives::status_bar::{StatusBarHit, StatusBarLayout};
use crate::types::WidgetId;
#[derive(Debug, Clone, Default)]
pub struct StatusBarInteraction {
layout: RefCell<Option<StatusBarLayout>>,
hovered: Option<WidgetId>,
pressed: Option<WidgetId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StatusBarAction {
Clicked(WidgetId),
Redraw,
Ignored,
}
impl StatusBarInteraction {
pub fn new() -> Self {
Self::default()
}
pub fn set_layout(&self, layout: StatusBarLayout) {
*self.layout.borrow_mut() = Some(layout);
}
pub fn hovered_id(&self) -> Option<&WidgetId> {
self.hovered.as_ref()
}
pub fn pressed_id(&self) -> Option<&WidgetId> {
self.pressed.as_ref()
}
pub fn handle(&mut self, event: &UiEvent, bar_rect: Rect) -> StatusBarAction {
match event {
UiEvent::MouseMoved { position, .. } => {
let new_hover = self.hit_test(bar_rect, *position);
if new_hover != self.hovered {
self.hovered = new_hover;
StatusBarAction::Redraw
} else {
StatusBarAction::Ignored
}
}
UiEvent::MouseDown {
button: MouseButton::Left,
position,
..
} => {
let hit = self.hit_test(bar_rect, *position);
if hit.is_some() {
self.pressed = hit;
StatusBarAction::Redraw
} else {
StatusBarAction::Ignored
}
}
UiEvent::MouseUp {
button: MouseButton::Left,
position,
..
} => {
let was_pressed = self.pressed.take();
if let Some(ref pressed_id) = was_pressed {
let hit = self.hit_test(bar_rect, *position);
if hit.as_ref() == Some(pressed_id) {
return StatusBarAction::Clicked(*pressed_id);
}
}
if was_pressed.is_some() {
StatusBarAction::Redraw
} else {
StatusBarAction::Ignored
}
}
_ => StatusBarAction::Ignored,
}
}
fn hit_test(&self, bar_rect: Rect, position: Point) -> Option<WidgetId> {
if position.x < bar_rect.x
|| position.x >= bar_rect.x + bar_rect.width
|| position.y < bar_rect.y
|| position.y >= bar_rect.y + bar_rect.height
{
return None;
}
let local_x = position.x - bar_rect.x;
let local_y = position.y - bar_rect.y;
let guard = self.layout.borrow();
match guard.as_ref().map(|l| l.hit_test(local_x, local_y)) {
Some(StatusBarHit::Segment(id)) => Some(id),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bar_rect() -> Rect {
Rect::new(0.0, 100.0, 80.0, 1.0)
}
fn test_layout() -> StatusBarLayout {
StatusBarLayout {
bar_width: 80.0,
bar_height: 1.0,
visible_segments: Vec::new(),
hit_regions: vec![
(
crate::event::Rect::new(0.0, 0.0, 10.0, 1.0),
StatusBarHit::Segment(WidgetId::new("run")),
),
(
crate::event::Rect::new(10.0, 0.0, 10.0, 1.0),
StatusBarHit::Segment(WidgetId::new("stop")),
),
],
resolved_right_start: 0,
}
}
#[test]
fn hover_sets_hovered_id() {
let mut sbi = StatusBarInteraction::new();
sbi.set_layout(test_layout());
let ev = UiEvent::MouseMoved {
position: Point { x: 5.0, y: 100.5 },
buttons: Default::default(),
};
let action = sbi.handle(&ev, bar_rect());
assert_eq!(action, StatusBarAction::Redraw);
assert_eq!(sbi.hovered_id(), Some(&WidgetId::new("run")));
}
#[test]
fn hover_outside_clears() {
let mut sbi = StatusBarInteraction::new();
sbi.set_layout(test_layout());
sbi.handle(
&UiEvent::MouseMoved {
position: Point { x: 5.0, y: 100.5 },
buttons: Default::default(),
},
bar_rect(),
);
let action = sbi.handle(
&UiEvent::MouseMoved {
position: Point { x: 5.0, y: 50.0 },
buttons: Default::default(),
},
bar_rect(),
);
assert_eq!(action, StatusBarAction::Redraw);
assert_eq!(sbi.hovered_id(), None);
}
#[test]
fn click_emits_clicked() {
let mut sbi = StatusBarInteraction::new();
sbi.set_layout(test_layout());
let rect = bar_rect();
sbi.handle(
&UiEvent::MouseDown {
button: MouseButton::Left,
position: Point { x: 15.0, y: 100.5 },
modifiers: Default::default(),
widget: None,
},
rect,
);
assert_eq!(sbi.pressed_id(), Some(&WidgetId::new("stop")));
let action = sbi.handle(
&UiEvent::MouseUp {
button: MouseButton::Left,
position: Point { x: 15.0, y: 100.5 },
widget: None,
},
rect,
);
assert_eq!(action, StatusBarAction::Clicked(WidgetId::new("stop")));
assert_eq!(sbi.pressed_id(), None);
}
#[test]
fn press_drag_away_does_not_click() {
let mut sbi = StatusBarInteraction::new();
sbi.set_layout(test_layout());
let rect = bar_rect();
sbi.handle(
&UiEvent::MouseDown {
button: MouseButton::Left,
position: Point { x: 5.0, y: 100.5 },
modifiers: Default::default(),
widget: None,
},
rect,
);
let action = sbi.handle(
&UiEvent::MouseUp {
button: MouseButton::Left,
position: Point { x: 50.0, y: 100.5 },
widget: None,
},
rect,
);
assert_eq!(action, StatusBarAction::Redraw);
}
}