nodes/waypoint_ui.pyΒΆ
Part of Dungeon Explorer.
1"""Waypoint selection popup: teleport to previously visited floors."""
2
3from simvx.core import Control, Property
4from simvx.core.input import MouseButton
5from simvx.core.ui.enums import AnchorPreset
6
7from ._back_button import back_button_hit, draw_back_button
8
9
10class WaypointUI(Control):
11 """Shows activated waypoints, lets player select and teleport."""
12
13 visible = Property(
14 False,
15 coerce=bool,
16 hint="Whether this node and its subtree are drawn",
17 on_change="_on_visible_changed",
18 )
19
20 def __init__(self, on_teleport=None, **kwargs):
21 super().__init__(name="WaypointUI", **kwargs)
22 self.set_anchor_preset(AnchorPreset.FULL_RECT)
23
24 self._waypoints: list[int] = []
25 self._cursor = 0
26 self._on_teleport = on_teleport
27
28 def set_waypoints(self, waypoints: list[int]):
29 self._waypoints = sorted(waypoints)
30
31 def show(self):
32 self._cursor = 0
33 self.show_overlay("blocking")
34
35 # -- Input --
36
37 def _on_gui_input(self, event):
38 if not self.visible:
39 return
40 if event.button == MouseButton.LEFT and event.pressed:
41 self._handle_click(float(event.position[0]), float(event.position[1]), event)
42 return
43 if event.key and event.pressed:
44 if event.key in ("w", "up"):
45 self._cursor = max(0, self._cursor - 1)
46 event.handled = True
47 elif event.key in ("s", "down"):
48 self._cursor = min(self._cursor + 1, max(0, len(self._waypoints) - 1))
49 event.handled = True
50 elif event.key in ("space", "enter", "return"):
51 if self._waypoints and 0 <= self._cursor < len(self._waypoints):
52 self._teleport_to(self._waypoints[self._cursor])
53 event.handled = True
54 elif event.key == "e":
55 self.cancel_requested.emit()
56 self.close_overlay()
57 event.handled = True
58
59 def _handle_click(self, mx: float, my: float, event):
60 if back_button_hit(mx, my):
61 self.cancel_requested.emit()
62 self.close_overlay()
63 event.handled = True
64 return
65 sw, sh = self._screen_size()
66 pw = 320
67 rows = max(1, len(self._waypoints))
68 ph = 110 + rows * 30 + 30
69 px = (sw - pw) / 2
70 py = (sh - ph) / 2
71 list_y = py + 60
72 for i, floor in enumerate(self._waypoints):
73 y = list_y + i * 30
74 if px + 20 <= mx <= px + pw - 20 and y - 2 <= my <= y + 26:
75 self._cursor = i
76 self._teleport_to(floor)
77 event.handled = True
78 return
79
80 def _teleport_to(self, floor: int):
81 if self._on_teleport:
82 self._on_teleport(floor)
83 self.cancel_requested.emit()
84 self.close_overlay()
85
86 # -- Rendering --
87
88 def _screen_size(self) -> tuple[float, float]:
89 if self.tree is not None:
90 sw, sh = self.tree.screen_size
91 return float(sw), float(sh)
92 return 1280.0, 720.0
93
94 def on_draw(self, renderer):
95 if not self.visible:
96 return
97 sw, sh = self._screen_size()
98 renderer.draw_rect((0, 0), (sw, sh), colour=(0.0, 0.0, 0.0, 0.6), filled=True)
99
100 # Panel: styled to match the other popups in the game.
101 pw = 320
102 rows = max(1, len(self._waypoints))
103 ph = 110 + rows * 30 + 30
104 px = (sw - pw) / 2
105 py = (sh - ph) / 2
106 renderer.draw_rect((px, py), (pw, ph), colour=(0.12, 0.12, 0.18, 0.95), filled=True)
107 renderer.draw_rect((px, py), (pw, 36), colour=(0.15, 0.15, 0.25, 1.0), filled=True)
108 renderer.draw_text("WAYPOINTS", (px + 12, py + 8), scale=1.5, colour=(0.3, 0.7, 1.0))
109
110 list_y = py + 60
111 cx = px + pw / 2
112 if not self._waypoints:
113 renderer.draw_text("No waypoints activated yet.", (px + 30, list_y), scale=1.0, colour=(0.5, 0.5, 0.5))
114 for i, floor in enumerate(self._waypoints):
115 y = list_y + i * 30
116 sel = i == self._cursor
117 colour = (1.0, 1.0, 1.0) if sel else (0.6, 0.6, 0.6)
118 if sel:
119 renderer.draw_rect((px + 20, y - 2), (pw - 40, 26), colour=(0.2, 0.2, 0.4, 0.8), filled=True)
120 renderer.draw_text(f"Floor {floor}", (cx - 30, y), scale=1.2, colour=colour)
121
122 renderer.draw_text("Space: Teleport Esc: Close", (px + 20, py + ph - 25), scale=0.8, colour=(0.4, 0.4, 0.4))
123 draw_back_button(renderer)