1use crate::editor::RUSTERIX;
2use crate::prelude::*;
3use rusterix::ShapeStack;
4use rusterix::prelude::*;
5use theframework::prelude::*;
6
7#[derive(PartialEq, Clone, Copy, Debug)]
8pub enum HudMode {
9 Selection,
10 Vertex,
11 Linedef,
12 Sector,
13 Effects,
14 Rect,
15 Terrain,
16 Entity,
17}
18
19pub struct Hud {
20 mode: HudMode,
21
22 icon_rects: Vec<TheDim>,
23
24 pub selected_icon_index: i32,
25
26 subdiv_rects: Vec<TheDim>,
27
28 mouse_pos: Vec2<i32>,
29
30 is_playing: bool,
31 light_icon: Option<TheRGBABuffer>,
32
33 plane_picker_rects: Vec<TheDim>,
35}
36
37impl Hud {
38 pub fn new(mode: HudMode) -> Self {
39 Self {
40 mode,
41
42 icon_rects: vec![],
43 selected_icon_index: 0,
44
45 subdiv_rects: vec![],
46
47 mouse_pos: Vec2::zero(),
48
49 is_playing: false,
50 light_icon: None,
51
52 plane_picker_rects: vec![],
53 }
54 }
55
56 pub fn draw(
57 &mut self,
58 buffer: &mut TheRGBABuffer,
59 map: &mut Map,
60 ctx: &mut TheContext,
61 server_ctx: &mut ServerContext,
62 id: Option<u32>,
63 assets: &Assets,
64 ) {
65 if (self.mode == HudMode::Linedef || self.mode == HudMode::Sector)
66 && self.light_icon.is_none()
67 {
68 if let Some(li) = ctx.ui.icon("light_small") {
69 self.light_icon = Some(li.clone());
70 }
71 }
72
73 let width = buffer.dim().width as usize;
74 let height = buffer.dim().height as usize;
75 let stride = buffer.stride();
76
77 let info_height = 20;
78 let bg_color = [50, 50, 50, 255];
79 let text_color = [150, 150, 150, 255];
81 let sel_text_color = [220, 220, 220, 255];
82
83 self.subdiv_rects = vec![];
84
85 ctx.draw.rect(
86 buffer.pixels_mut(),
87 &(0, 0, width, info_height),
88 stride,
89 &bg_color,
90 );
91
92 if let Some(v) = server_ctx.hover_cursor {
93 ctx.draw.text(
94 buffer.pixels_mut(),
95 &(10, 2),
96 stride,
97 &format!("{:.2}, {:.2}", v.x, v.y),
98 TheFontSettings {
99 size: 13.0,
100 ..Default::default()
101 },
102 &text_color,
103 &bg_color,
104 );
105 }
106
107 if let Some(v) = &server_ctx.background_progress {
108 ctx.draw.text(
109 buffer.pixels_mut(),
110 &(550, 2),
111 stride,
112 v,
113 TheFontSettings {
114 size: 13.0,
115 ..Default::default()
116 },
117 &text_color,
118 &bg_color,
119 );
120 }
121
122 let icon_size = 40;
125 let mut icons = 0;
126
127 if server_ctx.get_map_context() == MapContext::Region {
128 icons = if self.mode == HudMode::Vertex {
129 0
130 } else if self.mode == HudMode::Linedef {
131 0
132 } else {
133 1
134 };
135 } else if server_ctx.get_map_context() == MapContext::Screen {
136 icons = if self.mode == HudMode::Sector { 2 } else { 0 };
137 }
138
139 if self.mode == HudMode::Effects
140 || self.mode == HudMode::Rect
141 || self.mode == HudMode::Terrain
142 {
143 icons = 0;
144 }
145
146 self.icon_rects.clear();
147 let x = width as i32 - (icon_size * icons) - 1;
148 for i in 0..icons {
149 let rect = TheDim::rect(x + (i * icon_size), 20, icon_size, icon_size);
150 ctx.draw.rect(
151 buffer.pixels_mut(),
152 &rect.to_buffer_utuple(),
153 stride,
154 &bg_color,
155 );
156
157 let r = rect.to_buffer_utuple();
158 ctx.draw.text_rect(
159 buffer.pixels_mut(),
160 &(r.0, 1, r.2, 19),
161 stride,
162 &self.get_icon_text(i, server_ctx),
163 TheFontSettings {
164 size: 10.0,
165 ..Default::default()
166 },
167 &text_color,
168 &bg_color,
169 TheHorizontalAlign::Center,
170 TheVerticalAlign::Center,
171 );
172
173 let r = &rect.to_buffer_utuple();
174 ctx.draw.rect(
175 buffer.pixels_mut(),
176 &(r.0 + 1, r.1 + 1, r.2 - 2, r.3 - 2),
177 stride,
178 &[30, 30, 30, 255],
179 );
180
181 if let Some(id) = id {
182 let (tile, has_light) = self.get_icon(i, map, id, icon_size as usize);
183 if let Some(tile) = tile {
184 let texture = tile.textures[0].resized(icon_size as usize, icon_size as usize);
185 ctx.draw.blend_slice(
186 buffer.pixels_mut(),
187 &texture.data,
188 &rect.to_buffer_utuple(),
189 stride,
190 );
191 }
192 if has_light {
193 if let Some(light_icon) = &self.light_icon {
194 ctx.draw.blend_slice(
195 buffer.pixels_mut(),
196 light_icon.pixels(),
197 &(
198 rect.x as usize + 1,
199 rect.y as usize + 1,
200 light_icon.dim().width as usize,
201 light_icon.dim().height as usize,
202 ),
203 stride,
204 );
205 }
206 }
207 }
208
209 if i == self.selected_icon_index {
210 ctx.draw.rect_outline(
211 buffer.pixels_mut(),
212 &rect.to_buffer_utuple(),
213 stride,
214 &sel_text_color,
215 );
216 }
217
218 self.icon_rects.push(rect);
219 }
220
221 if (map.camera == MapCamera::TwoD || server_ctx.get_map_context() == MapContext::Screen)
223 && self.mode != HudMode::Terrain
224 && self.mode != HudMode::Rect
225 {
226 let x = 150;
227
228 let size = 20;
229 for i in 0..10 {
230 let rect = TheDim::rect(x + (i * size), 0, size, size);
231
232 let r = rect.to_buffer_utuple();
233 ctx.draw.text_rect(
234 buffer.pixels_mut(),
235 &(r.0, 1, r.2, 19),
236 stride,
237 &(i + 1).to_string(),
238 TheFontSettings {
239 size: 13.0,
240 ..Default::default()
241 },
242 &if (i + 1) as f32 == map.subdivisions || rect.contains(self.mouse_pos) {
243 sel_text_color
244 } else {
245 text_color
246 },
247 &bg_color,
248 TheHorizontalAlign::Center,
249 TheVerticalAlign::Center,
250 );
251 self.subdiv_rects.push(rect);
252 }
253 }
254
255 if self.mode == HudMode::Terrain {
257 if let Some(v) = server_ctx.hover_height {
258 ctx.draw.text(
259 buffer.pixels_mut(),
260 &(150, 2),
261 stride,
262 &format!("Elevation {v:.2}"),
263 TheFontSettings {
264 size: 13.0,
265 ..Default::default()
266 },
267 &text_color,
268 &bg_color,
269 );
270 }
271 }
272
273 if server_ctx.get_map_context() == MapContext::Character
276 || server_ctx.get_map_context() == MapContext::Item
277 {
278 if self.is_playing {
279 let size = 128;
280 let mut texture = Texture::alloc(size as usize, size as usize);
281
282 let mut stack = ShapeStack::new(Vec2::new(-5.0, -5.0), Vec2::new(5.0, 5.0));
283 stack.render_geometry(&mut texture, map, assets, false, &FxHashMap::default());
284
285 map.properties.set("shape", Value::Texture(texture));
286 }
287
288 if let Some(Value::Texture(texture)) = map.properties.get("shape") {
289 let w = texture.width as i32;
290 let h = texture.height as i32;
291 let preview_rect = TheDim::rect(width as i32 - w - 1, height as i32 - h - 1, w, h);
292 ctx.draw.blend_slice(
293 buffer.pixels_mut(),
294 &texture.data,
295 &preview_rect.to_buffer_utuple(),
296 stride,
297 );
298 }
299 } else if server_ctx.get_map_context() == MapContext::Screen {
300 if let Some(id) = map.selected_sectors.get(0) {
302 if let Some(sector) = map.find_sector(*id) {
303 if let Some(Value::Source(PixelSource::ShapeFXGraphId(id))) =
304 sector.properties.get("screen_graph")
305 {
306 if let Some(graph) = map.shapefx_graphs.get(id)
307 && self.icon_rects.len() == 2
308 {
309 let w = self.icon_rects[0].width;
310 let h = self.icon_rects[0].height;
311
312 let textures =
313 graph.create_screen_widgets(w as usize, h as usize, assets);
314
315 for i in 0..2 {
316 ctx.draw.blend_slice(
317 buffer.pixels_mut(),
318 &textures[i as usize].data,
319 &self.icon_rects[i as usize].to_buffer_utuple(),
320 stride,
321 );
322 }
323 }
324 }
325 }
326 }
327
328 }
344
345 if server_ctx.editor_view_mode != EditorViewMode::D2 && server_ctx.show_editing_geometry {
347 self.draw_plane_picker(buffer, ctx, server_ctx, width, height, stride);
348 }
349 }
350
351 fn draw_plane_picker(
352 &mut self,
353 buffer: &mut TheRGBABuffer,
354 _ctx: &mut TheContext,
355 server_ctx: &ServerContext,
356 width: usize,
357 height: usize,
358 stride: usize,
359 ) {
360 self.plane_picker_rects.clear();
361
362 let widget_size = 80;
363 let widget_margin = 20;
364
365 let x = width as i32 - widget_size - widget_margin;
367 let y = height as i32 - widget_size - widget_margin;
368
369 let cx = x + widget_size / 2;
371 let cy = y + widget_size / 2 + 25; let size = 15;
373
374 let iso_x = |ix: i32, iy: i32, _iz: i32| cx + (ix - iy) * size;
377 let iso_y = |ix: i32, iy: i32, iz: i32| cy + (ix + iy) * size / 2 - iz * size;
378
379 let p = [
381 (iso_x(-1, -1, 0), iso_y(-1, -1, 0)), (iso_x(1, -1, 0), iso_y(1, -1, 0)), (iso_x(1, 1, 0), iso_y(1, 1, 0)), (iso_x(-1, 1, 0), iso_y(-1, 1, 0)), (iso_x(-1, -1, 2), iso_y(-1, -1, 2)), (iso_x(1, -1, 2), iso_y(1, -1, 2)), (iso_x(1, 1, 2), iso_y(1, 1, 2)), (iso_x(-1, 1, 2), iso_y(-1, 1, 2)), ];
390
391 let xz_color = if server_ctx.gizmo_mode == GizmoMode::XZ {
393 [120, 200, 120, 200]
394 } else {
395 [80, 140, 80, 150]
396 };
397
398 let xy_color = if server_ctx.gizmo_mode == GizmoMode::XY {
399 [120, 120, 200, 200]
400 } else {
401 [80, 80, 140, 150]
402 };
403
404 let yz_color = if server_ctx.gizmo_mode == GizmoMode::YZ {
405 [200, 120, 120, 200]
406 } else {
407 [140, 80, 80, 150]
408 };
409
410 let widget_bounds = (x, y, x + widget_size, y + widget_size);
412 let fill_quad = |buffer: &mut [u8],
413 p0: (i32, i32),
414 p1: (i32, i32),
415 p2: (i32, i32),
416 p3: (i32, i32),
417 color: [u8; 4]| {
418 let points = [p0, p1, p2, p3];
420 let min_y = points.iter().map(|p| p.1).min().unwrap();
421 let max_y = points.iter().map(|p| p.1).max().unwrap();
422
423 for scan_y in min_y..=max_y {
424 if scan_y < widget_bounds.1 || scan_y >= widget_bounds.3 {
426 continue;
427 }
428
429 let mut x_intersects = Vec::new();
430 for i in 0..4 {
431 let a = points[i];
432 let b = points[(i + 1) % 4];
433 if (a.1 <= scan_y && b.1 > scan_y) || (b.1 <= scan_y && a.1 > scan_y) {
434 let t = (scan_y - a.1) as f32 / (b.1 - a.1) as f32;
435 let x = a.0 as f32 + t * (b.0 - a.0) as f32;
436 x_intersects.push(x as i32);
437 }
438 }
439 x_intersects.sort();
440 for i in (0..x_intersects.len()).step_by(2) {
441 if i + 1 < x_intersects.len() {
442 for px in x_intersects[i]..=x_intersects[i + 1] {
443 if px >= widget_bounds.0
445 && px < widget_bounds.2
446 && scan_y >= widget_bounds.1
447 && scan_y < widget_bounds.3
448 && px >= 0
449 && scan_y >= 0
450 && (px as usize) < width
451 && (scan_y as usize) < height
452 {
453 let offset = scan_y as usize * stride * 4 + px as usize * 4;
454 if offset + 3 < buffer.len() {
455 buffer[offset] = color[0];
456 buffer[offset + 1] = color[1];
457 buffer[offset + 2] = color[2];
458 buffer[offset + 3] = color[3];
459 }
460 }
461 }
462 }
463 }
464 }
465 };
466
467 fill_quad(buffer.pixels_mut(), p[4], p[5], p[6], p[7], xz_color);
470
471 fill_quad(buffer.pixels_mut(), p[3], p[7], p[6], p[2], yz_color);
473
474 fill_quad(buffer.pixels_mut(), p[1], p[5], p[6], p[2], xy_color);
476
477 let edge_color = [200, 200, 200, 255];
479 let draw_edge = |buf: &mut [u8], p0: (i32, i32), p1: (i32, i32)| {
480 let mut x0 = p0.0;
482 let mut y0 = p0.1;
483 let x1 = p1.0;
484 let y1 = p1.1;
485 let dx = (x1 - x0).abs();
486 let dy = (y1 - y0).abs();
487 let sx = if x0 < x1 { 1 } else { -1 };
488 let sy = if y0 < y1 { 1 } else { -1 };
489 let mut err = dx - dy;
490 loop {
491 if x0 >= widget_bounds.0
493 && x0 < widget_bounds.2
494 && y0 >= widget_bounds.1
495 && y0 < widget_bounds.3
496 && x0 >= 0
497 && y0 >= 0
498 && (x0 as usize) < width
499 && (y0 as usize) < height
500 {
501 let offset = y0 as usize * stride * 4 + x0 as usize * 4;
502 if offset + 3 < buf.len() {
503 buf[offset] = edge_color[0];
504 buf[offset + 1] = edge_color[1];
505 buf[offset + 2] = edge_color[2];
506 buf[offset + 3] = edge_color[3];
507 }
508 }
509 if x0 == x1 && y0 == y1 {
510 break;
511 }
512 let e2 = 2 * err;
513 if e2 > -dy {
514 err -= dy;
515 x0 += sx;
516 }
517 if e2 < dx {
518 err += dx;
519 y0 += sy;
520 }
521 }
522 };
523
524 let buf = buffer.pixels_mut();
526 draw_edge(buf, p[4], p[5]);
527 draw_edge(buf, p[5], p[6]);
528 draw_edge(buf, p[6], p[7]);
529 draw_edge(buf, p[7], p[4]);
530 draw_edge(buf, p[1], p[5]);
531 draw_edge(buf, p[2], p[6]);
532 draw_edge(buf, p[3], p[7]);
533 draw_edge(buf, p[1], p[2]);
534 draw_edge(buf, p[2], p[3]);
535
536 self.plane_picker_rects.push(TheDim::rect(
539 p[4].0.min(p[5].0).min(p[6].0).min(p[7].0),
540 p[4].1.min(p[5].1).min(p[6].1).min(p[7].1),
541 p[4].0.max(p[5].0).max(p[6].0).max(p[7].0) - p[4].0.min(p[5].0).min(p[6].0).min(p[7].0),
542 p[4].1.max(p[5].1).max(p[6].1).max(p[7].1) - p[4].1.min(p[5].1).min(p[6].1).min(p[7].1),
543 ));
544
545 self.plane_picker_rects.push(TheDim::rect(
547 p[3].0.min(p[7].0).min(p[6].0).min(p[2].0),
548 p[3].1.min(p[7].1).min(p[6].1).min(p[2].1),
549 p[3].0.max(p[7].0).max(p[6].0).max(p[2].0) - p[3].0.min(p[7].0).min(p[6].0).min(p[2].0),
550 p[3].1.max(p[7].1).max(p[6].1).max(p[2].1) - p[3].1.min(p[7].1).min(p[6].1).min(p[2].1),
551 ));
552
553 self.plane_picker_rects.push(TheDim::rect(
555 p[1].0.min(p[5].0).min(p[6].0).min(p[2].0),
556 p[1].1.min(p[5].1).min(p[6].1).min(p[2].1),
557 p[1].0.max(p[5].0).max(p[6].0).max(p[2].0) - p[1].0.min(p[5].0).min(p[6].0).min(p[2].0),
558 p[1].1.max(p[5].1).max(p[6].1).max(p[2].1) - p[1].1.min(p[5].1).min(p[6].1).min(p[2].1),
559 ));
560 }
561
562 pub fn clicked(
563 &mut self,
564 x: i32,
565 y: i32,
566 map: &mut Map,
567 _ui: &mut TheUI,
568 ctx: &mut TheContext,
569 server_ctx: &mut ServerContext,
570 ) -> bool {
571 if server_ctx.get_map_context() != MapContext::Region
572 && server_ctx.get_map_context() != MapContext::Screen
573 && server_ctx.get_map_context() != MapContext::Character
574 && server_ctx.get_map_context() != MapContext::Item
575 {
576 return false;
577 }
578
579 for (i, rect) in self.icon_rects.iter().enumerate() {
580 if rect.contains(Vec2::new(x, y)) {
581 self.selected_icon_index = i as i32;
582 server_ctx.selected_hud_icon_index = i as i32;
583 if self.mode == HudMode::Linedef {
584 server_ctx.selected_wall_row = Some(i as i32);
585 ctx.ui.send(TheEvent::Custom(
586 TheId::named("Map Selection Changed"),
587 TheValue::Empty,
588 ));
589 }
590 return true;
591 }
592 }
593 if self.mode != HudMode::Rect {
594 for (i, rect) in self.subdiv_rects.iter().enumerate() {
595 if rect.contains(Vec2::new(x, y)) {
596 map.subdivisions = (i + 1) as f32;
597 return true;
598 }
599 }
600 }
601
602 if server_ctx.editor_view_mode != EditorViewMode::D2 && server_ctx.show_editing_geometry {
604 for (i, rect) in self.plane_picker_rects.iter().enumerate() {
605 if rect.contains(Vec2::new(x, y)) {
606 server_ctx.gizmo_mode = match i {
607 0 => {
608 println!("Plane picker: XZ plane selected");
609 GizmoMode::XZ
610 }
611 1 => {
612 println!("Plane picker: YZ plane selected");
613 GizmoMode::YZ
614 }
615 2 => {
616 println!("Plane picker: XY plane selected");
617 GizmoMode::XY
618 }
619 _ => server_ctx.gizmo_mode,
620 };
621 return true;
622 }
623 }
624 }
625
626 if map.camera == MapCamera::TwoD && y < 20 {
627 return true;
628 }
629
630 false
631 }
632
633 pub fn dragged(
634 &mut self,
635 _x: i32,
636 _y: i32,
637 _map: &mut Map,
638 _ui: &mut TheUI,
639 _ctx: &mut TheContext,
640 _server_ctx: &mut ServerContext,
641 ) -> bool {
642 false
651 }
652
653 pub fn hovered(
654 &mut self,
655 x: i32,
656 y: i32,
657 _map: &mut Map,
658 _ui: &mut TheUI,
659 _ctx: &mut TheContext,
660 _server_ctx: &mut ServerContext,
661 ) -> bool {
662 self.mouse_pos = Vec2::new(x, y);
663
664 false
675 }
676
677 #[allow(clippy::collapsible_if)]
678 pub fn get_icon_text(&self, index: i32, server_ctx: &mut ServerContext) -> String {
679 let mut text: String = "".into();
680 if server_ctx.get_map_context() == MapContext::Region {
681 if self.mode == HudMode::Sector {
682 if index == 0 {
683 text = "TILE".into();
684 }
685 }
686 } else if server_ctx.get_map_context() == MapContext::Screen {
687 if index == 0 {
688 text = "NORM".into();
689 } else if index == 1 {
690 text = "ACTIVE".into();
691 }
692 }
693
694 text
695 }
696
697 #[allow(clippy::collapsible_if)]
698 pub fn get_icon(
699 &self,
700 index: i32,
701 map: &Map,
702 id: u32,
703 icon_size: usize,
704 ) -> (Option<rusterix::Tile>, bool) {
705 if self.mode == HudMode::Sector {
706 if let Some(sector) = map.find_sector(id) {
707 if index == 0 {
708 let has_light = sector.properties.get("floor_light").is_some();
709 if let Some(pixelsource) = sector.properties.get_default_source() {
710 if let Some(tile) = pixelsource.to_tile(
711 &RUSTERIX.read().unwrap().assets,
712 icon_size,
713 §or.properties,
714 map,
715 ) {
716 return (Some(tile), has_light);
717 }
718 }
719 return (None, has_light);
720 } else if index == 1 {
721 let has_light = sector.properties.get("ceiling_light").is_some();
722 if let Some(Value::Source(pixelsource)) =
723 §or.properties.get("ceiling_source")
724 {
725 if let Some(tile) = pixelsource.to_tile(
726 &RUSTERIX.read().unwrap().assets,
727 icon_size,
728 §or.properties,
729 map,
730 ) {
731 return (Some(tile), has_light);
732 }
733 }
734 return (None, has_light);
735 }
736 }
737 }
738
739 (None, false)
740 }
741}