Skip to main content

yog_cli/design/
gfx.rs

1//! A `YogGfxApi` table whose `draw2d_*` functions record into a display
2//! list instead of drawing — the game's side of the ABI, played by the IDE,
3//! so yog-ui's own `render` (the code that runs in game) produces the
4//! designer's picture. The GPU half of the table is inert.
5
6use std::cell::RefCell;
7
8use yog_abi::{YogGfxApi, YogStr};
9use yog_gfx::GfxContext;
10
11use super::raster::DrawOp;
12
13thread_local! {
14    static OPS: RefCell<Option<Vec<DrawOp>>> = const { RefCell::new(None) };
15}
16
17fn push(op: DrawOp) {
18    OPS.with(|o| {
19        if let Some(ops) = o.borrow_mut().as_mut() {
20            ops.push(op);
21        }
22    });
23}
24
25/// # Safety
26/// `s` must point to `len` readable bytes (or be null).
27unsafe fn string(s: YogStr) -> String {
28    if s.ptr.is_null() || s.len == 0 {
29        return String::new();
30    }
31    String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(s.ptr, s.len as usize) })
32        .into_owned()
33}
34
35unsafe extern "C" fn rect(x1: f32, y1: f32, x2: f32, y2: f32, color: u32) {
36    push(DrawOp::Rect {
37        x1,
38        y1,
39        x2,
40        y2,
41        color,
42    });
43}
44unsafe extern "C" fn gradient(x1: f32, y1: f32, x2: f32, y2: f32, top: u32, bottom: u32) {
45    push(DrawOp::Gradient {
46        x1,
47        y1,
48        x2,
49        y2,
50        top,
51        bottom,
52    });
53}
54unsafe extern "C" fn text(text: YogStr, x: f32, y: f32, color: u32, shadow: bool) {
55    let text = unsafe { string(text) };
56    push(DrawOp::Text {
57        text,
58        font: yog_ids::game::DEFAULT_FONT.into(),
59        x,
60        y,
61        color,
62        shadow,
63    });
64}
65unsafe extern "C" fn text_font(
66    text: YogStr,
67    font: YogStr,
68    x: f32,
69    y: f32,
70    color: u32,
71    shadow: bool,
72) {
73    let (text, font) = unsafe { (string(text), string(font)) };
74    let font = if font.trim().is_empty() {
75        yog_ids::game::DEFAULT_FONT.to_string()
76    } else {
77        font
78    };
79    push(DrawOp::Text {
80        text,
81        font,
82        x,
83        y,
84        color,
85        shadow,
86    });
87}
88/// Measured with the same glyphs the rasterizer draws (the document's fonts).
89unsafe extern "C" fn text_width(text: YogStr, font: YogStr) -> f32 {
90    let (text, font) = unsafe { (string(text), string(font)) };
91    super::font::Font::load(&super::images::resources(), &font).width(&text)
92}
93#[allow(clippy::too_many_arguments)]
94unsafe extern "C" fn image(
95    source: YogStr,
96    x: f32,
97    y: f32,
98    w: f32,
99    h: f32,
100    u: f32,
101    v: f32,
102    uw: f32,
103    vh: f32,
104    tint: u32,
105) {
106    let source = unsafe { string(source) };
107    push(DrawOp::Image {
108        source,
109        x,
110        y,
111        w,
112        h,
113        region: [u, v, uw, vh],
114        tint,
115    });
116}
117#[allow(clippy::too_many_arguments)]
118unsafe extern "C" fn text_scaled(text: YogStr, font: YogStr, x: f32, y: f32, scale: f32, color: u32, shadow: bool) {
119    let (text, font) = unsafe { (string(text), string(font)) };
120    let font = if font.is_empty() { yog_ids::game::DEFAULT_FONT.to_string() } else { font };
121    push(DrawOp::TextScaled { text, font, x, y, scale, color, shadow });
122}
123unsafe extern "C" fn blocks(list: YogStr, x: f32, y: f32, w: f32, h: f32, yaw: f32, pitch: f32) {
124    let list = unsafe { string(list) };
125    push(DrawOp::Blocks { list, x, y, w, h, yaw, pitch });
126}
127unsafe extern "C" fn svg(data: YogStr, x: f32, y: f32, w: f32, h: f32) {
128    let data = unsafe { string(data) };
129    push(DrawOp::Svg { data, x, y, w, h });
130}
131unsafe extern "C" fn entity(entity: YogStr, x: f32, y: f32, w: f32, h: f32, _yaw: f32, _pitch: f32) {
132    let entity = unsafe { string(entity) };
133    push(DrawOp::Entity { entity, x, y, w, h });
134}
135unsafe extern "C" fn image_size(source: YogStr, w: *mut f32, h: *mut f32) -> bool {
136    let source = unsafe { string(source) };
137    match super::images::source_size(&source) {
138        Some((sw, sh)) if !w.is_null() && !h.is_null() => {
139            // SAFETY: non-null out-pointers from the caller (`Draw2D::image_size`).
140            unsafe {
141                *w = sw;
142                *h = sh;
143            }
144            true
145        }
146        _ => false,
147    }
148}
149unsafe extern "C" fn item(id: YogStr, x: f32, y: f32, size: f32) {
150    let id = unsafe { string(id) };
151    push(DrawOp::Item { id, x, y, size });
152}
153unsafe extern "C" fn scissor(x: i32, y: i32, w: i32, h: i32) {
154    push(DrawOp::Scissor { x, y, w, h });
155}
156unsafe extern "C" fn clear_scissor() {
157    push(DrawOp::ClearScissor);
158}
159
160unsafe extern "C" fn zero() -> u32 {
161    0
162}
163unsafe extern "C" fn nop_u32(_: u32) {}
164unsafe extern "C" fn buf_data(_: u32, _: *const u8, _: u32, _: bool) {}
165unsafe extern "C" fn buf_subdata(_: u32, _: u32, _: *const u8, _: u32) {}
166#[allow(clippy::too_many_arguments)]
167unsafe extern "C" fn vao_attrib(_: u32, _: u32, _: u32, _: u8, _: u8, _: bool, _: u32, _: u32) {}
168unsafe extern "C" fn vao_set_ebo(_: u32, _: u32) {}
169unsafe extern "C" fn prog_create(_: YogStr, _: YogStr, _: *mut u32) -> bool {
170    false
171}
172unsafe extern "C" fn uni_1i(_: u32, _: YogStr, _: i32) {}
173unsafe extern "C" fn uni_1f(_: u32, _: YogStr, _: f32) {}
174unsafe extern "C" fn uni_2f(_: u32, _: YogStr, _: f32, _: f32) {}
175unsafe extern "C" fn uni_3f(_: u32, _: YogStr, _: f32, _: f32, _: f32) {}
176unsafe extern "C" fn uni_4f(_: u32, _: YogStr, _: f32, _: f32, _: f32, _: f32) {}
177unsafe extern "C" fn uni_mat4(_: u32, _: YogStr, _: *const f32) {}
178unsafe extern "C" fn tex_create(_: u32, _: u32, _: *const u8, _: bool) -> u32 {
179    0
180}
181unsafe extern "C" fn tex_bind(_: u32, _: u32) {}
182unsafe extern "C" fn tex_from_mc(_: YogStr) -> u32 {
183    0
184}
185unsafe extern "C" fn draw_arrays(_: u32, _: u32, _: u8, _: u32, _: u32) {}
186unsafe extern "C" fn draw_elements(_: u32, _: u32, _: u32, _: u8, _: u32, _: bool) {}
187unsafe extern "C" fn set_blend(_: bool, _: u32, _: u32) {}
188unsafe extern "C" fn set_depth(_: bool, _: bool) {}
189unsafe extern "C" fn set_viewport(_: i32, _: i32, _: i32, _: i32) {}
190
191/// The recording table for a `screen_w × screen_h` GUI.
192pub fn api(screen_w: i32, screen_h: i32, scale: f32) -> YogGfxApi {
193    YogGfxApi {
194        screen_w,
195        screen_h,
196        delta_tick: 0.0,
197        scale_factor: scale,
198        view_proj: [0.0; 16],
199        camera_pos: [0.0; 3],
200        player_pos: [0.0; 3],
201        _pad1: 0.0,
202        buf_create: zero,
203        buf_delete: nop_u32,
204        buf_data,
205        buf_subdata,
206        vao_create: zero,
207        vao_delete: nop_u32,
208        vao_attrib,
209        vao_set_ebo,
210        prog_create,
211        prog_delete: nop_u32,
212        prog_uniform_1i: uni_1i,
213        prog_uniform_1f: uni_1f,
214        prog_uniform_2f: uni_2f,
215        prog_uniform_3f: uni_3f,
216        prog_uniform_4f: uni_4f,
217        prog_uniform_mat4: uni_mat4,
218        tex_create,
219        tex_delete: nop_u32,
220        tex_bind,
221        tex_from_mc,
222        draw_arrays,
223        draw_elements,
224        set_blend,
225        set_depth,
226        set_scissor: scissor,
227        clear_scissor,
228        set_viewport,
229        draw2d_rect: rect,
230        draw2d_gradient: gradient,
231        draw2d_text: text,
232        draw2d_item: item,
233        draw2d_text_font: text_font,
234        text_width,
235        draw2d_image: image,
236        image_size,
237        draw2d_svg: svg,
238        draw2d_entity: entity,
239        draw2d_text_scaled: text_scaled,
240        draw2d_blocks: blocks,
241    }
242}
243
244/// Runs `draw` against a recording context and returns what it drew.
245pub fn record(screen_w: i32, screen_h: i32, draw: impl FnOnce(&GfxContext)) -> Vec<DrawOp> {
246    let table = api(screen_w, screen_h, 1.0);
247    // SAFETY: `table` outlives `ctx`, and every function in it is sound to call.
248    let ctx = unsafe { GfxContext::from_raw(&table) };
249    OPS.with(|o| *o.borrow_mut() = Some(Vec::new()));
250    draw(&ctx);
251    OPS.with(|o| o.borrow_mut().take().unwrap_or_default())
252}