Skip to main content

syntax_profile_demo/
syntax_profile_demo.rs

1//! Syntax highlighting + profiling demo.
2//!
3//! Shows how to:
4//! - render tokenised code with `Window::write_spans_colored`
5//! - capture per-frame timing via `App::set_frame_profile_hook`
6//! - use a frame budget with `App::with_frame_budget`
7
8use minui::Window;
9use minui::prelude::*;
10use std::sync::{Arc, Mutex};
11use std::time::Duration;
12
13#[derive(Clone)]
14struct DemoState {
15    frame_count: u64,
16    latest_profile: Arc<Mutex<Option<FrameProfile>>>,
17}
18
19fn main() -> minui::Result<()> {
20    let profile_store = Arc::new(Mutex::new(None));
21
22    let mut app = App::new(DemoState {
23        frame_count: 0,
24        latest_profile: Arc::clone(&profile_store),
25    })?
26    .with_frame_rate(Duration::from_millis(16))
27    .with_frame_budget(Duration::from_millis(16));
28
29    {
30        let store = Arc::clone(&profile_store);
31        app.set_frame_profile_hook(move |profile| {
32            if let Ok(mut slot) = store.lock() {
33                *slot = Some(*profile);
34            }
35        });
36    }
37
38    app.run(
39        |state, event| {
40            match event {
41                Event::KeyWithModifiers(k) if matches!(k.key, KeyKind::Char('q')) => return false,
42                Event::Character('q') => return false,
43                Event::Frame => {
44                    state.frame_count = state.frame_count.saturating_add(1);
45                }
46                _ => {}
47            }
48            true
49        },
50        |state, window| {
51            draw_header(window)?;
52            draw_code(window)?;
53            draw_profile_footer(state, window)?;
54            window.flush()?;
55            Ok(())
56        },
57    )
58}
59
60fn draw_header(window: &mut dyn Window) -> minui::Result<()> {
61    window.write_str_colored(
62        0,
63        0,
64        "MinUI syntax + frame profiling demo (press q to quit)",
65        Color::LightCyan.fg(),
66    )?;
67    window.write_str_colored(
68        1,
69        0,
70        "write_spans_colored() is used below for token-based colouring.",
71        Color::DarkGray.fg(),
72    )?;
73    Ok(())
74}
75
76fn draw_code(window: &mut dyn Window) -> minui::Result<()> {
77    let kw = Color::LightMagenta.fg();
78    let ident = Color::LightBlue.fg();
79    let punct = Color::LightGray.fg();
80    let number = Color::LightYellow.fg();
81    let string = Color::LightGreen.fg();
82    let comment = Color::DarkGray.fg();
83    let plain = Color::White.fg();
84
85    window.write_spans_colored(
86        3,
87        0,
88        &[
89            ColoredSpan::new("fn", kw),
90            ColoredSpan::new(" ", plain),
91            ColoredSpan::new("render_status", ident),
92            ColoredSpan::new("(", punct),
93            ColoredSpan::new("fps", ident),
94            ColoredSpan::new(": ", punct),
95            ColoredSpan::new("u32", ident),
96            ColoredSpan::new(") {", punct),
97        ],
98    )?;
99
100    window.write_spans_colored(
101        4,
102        0,
103        &[
104            ColoredSpan::new("    ", plain),
105            ColoredSpan::new("let", kw),
106            ColoredSpan::new(" budget_ms = ", plain),
107            ColoredSpan::new("16", number),
108            ColoredSpan::new(";", punct),
109            ColoredSpan::new(" // ~60 FPS target", comment),
110        ],
111    )?;
112
113    window.write_spans_colored(
114        5,
115        0,
116        &[
117            ColoredSpan::new("    ", plain),
118            ColoredSpan::new("println!", ident),
119            ColoredSpan::new("(", punct),
120            ColoredSpan::new("\"fps={} budget={}ms\"", string),
121            ColoredSpan::new(", fps, budget_ms", plain),
122            ColoredSpan::new(");", punct),
123        ],
124    )?;
125
126    window.write_spans_colored(6, 0, &[ColoredSpan::new("}", punct)])?;
127    Ok(())
128}
129
130fn draw_profile_footer(state: &DemoState, window: &mut dyn Window) -> minui::Result<()> {
131    let (w, h) = window.get_size();
132    if h == 0 {
133        return Ok(());
134    }
135
136    let y = h.saturating_sub(1);
137    let profile = state.latest_profile.lock().ok().and_then(|g| *g);
138
139    if let Some(p) = profile {
140        let colour = if p.over_budget {
141            Color::LightRed.fg()
142        } else {
143            Color::LightGreen.fg()
144        };
145        let budget_ms = p.budget.map(|d| d.as_millis()).unwrap_or(0);
146        let line = format!(
147            "frame={} events={} total={}ms input={}ms update={}ms draw={}ms budget={}ms over_budget={}",
148            state.frame_count,
149            p.events_processed,
150            p.frame_time.as_millis(),
151            p.input_poll_time.as_millis(),
152            p.update_time.as_millis(),
153            p.draw_time.as_millis(),
154            budget_ms,
155            p.over_budget
156        );
157        let clipped = clip_to_cells(&line, w, TabPolicy::SingleCell);
158        window.write_str_colored(y, 0, &clipped, colour)?;
159    } else {
160        window.write_str_colored(y, 0, "Waiting for frame profile...", Color::DarkGray.fg())?;
161    }
162
163    Ok(())
164}