agg_gui/widgets/window/builder.rs
1//! Builder methods for [`super::Window`] — extracted into a submodule so
2//! `window.rs` stays under the 800-line guardrail. These are all plain
3//! setter / configuration calls that mutate `self` and return it; they
4//! live in a sibling `impl Window` block that has full access to private
5//! fields by virtue of being inside the parent `window` module.
6
7use std::cell::{Cell, RefCell};
8use std::rc::Rc;
9
10use crate::geometry::{Rect, Size};
11use crate::layout_props::{HAnchor, Insets, VAnchor};
12
13use super::{ClickAwayAction, CloseReason, Window, VISIBILITY_FADE_SECS};
14
15impl Window {
16 /// Treat the window's content as live: invalidate the backbuffer
17 /// every frame so custom paint code reading from a mutating data
18 /// source (network feed, simulation state, sensor stream) is
19 /// rasterised fresh. Automatically skipped when the window is
20 /// collapsed or hidden — no wasted work when the user can't see
21 /// the content.
22 ///
23 /// Use this for diagnostics graphs, telemetry views, or any widget
24 /// whose `paint()` reads from an `Rc<RefCell<…>>` model that the
25 /// framework can't observe. The alternative — composing live UI
26 /// from widgets that auto-invalidate on setter calls
27 /// ([`Label::set_text`](crate::widgets::Label), etc.) — is preferred
28 /// when feasible, but for custom direct-to-`DrawCtx` widgets this
29 /// is the simplest correct fix.
30 ///
31 /// See [`Window::new`] for the full discussion of the back-buffer
32 /// invalidation contract and the canonical "stale pixels" gotcha
33 /// this flag solves.
34 pub fn with_live_content(mut self, live: bool) -> Self {
35 self.live_content = live;
36 self
37 }
38
39 /// Register a callback fired whenever this window requests a raise
40 /// (click-to-front or visibility rising-edge from the sidebar).
41 /// Receives the window title. The demo uses this to feed a shared
42 /// z-order tracker that gets persisted to disk.
43 pub fn on_raised(mut self, cb: impl FnMut(&str) + 'static) -> Self {
44 self.on_raised = Some(Box::new(cb));
45 self
46 }
47
48 pub fn with_bounds(mut self, b: Rect) -> Self {
49 self.pre_collapse_h = b.height;
50 self.bounds = b;
51 if self.maximized {
52 self.pre_maximize_bounds = b;
53 }
54 self
55 }
56 pub fn with_font_size(mut self, size: f64) -> Self {
57 self.font_size = size;
58 self
59 }
60
61 pub fn with_visible_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
62 let visible = cell.get();
63 self.last_visible.set(visible);
64 self.fade_out_active.set(false);
65 self.visibility_anim =
66 crate::animation::Tween::new(if visible { 1.0 } else { 0.0 }, VISIBILITY_FADE_SECS);
67 self.visible_cell = Some(cell);
68 self
69 }
70
71 pub fn with_reset_cell(mut self, cell: Rc<Cell<Option<Rect>>>) -> Self {
72 self.reset_to = Some(cell);
73 self
74 }
75
76 pub fn with_position_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
77 self.position_cell = Some(cell);
78 self
79 }
80
81 /// Wire the window's canvas-maximized state into external persistence.
82 ///
83 /// Call after [`with_bounds`] when restoring saved state so the current
84 /// bounds become the pre-maximize bounds used by the first layout pass.
85 pub fn with_maximized_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
86 self.maximized = cell.get();
87 if self.maximized {
88 self.pre_maximize_bounds = self.bounds;
89 }
90 self.maximized_cell = Some(cell);
91 self
92 }
93
94 pub fn with_margin(mut self, m: Insets) -> Self {
95 self.base.margin = m;
96 self
97 }
98 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
99 self.base.h_anchor = h;
100 self
101 }
102 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
103 self.base.v_anchor = v;
104 self
105 }
106 pub fn with_min_size(mut self, s: Size) -> Self {
107 self.base.min_size = s;
108 self
109 }
110 pub fn with_max_size(mut self, s: Size) -> Self {
111 self.base.max_size = s;
112 self
113 }
114
115 pub fn with_constrain(mut self, constrain: bool) -> Self {
116 self.constrain = constrain;
117 self
118 }
119
120 /// Make this window an app-modal layer while it is visible. The `App`
121 /// routes every pointer and keyboard event into the window's subtree,
122 /// so a floating dialog swallows all input over its bounds instead of
123 /// leaking clicks to widgets underneath. Off by default — only floating
124 /// dialogs (e.g. [`color_wheel_picker_dialog`](crate::color_wheel_picker_dialog))
125 /// opt in; ordinary windows keep normal per-window hit-testing.
126 pub fn with_modal(mut self, modal: bool) -> Self {
127 self.modal = modal;
128 self
129 }
130
131 /// Opt this window in/out of the generic retained GL-FBO backbuffer.
132 /// Disabling renders directly into the inherited parent target.
133 pub fn with_gl_backbuffer(mut self, enabled: bool) -> Self {
134 self.use_gl_backbuffer = enabled;
135 self.backbuffer.invalidate();
136 self
137 }
138
139 /// Make the window size itself to the content's preferred size every frame.
140 /// Top-left pin: as content grows/shrinks, the title bar stays where it is.
141 pub fn with_auto_size(mut self, auto: bool) -> Self {
142 self.auto_size = auto;
143 self
144 }
145
146 /// Toggle user-dragged resize. `false` hides every edge/corner handle
147 /// and disables resize hit-tests. Default: `true`. Matches egui's
148 /// `Window::resizable(bool)`.
149 pub fn with_resizable(mut self, on: bool) -> Self {
150 self.resizable = on;
151 self
152 }
153
154 /// Drive `resizable` from a shared cell read every `layout()`. Lets a
155 /// demo's checkbox steer the real host window at runtime. Follows the
156 /// [`with_maximized_cell`](Self::with_maximized_cell) precedent.
157 pub fn with_resizable_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
158 self.resizable = cell.get();
159 self.resizable_cell = Some(cell);
160 self
161 }
162
163 /// Drive `auto_size` from a shared cell read every `layout()`.
164 pub fn with_auto_size_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
165 self.auto_size = cell.get();
166 self.auto_size_cell = Some(cell);
167 self
168 }
169
170 /// Drive `collapsible` (whether the collapse chevron is offered) from a
171 /// shared cell read every `layout()`. Matches egui `Window::collapsible`.
172 pub fn with_collapsible_cell(mut self, cell: Rc<Cell<bool>>) -> Self {
173 self.collapsible = cell.get();
174 self.collapsible_cell = Some(cell);
175 self
176 }
177
178 /// Drive the displayed title text from a shared cell read every
179 /// `layout()`. Only the title-bar *label* changes — the window's
180 /// identity string (`title()`, used as the persistence / z-order key)
181 /// is intentionally left fixed so live retitling can't corrupt saved
182 /// state. Mirrors egui's demo, which sets a fixed `Id` precisely
183 /// because it changes the visible title.
184 pub fn with_title_cell(mut self, cell: Rc<RefCell<String>>) -> Self {
185 self.title_cell = Some(cell);
186 self
187 }
188
189 /// Fine-grained axis-locking of the resize handles — pass `(true, false)`
190 /// for a horizontally-only resizable window, etc. Implies
191 /// `with_resizable(true)`. Matches egui's `Window::resizable([h, v])`.
192 pub fn with_resizable_axes(mut self, h: bool, v: bool) -> Self {
193 self.resizable = h || v;
194 self.resizable_h = h;
195 self.resizable_v = v;
196 self
197 }
198
199 /// Lock the window's height to its content's required height.
200 /// The user can grab a vertical resize handle but the next
201 /// layout snaps back — egui's W4 "no scroll, no clip, no
202 /// whitespace" contract. Requires the content tree to expose
203 /// its required height via [`Widget::measure_min_height`]; our
204 /// `FlexColumn`, `Label`, `TextArea`, and `Container::with_fit_height`
205 /// all do.
206 pub fn with_tight_content_fit(mut self, on: bool) -> Self {
207 self.tight_content_fit = on;
208 self
209 }
210
211 /// Floor-only variant of [`with_tight_content_fit`]: refuses to
212 /// shrink past content but allows the user to pull the window
213 /// taller (whitespace below). Used for windows whose content
214 /// includes a flex-fill child like a multiline `TextArea` —
215 /// matches egui's W5 where the TextEdit fills extra height and
216 /// the user can grow the window further.
217 pub fn with_height_floor_to_content(mut self, on: bool) -> Self {
218 self.floor_content_height = on;
219 self
220 }
221
222 /// Wrap the window's content in a built-in vertical [`ScrollView`].
223 /// Matches egui's `Window::vscroll(true)`: lets the user shrink the
224 /// window below content height without the caller having to wrap the
225 /// content in a `ScrollView` manually. Eager — happens at builder
226 /// time so the rest of the layout / event / paint paths see a single
227 /// child as usual. Has no effect when called with `false` (matches
228 /// the default).
229 ///
230 /// Don't combine with [`with_auto_size`]: the ScrollView claims its
231 /// full available height, which would make auto-sizing grow the
232 /// window to the canvas. egui's demo never combines the two flags
233 /// either.
234 pub fn with_vscroll(mut self, vscroll: bool) -> Self {
235 if vscroll {
236 if let Some(content) = self.children.pop() {
237 let scroll = crate::widgets::ScrollView::new(content)
238 .vertical(true)
239 .horizontal(false);
240 self.children.push(Box::new(scroll));
241 }
242 }
243 self
244 }
245
246 /// Register a callback fired whenever the window closes, receiving the
247 /// [`CloseReason`] (× button, Escape, or click-away) so the host can react
248 /// per route — the colour dialog commits a live change on click-away but
249 /// cancels it on Escape / ×.
250 pub fn on_close(mut self, cb: impl FnMut(CloseReason) + 'static) -> Self {
251 self.on_close = Some(Box::new(cb));
252 self
253 }
254
255 /// Set what a modal window does when the pointer is pressed *outside* its
256 /// bounds. Default [`ClickAwayAction::None`] swallows the press and keeps the
257 /// window open (unchanged behaviour for ordinary modal windows);
258 /// [`ClickAwayAction::Close`] dismisses via [`Window::close`] with
259 /// [`CloseReason::ClickAway`] and swallows the press. Only takes effect when
260 /// the window is also [`with_modal(true)`](Self::with_modal).
261 pub fn with_click_away(mut self, action: ClickAwayAction) -> Self {
262 self.click_away = action;
263 self
264 }
265}