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::draw_ctx::DrawCtx;
6use crate::event::{Event, EventResult};
7use crate::geometry::{Rect, Size};
8use crate::layout_props::{resolve_fit_or_stretch, HAnchor, Insets, VAnchor, WidgetBase};
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 {
28            bounds: Rect::default(),
29            children: Vec::new(),
30            base: WidgetBase::new(),
31        }
32    }
33
34    pub fn add(mut self, child: Box<dyn Widget>) -> Self {
35        self.children.push(child);
36        self
37    }
38
39    pub fn with_margin(mut self, m: Insets) -> Self {
40        self.base.margin = m;
41        self
42    }
43    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
44        self.base.h_anchor = h;
45        self
46    }
47    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
48        self.base.v_anchor = v;
49        self
50    }
51    pub fn with_min_size(mut self, s: Size) -> Self {
52        self.base.min_size = s;
53        self
54    }
55    pub fn with_max_size(mut self, s: Size) -> Self {
56        self.base.max_size = s;
57        self
58    }
59}
60
61impl Default for Stack {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl Widget for Stack {
68    fn type_name(&self) -> &'static str {
69        "Stack"
70    }
71    fn bounds(&self) -> Rect {
72        self.bounds
73    }
74    fn set_bounds(&mut self, b: Rect) {
75        self.bounds = b;
76    }
77    fn children(&self) -> &[Box<dyn Widget>] {
78        &self.children
79    }
80    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
81        &mut self.children
82    }
83
84    fn margin(&self) -> Insets {
85        self.base.margin
86    }
87    fn h_anchor(&self) -> HAnchor {
88        self.base.h_anchor
89    }
90    fn v_anchor(&self) -> VAnchor {
91        self.base.v_anchor
92    }
93    fn min_size(&self) -> Size {
94        self.base.min_size
95    }
96    fn max_size(&self) -> Size {
97        self.base.max_size
98    }
99
100    fn layout(&mut self, available: Size) -> Size {
101        for child in &mut self.children {
102            child.layout(available);
103            child.set_bounds(Rect::new(0.0, 0.0, available.width, available.height));
104        }
105
106        // Bring-to-front pass — **after** children.layout on purpose.
107        //
108        // A raise can be requested from two places:
109        //   1. Widget input handlers (e.g. `Window::on_event` firing on a
110        //      MouseDown inside the window — "click to raise").  These run
111        //      BEFORE the frame's layout pass, so the flag is already set
112        //      by the time we get here.
113        //   2. Widget `layout()` itself (e.g. `Window` detects the
114        //      `visible_cell` false→true rising edge at layout time, so
115        //      toggling a demo on from the sidebar raises its window).
116        //      These set the flag DURING this very layout pass.
117        //
118        // Draining the flags AFTER children.layout catches both cases in
119        // the same frame — no one-frame visual delay.  The reactive-mode
120        // event loop only renders once per event, so a one-frame delay
121        // means the raise is invisible until the next unrelated event
122        // arrives, which is what the user reported (sidebar-opened windows
123        // appearing in the back).
124        let mut i = 0;
125        let mut raised: Vec<Box<dyn Widget>> = Vec::new();
126        while i < self.children.len() {
127            if self.children[i].take_raise_request() {
128                raised.push(self.children.remove(i));
129                // Don't advance `i` — the list just shortened.
130            } else {
131                i += 1;
132            }
133        }
134        for r in raised {
135            self.children.push(r);
136        }
137
138        available
139    }
140
141    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
142
143    fn on_event(&mut self, _: &Event) -> EventResult {
144        EventResult::Ignored
145    }
146}
147
148// ---------------------------------------------------------------------------
149// Padding — wraps one child with per-side insets
150// ---------------------------------------------------------------------------
151
152/// Surrounds a single child with configurable per-side padding.
153pub struct Padding {
154    bounds: Rect,
155    children: Vec<Box<dyn Widget>>,
156    base: WidgetBase,
157    insets: Insets,
158}
159
160impl Padding {
161    /// Explicit per-side padding.
162    pub fn new(insets: Insets, child: Box<dyn Widget>) -> Self {
163        Self {
164            bounds: Rect::default(),
165            children: vec![child],
166            base: WidgetBase::new(),
167            insets,
168        }
169    }
170
171    /// Uniform padding on all four sides.
172    pub fn uniform(amount: f64, child: Box<dyn Widget>) -> Self {
173        Self::new(Insets::all(amount), child)
174    }
175
176    pub fn with_margin(mut self, m: Insets) -> Self {
177        self.base.margin = m;
178        self
179    }
180    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
181        self.base.h_anchor = h;
182        self
183    }
184    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
185        self.base.v_anchor = v;
186        self
187    }
188    pub fn with_min_size(mut self, s: Size) -> Self {
189        self.base.min_size = s;
190        self
191    }
192    pub fn with_max_size(mut self, s: Size) -> Self {
193        self.base.max_size = s;
194        self
195    }
196}
197
198impl Widget for Padding {
199    fn type_name(&self) -> &'static str {
200        "Padding"
201    }
202    fn bounds(&self) -> Rect {
203        self.bounds
204    }
205    fn set_bounds(&mut self, b: Rect) {
206        self.bounds = b;
207    }
208    fn children(&self) -> &[Box<dyn Widget>] {
209        &self.children
210    }
211    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
212        &mut self.children
213    }
214
215    fn margin(&self) -> Insets {
216        self.base.margin
217    }
218    fn h_anchor(&self) -> HAnchor {
219        self.base.h_anchor
220    }
221    fn v_anchor(&self) -> VAnchor {
222        self.base.v_anchor
223    }
224    fn min_size(&self) -> Size {
225        self.base.min_size
226    }
227    fn max_size(&self) -> Size {
228        self.base.max_size
229    }
230
231    fn layout(&mut self, available: Size) -> Size {
232        let p = &self.insets;
233        let inner = Size::new(
234            (available.width - p.left - p.right).max(0.0),
235            (available.height - p.top - p.bottom).max(0.0),
236        );
237        if let Some(child) = self.children.first_mut() {
238            let desired = child.layout(inner);
239            // In Y-up coordinates: origin of the child content is at (left, bottom).
240            child.set_bounds(Rect::new(p.left, p.bottom, desired.width, desired.height));
241        }
242        // Report total size including insets.
243        let content_w = self.children.first().map_or(0.0, |c| c.bounds().width);
244        let content_h = self.children.first().map_or(0.0, |c| c.bounds().height);
245        Size::new(content_w + p.left + p.right, content_h + p.top + p.bottom)
246    }
247
248    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
249
250    fn on_event(&mut self, _: &Event) -> EventResult {
251        EventResult::Ignored
252    }
253}
254
255// ---------------------------------------------------------------------------
256// SizedBox — forces specific width and/or height, with anchor-aware child placement
257// ---------------------------------------------------------------------------
258
259/// Forces a specific size on its optional child.
260///
261/// If `width` or `height` is `None`, the available size on that axis is passed
262/// through unchanged.  The child is placed within the box using its own
263/// `h_anchor` and `v_anchor`, respecting its `margin`.
264pub struct SizedBox {
265    bounds: Rect,
266    children: Vec<Box<dyn Widget>>,
267    base: WidgetBase,
268    pub width: Option<f64>,
269    pub height: Option<f64>,
270}
271
272impl SizedBox {
273    pub fn new() -> Self {
274        Self {
275            bounds: Rect::default(),
276            children: Vec::new(),
277            base: WidgetBase::new(),
278            width: None,
279            height: None,
280        }
281    }
282
283    pub fn with_width(mut self, w: f64) -> Self {
284        self.width = Some(w);
285        self
286    }
287    pub fn with_height(mut self, h: f64) -> Self {
288        self.height = Some(h);
289        self
290    }
291
292    pub fn with_child(mut self, child: Box<dyn Widget>) -> Self {
293        self.children.clear();
294        self.children.push(child);
295        self
296    }
297
298    /// Create a fixed-size empty box (gap / spacer with exact dimensions).
299    pub fn fixed(width: f64, height: f64) -> Self {
300        Self::new().with_width(width).with_height(height)
301    }
302
303    pub fn with_margin(mut self, m: Insets) -> Self {
304        self.base.margin = m;
305        self
306    }
307    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
308        self.base.h_anchor = h;
309        self
310    }
311    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
312        self.base.v_anchor = v;
313        self
314    }
315    pub fn with_min_size(mut self, s: Size) -> Self {
316        self.base.min_size = s;
317        self
318    }
319    pub fn with_max_size(mut self, s: Size) -> Self {
320        self.base.max_size = s;
321        self
322    }
323}
324
325impl Default for SizedBox {
326    fn default() -> Self {
327        Self::new()
328    }
329}
330
331impl Widget for SizedBox {
332    fn type_name(&self) -> &'static str {
333        "SizedBox"
334    }
335    fn bounds(&self) -> Rect {
336        self.bounds
337    }
338    fn set_bounds(&mut self, b: Rect) {
339        self.bounds = b;
340    }
341    fn children(&self) -> &[Box<dyn Widget>] {
342        &self.children
343    }
344    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
345        &mut self.children
346    }
347
348    fn margin(&self) -> Insets {
349        self.base.margin
350    }
351    fn h_anchor(&self) -> HAnchor {
352        self.base.h_anchor
353    }
354    fn v_anchor(&self) -> VAnchor {
355        self.base.v_anchor
356    }
357    fn min_size(&self) -> Size {
358        self.base.min_size
359    }
360    fn max_size(&self) -> Size {
361        self.base.max_size
362    }
363
364    fn layout(&mut self, available: Size) -> Size {
365        // Fall back to the available axis only for dimensions that haven't been
366        // explicitly set AND don't have a child to size to; otherwise use the
367        // child's natural size on that axis so the SizedBox reports a sensible
368        // height when only a width was supplied (e.g. narrow DragValue wrapper).
369        //
370        // When neither height nor child is present (pure horizontal spacer,
371        // e.g. `SizedBox::new().with_width(8.0)`), default the height to zero
372        // so the spacer doesn't inflate the parent row/column to the full
373        // available axis — which would otherwise push sibling widgets off
374        // screen.
375        let w = self.width.unwrap_or(available.width);
376        let mut h = self.height.unwrap_or_else(|| {
377            if self.children.is_empty() {
378                0.0
379            } else {
380                available.height
381            }
382        });
383
384        if let Some(child) = self.children.first_mut() {
385            let scale = device_scale();
386            let m = child.margin().scale(scale);
387            let slot_w = (w - m.left - m.right).max(0.0);
388            let slot_h = (h - m.top - m.bottom).max(0.0);
389
390            let desired = child.layout(Size::new(slot_w, slot_h));
391
392            // If the caller didn't pin the height, shrink to the child's
393            // natural height plus its vertical margin.
394            if self.height.is_none() {
395                h = (desired.height + m.vertical())
396                    .clamp(self.base.min_size.height, self.base.max_size.height);
397            }
398
399            // Horizontal placement within the box (margin already limits slot).
400            let h_anchor = child.h_anchor();
401            let min_w = child.min_size().width;
402            let max_w = child.max_size().width;
403            let child_w = if h_anchor.is_stretch() {
404                slot_w.clamp(min_w, max_w)
405            } else if h_anchor == HAnchor::MAX_FIT_OR_STRETCH {
406                resolve_fit_or_stretch(desired.width, slot_w, true).clamp(min_w, max_w)
407            } else if h_anchor == HAnchor::MIN_FIT_OR_STRETCH {
408                resolve_fit_or_stretch(desired.width, slot_w, false).clamp(min_w, max_w)
409            } else {
410                desired.width.clamp(min_w, max_w)
411            };
412
413            let child_x = if h_anchor.contains(HAnchor::RIGHT) && !h_anchor.contains(HAnchor::LEFT)
414            {
415                (w - m.right - child_w).max(0.0)
416            } else if h_anchor.contains(HAnchor::CENTER) && !h_anchor.is_stretch() {
417                m.left + (slot_w - child_w) * 0.5
418            } else {
419                m.left
420            };
421
422            // Vertical placement (Y-up: BOTTOM = low Y).
423            let v_anchor = child.v_anchor();
424            let min_h = child.min_size().height;
425            let max_h = child.max_size().height;
426            let child_h = if v_anchor.is_stretch() {
427                slot_h.clamp(min_h, max_h)
428            } else if v_anchor == VAnchor::MAX_FIT_OR_STRETCH {
429                resolve_fit_or_stretch(desired.height, slot_h, true).clamp(min_h, max_h)
430            } else if v_anchor == VAnchor::MIN_FIT_OR_STRETCH {
431                resolve_fit_or_stretch(desired.height, slot_h, false).clamp(min_h, max_h)
432            } else {
433                desired.height.clamp(min_h, max_h)
434            };
435
436            // When a dimension is explicitly pinned, the child must fit —
437            // otherwise a child whose `layout` ignores the slot budget (e.g.
438            // `TextField` returning a font-derived natural height) paints
439            // outside the SizedBox and clips into siblings above it.  Widgets
440            // re-read `self.bounds` during paint, so shrinking here propagates
441            // cleanly.
442            let child_w = if self.width.is_some() {
443                child_w.min(slot_w)
444            } else {
445                child_w
446            };
447            let child_h = if self.height.is_some() {
448                child_h.min(slot_h)
449            } else {
450                child_h
451            };
452
453            let child_y = if v_anchor.contains(VAnchor::TOP) && !v_anchor.contains(VAnchor::BOTTOM)
454            {
455                (h - m.top - child_h).max(0.0)
456            } else if v_anchor.contains(VAnchor::CENTER) && !v_anchor.is_stretch() {
457                m.bottom + (slot_h - child_h) * 0.5
458            } else {
459                m.bottom
460            };
461
462            child.set_bounds(Rect::new(child_x, child_y, child_w, child_h));
463        }
464
465        Size::new(w, h)
466    }
467
468    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
469
470    fn on_event(&mut self, _: &Event) -> EventResult {
471        EventResult::Ignored
472    }
473}
474
475// ---------------------------------------------------------------------------
476// Spacer — flexible empty space for use in flex layouts
477// ---------------------------------------------------------------------------
478
479/// An invisible leaf widget that expands to fill available space.
480///
481/// Used as a `flex` child in [`FlexColumn`][crate::FlexColumn] or
482/// [`FlexRow`][crate::FlexRow] to push siblings apart.
483pub struct Spacer {
484    bounds: Rect,
485    children: Vec<Box<dyn Widget>>,
486    base: WidgetBase,
487}
488
489impl Spacer {
490    pub fn new() -> Self {
491        Self {
492            bounds: Rect::default(),
493            children: Vec::new(),
494            base: WidgetBase::new(),
495        }
496    }
497
498    pub fn with_margin(mut self, m: Insets) -> Self {
499        self.base.margin = m;
500        self
501    }
502    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
503        self.base.h_anchor = h;
504        self
505    }
506    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
507        self.base.v_anchor = v;
508        self
509    }
510    pub fn with_min_size(mut self, s: Size) -> Self {
511        self.base.min_size = s;
512        self
513    }
514    pub fn with_max_size(mut self, s: Size) -> Self {
515        self.base.max_size = s;
516        self
517    }
518}
519
520impl Default for Spacer {
521    fn default() -> Self {
522        Self::new()
523    }
524}
525
526impl Widget for Spacer {
527    fn type_name(&self) -> &'static str {
528        "Spacer"
529    }
530    fn bounds(&self) -> Rect {
531        self.bounds
532    }
533    fn set_bounds(&mut self, b: Rect) {
534        self.bounds = b;
535    }
536    fn children(&self) -> &[Box<dyn Widget>] {
537        &self.children
538    }
539    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
540        &mut self.children
541    }
542
543    fn margin(&self) -> Insets {
544        self.base.margin
545    }
546    fn h_anchor(&self) -> HAnchor {
547        self.base.h_anchor
548    }
549    fn v_anchor(&self) -> VAnchor {
550        self.base.v_anchor
551    }
552    fn min_size(&self) -> Size {
553        self.base.min_size
554    }
555    fn max_size(&self) -> Size {
556        self.base.max_size
557    }
558
559    fn layout(&mut self, available: Size) -> Size {
560        available
561    }
562
563    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
564
565    fn on_event(&mut self, _: &Event) -> EventResult {
566        EventResult::Ignored
567    }
568}
569
570// ---------------------------------------------------------------------------
571// Separator — a thin horizontal or vertical divider line
572// ---------------------------------------------------------------------------
573
574/// A thin horizontal or vertical divider line.
575///
576/// When no explicit colour is set via [`with_color`](Separator::with_color),
577/// the separator reads its colour from the active theme's `separator` field at
578/// paint time, so it automatically adapts to dark / light mode.
579pub struct Separator {
580    bounds: Rect,
581    children: Vec<Box<dyn Widget>>,
582    base: WidgetBase,
583    vertical: bool,
584    line_inset: f64,
585    /// `None` → use `ctx.visuals().separator` at paint time.
586    color: Option<Color>,
587}
588
589impl Separator {
590    /// Create a horizontal separator (the common case).
591    pub fn horizontal() -> Self {
592        Self {
593            bounds: Rect::default(),
594            children: Vec::new(),
595            base: WidgetBase::new(),
596            vertical: false,
597            line_inset: 4.0,
598            color: None,
599        }
600    }
601
602    /// Create a vertical separator.
603    pub fn vertical() -> Self {
604        Self {
605            vertical: true,
606            ..Self::horizontal()
607        }
608    }
609
610    pub fn with_line_inset(mut self, m: f64) -> Self {
611        self.line_inset = m;
612        self
613    }
614    pub fn with_color(mut self, c: Color) -> Self {
615        self.color = Some(c);
616        self
617    }
618
619    pub fn with_margin(mut self, m: Insets) -> Self {
620        self.base.margin = m;
621        self
622    }
623    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
624        self.base.h_anchor = h;
625        self
626    }
627    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
628        self.base.v_anchor = v;
629        self
630    }
631    pub fn with_min_size(mut self, s: Size) -> Self {
632        self.base.min_size = s;
633        self
634    }
635    pub fn with_max_size(mut self, s: Size) -> Self {
636        self.base.max_size = s;
637        self
638    }
639}
640
641impl Widget for Separator {
642    fn type_name(&self) -> &'static str {
643        "Separator"
644    }
645    fn bounds(&self) -> Rect {
646        self.bounds
647    }
648    fn set_bounds(&mut self, b: Rect) {
649        self.bounds = b;
650    }
651    fn children(&self) -> &[Box<dyn Widget>] {
652        &self.children
653    }
654    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
655        &mut self.children
656    }
657
658    fn margin(&self) -> Insets {
659        self.base.margin
660    }
661    fn h_anchor(&self) -> HAnchor {
662        self.base.h_anchor
663    }
664    fn v_anchor(&self) -> VAnchor {
665        self.base.v_anchor
666    }
667    fn min_size(&self) -> Size {
668        self.base.min_size
669    }
670    fn max_size(&self) -> Size {
671        self.base.max_size
672    }
673
674    fn layout(&mut self, available: Size) -> Size {
675        if self.vertical {
676            Size::new(1.0 + self.line_inset * 2.0, available.height)
677        } else {
678            Size::new(available.width, 1.0 + self.line_inset * 2.0)
679        }
680    }
681
682    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
683        let w = self.bounds.width;
684        let h = self.bounds.height;
685        let color = self.color.unwrap_or_else(|| ctx.visuals().separator);
686        ctx.set_fill_color(color);
687        ctx.begin_path();
688        if self.vertical {
689            ctx.rect(self.line_inset, 0.0, 1.0, h);
690        } else {
691            ctx.rect(0.0, self.line_inset, w, 1.0);
692        }
693        ctx.fill();
694    }
695
696    fn on_event(&mut self, _: &Event) -> EventResult {
697        EventResult::Ignored
698    }
699}