rust_widgets 2.3.1

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 60+ widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT

use crate::compat::HashMap;
use crate::compat::Instant;
use core::time::Duration;
#[derive(Debug, Clone, Copy)]
/// Accumulated timing statistics for a single named section.
pub struct ProfileEntry {
    /// Instant at which the most recent `begin` call for this section happened.
    /// Only the last start is retained; elapsed time is measured against it.
    pub start: Instant,
    /// Total time spent inside the section, summed across every completed call.
    pub duration: Duration,
    /// Number of times the section was started *and* finished.
    pub call_count: u64,
}
impl Default for ProfileEntry {
    fn default() -> Self {
        Self { start: Instant::now(), duration: Duration::ZERO, call_count: 0 }
    }
}
/// Named-section profiler that accumulates per-section call counts and durations.
///
/// Only one section can be open at a time: `begin` overwrites any section that
/// was started but never ended, and that abandoned interval is not recorded.
/// Disabled instances make `begin`/`end`/`measure` no-ops.
pub struct Profiler {
    entries: HashMap<String, ProfileEntry>,
    current: Option<(String, Instant)>,
    enabled: bool,
}
impl Profiler {
    /// Creates a profiler with no recorded sections, already enabled.
    pub fn new() -> Self {
        Self { entries: HashMap::new(), current: None, enabled: true }
    }
    /// Resumes recording. Has no effect on sections already accumulated.
    pub fn enable(&mut self) {
        self.enabled = true;
    }
    /// Suspends recording. `begin`/`end` become no-ops while disabled, so
    /// measurements taken in that window are silently dropped.
    pub fn disable(&mut self) {
        self.enabled = false;
    }
    /// Returns whether recording is currently active.
    pub fn is_enabled(&self) -> bool {
        self.enabled
    }
    /// Starts timing a section under `name`, replacing any section still open.
    /// No-op when the profiler is disabled.
    pub fn begin(&mut self, name: &str) {
        if !self.enabled {
            return;
        }
        self.current = Some((name.to_string(), Instant::now()));
    }
    /// Stops timing the open section and adds the elapsed wall-clock time to it.
    /// Does nothing if no section is open or the profiler is disabled.
    pub fn end(&mut self) {
        if !self.enabled {
            return;
        }
        if let Some((name, start)) = self.current.take() {
            let duration = start.elapsed();
            let entry = self.entries.entry(name).or_default();
            entry.duration += duration;
            entry.call_count += 1;
        }
    }
    /// Times a single call to `f` as section `name` and returns its result.
    /// The call is only recorded while the profiler is enabled; `f` itself always runs.
    pub fn measure<F, R>(&mut self, name: &str, f: F) -> R
    where
        F: FnOnce() -> R,
    {
        self.begin(name);
        let result = f();
        self.end();
        result
    }
    /// Returns the accumulated entry for `name`, or `None` if it was never measured.
    pub fn get_stats(&self, name: &str) -> Option<&ProfileEntry> {
        self.entries.get(name)
    }
    /// Returns the mean wall-clock duration per completed call of `name`.
    /// `None` if the section is unknown or has never been completed.
    pub fn get_average_duration(&self, name: &str) -> Option<Duration> {
        self.entries.get(name).and_then(|e| {
            if e.call_count > 0 {
                Some(e.duration / e.call_count as u32)
            } else {
                None
            }
        })
    }
    /// Returns the sum of the accumulated durations of every recorded section,
    /// not the elapsed wall-clock time of the process. Zero when nothing was measured.
    pub fn get_total_duration(&self) -> Duration {
        self.entries.values().map(|e| e.duration).sum()
    }
    /// Returns every section whose accumulated duration is at least `threshold`,
    /// sorted from slowest to fastest. The threshold is inclusive and compares
    /// against totals, not per-call averages.
    pub fn get_hotspots(&self, threshold: Duration) -> Vec<(&str, Duration)> {
        let mut hotspots: Vec<_> = self
            .entries
            .iter()
            .filter(|(_, e)| e.duration >= threshold)
            .map(|(name, e)| (name.as_str(), e.duration))
            .collect();
        hotspots.sort_by_key(|b| core::cmp::Reverse(b.1));
        hotspots
    }
    /// Borrows the raw section table, keyed by the name passed to `begin`.
    pub fn get_all_stats(&self) -> &HashMap<String, ProfileEntry> {
        &self.entries
    }
    /// Drops all accumulated statistics and any section still open.
    pub fn reset(&mut self) {
        self.entries.clear();
        self.current = None;
    }
    /// Builds a snapshot report with one entry per recorded section, sorted by
    /// descending total duration. The report is detached from later measurements.
    pub fn report(&self) -> ProfileReport {
        let mut entries: Vec<_> = self
            .entries
            .iter()
            .map(|(name, entry)| ProfileReportEntry {
                name: name.clone(),
                total_duration: entry.duration,
                call_count: entry.call_count,
                average_duration: if entry.call_count > 0 {
                    entry.duration / entry.call_count as u32
                } else {
                    Duration::ZERO
                },
            })
            .collect();
        entries.sort_by_key(|b| core::cmp::Reverse(b.total_duration));
        ProfileReport { entries, total_duration: self.get_total_duration() }
    }
}
crate::impl_default_via_new!(Profiler);
#[derive(Debug, Clone)]
/// One row of a [`ProfileReport`]: totals for a single named section.
pub struct ProfileReportEntry {
    /// Section name exactly as passed to `Profiler::begin`.
    pub name: String,
    /// Total wall-clock time accumulated by the section.
    pub total_duration: Duration,
    /// Number of completed calls that contributed to `total_duration`.
    pub call_count: u64,
    /// `total_duration` divided by `call_count`; `Duration::ZERO` when
    /// `call_count` is zero.
    pub average_duration: Duration,
}
#[derive(Debug, Clone)]
/// Immutable snapshot of a [`Profiler`] at the time `report` was called.
pub struct ProfileReport {
    /// Per-section rows, sorted by descending `total_duration`.
    pub entries: Vec<ProfileReportEntry>,
    /// Sum of all section totals in this snapshot.
    pub total_duration: Duration,
}
impl ProfileReport {
    /// Renders a multi-line plain-text summary: the overall total, then one
    /// line per section with its name, total, call count and average.
    pub fn to_string_summary(&self) -> String {
        let mut result = String::new();
        result.push_str(&format!("Total: {:?}\n\n", self.total_duration));
        for entry in &self.entries {
            result.push_str(&format!(
                "{}: {:?} ({} calls, avg {:?})\n",
                entry.name, entry.total_duration, entry.call_count, entry.average_duration
            ));
        }
        result
    }
}
/// Per-frame timing profiler with a rolling window of recent frame durations
/// and per-section timings for the frame currently in progress.
///
/// `begin_frame` clears the section table, so section timings always refer to the
/// most recently started frame; `frame_times` keeps at most `max_frames` samples
/// (the oldest is dropped on overflow).
pub struct FrameProfiler {
    frame_times: Vec<Duration>,
    max_frames: usize,
    current_frame_start: Option<Instant>,
    sections: HashMap<String, Duration>,
    current_section: Option<(String, Instant)>,
}
impl FrameProfiler {
    /// Creates a profiler that retains up to `max_frames` frame durations.
    /// With `max_frames` of zero, `end_frame` never stores a sample.
    pub fn new(max_frames: usize) -> Self {
        Self {
            frame_times: Vec::with_capacity(max_frames),
            max_frames,
            current_frame_start: None,
            sections: HashMap::new(),
            current_section: None,
        }
    }
    /// Marks the start of a frame and discards all section timings from the
    /// previous frame. Replaces any frame start that was not closed.
    pub fn begin_frame(&mut self) {
        self.current_frame_start = Some(Instant::now());
        self.sections.clear();
    }
    /// Closes the current frame and records its duration, evicting the oldest
    /// sample when the window is full. No-op if no frame is open.
    pub fn end_frame(&mut self) {
        if let Some(start) = self.current_frame_start.take() {
            let duration = start.elapsed();
            if self.frame_times.len() >= self.max_frames {
                self.frame_times.remove(0);
            }
            self.frame_times.push(duration);
        }
    }
    /// Starts timing a named section of the current frame, replacing any section
    /// still open. Section names are independent of frame boundaries.
    pub fn begin_section(&mut self, name: &str) {
        self.current_section = Some((name.to_string(), Instant::now()));
    }
    /// Stops the open section and adds the elapsed time to its running total for
    /// the current frame. Does nothing if no section is open.
    pub fn end_section(&mut self) {
        if let Some((name, start)) = self.current_section.take() {
            let duration = start.elapsed();
            *self.sections.entry(name).or_default() += duration;
        }
    }
    /// Returns the mean of the recorded frame durations, or `Duration::ZERO`
    /// when no frames have been recorded.
    pub fn average_frame_time(&self) -> Duration {
        if self.frame_times.is_empty() {
            return Duration::ZERO;
        }
        let total: Duration = self.frame_times.iter().sum();
        total / self.frame_times.len() as u32
    }
    /// Returns frames per second derived from `average_frame_time`
    /// (1e9 / average nanoseconds). Returns `0.0` when no frame has been timed.
    pub fn fps(&self) -> f32 {
        let avg = self.average_frame_time();
        if avg.is_zero() {
            return 0.0;
        }
        1_000_000_000.0 / avg.as_nanos() as f32
    }
    /// Returns the fastest recorded frame duration, or `Duration::ZERO` when no
    /// frames have been recorded.
    pub fn min_frame_time(&self) -> Duration {
        self.frame_times.iter().min().copied().unwrap_or(Duration::ZERO)
    }
    /// Returns the slowest recorded frame duration, or `Duration::ZERO` when no
    /// frames have been recorded.
    pub fn max_frame_time(&self) -> Duration {
        self.frame_times.iter().max().copied().unwrap_or(Duration::ZERO)
    }
    /// Returns how many frame durations are currently held, capped at the
    /// `max_frames` value given to `new`.
    pub fn frame_count(&self) -> usize {
        self.frame_times.len()
    }
    /// Borrows the per-section totals for the frame in progress. The map is
    /// emptied by `begin_frame` and is not cleared by `end_frame`.
    pub fn sections(&self) -> &HashMap<String, Duration> {
        &self.sections
    }
    /// Drops the recorded frame durations and all section timings. Does not
    /// close a frame that is currently open.
    pub fn clear(&mut self) {
        self.frame_times.clear();
        self.sections.clear();
    }
}
impl Default for FrameProfiler {
    fn default() -> Self {
        Self::new(60)
    }
}
/// Combines a [`Profiler`] for named sections with a [`FrameProfiler`] for frame
/// timing, so section and frame data come from one set of `begin`/`end` calls.
///
/// While disabled, every measurement entry point is skipped, but the accessors
/// still return whatever was collected before the monitor was disabled.
pub struct PerformanceMonitor {
    profiler: Profiler,
    frame_profiler: FrameProfiler,
    enabled: bool,
}
impl PerformanceMonitor {
    /// Creates a monitor with an empty section profiler, a frame profiler
    /// keeping the last 60 frame durations, and measurement enabled.
    pub fn new() -> Self {
        Self { profiler: Profiler::new(), frame_profiler: FrameProfiler::new(60), enabled: true }
    }
    /// Borrows the underlying section profiler.
    pub fn profiler(&self) -> &Profiler {
        &self.profiler
    }
    /// Mutably borrows the underlying section profiler, for direct control over
    /// its enable state and measurements.
    pub fn profiler_mut(&mut self) -> &mut Profiler {
        &mut self.profiler
    }
    /// Borrows the underlying frame profiler.
    pub fn frame_profiler(&self) -> &FrameProfiler {
        &self.frame_profiler
    }
    /// Mutably borrows the underlying frame profiler.
    pub fn frame_profiler_mut(&mut self) -> &mut FrameProfiler {
        &mut self.frame_profiler
    }
    /// Resumes measurement and re-enables the section profiler.
    pub fn enable(&mut self) {
        self.enabled = true;
        self.profiler.enable();
    }
    /// Suspends measurement and disables the section profiler. Already-collected
    /// statistics remain readable through the accessors.
    pub fn disable(&mut self) {
        self.enabled = false;
        self.profiler.disable();
    }
    /// Returns whether measurement is currently active.
    pub fn is_enabled(&self) -> bool {
        self.enabled
    }
    /// Opens a frame on the frame profiler; no-op while disabled.
    pub fn begin_frame(&mut self) {
        if self.enabled {
            self.frame_profiler.begin_frame();
        }
    }
    /// Closes the open frame; no-op while disabled.
    pub fn end_frame(&mut self) {
        if self.enabled {
            self.frame_profiler.end_frame();
        }
    }
    /// Opens a section in both profilers; no-op while disabled. Because the
    /// frame profiler tracks one section at a time, this replaces any section
    /// that was opened but not closed.
    pub fn begin_section(&mut self, name: &str) {
        if self.enabled {
            self.frame_profiler.begin_section(name);
            self.profiler.begin(name);
        }
    }
    /// Closes the open section in both profilers; no-op while disabled.
    pub fn end_section(&mut self) {
        if self.enabled {
            self.profiler.end();
            self.frame_profiler.end_section();
        }
    }
    /// Times one call to `f` as a section and returns its result.
    /// `f` always runs, even while the monitor is disabled.
    pub fn measure<F, R>(&mut self, name: &str, f: F) -> R
    where
        F: FnOnce() -> R,
    {
        self.begin_section(name);
        let result = f();
        self.end_section();
        result
    }
    /// Snapshots both profilers into a [`PerformanceReport`].
    pub fn report(&self) -> PerformanceReport {
        PerformanceReport {
            profiler_report: self.profiler.report(),
            average_frame_time: self.frame_profiler.average_frame_time(),
            fps: self.frame_profiler.fps(),
            min_frame_time: self.frame_profiler.min_frame_time(),
            max_frame_time: self.frame_profiler.max_frame_time(),
            frame_count: self.frame_profiler.frame_count(),
        }
    }
    /// Clears all section statistics and all recorded frame timings.
    pub fn reset(&mut self) {
        self.profiler.reset();
        self.frame_profiler.clear();
    }
}
crate::impl_default_via_new!(PerformanceMonitor);
#[derive(Debug, Clone)]
/// Combined snapshot of section timings and frame timings.
pub struct PerformanceReport {
    /// Named-section statistics, sorted by descending total duration.
    pub profiler_report: ProfileReport,
    /// Mean frame duration across the retained frame window; `Duration::ZERO`
    /// when no frame has been recorded.
    pub average_frame_time: Duration,
    /// Frames per second derived from `average_frame_time`; `0.0` when no frame
    /// has been recorded.
    pub fps: f32,
    /// Fastest retained frame duration; `Duration::ZERO` when no frame has been recorded.
    pub min_frame_time: Duration,
    /// Slowest retained frame duration; `Duration::ZERO` when no frame has been recorded.
    pub max_frame_time: Duration,
    /// Number of frame durations in the retained window.
    pub frame_count: usize,
}
impl PerformanceReport {
    /// Renders a multi-line plain-text summary: FPS and frame statistics first,
    /// then the section summary from [`ProfileReport::to_string_summary`].
    pub fn to_string_summary(&self) -> String {
        format!(
            "FPS: {:.1}\nAvg Frame: {:?}\nMin Frame: {:?}\nMax Frame: {:?}\nFrames: {}\n\n{}",
            self.fps,
            self.average_frame_time,
            self.min_frame_time,
            self.max_frame_time,
            self.frame_count,
            self.profiler_report.to_string_summary()
        )
    }
}
#[cfg(all(test, not(alloc_frugal)))]
mod tests {
    use super::*;
    use std::thread::sleep;
    #[test]
    fn test_profiler() {
        let mut profiler = Profiler::new();
        profiler.begin("test");
        sleep(Duration::from_millis(1));
        profiler.end();
        let stats = profiler.get_stats("test").unwrap();
        assert_eq!(stats.call_count, 1);
        assert!(stats.duration > Duration::ZERO);
    }
    #[test]
    fn test_frame_profiler() {
        let mut profiler = FrameProfiler::new(10);
        for _ in 0..5 {
            profiler.begin_frame();
            sleep(Duration::from_millis(1));
            profiler.end_frame();
        }
        assert_eq!(profiler.frame_count(), 5);
        assert!(profiler.fps() > 0.0);
    }
}