1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! `ChevronWidget` — a small clickable arrow that toggles a
//! collapsed/expanded state. Composes into title bars, accordion
//! headers, tree rows, anywhere a "fold" affordance is needed.
//!
//! The chevron is a real `Widget`: it has its own bounds, paints
//! itself, and consumes mouse-down events that land inside it. The
//! parent uses standard `children_mut()` + `layout()` to place it,
//! and supplies an `on_click` closure to act on the toggle. Parents
//! that need to share collapse state across multiple widgets pass an
//! `Rc<Cell<bool>>` for the chevron to read each paint — keeping a
//! single source of truth without copy-on-every-frame boilerplate.
use std::cell::Cell;
use std::rc::Rc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult, MouseButton};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::WidgetBase;
use crate::widget::Widget;
use crate::widgets::window::chrome::paint_chevron;
/// Logical size of the chevron's hit / paint region. The arrow itself
/// is ~8 px wide; the surrounding padding gives the user a comfortable
/// click target.
pub const CHEVRON_SIZE: f64 = 16.0;
/// A clickable collapse / expand chevron.
pub struct ChevronWidget {
bounds: Rect,
base: WidgetBase,
children: Vec<Box<dyn Widget>>,
/// Shared collapse flag — the chevron reads this each paint to pick
/// its glyph orientation. The parent writes it when the user (or
/// any other code path) toggles the fold.
collapsed: Rc<Cell<bool>>,
/// Shared glyph colour cell — the parent writes the theme colour
/// each paint pass; the chevron reads it without needing a typed
/// downcast through the children Vec. Defaults to white so a
/// caller that never wires the cell still gets a visible glyph.
color: Rc<Cell<Color>>,
/// Invoked on left-click. Parents put their toggle logic in here.
on_click: Option<Box<dyn FnMut()>>,
}
impl ChevronWidget {
/// Build a chevron sharing `collapsed` with its parent. The parent
/// is the source of truth — the chevron only renders + emits clicks.
pub fn new(collapsed: Rc<Cell<bool>>) -> Self {
Self {
bounds: Rect::new(0.0, 0.0, CHEVRON_SIZE, CHEVRON_SIZE),
base: WidgetBase::new(),
children: Vec::new(),
collapsed,
color: Rc::new(Cell::new(Color::white())),
on_click: None,
}
}
/// Wire a left-click handler. Typical implementations flip the
/// shared `collapsed` cell and request a redraw / notify their
/// owner. Builder form — chain at construction.
pub fn on_click(mut self, f: impl FnMut() + 'static) -> Self {
self.on_click = Some(Box::new(f));
self
}
/// Hand a shared colour cell to the chevron. The parent keeps a
/// clone of the returned cell and writes the active theme colour
/// into it each paint pass; the chevron picks it up automatically.
pub fn with_color_cell(mut self, c: Rc<Cell<Color>>) -> Self {
self.color = c;
self
}
}
impl Widget for ChevronWidget {
fn type_name(&self) -> &'static str {
"ChevronWidget"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn layout(&mut self, _available: Size) -> Size {
Size::new(self.bounds.width, self.bounds.height)
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
let cx = self.bounds.width * 0.5;
let cy = self.bounds.height * 0.5;
paint_chevron(ctx, cx, cy, self.collapsed.get(), self.color.get());
}
fn hit_test(&self, local: Point) -> bool {
local.x >= 0.0
&& local.x <= self.bounds.width
&& local.y >= 0.0
&& local.y <= self.bounds.height
}
fn on_event(&mut self, event: &Event) -> EventResult {
if let Event::MouseDown {
button: MouseButton::Left,
..
} = event
{
if let Some(cb) = self.on_click.as_mut() {
cb();
}
crate::animation::request_draw();
return EventResult::Consumed;
}
EventResult::Ignored
}
}