agg_gui/widgets/performance/
run_mode.rs1use std::cell::Cell;
22use std::rc::Rc;
23use std::sync::Arc;
24
25use crate::animation;
26use crate::draw_ctx::DrawCtx;
27use crate::event::{Event, EventResult};
28use crate::geometry::{Rect, Size};
29use crate::text::Font;
30use crate::widget::{paint_subtree, Widget};
31use crate::widgets::{Button, Label};
32
33use super::SharedFrameHistory;
34
35#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44pub enum RunMode {
45 Reactive,
46 Continuous,
47}
48
49pub fn shared_run_mode(initial: RunMode) -> Rc<Cell<RunMode>> {
52 Rc::new(Cell::new(initial))
53}
54
55pub struct RunModeRow {
62 bounds: Rect,
63 children: Vec<Box<dyn Widget>>,
64}
65
66impl RunModeRow {
67 pub const ROW_HEIGHT: f64 = 32.0;
69 const BTN_H: f64 = 24.0;
70
71 pub fn new(font: Arc<Font>, run_mode: Rc<Cell<RunMode>>) -> Self {
72 let segments: [(&'static str, RunMode); 2] = [
73 ("Reactive", RunMode::Reactive),
74 ("Continuous", RunMode::Continuous),
75 ];
76 let children: Vec<Box<dyn Widget>> = segments
77 .iter()
78 .map(|(label, this_mode)| {
79 let mode_active = Rc::clone(&run_mode);
80 let mode_click = Rc::clone(&run_mode);
81 let this = *this_mode;
82 let btn = Button::new(*label, Arc::clone(&font))
83 .with_font_size(12.0)
84 .with_subtle()
85 .with_active_fn(move || mode_active.get() == this)
86 .on_click(move || {
87 if mode_click.get() != this {
88 mode_click.set(this);
89 animation::request_draw();
90 }
91 });
92 Box::new(btn) as Box<dyn Widget>
93 })
94 .collect();
95 Self {
96 bounds: Rect::default(),
97 children,
98 }
99 }
100}
101
102impl Widget for RunModeRow {
103 fn type_name(&self) -> &'static str {
104 "RunModeRow"
105 }
106 fn bounds(&self) -> Rect {
107 self.bounds
108 }
109 fn set_bounds(&mut self, b: Rect) {
110 self.bounds = b;
111 }
112 fn children(&self) -> &[Box<dyn Widget>] {
113 &self.children
114 }
115 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
116 &mut self.children
117 }
118
119 fn layout(&mut self, available: Size) -> Size {
120 let row_h = Self::ROW_HEIGHT;
121 self.bounds = Rect::new(0.0, 0.0, available.width, row_h);
122 let gy = (row_h - Self::BTN_H) * 0.5;
123 let gap = 4.0;
124 let btn_w = ((available.width - gap) * 0.5).max(40.0);
125 for (i, child) in self.children.iter_mut().enumerate() {
126 child.layout(Size::new(btn_w, Self::BTN_H));
127 child.set_bounds(Rect::new(i as f64 * (btn_w + gap), gy, btn_w, Self::BTN_H));
128 }
129 Size::new(available.width, row_h)
130 }
131
132 fn paint(&mut self, _ctx: &mut dyn DrawCtx) {
133 }
135
136 fn on_event(&mut self, _event: &Event) -> EventResult {
137 EventResult::Ignored
138 }
139}
140
141pub struct RunModeDesc {
150 bounds: Rect,
151 children: Vec<Box<dyn Widget>>,
152 run_mode: Rc<Cell<RunMode>>,
153 history: SharedFrameHistory,
154 label: Label,
155}
156
157impl RunModeDesc {
158 pub fn new(font: Arc<Font>, run_mode: Rc<Cell<RunMode>>, history: SharedFrameHistory) -> Self {
159 let mut label = Label::new("", Arc::clone(&font))
160 .with_font_size(10.0)
161 .with_wrap(true);
162 label.buffered = false;
163 Self {
164 bounds: Rect::default(),
165 children: Vec::new(),
166 run_mode,
167 history,
168 label,
169 }
170 }
171}
172
173impl Widget for RunModeDesc {
174 fn type_name(&self) -> &'static str {
175 "RunModeDesc"
176 }
177 fn bounds(&self) -> Rect {
178 self.bounds
179 }
180 fn set_bounds(&mut self, b: Rect) {
181 self.bounds = b;
182 }
183 fn children(&self) -> &[Box<dyn Widget>] {
184 &self.children
185 }
186 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
187 &mut self.children
188 }
189
190 fn layout(&mut self, available: Size) -> Size {
191 self.label
194 .set_text("Only running UI code when there are animations or input.".to_owned());
195 let s = self
196 .label
197 .layout(Size::new(available.width, f64::MAX / 2.0));
198 self.label
199 .set_bounds(Rect::new(0.0, 0.0, s.width, s.height));
200 let h = s.height.max(available.height).max(14.0);
201 self.bounds = Rect::new(0.0, 0.0, available.width, h);
202 Size::new(available.width, h)
203 }
204
205 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
206 let v = ctx.visuals();
207 let text = match self.run_mode.get() {
208 RunMode::Reactive => {
209 "Only running UI code when there are animations or input.".to_owned()
210 }
211 RunMode::Continuous => {
212 let hist = self.history.borrow();
213 let fps = if hist.mean_ms() < 0.001 {
214 0.0
215 } else {
216 1000.0 / hist.mean_ms()
217 };
218 format!("Running continuously as fast as possible. FPS: {fps:.1}")
219 }
220 };
221 self.label.set_text(text);
222 self.label.set_color(v.text_dim);
223
224 let lh = self.label.bounds().height;
225 let ly = ((self.bounds.height - lh) * 0.5).max(0.0);
226 ctx.save();
227 ctx.translate(0.0, ly);
228 paint_subtree(&mut self.label, ctx);
229 ctx.restore();
230 }
231
232 fn on_event(&mut self, _: &Event) -> EventResult {
233 EventResult::Ignored
234 }
235
236 fn needs_draw(&self) -> bool {
237 matches!(self.run_mode.get(), RunMode::Continuous)
242 }
243}