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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! `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>>,
/// Shared "affordance active" flag. When `false` the chevron paints
/// nothing and ignores clicks — lets a parent hide the collapse
/// affordance at runtime (e.g. a Window whose `collapsible` flag is
/// toggled off) without rebuilding the widget tree. Defaults to `true`.
enabled: Rc<Cell<bool>>,
/// 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())),
enabled: Rc::new(Cell::new(true)),
on_click: None,
}
}
/// Share the "affordance active" cell with the parent. When the parent
/// sets it to `false`, the chevron becomes invisible and non-interactive.
pub fn with_enabled_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
self.enabled = cell;
self
}
/// 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) {
if !self.enabled.get() {
return;
}
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 {
self.enabled.get()
&& 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 !self.enabled.get() {
return EventResult::Ignored;
}
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
}
}