Skip to main content

ui_styling/
ui_styling.rs

1//! # Modern UI Styling 1:1 Qud Match (Equipment Focus)
2//!
3//! Recreates the "Caves of Qud" Equipment screen with 1:1 visual fidelity.
4//! Features:
5//!   · Complex paper-doll layout with schematic lines
6//!   · Filterable inventory list
7//!   · Detailed context interaction menu
8
9use 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
14// ── Qud Palette ───────────────────────────────────────────────────────────────
15
16const 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, // EQUIPMENT
42            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        // ── 1. Top Tab Bar ──
76        self.draw_qud_tabs(engine, sw, th);
77
78        // ── 2. Header Area ──
79        self.draw_qud_header(engine, th, sw);
80
81        // ── 3. Main Content ──
82        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        // ── 4. Footer ──
89        self.draw_qud_footer(engine, th, sw, sh);
90
91        // ── 5. Context Menu ──
92        if self.context_menu_open {
93            engine.ui.push_layer(jengine::ui::UILayer::Overlay); // Draw on top
94            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        // ── 1. Schematic Paper Doll ──
150        let cx = tw + (split_x - tw) * 0.5;
151        let cy = start_y + 150.0;
152        let slot_sz = 45.0;
153
154        // Slot Definitions: (name, ox, oy) relative to center
155        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        // Draw Lines
164        engine.ui.ui_hline(cx - 160.0, cy, 320.0, 1.0, QUD_DIM); // Main horizontal
165        engine.ui.ui_vline(cx, cy - 120.0, 280.0, 1.0, QUD_DIM); // Main vertical
166        engine.ui.ui_hline(cx + 80.0, cy + 160.0, 80.0, 1.0, QUD_DIM); // Missile connector
167
168        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        // ── 2. Filter & List (Right) ──
191        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        // Main Panel (Solid background, squared corners)
237        Panel::new(mx, my, mw, mh)
238            .with_color(QUD_BG) // Use solid background
239            .with_border(QUD_GOLD, 2.0)
240            .with_pattern(1, 2.0)
241            .with_radius(0.0) // Squared corners
242            .draw(engine);
243        
244        // Header with Icon
245        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        // Options
250        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}