Skip to main content

cranpose_ui/
draw.rs

1use std::rc::Rc;
2
3use crate::modifier::Size;
4use crate::text::AppContextTextMeasurer;
5use cranpose_ui_graphics::{DrawPrimitive, DrawScope, DrawScopeDefault};
6
7/// A draw command records into a scope the CONSUMER provides, instead of
8/// returning a freshly allocated primitive vector. This is the boundary that
9/// lets recording storage live with the command's stable identity and be
10/// reused frame over frame (the retained draw-command recorder), rather than
11/// being reallocated, moved, and dropped on every execution.
12pub type DrawCommandFn = Rc<dyn Fn(&mut DrawScopeDefault)>;
13
14#[derive(Clone)]
15pub enum DrawCommand {
16    Behind(DrawCommandFn),
17    WithContent(DrawCommandFn),
18    Overlay(DrawCommandFn),
19}
20
21/// The scope every framework-run draw command records into: sized by the
22/// consumer, measuring text with the app's fonts.
23pub fn command_draw_scope(size: Size) -> DrawScopeDefault {
24    DrawScopeDefault::with_text_measurer(size, AppContextTextMeasurer::shared())
25}
26
27/// [`command_draw_scope`] recording into storage the consumer already owns —
28/// the retained-recorder path where a command's buffer keeps its capacity
29/// across frames.
30pub fn command_draw_scope_reusing(size: Size, storage: Vec<DrawPrimitive>) -> DrawScopeDefault {
31    DrawScopeDefault::with_text_measurer_reusing(size, AppContextTextMeasurer::shared(), storage)
32}
33
34/// The full retained form of [`command_draw_scope`]: the compact recording
35/// buffers and the materialization target both come back to the caller via
36/// [`DrawScopeDefault::finish`], so a command re-recording every frame
37/// allocates nothing in the steady state.
38pub fn command_draw_scope_retained(
39    size: Size,
40    recording: cranpose_ui_graphics::CommandRecording,
41    out: Vec<DrawPrimitive>,
42) -> DrawScopeDefault {
43    DrawScopeDefault::with_recording(size, Some(AppContextTextMeasurer::shared()), recording, out)
44}
45
46#[derive(Default, Clone)]
47pub struct DrawCacheBuilder {
48    behind: Vec<DrawCommandFn>,
49    with_content: Vec<DrawCommandFn>,
50    overlay: Vec<DrawCommandFn>,
51}
52
53impl DrawCacheBuilder {
54    pub fn on_draw_behind(&mut self, f: impl Fn(&mut dyn DrawScope) + 'static) {
55        self.behind
56            .push(Rc::new(move |scope: &mut DrawScopeDefault| f(scope)));
57    }
58
59    pub fn on_draw_with_content(&mut self, f: impl Fn(&mut dyn DrawScope) + 'static) {
60        self.with_content
61            .push(Rc::new(move |scope: &mut DrawScopeDefault| f(scope)));
62    }
63
64    pub fn finish(self) -> Vec<DrawCommand> {
65        let mut commands = Vec::new();
66        commands.extend(self.behind.into_iter().map(DrawCommand::Behind));
67        commands.extend(self.with_content.into_iter().map(DrawCommand::WithContent));
68        commands.extend(self.overlay.into_iter().map(DrawCommand::Overlay));
69        commands
70    }
71}
72
73pub fn execute_draw_commands(commands: &[DrawCommand], size: Size) -> Vec<DrawPrimitive> {
74    let mut primitives = Vec::new();
75    for command in commands {
76        match command {
77            DrawCommand::Behind(f) | DrawCommand::WithContent(f) | DrawCommand::Overlay(f) => {
78                let mut scope = command_draw_scope(size);
79                f(&mut scope);
80                primitives.extend(scope.into_primitives());
81            }
82        }
83    }
84    primitives
85}