Skip to main content

agg_gui/widgets/
primitives.rs

1//! Primitive layout widgets: Stack, Padding, SizedBox, Spacer, Separator.
2
3use crate::color::Color;
4use crate::device_scale::device_scale;
5use crate::event::{Event, EventResult};
6use crate::geometry::{Rect, Size};
7use crate::draw_ctx::DrawCtx;
8use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase, resolve_fit_or_stretch};
9use crate::widget::Widget;
10
11// ---------------------------------------------------------------------------
12// Stack — overlays children at the same position (first = back, last = front)
13// ---------------------------------------------------------------------------
14
15/// Stacks children on top of each other, each sized to fill the stack's area.
16///
17/// Paint order: first child is drawn first (furthest back). The last child
18/// appears on top. Hit testing also follows paint order (reverse).
19pub struct Stack {
20    bounds: Rect,
21    children: Vec<Box<dyn Widget>>,
22    base: WidgetBase,
23}
24
25impl Stack {
26    pub fn new() -> Self {
27        Self { bounds: Rect::default(), children: Vec::new(), base: WidgetBase::new() }
28    }
29
30    pub fn add(mut self, child: Box<dyn Widget>) -> Self {
31        self.children.push(child);
32        self
33    }
34
35    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
36    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
37    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
38    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
39    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
40}
41
42impl Default for Stack { fn default() -> Self { Self::new() } }
43
44impl Widget for Stack {
45    fn type_name(&self) -> &'static str { "Stack" }
46    fn bounds(&self) -> Rect { self.bounds }
47    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
48    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
49    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
50
51    fn margin(&self)   -> Insets  { self.base.margin }
52    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
53    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
54    fn min_size(&self) -> Size    { self.base.min_size }
55    fn max_size(&self) -> Size    { self.base.max_size }
56
57    fn layout(&mut self, available: Size) -> Size {
58        for child in &mut self.children {
59            child.layout(available);
60            child.set_bounds(Rect::new(0.0, 0.0, available.width, available.height));
61        }
62
63        // Bring-to-front pass — **after** children.layout on purpose.
64        //
65        // A raise can be requested from two places:
66        //   1. Widget input handlers (e.g. `Window::on_event` firing on a
67        //      MouseDown inside the window — "click to raise").  These run
68        //      BEFORE the frame's layout pass, so the flag is already set
69        //      by the time we get here.
70        //   2. Widget `layout()` itself (e.g. `Window` detects the
71        //      `visible_cell` false→true rising edge at layout time, so
72        //      toggling a demo on from the sidebar raises its window).
73        //      These set the flag DURING this very layout pass.
74        //
75        // Draining the flags AFTER children.layout catches both cases in
76        // the same frame — no one-frame visual delay.  The reactive-mode
77        // event loop only renders once per event, so a one-frame delay
78        // means the raise is invisible until the next unrelated event
79        // arrives, which is what the user reported (sidebar-opened windows
80        // appearing in the back).
81        let mut i = 0;
82        let mut raised: Vec<Box<dyn Widget>> = Vec::new();
83        while i < self.children.len() {
84            if self.children[i].take_raise_request() {
85                raised.push(self.children.remove(i));
86                // Don't advance `i` — the list just shortened.
87            } else {
88                i += 1;
89            }
90        }
91        for r in raised {
92            self.children.push(r);
93        }
94
95        available
96    }
97
98    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
99
100    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
101}
102
103// ---------------------------------------------------------------------------
104// Padding — wraps one child with per-side insets
105// ---------------------------------------------------------------------------
106
107/// Surrounds a single child with configurable per-side padding.
108pub struct Padding {
109    bounds: Rect,
110    children: Vec<Box<dyn Widget>>,
111    base: WidgetBase,
112    insets: Insets,
113}
114
115impl Padding {
116    /// Explicit per-side padding.
117    pub fn new(insets: Insets, child: Box<dyn Widget>) -> Self {
118        Self { bounds: Rect::default(), children: vec![child], base: WidgetBase::new(), insets }
119    }
120
121    /// Uniform padding on all four sides.
122    pub fn uniform(amount: f64, child: Box<dyn Widget>) -> Self {
123        Self::new(Insets::all(amount), child)
124    }
125
126    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
127    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
128    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
129    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
130    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
131}
132
133impl Widget for Padding {
134    fn type_name(&self) -> &'static str { "Padding" }
135    fn bounds(&self) -> Rect { self.bounds }
136    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
137    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
138    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
139
140    fn margin(&self)   -> Insets  { self.base.margin }
141    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
142    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
143    fn min_size(&self) -> Size    { self.base.min_size }
144    fn max_size(&self) -> Size    { self.base.max_size }
145
146    fn layout(&mut self, available: Size) -> Size {
147        let p = &self.insets;
148        let inner = Size::new(
149            (available.width  - p.left - p.right ).max(0.0),
150            (available.height - p.top  - p.bottom).max(0.0),
151        );
152        if let Some(child) = self.children.first_mut() {
153            let desired = child.layout(inner);
154            // In Y-up coordinates: origin of the child content is at (left, bottom).
155            child.set_bounds(Rect::new(p.left, p.bottom, desired.width, desired.height));
156        }
157        // Report total size including insets.
158        let content_w = self.children.first().map_or(0.0, |c| c.bounds().width);
159        let content_h = self.children.first().map_or(0.0, |c| c.bounds().height);
160        Size::new(content_w + p.left + p.right, content_h + p.top + p.bottom)
161    }
162
163    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
164
165    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
166}
167
168// ---------------------------------------------------------------------------
169// SizedBox — forces specific width and/or height, with anchor-aware child placement
170// ---------------------------------------------------------------------------
171
172/// Forces a specific size on its optional child.
173///
174/// If `width` or `height` is `None`, the available size on that axis is passed
175/// through unchanged.  The child is placed within the box using its own
176/// `h_anchor` and `v_anchor`, respecting its `margin`.
177pub struct SizedBox {
178    bounds: Rect,
179    children: Vec<Box<dyn Widget>>,
180    base: WidgetBase,
181    pub width: Option<f64>,
182    pub height: Option<f64>,
183}
184
185impl SizedBox {
186    pub fn new() -> Self {
187        Self {
188            bounds: Rect::default(),
189            children: Vec::new(),
190            base: WidgetBase::new(),
191            width: None,
192            height: None,
193        }
194    }
195
196    pub fn with_width(mut self, w: f64) -> Self { self.width = Some(w); self }
197    pub fn with_height(mut self, h: f64) -> Self { self.height = Some(h); self }
198
199    pub fn with_child(mut self, child: Box<dyn Widget>) -> Self {
200        self.children.clear();
201        self.children.push(child);
202        self
203    }
204
205    /// Create a fixed-size empty box (gap / spacer with exact dimensions).
206    pub fn fixed(width: f64, height: f64) -> Self {
207        Self::new().with_width(width).with_height(height)
208    }
209
210    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
211    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
212    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
213    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
214    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
215}
216
217impl Default for SizedBox { fn default() -> Self { Self::new() } }
218
219impl Widget for SizedBox {
220    fn type_name(&self) -> &'static str { "SizedBox" }
221    fn bounds(&self) -> Rect { self.bounds }
222    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
223    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
224    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
225
226    fn margin(&self)   -> Insets  { self.base.margin }
227    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
228    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
229    fn min_size(&self) -> Size    { self.base.min_size }
230    fn max_size(&self) -> Size    { self.base.max_size }
231
232    fn layout(&mut self, available: Size) -> Size {
233        // Fall back to the available axis only for dimensions that haven't been
234        // explicitly set AND don't have a child to size to; otherwise use the
235        // child's natural size on that axis so the SizedBox reports a sensible
236        // height when only a width was supplied (e.g. narrow DragValue wrapper).
237        //
238        // When neither height nor child is present (pure horizontal spacer,
239        // e.g. `SizedBox::new().with_width(8.0)`), default the height to zero
240        // so the spacer doesn't inflate the parent row/column to the full
241        // available axis — which would otherwise push sibling widgets off
242        // screen.
243        let w = self.width.unwrap_or(available.width);
244        let mut h = self.height.unwrap_or_else(|| {
245            if self.children.is_empty() { 0.0 } else { available.height }
246        });
247
248        if let Some(child) = self.children.first_mut() {
249            let scale  = device_scale();
250            let m      = child.margin().scale(scale);
251            let slot_w = (w - m.left - m.right ).max(0.0);
252            let slot_h = (h - m.top  - m.bottom).max(0.0);
253
254            let desired = child.layout(Size::new(slot_w, slot_h));
255
256            // If the caller didn't pin the height, shrink to the child's
257            // natural height plus its vertical margin.
258            if self.height.is_none() {
259                h = (desired.height + m.vertical())
260                    .clamp(self.base.min_size.height, self.base.max_size.height);
261            }
262
263            // Horizontal placement within the box (margin already limits slot).
264            let h_anchor = child.h_anchor();
265            let min_w    = child.min_size().width;
266            let max_w    = child.max_size().width;
267            let child_w  = if h_anchor.is_stretch() {
268                slot_w.clamp(min_w, max_w)
269            } else if h_anchor == HAnchor::MAX_FIT_OR_STRETCH {
270                resolve_fit_or_stretch(desired.width, slot_w, true).clamp(min_w, max_w)
271            } else if h_anchor == HAnchor::MIN_FIT_OR_STRETCH {
272                resolve_fit_or_stretch(desired.width, slot_w, false).clamp(min_w, max_w)
273            } else {
274                desired.width.clamp(min_w, max_w)
275            };
276
277            let child_x = if h_anchor.contains(HAnchor::RIGHT) && !h_anchor.contains(HAnchor::LEFT) {
278                (w - m.right - child_w).max(0.0)
279            } else if h_anchor.contains(HAnchor::CENTER) && !h_anchor.is_stretch() {
280                m.left + (slot_w - child_w) * 0.5
281            } else {
282                m.left
283            };
284
285            // Vertical placement (Y-up: BOTTOM = low Y).
286            let v_anchor = child.v_anchor();
287            let min_h    = child.min_size().height;
288            let max_h    = child.max_size().height;
289            let child_h  = if v_anchor.is_stretch() {
290                slot_h.clamp(min_h, max_h)
291            } else if v_anchor == VAnchor::MAX_FIT_OR_STRETCH {
292                resolve_fit_or_stretch(desired.height, slot_h, true).clamp(min_h, max_h)
293            } else if v_anchor == VAnchor::MIN_FIT_OR_STRETCH {
294                resolve_fit_or_stretch(desired.height, slot_h, false).clamp(min_h, max_h)
295            } else {
296                desired.height.clamp(min_h, max_h)
297            };
298
299            // When a dimension is explicitly pinned, the child must fit —
300            // otherwise a child whose `layout` ignores the slot budget (e.g.
301            // `TextField` returning a font-derived natural height) paints
302            // outside the SizedBox and clips into siblings above it.  Widgets
303            // re-read `self.bounds` during paint, so shrinking here propagates
304            // cleanly.
305            let child_w = if self.width.is_some()  { child_w.min(slot_w) } else { child_w };
306            let child_h = if self.height.is_some() { child_h.min(slot_h) } else { child_h };
307
308            let child_y = if v_anchor.contains(VAnchor::TOP) && !v_anchor.contains(VAnchor::BOTTOM) {
309                (h - m.top - child_h).max(0.0)
310            } else if v_anchor.contains(VAnchor::CENTER) && !v_anchor.is_stretch() {
311                m.bottom + (slot_h - child_h) * 0.5
312            } else {
313                m.bottom
314            };
315
316            child.set_bounds(Rect::new(child_x, child_y, child_w, child_h));
317        }
318
319        Size::new(w, h)
320    }
321
322    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
323
324    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
325}
326
327// ---------------------------------------------------------------------------
328// Spacer — flexible empty space for use in flex layouts
329// ---------------------------------------------------------------------------
330
331/// An invisible leaf widget that expands to fill available space.
332///
333/// Used as a `flex` child in [`FlexColumn`][crate::FlexColumn] or
334/// [`FlexRow`][crate::FlexRow] to push siblings apart.
335pub struct Spacer {
336    bounds: Rect,
337    children: Vec<Box<dyn Widget>>,
338    base: WidgetBase,
339}
340
341impl Spacer {
342    pub fn new() -> Self {
343        Self { bounds: Rect::default(), children: Vec::new(), base: WidgetBase::new() }
344    }
345
346    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
347    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
348    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
349    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
350    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
351}
352
353impl Default for Spacer { fn default() -> Self { Self::new() } }
354
355impl Widget for Spacer {
356    fn type_name(&self) -> &'static str { "Spacer" }
357    fn bounds(&self) -> Rect { self.bounds }
358    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
359    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
360    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
361
362    fn margin(&self)   -> Insets  { self.base.margin }
363    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
364    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
365    fn min_size(&self) -> Size    { self.base.min_size }
366    fn max_size(&self) -> Size    { self.base.max_size }
367
368    fn layout(&mut self, available: Size) -> Size { available }
369
370    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
371
372    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
373}
374
375// ---------------------------------------------------------------------------
376// Separator — a thin horizontal or vertical divider line
377// ---------------------------------------------------------------------------
378
379/// A thin horizontal or vertical divider line.
380///
381/// When no explicit colour is set via [`with_color`](Separator::with_color),
382/// the separator reads its colour from the active theme's `separator` field at
383/// paint time, so it automatically adapts to dark / light mode.
384pub struct Separator {
385    bounds: Rect,
386    children: Vec<Box<dyn Widget>>,
387    base: WidgetBase,
388    vertical: bool,
389    line_inset: f64,
390    /// `None` → use `ctx.visuals().separator` at paint time.
391    color: Option<Color>,
392}
393
394impl Separator {
395    /// Create a horizontal separator (the common case).
396    pub fn horizontal() -> Self {
397        Self {
398            bounds: Rect::default(),
399            children: Vec::new(),
400            base: WidgetBase::new(),
401            vertical: false,
402            line_inset: 4.0,
403            color: None,
404        }
405    }
406
407    /// Create a vertical separator.
408    pub fn vertical() -> Self {
409        Self { vertical: true, ..Self::horizontal() }
410    }
411
412    pub fn with_line_inset(mut self, m: f64) -> Self { self.line_inset = m; self }
413    pub fn with_color(mut self, c: Color) -> Self { self.color = Some(c); self }
414
415    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
416    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
417    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
418    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
419    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
420}
421
422impl Widget for Separator {
423    fn type_name(&self) -> &'static str { "Separator" }
424    fn bounds(&self) -> Rect { self.bounds }
425    fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
426    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
427    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
428
429    fn margin(&self)   -> Insets  { self.base.margin }
430    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
431    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
432    fn min_size(&self) -> Size    { self.base.min_size }
433    fn max_size(&self) -> Size    { self.base.max_size }
434
435    fn layout(&mut self, available: Size) -> Size {
436        if self.vertical {
437            Size::new(1.0 + self.line_inset * 2.0, available.height)
438        } else {
439            Size::new(available.width, 1.0 + self.line_inset * 2.0)
440        }
441    }
442
443    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
444        let w = self.bounds.width;
445        let h = self.bounds.height;
446        let color = self.color.unwrap_or_else(|| ctx.visuals().separator);
447        ctx.set_fill_color(color);
448        ctx.begin_path();
449        if self.vertical {
450            ctx.rect(self.line_inset, 0.0, 1.0, h);
451        } else {
452            ctx.rect(0.0, self.line_inset, w, 1.0);
453        }
454        ctx.fill();
455    }
456
457    fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
458}