Skip to main content

yog_book/renderer/
overlay.rs

1//! What's drawn around the widgets: sprites from the sheets before them,
2//! and after them what yog-ui doesn't draw — items, blocks, entities, the
3//! game font's text, mods' pictures.
4
5use yog_gfx::{Color, GfxContext, Rect, Vec2};
6
7use super::geometry::{
8    BLOCK_PITCH, ENTITY_TURN, ENTITY_YAW, MULTIBLOCK_SWAY, MULTIBLOCK_YAW, SLOT_YAW,
9};
10use super::{CRAFTING_SHEET, recipe::normalize_item_id};
11
12#[derive(Clone, PartialEq)]
13pub(crate) enum SpriteSheet {
14    Book,
15    Crafting,
16    /// Any image source (a recipe layout's).
17    Image(String),
18}
19
20/// A part of a sheet drawn before yog-ui.
21#[derive(Clone)]
22pub(crate) struct BgSprite {
23    pub sheet: SpriteSheet,
24    /// Source rect in pixels on the sheet.
25    pub src: Rect,
26    /// Destination on screen.
27    pub dst: Rect,
28}
29
30/// Draws a mod's picture (`BookPage::Drawn`): `(ctx, drawer, data, [x, y, w, h])`.
31pub type PageDrawer<'a> = &'a mut dyn FnMut(&GfxContext, &str, &yog_data::Value, [f32; 4]);
32
33/// Something drawn over the widgets, in its box on screen.
34#[derive(Clone)]
35pub(crate) enum OverlayCmd {
36    /// A mod's picture: its drawer (`BookPage::Drawn`) draws `data` in the box.
37    Drawn {
38        drawer: String,
39        data: yog_data::Value,
40        at: Rect,
41    },
42    /// One of `items` at a time, a new one every second (an ingredient's
43    /// alternatives, its tags' items among them).
44    McItems { items: Vec<String>, at: Rect },
45    /// Blocks in 3D: slowly turning (a multiblock on show), else at a
46    /// slot's angle (a station).
47    Blocks {
48        blocks: Vec<((i32, i32, i32), String)>,
49        at: Rect,
50        turning: bool,
51    },
52    /// A living entity standing in the box.
53    Entity { entity: String, at: Rect },
54    /// The game font's text (the nameplate's title and subtitle).
55    McText {
56        text: String,
57        at: Vec2,
58        color: Color,
59    },
60    /// An item's icon.
61    McItem { item_id: String, at: Rect },
62    /// A texture drawn whole (16×16) — an icon naming a texture rather
63    /// than an item, as Patchouli's `BookIcon.from` reads one.
64    McTexture { path: String, at: Rect },
65}
66
67impl OverlayCmd {
68    /// A book author's icon (a category's, an entry's): a `.png` is a
69    /// texture, anything else an item (Patchouli's `BookIcon.from`).
70    pub(crate) fn icon(icon: &str, at: Rect) -> OverlayCmd {
71        if icon.ends_with(yog_ids::res::PNG) {
72            OverlayCmd::McTexture {
73                path: icon.to_string(),
74                at,
75            }
76        } else {
77            OverlayCmd::McItem {
78                item_id: icon.to_string(),
79                at,
80            }
81        }
82    }
83
84    /// It moves by itself: the host should keep redrawing.
85    pub(crate) fn animated(&self) -> bool {
86        match self {
87            OverlayCmd::Entity { .. } | OverlayCmd::Blocks { .. } => true,
88            OverlayCmd::McItems { items, .. } => items.iter().filter(|i| !is_tag(i)).count() > 1,
89            _ => false,
90        }
91    }
92}
93
94/// An ingredient's tag (`#ns:path`), not an item.
95fn is_tag(item: &str) -> bool {
96    item.starts_with(yog_ids::sep::TAG_SHOWN)
97}
98
99/// The sprites, onto `book_sheet` / the crafting sheet / their own images.
100pub(crate) fn draw_sprites(ctx: &GfxContext, sprites: &[BgSprite], book_sheet: &str) {
101    let d2 = ctx.draw2d();
102    for spr in sprites {
103        let sheet = match &spr.sheet {
104            SpriteSheet::Book => book_sheet,
105            SpriteSheet::Crafting => CRAFTING_SHEET,
106            SpriteSheet::Image(source) => source.as_str(),
107        };
108        d2.image_region(sheet, spr.dst, Some(spr.src), Color::WHITE);
109    }
110}
111
112/// The overlays drawn in 3D or by mods, `t` seconds into the book being open.
113pub(crate) fn draw_scenes(
114    ctx: &GfxContext,
115    overlays: &[OverlayCmd],
116    t: f32,
117    draw: super::PageDrawer<'_>,
118) {
119    let d2 = ctx.draw2d();
120    for ov in overlays {
121        match ov {
122            OverlayCmd::Drawn { drawer, data, at } => {
123                draw(ctx, drawer, data, [at.x, at.y, at.w, at.h])
124            }
125            OverlayCmd::Entity { entity, at } => {
126                d2.entity(entity, *at, ENTITY_YAW + t * ENTITY_TURN, 0.0)
127            }
128            OverlayCmd::Blocks {
129                blocks,
130                at,
131                turning,
132            } => {
133                let list: Vec<((i32, i32, i32), &str)> =
134                    blocks.iter().map(|(p, b)| (*p, b.as_str())).collect();
135                let yaw = if *turning {
136                    MULTIBLOCK_YAW + t * MULTIBLOCK_SWAY
137                } else {
138                    SLOT_YAW
139                };
140                d2.blocks(&list, *at, yaw, BLOCK_PITCH);
141            }
142            _ => {}
143        }
144    }
145}
146
147/// The item stacks and the game font's text — last: item rendering changes
148/// GL state. An ingredient's alternatives turn every `second`.
149pub(crate) fn draw_items(ctx: &GfxContext, overlays: &[OverlayCmd], second: usize) {
150    let d2 = ctx.draw2d();
151    for ov in overlays {
152        match ov {
153            OverlayCmd::McItems { items, at } => {
154                let shown: Vec<&String> = items.iter().filter(|i| !is_tag(i)).collect();
155                if !shown.is_empty() {
156                    let item = shown[second % shown.len()];
157                    d2.item(
158                        &normalize_item_id(item),
159                        Vec2::new(at.x, at.y),
160                        at.w.min(at.h),
161                    );
162                }
163            }
164            OverlayCmd::McItem { item_id, at } => d2.item(
165                &normalize_item_id(item_id),
166                Vec2::new(at.x, at.y),
167                at.w.min(at.h),
168            ),
169            OverlayCmd::McText { text, at, color } => d2.text(text, *at, *color, false),
170            // A texture of its own, not an atlas region: drawn whole into the box.
171            OverlayCmd::McTexture { path, at } => d2.image(path, *at),
172            _ => {}
173        }
174    }
175}