Skip to main content

pfx_game/
prompt.rs

1use pfx_input::glyph::BOX;
2use pfx_input::{Control, Glyph, GlyphAtlas, Input, Key, KeyLabels, KeycapLabel, glyph_for};
3use pfx_live::text::{RichQuad, page_quads};
4use pfx_play::{Prompt, PromptOf};
5use pfx_text::{Face, Placement, Representation, TextEngine};
6
7pub const EVERYWHERE: [f32; 4] = [-f32::MAX, -f32::MAX, f32::MAX, f32::MAX];
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Part {
11    Glyph(Glyph),
12    Key(Key),
13}
14
15pub(crate) fn placement(pixels_per_unit: f32) -> Placement {
16    Placement {
17        scale: pixels_per_unit.max(f32::MIN_POSITIVE),
18        representation: Representation::Msdf,
19        origin: [0.0, 0.0],
20        alpha: 1.0,
21        clip: EVERYWHERE,
22        turn: [0.0; 3],
23    }
24}
25
26#[derive(Clone, Debug, Default, PartialEq)]
27pub struct PromptQuads {
28    pub glyphs: Vec<RichQuad>,
29    pub labels: Vec<(u32, Vec<RichQuad>)>,
30    pub width: f32,
31}
32
33#[derive(Clone, Debug, PartialEq)]
34pub struct Style {
35    pub family: String,
36    pub weight: u16,
37    pub gap: f32,
38    pub floor: f32,
39    pub pixels_per_unit: f32,
40}
41
42impl Style {
43    pub fn of(prompt: &Prompt, pixels_per_unit: f32) -> Self {
44        Self {
45            family: prompt.family.clone(),
46            weight: prompt.weight,
47            gap: prompt.gap,
48            floor: prompt.floor,
49            pixels_per_unit,
50        }
51    }
52}
53
54pub struct Sources<'a> {
55    pub atlas: &'a GlyphAtlas,
56    pub text: Option<&'a mut TextEngine>,
57    pub labels: &'a KeyLabels,
58}
59
60pub fn parts(input: &Input, of: &PromptOf) -> Vec<Part> {
61    let style = input.glyph_style();
62    let part = |control: Control| match control {
63        Control::Key(key) => Part::Key(key),
64        other => Part::Glyph(glyph_for(style, other)),
65    };
66    match of {
67        PromptOf::Action(name) => input.prompt(name).into_iter().map(part).collect(),
68        PromptOf::Control(control) => vec![part(*control)],
69        PromptOf::Glyph(glyph) => vec![Part::Glyph(*glyph)],
70    }
71}
72
73pub fn layout(
74    prompt: &[Part],
75    at: [f32; 2],
76    size: f32,
77    style: &Style,
78    sources: &mut Sources<'_>,
79) -> Result<PromptQuads, String> {
80    let scale = size / BOX;
81    let floor = if size > 0.0 {
82        style.floor.max(0.0) * BOX / size
83    } else {
84        0.0
85    };
86    let face = Face {
87        family: style.family.clone(),
88        size: 1.0,
89        line: 1.25,
90        weight: style.weight,
91        italic: false,
92        spacing: 0.0,
93    };
94    let placement = placement(style.pixels_per_unit);
95    let [mut x, y] = at;
96    let mut quads = PromptQuads::default();
97    for part in prompt {
98        let (glyph, cap) = match part {
99            Part::Glyph(glyph) => (*glyph, None),
100            Part::Key(key) => {
101                let Some(engine) = sources.text.as_deref_mut() else {
102                    return Err("a key prompt needs a font for its keycap (Config::font)".into());
103                };
104                let cap = KeycapLabel::key_with_floor(engine, &face, sources.labels, *key, floor)
105                    .map_err(|error| format!("the keycap of {key:?}: {error:?}"))?;
106                (cap.glyph, Some(cap))
107            }
108        };
109        let pieces = sources
110            .atlas
111            .pieces(glyph)
112            .ok_or_else(|| format!("the input glyph atlas has no {glyph:?}"))?;
113        for cell in &pieces {
114            quads.glyphs.push(RichQuad {
115                rect: [
116                    x + cell.offset[0] * scale,
117                    y + cell.offset[1] * scale,
118                    cell.size[0] * scale,
119                    cell.size[1] * scale,
120                ],
121                uv: cell.uv,
122                origin: [x, y],
123                alpha: 1.0,
124                clip: EVERYWHERE,
125                turn: [0.0; 3],
126                color: [1.0; 4],
127            });
128        }
129        if let (Some(cap), Some(engine)) = (cap, sources.text.as_deref_mut()) {
130            let placed = cap
131                .place(engine, &face, [1.0; 4], [x, y], size, &placement)
132                .map_err(|error| format!("the keycap label {:?}: {error:?}", cap.text))?;
133            quads.labels.extend(page_quads(&placed.quads));
134        }
135        let advance = pieces.first().map_or(BOX, |cell| cell.box_size[0]) * scale;
136        quads.width = x + advance - at[0];
137        x += advance + style.gap;
138    }
139    Ok(quads)
140}