Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

features/waker.rs

//! Data arriving off the loop's thread: a "PTY" that produces a line every
//! 40 ms on a thread of its own, and a view that shows the last ten. The
//! loop parks between events (`ControlFlow::Wait`), so without a
//! [`kui_native::Waker`] the window would show whatever it drew at the last
//! key press. `setup` hands the app the waker once; the thread clones it
//! and calls `wake()` after every line, and the loop draws. Under
//! `KUI_WAKER_LINES=n` the app closes after `n` lines and prints how many
//! frames it drew for them, which is what the by-hand check reads; and
//! `teardown` prints once more as the window goes — closed by the app,
//! by its button or by Quit alike — which is where an app saves what it
//! would lose (backlog F74).
//!
//! Run: cargo run -p kui-native --example waker

use std::sync::{Arc, Mutex};
use std::time::Duration;

use kui_devtools::Example;
use kui_native::{App, NodeSpec, TextStyle, Ui, Waker, WindowCommand};

struct Feed {
    lines: Arc<Mutex<Vec<String>>>,
    frames: usize,
    limit: Option<usize>,
}

impl App for Feed {
    fn setup(&mut self, waker: Waker) {
        let lines = Arc::clone(&self.lines);
        let limit = self.limit;
        std::thread::spawn(move || {
            let mut n = 0usize;
            loop {
                std::thread::sleep(Duration::from_millis(40));
                n += 1;
                lines
                    .lock()
                    .unwrap()
                    .push(format!("line {n}: {:>6} bytes", n * 137 % 4096));
                waker.wake();
                if limit.is_some_and(|l| n >= l) {
                    break;
                }
            }
        });
    }

    fn teardown(&mut self) {
        println!("teardown after {} frames", self.frames);
    }

    fn view(&mut self, ui: &mut Ui<'_>) {
        let t = ui.theme();
        self.frames += 1;
        let lines = self.lines.lock().unwrap().clone();
        if let Some(limit) = self.limit
            && lines.len() >= limit
        {
            println!(
                "{} lines arrived, {} frames drawn",
                lines.len(),
                self.frames
            );
            ui.window_command(WindowCommand::Close(ui.env().window.id));
        }
        ui.with(
            NodeSpec::column().fill().pad(16.0).gap(4.0).bg(t.bg),
            |ui| {
                ui.text(
                    &format!(
                        "{} lines, {} frames — no input, a thread woke the loop",
                        lines.len(),
                        self.frames
                    ),
                    TextStyle::new(13.0).mono().color(t.muted),
                );
                for line in lines.iter().rev().take(10).rev() {
                    ui.text_in(
                        NodeSpec::row().height(18.0),
                        line,
                        TextStyle::new(13.0).mono().color(t.fg),
                    );
                }
            },
        );
    }
}

impl Example for Feed {
    fn window(&self) -> kui_devtools::Window {
        kui_devtools::Window::default().size(480.0, 260.0)
    }
}

kui_devtools::main!(Feed {
    lines: Arc::new(Mutex::new(Vec::new())),
    frames: 0,
    limit: std::env::var("KUI_WAKER_LINES")
        .ok()
        .and_then(|s| s.parse().ok()),
});