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