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}