1use jengine::engine::{Color, Game, jEngine, KeyCode};
10use jengine::renderer::text::Font;
11use jengine::ui::modern::Panel;
12use jengine::{DEFAULT_FONT_METADATA, DEFAULT_TILE_H, DEFAULT_TILE_W, DEFAULT_TILESET};
13
14const QUD_BG: Color = Color([0.02, 0.06, 0.06, 1.0]);
17const QUD_PANEL: Color = Color([0.03, 0.08, 0.08, 0.8]);
18const QUD_TEAL: Color = Color([0.25, 0.63, 0.50, 1.0]);
19const QUD_GREEN: Color = Color([0.00, 1.00, 0.00, 1.0]);
20const QUD_GOLD: Color = Color([0.94, 0.94, 0.19, 1.0]);
21const QUD_ORANGE: Color = Color([1.00, 0.50, 0.00, 1.0]);
22const QUD_DIM: Color = Color([0.38, 0.44, 0.44, 1.0]);
23const QUD_WHITE: Color = Color([0.85, 0.92, 0.88, 1.0]);
24const QUD_CYAN: Color = Color([0.00, 0.80, 0.80, 1.0]);
25
26const BASE_FS: f32 = 12.0;
27const PANEL_BG: Color = Color([0.06, 0.09, 0.09, 1.0]);
28
29struct QudModernDemo {
30 font_loaded: bool,
31 active_tab: usize,
32 inv_filter_idx: usize,
33 context_menu_open: bool,
34 selected_item_name: String,
35}
36
37impl QudModernDemo {
38 fn new() -> Self {
39 Self {
40 font_loaded: false,
41 active_tab: 3, inv_filter_idx: 0,
43 context_menu_open: false,
44 selected_item_name: "steel long sword".to_string(),
45 }
46 }
47}
48
49impl Game for QudModernDemo {
50 fn update(&mut self, engine: &mut jEngine) {
51 if engine.is_key_pressed(KeyCode::Escape) {
52 if self.context_menu_open { self.context_menu_open = false; }
53 else { engine.request_quit(); }
54 }
55 }
56
57 fn render(&mut self, engine: &mut jEngine) {
58 if !self.font_loaded {
59 if let Ok(font) = Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
60 engine.ui.text.set_font(font);
61 }
62 engine.set_scanlines(true);
63 engine.set_bloom(true);
64 self.font_loaded = true;
65 }
66
67 engine.clear();
68 let tw = engine.tile_width() as f32;
69 let th = engine.tile_height() as f32;
70 let sw = engine.grid_width() as f32 * tw;
71 let sh = engine.grid_height() as f32 * th;
72
73 engine.ui.ui_rect(0.0, 0.0, sw, sh, QUD_BG);
74
75 self.draw_qud_tabs(engine, sw, th);
77
78 self.draw_qud_header(engine, th, sw);
80
81 match self.active_tab {
83 1 => self.draw_placeholder_skills(engine, th, sw, sh),
84 3 => self.draw_equipment_tab(engine, th, sw, sh),
85 _ => { engine.ui.ui_text(sw*0.5-50.0, sh*0.5, "Tab Coming Soon", QUD_DIM, Color::TRANSPARENT, Some(24.0)); }
86 }
87
88 self.draw_qud_footer(engine, th, sw, sh);
90
91 if self.context_menu_open {
93 engine.ui.push_layer(jengine::ui::UILayer::Overlay); self.draw_context_menu(engine, sw, sh);
95 engine.ui.pop_layer();
96 }
97 }
98}
99
100impl QudModernDemo {
101 fn draw_qud_tabs(&mut self, engine: &mut jEngine, sw: f32, th: f32) {
102 Panel::new(0.0, 0.0, sw, th * 1.5).with_color(Color([0.1, 0.15, 0.15, 0.3])).with_border(QUD_TEAL, 1.0).with_pattern(1, 4.0).draw(engine);
103 let tabs = [
104 "Kp Home", "SKILLS", "ATTRIBUTES & POWERS", "EQUIPMENT",
105 "TINKERING", "JOURNAL", "QUESTS", "REPUTATION", "MESSAGE LOG", "Kp Prior"
106 ];
107 let mut tx = 20.0;
108 for (i, tab) in tabs.iter().enumerate() {
109 let is_active = i == self.active_tab;
110 let tab_w = (tab.len() as f32 * 7.0) + 20.0;
111
112 if engine.input.was_clicked(tx - 5.0, 0.0, tab_w, th * 1.5) { self.active_tab = i; }
113
114 if is_active {
115 Panel::new(tx - 5.0, 5.0, tab_w, th).with_color(Color([0.2, 0.4, 0.4, 0.4])).draw(engine);
116 engine.ui.ui_text(tx - 12.0, 8.0, "/", QUD_CYAN, Color::TRANSPARENT, Some(BASE_FS));
117 }
118 engine.ui.ui_text(tx, 8.0, tab, if is_active { QUD_WHITE } else { QUD_DIM }, Color::TRANSPARENT, Some(BASE_FS));
119 tx += tab_w + 5.0;
120 engine.ui.ui_vline(tx - 2.0, 0.0, th * 1.5, 1.0, QUD_TEAL);
121 }
122 }
123
124 fn draw_qud_header(&self, engine: &mut jEngine, th: f32, sw: f32) {
125 let hy = th * 2.0;
126 engine.ui.ui_text(40.0, hy, "@", QUD_GREEN, Color::TRANSPARENT, Some(24.0));
127 engine.ui.ui_text(100.0, hy + 4.0, "STR: 18 AGI: 19 TOU: 19 INT: 19 WIL: 18 EGO: 18", QUD_GREEN, Color::TRANSPARENT, Some(BASE_FS));
128 engine.ui.ui_text(sw * 0.4, hy + 4.0, "Skill Points (SP): 490", QUD_WHITE, Color::TRANSPARENT, Some(BASE_FS));
129 engine.ui.ui_hline(20.0, hy + th * 1.2, sw - 40.0, 1.0, QUD_TEAL);
130 engine.ui.ui_text(sw * 0.5 - 6.0, hy + th * 1.2 - 6.0, "◆", QUD_TEAL, Color::TRANSPARENT, Some(BASE_FS));
131 }
132
133 fn draw_qud_footer(&self, engine: &mut jEngine, th: f32, sw: f32, sh: f32) {
134 let fy = sh - th * 2.0;
135 Panel::new(0.0, fy, sw, th * 2.0).with_color(Color([0.1, 0.15, 0.15, 0.3])).with_border(QUD_TEAL, 1.0).with_pattern(1, 4.0).draw(engine);
136 Panel::new(20.0, fy + 5.0, 120.0, 20.0).with_color(Color([0.0, 0.0, 0.0, 0.4])).with_border(QUD_DIM, 1.0).with_radius(4.0).draw(engine);
137 engine.ui.ui_text(30.0, fy + 8.0, "<search>", QUD_DIM, Color::TRANSPARENT, Some(BASE_FS));
138 engine.ui.ui_text(sw * 0.5, fy + 8.0, "navigation [c:gold][Space][/c] Accept", QUD_WHITE, Color::TRANSPARENT, Some(BASE_FS));
139 }
140
141 fn draw_equipment_tab(&mut self, engine: &mut jEngine, th: f32, sw: f32, sh: f32) {
142 let tw = engine.tile_width() as f32;
143 let start_y = th * 4.5;
144 let split_x = sw * 0.45;
145
146 Panel::new(tw, start_y, sw - tw * 2.0, sh - start_y - th * 3.0).with_color(QUD_PANEL).with_border(QUD_TEAL, 1.0).draw(engine);
147 engine.ui.ui_vline(split_x, start_y, sh - start_y - th * 3.0, 1.0, QUD_TEAL);
148
149 let cx = tw + (split_x - tw) * 0.5;
151 let cy = start_y + 150.0;
152 let slot_sz = 45.0;
153
154 let slots = [
156 ("Face", 0.0, -120.0), ("Head", 0.0, -60.0), ("Floating Nearby", 100.0, -100.0),
157 ("Worn on Hands", -100.0, -40.0), ("Body", 0.0, 0.0), ("Left Arm", -80.0, 0.0), ("Right Arm", 80.0, 0.0),
158 ("Left Hand", -160.0, 0.0), ("Right Hand", 160.0, 0.0), ("Worn on Back", 0.0, 80.0),
159 ("Feet", 0.0, 160.0), ("Thrown Weapon", -120.0, 160.0),
160 ("Left Missile", 80.0, 160.0), ("Right Missile", 160.0, 160.0)
161 ];
162
163 engine.ui.ui_hline(cx - 160.0, cy, 320.0, 1.0, QUD_DIM); engine.ui.ui_vline(cx, cy - 120.0, 280.0, 1.0, QUD_DIM); engine.ui.ui_hline(cx + 80.0, cy + 160.0, 80.0, 1.0, QUD_DIM); for (name, ox, oy) in slots {
169 let sx = cx + ox - slot_sz * 0.5;
170 let sy = cy + oy - slot_sz * 0.5;
171
172 Panel::new(sx, sy, slot_sz, slot_sz)
173 .with_color(Color([0.0, 0.0, 0.0, 0.4]))
174 .with_border(QUD_TEAL, 1.0)
175 .draw(engine);
176
177 let short_name = name.split_whitespace().last().unwrap();
178 engine.ui.ui_text(sx, sy + slot_sz + 5.0, short_name, QUD_DIM, Color::TRANSPARENT, Some(10.0));
179
180 if name == "Right Hand" {
181 engine.ui.ui_text(sx + 10.0, sy + 10.0, "🗡", QUD_WHITE, Color::TRANSPARENT, Some(24.0));
182 }
183
184 if engine.input.was_clicked(sx, sy, slot_sz, slot_sz) {
185 self.context_menu_open = true;
186 self.selected_item_name = if name == "Right Hand" { "steel long sword".to_string() } else { format!("{} Slot", name) };
187 }
188 }
189
190 let mut fx = split_x + 20.0;
192 let filters = ["ALL", "🗡", "🛡", "🧪", "📜", "💍"];
193 for (i, f) in filters.iter().enumerate() {
194 let active = i == self.inv_filter_idx;
195 Panel::new(fx, start_y + 15.0, 35.0, 35.0)
196 .with_color(if active { QUD_TEAL } else { Color([0.0, 0.0, 0.0, 0.3]) })
197 .with_border(QUD_TEAL, 1.0)
198 .with_radius(4.0)
199 .draw(engine);
200 engine.ui.ui_text(fx + 8.0, start_y + 25.0, f, if active { QUD_BG } else { QUD_WHITE }, Color::TRANSPARENT, Some(16.0));
201 if engine.input.was_clicked(fx, start_y + 15.0, 35.0, 35.0) { self.inv_filter_idx = i; }
202 fx += 45.0;
203 }
204
205 let mut iy = start_y + 80.0;
206 let items = [
207 ("a)", "[-] Ammo | 0 lbs. |", QUD_DIM),
208 ("b)", " lead slug x18", QUD_WHITE),
209 ("c)", "[-] Corpses | 115 lbs. |", QUD_DIM),
210 ("d)", " [c:orange]croc corpse[/c]", QUD_ORANGE),
211 ("e)", " [c:white]cave spider corpse[/c]", QUD_WHITE),
212 ("f)", "[-] Misc | 5 lbs. |", QUD_DIM),
213 ("g)", " [c:gold]steel long sword[/c]", QUD_GOLD),
214 ];
215
216 for (key, name, _col) in items {
217 if name.contains("steel") {
218 Panel::new(split_x + 10.0, iy - 2.0, sw - split_x - tw * 3.0, BASE_FS + 4.0).with_color(Color([1.0, 1.0, 1.0, 0.08])).draw(engine);
219 }
220 engine.ui.ui_text(split_x + 25.0, iy, &format!("{} {}", key, name), QUD_WHITE, Color::TRANSPARENT, Some(BASE_FS));
221 iy += BASE_FS * 1.5;
222
223 if engine.input.was_clicked(split_x + 10.0, iy - 25.0, sw - split_x - tw * 3.0, BASE_FS + 4.0) {
224 self.context_menu_open = true;
225 self.selected_item_name = name.replace("[c:gold]", "").replace("[/c]", "").trim().to_string();
226 }
227 }
228 }
229
230 fn draw_context_menu(&mut self, engine: &mut jEngine, sw: f32, sh: f32) {
231 let mw = 280.0;
232 let mh = 350.0;
233 let mx = (sw - mw) * 0.5;
234 let my = (sh - mh) * 0.5;
235
236 Panel::new(mx, my, mw, mh)
238 .with_color(QUD_BG) .with_border(QUD_GOLD, 2.0)
240 .with_pattern(1, 2.0)
241 .with_radius(0.0) .draw(engine);
243
244 engine.ui.ui_text(mx + mw * 0.5 - 15.0, my + 20.0, "🗡", QUD_WHITE, Color::TRANSPARENT, Some(48.0));
246 engine.ui.ui_text(mx + 40.0, my + 80.0, &self.selected_item_name, QUD_WHITE, Color::TRANSPARENT, Some(18.0));
247 engine.ui.ui_hline(mx + 20.0, my + 110.0, mw - 40.0, 1.0, QUD_TEAL);
248
249 let options = ["[i] mark important", "[l] look", "[n] add notes", "[r] remove", "[Esc] Cancel"];
251 for (i, opt) in options.iter().enumerate() {
252 let oy = my + 130.0 + i as f32 * 35.0;
253 if engine.input.is_mouse_over(mx + 20.0, oy - 5.0, mw - 40.0, 30.0) {
254 Panel::new(mx + 20.0, oy - 5.0, mw - 40.0, 30.0).with_color(Color([1.0, 1.0, 1.0, 0.1])).with_radius(4.0).draw(engine);
255 }
256 engine.ui.ui_text(mx + 30.0, oy, opt, QUD_WHITE, Color::TRANSPARENT, Some(BASE_FS));
257 }
258 }
259
260 fn draw_placeholder_skills(&self, engine: &mut jEngine, _th: f32, sw: f32, sh: f32) {
261 engine.ui.ui_text(sw*0.5-80.0, sh*0.5, "Skills Tab Content", QUD_WHITE, Color::TRANSPARENT, Some(20.0));
262 }
263}
264
265fn main() {
266 jEngine::builder().with_title("jengine — 1:1 Qud Modern").with_size(1280, 720).with_tileset(DEFAULT_TILESET, 16, 24).run(QudModernDemo::new());
267}