1use super::*;
4
5pub(super) fn argb_to_rgba(c: u32, alpha: u8) -> [u8; 4] {
6 let a = ((c >> 24) & 0xFF) * alpha as u32 / 255;
7 [(c >> 16) as u8, (c >> 8) as u8, c as u8, a as u8]
8}
9
10pub(super) fn modulate(a: u32, b: u32) -> u32 {
11 let ch = |s: u32| ((a >> s & 0xFF) * (b >> s & 0xFF) / 255) << s;
12 ch(24) | ch(16) | ch(8) | ch(0)
13}
14
15impl Ui {
16 pub fn draw(&self, root: NodeId) -> Vec<Quad> {
19 let mut out = Vec::new();
20 self.draw_node(root, 0xFFFF_FFFF, None, &mut out);
21 self.draw_popups(root, &mut out);
22 self.draw_tooltip(root, &mut out);
23 out
24 }
25
26 pub(crate) fn line_quads(
29 out: &mut Vec<Quad>,
30 s: &SpriteRef,
31 [x0, y0, x1, y1]: [i32; 4],
32 color: u32,
33 clip: Option<[i32; 4]>,
34 ) {
35 if y0 == y1 || x0 == x1 {
36 let rect = [x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1)];
37 let rect = if y0 == y1 {
38 [rect[0], rect[1], rect[2], rect[1] + 1]
39 } else {
40 [rect[0], rect[1], rect[0] + 1, rect[3]]
41 };
42 Self::sprite_quad(out, s, rect, color, clip);
43 return;
44 }
45 let steps = (x1 - x0).abs().max((y1 - y0).abs());
46 for k in 0..steps {
47 let x = x0 + (x1 - x0) * k / steps;
48 let y = y0 + (y1 - y0) * k / steps;
49 Self::sprite_quad(out, s, [x, y, x + 1, y + 1], color, clip);
50 }
51 }
52
53 pub(super) fn sprite_quad(
54 out: &mut Vec<Quad>,
55 s: &SpriteRef,
56 rect: [i32; 4],
57 color: u32,
58 clip: Option<[i32; 4]>,
59 ) {
60 if rect[2] <= rect[0] || rect[3] <= rect[1] {
61 return;
62 }
63 out.push(Quad {
64 texture: Texture::Sheet(s.sheet),
65 rect: rect.map(|v| v as f32),
66 uv: s.uv,
67 color: argb_to_rgba(color, 255),
68 clip,
69 rotation: 0.0,
70 });
71 }
72
73 pub(super) fn draw_text(
74 &self,
75 t: &TextData,
76 origin: [i32; 2],
77 color: u32,
78 clip: Option<[i32; 4]>,
79 out: &mut Vec<Quad>,
80 ) {
81 let Some(font) = t.font else { return };
82 let Some(sheet) = self.res.fonts[font].sheet else {
83 return;
84 };
85 let sprites = &self.res.sheets[sheet].sheet.sprites;
86 if t.shadow {
87 let mut shadow = t.clone();
88 shadow.shadow = false;
89 self.draw_text(
90 &shadow,
91 [origin[0] + 1, origin[1] + 1],
92 color & 0xFF000000,
93 clip,
94 out,
95 );
96 }
97 for g in &t.layout.glyphs {
98 let Some(sp) = sprites.get(g.glyph) else {
99 continue;
100 };
101 let (x, y) = (origin[0] + g.x, origin[1] + g.y);
102 out.push(Quad {
103 texture: Texture::Sheet(sheet),
104 rect: [
105 x as f32,
106 y as f32,
107 (x + sp.width) as f32,
108 (y + sp.height) as f32,
109 ],
110 uv: sp.uv,
111 color: argb_to_rgba(color, 255),
112 clip,
113 rotation: 0.0,
114 });
115 }
116 }
117
118 pub(super) fn draw_node(
119 &self,
120 id: NodeId,
121 inherited: u32,
122 clip: Option<[i32; 4]>,
123 out: &mut Vec<Quad>,
124 ) {
125 let n = &self.nodes[id];
126 if !n.visible() {
127 return;
128 }
129 let mut color = modulate(n.color, inherited);
130 if n.flags & flags::DIM != 0 {
131 color = (color & 0x00FF_FFFF) | ((color >> 25) << 24);
132 }
133 let [l, t, r, b] = n.rect;
134 if let Some(border) = n.border {
135 self.draw_node(border, color, clip, out);
136 }
137 match &n.kind {
138 Kind::Control(c) => self.draw_control(id, c, color, clip, out),
139 Kind::Border { sprites } => {
140 let w = |i: usize| sprites[i].map_or(0, |s| s.width);
141 let h = |i: usize| sprites[i].map_or(0, |s| s.height);
142 let cells = [
143 [l, t, l + w(0), t + h(0)],
144 [l + w(0), t, r - w(2), t + h(1)],
145 [r - w(2), t, r, t + h(2)],
146 [l, t + h(0), l + w(3), b - h(6)],
147 [l + w(3), t + h(1), r - w(5), b - h(7)],
148 [r - w(5), t + h(2), r, b - h(8)],
149 [l, b - h(6), l + w(6), b],
150 [l + w(6), b - h(7), r - w(8), b],
151 [r - w(8), b - h(8), r, b],
152 ];
153 for (s, rect) in sprites.iter().zip(cells) {
154 if let Some(s) = s {
155 Self::sprite_quad(out, s, rect, color, clip);
156 }
157 }
158 }
159 Kind::Image {
160 sprite: Some(s), ..
161 } => {
162 Self::sprite_quad(out, s, n.rect, color, clip);
163 if n.rotation != 0.0 {
164 if let Some(q) = out.last_mut() {
165 q.rotation = n.rotation;
166 }
167 }
168 }
169 Kind::Lines {
170 sprite: Some(s),
171 lines,
172 ..
173 } => {
174 let r = n.rect;
176 let clip = Some(match clip {
177 Some(c) => [
178 c[0].max(r[0]),
179 c[1].max(r[1]),
180 c[2].min(r[2]),
181 c[3].min(r[3]),
182 ],
183 None => r,
184 });
185 for &[x0, y0, x1, y1] in lines {
186 let steps = (x1 - x0).abs().max((y1 - y0).abs()).max(1);
187 for i in 0..=steps {
188 let x = r[0] + x0 + (x1 - x0) * i / steps;
189 let y = r[1] + y0 + (y1 - y0) * i / steps;
190 Self::sprite_quad(out, s, [x, y, x + 1, y + 1], color, clip);
191 }
192 }
193 }
194 Kind::AnimatedImage { sprites, frame, .. } => {
195 if let Some(Some(sprite)) = sprites.get(*frame) {
196 Self::sprite_quad(out, sprite, n.rect, color, clip);
197 }
198 }
199 Kind::Button {
200 states,
201 content,
202 colors,
203 state,
204 } => {
205 let state = &if n.usable() { *state } else { 1 };
208 if let Some(s) = states[*state].or(states[0]) {
209 self.draw_node(s, color, clip, out);
210 }
211 if let Some(c) = content {
212 let tint = if colors[*state] != 0 {
213 modulate(colors[*state], color)
214 } else {
215 color
216 };
217 self.draw_node(*c, tint, clip, out);
218 }
219 }
220 Kind::Text(text) => {
221 let x = match text.justify {
222 1 => l + ((r - l) - text.layout.width) / 2,
223 2 => r - text.layout.width,
224 _ => l,
225 };
226 self.draw_text(text, [x, t], color, clip, out);
227 }
228 Kind::Label {
229 text,
230 icon,
231 icon_size,
232 text_alignment,
233 orientation,
234 gap,
235 icon_color,
236 ..
237 } => {
238 let iw = if icon_size[0] > 0 {
239 icon_size[0]
240 } else {
241 icon.map_or(0, |s| s.width)
242 };
243 let ih = if icon_size[1] > 0 {
244 icon_size[1]
245 } else {
246 icon.map_or(0, |s| s.height)
247 };
248 let (tw, th) = (text.layout.width, text.layout.height);
249 let (text_area, icon_pos) = match orientation {
251 0 => ([l, t, r - gap - iw, b], [r - iw, t + ((b - t) - ih) / 2]),
252 1 => ([l + iw + gap, t, r, b], [l, t + ((b - t) - ih) / 2]),
253 3 => ([l, t + ih + gap, r, b], [l + ((r - l) - iw) / 2, t]),
254 _ => ([l, t, r, b - ih - gap], [l + ((r - l) - iw) / 2, b - ih]),
255 };
256 let [al, at, ar, ab] = text_area;
257 let x = match text_alignment % 3 {
258 1 => al + ((ar - al) - tw) / 2,
259 2 => ar - tw,
260 _ => al,
261 };
262 let y = match text_alignment / 3 {
263 1 => at + ((ab - at) - th) / 2,
264 2 => ab - th,
265 _ => at,
266 };
267 if let Some(s) = icon {
268 Self::sprite_quad(
269 out,
270 s,
271 [icon_pos[0], icon_pos[1], icon_pos[0] + iw, icon_pos[1] + ih],
272 *icon_color,
273 clip,
274 );
275 }
276 self.draw_text(text, [x, y], color, clip, out);
277 }
278 Kind::List(list) => {
279 let list_clip = controls::intersect(clip, [l, t, r, b]);
280 let first = (list.scroll / list.row_height.max(1)).max(0) as usize;
281 let rows = ((b - t) / list.row_height.max(1) + 2) as usize;
282 for i in first..(first + rows).min(list.items.len()) {
283 let y = t + i as i32 * list.row_height - list.scroll;
284 let row = [l, y, r, y + list.row_height];
285 let tint = if list.selected == Some(i) {
286 Some(0xC0C9_A24A)
287 } else if list.hovered == Some(i) {
288 Some(0x40FF_FFFF)
289 } else {
290 None
291 };
292 if let (Some(c), Some(s)) = (tint, &list.highlight) {
293 Self::sprite_quad(out, s, row, c, list_clip);
294 }
295 let layout = match list.font {
296 Some(f) => TextLayout::compute(&self.res, f, &list.items[i], None, 0),
297 None => continue,
298 };
299 let text = TextData {
300 font: list.font,
301 text: String::new(),
302 max_width: 0,
303 justify: 0,
304 wrap: false,
305 shadow: false,
306 ellipsis_width: 0,
307 layout,
308 };
309 let mut x = l + 6;
312 if let Some(Some(s)) = list.icons.get(i) {
313 let h = s.height.min(list.row_height).max(1);
314 let w = (s.width * h / s.height.max(1)).max(1);
315 let iy = y + (list.row_height - h) / 2;
316 Self::sprite_quad(out, s, [x, iy, x + w, iy + h], color, list_clip);
317 x += w + 4;
318 }
319 let ty = y + (list.row_height - text.layout.height) / 2;
320 self.draw_text(&text, [x, ty], color, list_clip, out);
321 }
322 let total = list.items.len() as i32 * list.row_height;
324 if total > b - t {
325 if let Some(s) = &list.highlight {
326 let h = ((b - t) * (b - t) / total).max(12);
327 let y = t + (list.scroll * ((b - t) - h)) / (total - (b - t)).max(1);
328 Self::sprite_quad(out, s, [r - 4, y, r, y + h], 0x80FF_FFFF, list_clip);
329 }
330 }
331 }
332 Kind::Picture { texture, size } if size[0] > 0 && size[1] > 0 => {
333 let (aw, ah) = ((r - l) as f32, (b - t) as f32);
335 let scale = (aw / size[0] as f32).min(ah / size[1] as f32).min(4.0);
336 let (w, h) = (size[0] as f32 * scale, size[1] as f32 * scale);
337 let (x, y) = (l as f32 + (aw - w) / 2.0, t as f32 + (ah - h) / 2.0);
338 out.push(Quad {
339 texture: Texture::External(*texture),
340 rect: [x, y, x + w, y + h],
341 uv: [0.0, 0.0, 1.0, 1.0],
342 color: [255, 255, 255, 255],
343 clip,
344 rotation: 0.0,
345 });
346 }
347 _ => {}
348 }
349 for &c in &n.children {
350 self.draw_node(c, color, clip, out);
351 }
352 }
353}