pub struct UI {
pub ui_vertices: Vec<UIVertex>,
pub tile_w: u32,
pub tile_h: u32,
pub theme: Theme,
pub text: TextLayer,
/* private fields */
}Fields§
§ui_vertices: Vec<UIVertex>§tile_w: u32§tile_h: u32§theme: Theme§text: TextLayerImplementations§
Source§impl UI
impl UI
pub fn new(tile_w: u32, tile_h: u32) -> Self
pub fn clear(&mut self)
pub fn push_scissor(&mut self, rect: Rect)
pub fn pop_scissor(&mut self)
pub fn set_layer(&mut self, layer: UILayer)
Sourcepub fn push_layer(&mut self, layer: UILayer)
pub fn push_layer(&mut self, layer: UILayer)
Examples found in repository?
examples/ui_styling.rs (line 93)
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 }Sourcepub fn pop_layer(&mut self)
pub fn pop_layer(&mut self)
Examples found in repository?
examples/ui_styling.rs (line 95)
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 }pub fn ui_panel( &mut self, x: f32, y: f32, w: f32, h: f32, color: Color, border: Color, thickness: f32, radius: [f32; 4], mode: u32, param: f32, )
Sourcepub fn ui_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color)
pub fn ui_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color)
Examples found in repository?
examples/scenes.rs (line 28)
25 fn draw(&mut self, engine: &mut jEngine) {
26 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
27 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
28 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.03, 0.05, 0.05, 1.0]));
29 Panel::new(sw * 0.5 - 200.0, sh * 0.5 - 100.0, 400.0, 200.0).with_color(PANEL_BG).with_border(BORDER, 1.0).with_radius(12.0).draw(engine);
30 engine.ui.ui_text(sw * 0.5 - 60.0, sh * 0.5 - 40.0, "SCENE DEMO", TEXT, Color::TRANSPARENT, Some(24.0));
31 engine.ui.ui_text(sw * 0.5 - 80.0, sh * 0.5 + 20.0, "Press [Enter] to Start", BORDER, Color::TRANSPARENT, Some(14.0));
32 }
33}
34
35struct GameplayScene { player_pos: [f32; 2] }
36impl GameplayScene { fn new() -> Self { Self { player_pos: [400.0, 300.0] } } }
37impl Scene for GameplayScene {
38 fn update(&mut self, engine: &mut jEngine) -> SceneAction {
39 if engine.is_key_pressed(KeyCode::Escape) { return SceneAction::Push(Box::new(PauseScene)); }
40 let speed = 200.0 * engine.dt();
41 if engine.is_key_held(KeyCode::ArrowUp) { self.player_pos[1] -= speed; }
42 if engine.is_key_held(KeyCode::ArrowDown) { self.player_pos[1] += speed; }
43 if engine.is_key_held(KeyCode::ArrowLeft) { self.player_pos[0] -= speed; }
44 if engine.is_key_held(KeyCode::ArrowRight) { self.player_pos[0] += speed; }
45 SceneAction::None
46 }
47 fn draw(&mut self, engine: &mut jEngine) {
48 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
49 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
50 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.05, 0.08, 0.08, 1.0]));
51 engine.ui.ui_text(20.0, 20.0, "Gameplay: Use Arrows to move, Esc to Pause", TEXT, Color::TRANSPARENT, Some(14.0));
52 Panel::new(self.player_pos[0] - 15.0, self.player_pos[1] - 15.0, 30.0, 30.0).with_color(Color::CYAN).with_radius(15.0).draw(engine);
53 }
54}
55
56struct PauseScene;
57impl Scene for PauseScene {
58 fn is_transparent(&self) -> bool { true }
59 fn update(&mut self, engine: &mut jEngine) -> SceneAction {
60 if engine.is_key_pressed(KeyCode::Escape) || engine.is_key_pressed(KeyCode::Enter) { SceneAction::Pop }
61 else if engine.is_key_pressed(KeyCode::Backspace) { SceneAction::ReplaceAll(Box::new(TitleScene)) }
62 else { SceneAction::None }
63 }
64 fn draw(&mut self, engine: &mut jEngine) {
65 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
66 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
67 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.0, 0.0, 0.0, 0.6]));
68 Panel::new(sw * 0.5 - 150.0, sh * 0.5 - 80.0, 300.0, 160.0).with_color(PANEL_BG).with_border(BORDER, 1.0).with_radius(8.0).draw(engine);
69 engine.ui.ui_text(sw * 0.5 - 40.0, sh * 0.5 - 30.0, "PAUSED", TEXT, Color::TRANSPARENT, Some(20.0));
70 engine.ui.ui_text(sw * 0.5 - 100.0, sh * 0.5 + 20.0, "[Enter] Resume [Bksp] Menu", BORDER, Color::TRANSPARENT, Some(12.0));
71 }More examples
examples/ui_styling.rs (line 73)
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 }examples/stress_test.rs (line 173)
149 fn render(&mut self, engine: &mut jEngine) {
150 if !self.font_loaded {
151 if let Ok(font) = jengine::renderer::text::Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
152 engine.renderer.set_mtsdf_distance_range(font.distance_range);
153 engine.ui.text.set_font(font);
154 }
155 self.font_loaded = true;
156 }
157
158 engine.clear();
159
160 // ── Draw Entities (as small circles for speed) ──
161 for (_e, (_m, pos)) in self.world.query_multi::<(EntityMarker, Position)>() {
162 engine.draw_particle(pos.x, pos.y, Color([0.4, 0.7, 1.0, 1.0]), 4.0);
163 }
164
165 // ── Draw Particles ──
166 for (_e, (_l, pos)) in self.world.query_multi::<(Life, Position)>() {
167 engine.draw_particle(pos.x, pos.y, Color([1.0, 0.5, 0.2, 0.8]), 2.0);
168 }
169
170 // ── Stats UI ──
171 let th = engine.tile_height() as f32;
172
173 engine.ui.ui_rect(0.0, 0.0, 350.0, th * 8.0, Color([0.0, 0.0, 0.0, 0.7]));
174
175 let mut y = 10.0;
176 let lines = [
177 format!("STRESS TEST - [Space] for Nova"),
178 format!("Entities: {} (Q/W to adj)", self.entity_target_count),
179 format!("P-Rate: {} / frame (A/S to adj)", self.particle_spawn_rate),
180 format!("Total ECS: {}", self.world.entity_count()),
181 format!("FPS: {:.1}", 1.0 / engine.dt().max(0.001)),
182 ];
183
184 for line in lines {
185 engine.ui.ui_text(10.0, y, &line, Color::WHITE, Color::TRANSPARENT, None);
186 y += th;
187 }
188 }examples/layout_demo.rs (line 40)
27 fn render(&mut self, engine: &mut jEngine) {
28 if !self.font_loaded {
29 if let Ok(font) = Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
30 engine.ui.text.set_font(font);
31 }
32 self.font_loaded = true;
33 }
34
35 engine.clear();
36 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
37 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
38
39 // Background
40 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.05, 0.05, 0.08, 1.0]));
41
42 // ── Centered Menu ──
43 let mut menu = VStack::new(Alignment::Center)
44 .with_spacing(20.0)
45 .with_padding(Padding::all(40.0))
46 .with_bg(Color([0.08, 0.08, 0.12, 0.9]))
47 .with_border(BorderStyle::Thin, Color([0.3, 0.6, 0.5, 1.0]))
48 .with_radius(16.0)
49 .add(TextWidget { text: "JENGINE".to_string(), size: Some(64.0), color: Some(Color([1.0, 0.9, 0.2, 1.0])) })
50 .add(TextWidget { text: "Modern Layout Demo".to_string(), size: Some(24.0), color: Some(Color([0.4, 0.8, 0.7, 1.0])) })
51 .add(Spacer { size: 20.0, horizontal: false })
52 .add(
53 HStack::new(Alignment::Center)
54 .with_spacing(15.0)
55 .add(RectWidget { w: 80.0, h: 2.0, color: Color([0.5, 0.5, 0.5, 1.0]), radius: 1.0 })
56 .add(TextWidget { text: "v1.0".to_string(), size: Some(14.0), color: Some(Color([0.5, 0.5, 0.5, 1.0])) })
57 .add(RectWidget { w: 80.0, h: 2.0, color: Color([0.5, 0.5, 0.5, 1.0]), radius: 1.0 })
58 )
59 .add(Spacer { size: 30.0, horizontal: false })
60 .add(TextWidget { text: "> Start Game".to_string(), size: Some(32.0), color: Some(Color::WHITE) })
61 .add(TextWidget { text: " Options".to_string(), size: Some(32.0), color: Some(Color([0.7, 0.7, 0.7, 1.0])) })
62 .add(TextWidget { text: " Quit".to_string(), size: Some(32.0), color: Some(Color([0.7, 0.7, 0.7, 1.0])) });
63
64 let (mw, mh) = menu.size(engine);
65 Widget::draw(&mut menu, engine, (sw - mw) * 0.5, (sh - mh) * 0.5, mw, None);
66
67 engine.ui.ui_text(sw * 0.5 - 60.0, sh - 40.0, "[Esc] to Quit", Color([0.4, 0.4, 0.4, 1.0]), Color::TRANSPARENT, Some(16.0));
68 }examples/ui_widgets.rs (line 61)
47 fn render(&mut self, engine: &mut jEngine) {
48 if !self.font_loaded {
49 if let Ok(font) = Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
50 engine.ui.text.set_font(font);
51 }
52 self.font_loaded = true;
53 }
54
55 engine.clear();
56 let tw = engine.tile_width() as f32;
57 let th = engine.tile_height() as f32;
58 let sw = engine.grid_width() as f32 * tw;
59 let sh = engine.grid_height() as f32 * th;
60
61 engine.ui.ui_rect(0.0, 0.0, sw, sh, BG);
62
63 // ── Title ──
64 Panel::new(0.0, 0.0, sw, 50.0).with_color(Color([0.08, 0.12, 0.12, 1.0])).with_border(BORDER, 1.0).draw(engine);
65 engine.ui.ui_text(20.0, 15.0, "JENGINE — Modern UGUI Showcase", HEAD, Color::TRANSPARENT, Some(24.0));
66
67 let col_w = sw * 0.5;
68 let start_y = 70.0;
69
70 // ── Left: Primitives ──
71 let lx = 20.0;
72 engine.ui.ui_text(lx, start_y, "MODERN PRIMITIVES", HEAD, Color::TRANSPARENT, Some(18.0));
73
74 // Rounded Box
75 engine.ui.ui_text(lx, start_y + 40.0, "Panel with 12px corners:", DIM, Color::TRANSPARENT, Some(14.0));
76 Panel::new(lx, start_y + 60.0, col_w - 40.0, 80.0)
77 .with_color(Color([0.1, 0.15, 0.15, 1.0]))
78 .with_border(BORDER, 1.0)
79 .with_radius(12.0)
80 .draw(engine);
81 engine.ui.ui_text(lx + 20.0, start_y + 90.0, "Smooth SDF edges at any resolution.", BODY, Color::TRANSPARENT, Some(14.0));
82
83 // Progress Bar
84 engine.ui.ui_text(lx, start_y + 160.0, "Procedural Progress Bar:", DIM, Color::TRANSPARENT, Some(14.0));
85 Panel::new(lx, start_y + 180.0, col_w - 40.0, 25.0).with_color(Color([0.05, 0.1, 0.05, 1.0])).with_radius(12.5).draw(engine);
86 Panel::new(lx, start_y + 180.0, (col_w - 40.0) * self.hp_pct, 25.0).with_color(Color([0.2, 0.8, 0.3, 1.0])).with_radius(12.5).draw(engine);
87
88 // Pattern
89 engine.ui.ui_text(lx, start_y + 220.0, "Procedural Pattern (Crosshatch):", DIM, Color::TRANSPARENT, Some(14.0));
90 Panel::new(lx, start_y + 240.0, col_w - 40.0, 60.0)
91 .with_color(Color([0.1, 0.1, 0.2, 0.5]))
92 .with_border(Color::CYAN, 1.0)
93 .with_pattern(1, 4.0)
94 .with_radius(8.0)
95 .draw(engine);
96
97 // ── Right: Interactive ──
98 let rx = col_w + 20.0;
99 let ctrl_w = col_w - 40.0;
100 engine.ui.ui_text(rx, start_y, "INTERACTIVE WIDGETS", HEAD, Color::TRANSPARENT, Some(18.0));
101
102 engine.ui.ui_text(rx, start_y + 40.0, "Dropdown:", DIM, Color::TRANSPARENT, Some(14.0));
103 self.dropdown.draw(engine, rx, start_y + 60.0, ctrl_w);
104
105 engine.ui.ui_text(rx, start_y + 120.0, "ToggleSelector:", DIM, Color::TRANSPARENT, Some(14.0));
106 self.toggle.draw(engine, rx, start_y + 140.0, ctrl_w);
107
108 engine.ui.ui_text(rx, start_y + 200.0, "InputBox:", DIM, Color::TRANSPARENT, Some(14.0));
109 self.input.draw(engine, rx, start_y + 220.0, ctrl_w);
110 }Additional examples can be found in:
pub fn ui_box( &mut self, x: f32, y: f32, w: f32, h: f32, style: BorderStyle, fg: Color, bg: Color, )
pub fn ui_pattern( &mut self, x: f32, y: f32, w: f32, h: f32, color: Color, scale: f32, )
Sourcepub fn ui_text(
&mut self,
x: f32,
y: f32,
text: &str,
fg: Color,
bg: Color,
font_size: Option<f32>,
)
pub fn ui_text( &mut self, x: f32, y: f32, text: &str, fg: Color, bg: Color, font_size: Option<f32>, )
Examples found in repository?
examples/scenes.rs (line 30)
25 fn draw(&mut self, engine: &mut jEngine) {
26 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
27 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
28 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.03, 0.05, 0.05, 1.0]));
29 Panel::new(sw * 0.5 - 200.0, sh * 0.5 - 100.0, 400.0, 200.0).with_color(PANEL_BG).with_border(BORDER, 1.0).with_radius(12.0).draw(engine);
30 engine.ui.ui_text(sw * 0.5 - 60.0, sh * 0.5 - 40.0, "SCENE DEMO", TEXT, Color::TRANSPARENT, Some(24.0));
31 engine.ui.ui_text(sw * 0.5 - 80.0, sh * 0.5 + 20.0, "Press [Enter] to Start", BORDER, Color::TRANSPARENT, Some(14.0));
32 }
33}
34
35struct GameplayScene { player_pos: [f32; 2] }
36impl GameplayScene { fn new() -> Self { Self { player_pos: [400.0, 300.0] } } }
37impl Scene for GameplayScene {
38 fn update(&mut self, engine: &mut jEngine) -> SceneAction {
39 if engine.is_key_pressed(KeyCode::Escape) { return SceneAction::Push(Box::new(PauseScene)); }
40 let speed = 200.0 * engine.dt();
41 if engine.is_key_held(KeyCode::ArrowUp) { self.player_pos[1] -= speed; }
42 if engine.is_key_held(KeyCode::ArrowDown) { self.player_pos[1] += speed; }
43 if engine.is_key_held(KeyCode::ArrowLeft) { self.player_pos[0] -= speed; }
44 if engine.is_key_held(KeyCode::ArrowRight) { self.player_pos[0] += speed; }
45 SceneAction::None
46 }
47 fn draw(&mut self, engine: &mut jEngine) {
48 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
49 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
50 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.05, 0.08, 0.08, 1.0]));
51 engine.ui.ui_text(20.0, 20.0, "Gameplay: Use Arrows to move, Esc to Pause", TEXT, Color::TRANSPARENT, Some(14.0));
52 Panel::new(self.player_pos[0] - 15.0, self.player_pos[1] - 15.0, 30.0, 30.0).with_color(Color::CYAN).with_radius(15.0).draw(engine);
53 }
54}
55
56struct PauseScene;
57impl Scene for PauseScene {
58 fn is_transparent(&self) -> bool { true }
59 fn update(&mut self, engine: &mut jEngine) -> SceneAction {
60 if engine.is_key_pressed(KeyCode::Escape) || engine.is_key_pressed(KeyCode::Enter) { SceneAction::Pop }
61 else if engine.is_key_pressed(KeyCode::Backspace) { SceneAction::ReplaceAll(Box::new(TitleScene)) }
62 else { SceneAction::None }
63 }
64 fn draw(&mut self, engine: &mut jEngine) {
65 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
66 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
67 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.0, 0.0, 0.0, 0.6]));
68 Panel::new(sw * 0.5 - 150.0, sh * 0.5 - 80.0, 300.0, 160.0).with_color(PANEL_BG).with_border(BORDER, 1.0).with_radius(8.0).draw(engine);
69 engine.ui.ui_text(sw * 0.5 - 40.0, sh * 0.5 - 30.0, "PAUSED", TEXT, Color::TRANSPARENT, Some(20.0));
70 engine.ui.ui_text(sw * 0.5 - 100.0, sh * 0.5 + 20.0, "[Enter] Resume [Bksp] Menu", BORDER, Color::TRANSPARENT, Some(12.0));
71 }More examples
examples/ui_styling.rs (line 85)
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 }examples/stress_test.rs (line 185)
149 fn render(&mut self, engine: &mut jEngine) {
150 if !self.font_loaded {
151 if let Ok(font) = jengine::renderer::text::Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
152 engine.renderer.set_mtsdf_distance_range(font.distance_range);
153 engine.ui.text.set_font(font);
154 }
155 self.font_loaded = true;
156 }
157
158 engine.clear();
159
160 // ── Draw Entities (as small circles for speed) ──
161 for (_e, (_m, pos)) in self.world.query_multi::<(EntityMarker, Position)>() {
162 engine.draw_particle(pos.x, pos.y, Color([0.4, 0.7, 1.0, 1.0]), 4.0);
163 }
164
165 // ── Draw Particles ──
166 for (_e, (_l, pos)) in self.world.query_multi::<(Life, Position)>() {
167 engine.draw_particle(pos.x, pos.y, Color([1.0, 0.5, 0.2, 0.8]), 2.0);
168 }
169
170 // ── Stats UI ──
171 let th = engine.tile_height() as f32;
172
173 engine.ui.ui_rect(0.0, 0.0, 350.0, th * 8.0, Color([0.0, 0.0, 0.0, 0.7]));
174
175 let mut y = 10.0;
176 let lines = [
177 format!("STRESS TEST - [Space] for Nova"),
178 format!("Entities: {} (Q/W to adj)", self.entity_target_count),
179 format!("P-Rate: {} / frame (A/S to adj)", self.particle_spawn_rate),
180 format!("Total ECS: {}", self.world.entity_count()),
181 format!("FPS: {:.1}", 1.0 / engine.dt().max(0.001)),
182 ];
183
184 for line in lines {
185 engine.ui.ui_text(10.0, y, &line, Color::WHITE, Color::TRANSPARENT, None);
186 y += th;
187 }
188 }examples/layout_demo.rs (line 67)
27 fn render(&mut self, engine: &mut jEngine) {
28 if !self.font_loaded {
29 if let Ok(font) = Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
30 engine.ui.text.set_font(font);
31 }
32 self.font_loaded = true;
33 }
34
35 engine.clear();
36 let sw = engine.grid_width() as f32 * engine.tile_width() as f32;
37 let sh = engine.grid_height() as f32 * engine.tile_height() as f32;
38
39 // Background
40 engine.ui.ui_rect(0.0, 0.0, sw, sh, Color([0.05, 0.05, 0.08, 1.0]));
41
42 // ── Centered Menu ──
43 let mut menu = VStack::new(Alignment::Center)
44 .with_spacing(20.0)
45 .with_padding(Padding::all(40.0))
46 .with_bg(Color([0.08, 0.08, 0.12, 0.9]))
47 .with_border(BorderStyle::Thin, Color([0.3, 0.6, 0.5, 1.0]))
48 .with_radius(16.0)
49 .add(TextWidget { text: "JENGINE".to_string(), size: Some(64.0), color: Some(Color([1.0, 0.9, 0.2, 1.0])) })
50 .add(TextWidget { text: "Modern Layout Demo".to_string(), size: Some(24.0), color: Some(Color([0.4, 0.8, 0.7, 1.0])) })
51 .add(Spacer { size: 20.0, horizontal: false })
52 .add(
53 HStack::new(Alignment::Center)
54 .with_spacing(15.0)
55 .add(RectWidget { w: 80.0, h: 2.0, color: Color([0.5, 0.5, 0.5, 1.0]), radius: 1.0 })
56 .add(TextWidget { text: "v1.0".to_string(), size: Some(14.0), color: Some(Color([0.5, 0.5, 0.5, 1.0])) })
57 .add(RectWidget { w: 80.0, h: 2.0, color: Color([0.5, 0.5, 0.5, 1.0]), radius: 1.0 })
58 )
59 .add(Spacer { size: 30.0, horizontal: false })
60 .add(TextWidget { text: "> Start Game".to_string(), size: Some(32.0), color: Some(Color::WHITE) })
61 .add(TextWidget { text: " Options".to_string(), size: Some(32.0), color: Some(Color([0.7, 0.7, 0.7, 1.0])) })
62 .add(TextWidget { text: " Quit".to_string(), size: Some(32.0), color: Some(Color([0.7, 0.7, 0.7, 1.0])) });
63
64 let (mw, mh) = menu.size(engine);
65 Widget::draw(&mut menu, engine, (sw - mw) * 0.5, (sh - mh) * 0.5, mw, None);
66
67 engine.ui.ui_text(sw * 0.5 - 60.0, sh - 40.0, "[Esc] to Quit", Color([0.4, 0.4, 0.4, 1.0]), Color::TRANSPARENT, Some(16.0));
68 }examples/ecs.rs (lines 239-246)
182 fn render(&mut self, engine: &mut jEngine) {
183 // Register the bitmap font once so that `ui_text` can render glyphs.
184 if !self.font_loaded {
185 if let Ok(font) = Font::from_mtsdf_json(DEFAULT_FONT_METADATA) {
186 engine.ui.text.set_font(font);
187 }
188 self.font_loaded = true;
189 }
190
191 engine.clear();
192
193 let gw = engine.grid_width();
194 let gh = engine.grid_height();
195
196 // Checkerboard background — makes individual tile positions easy to see.
197 for y in 0..gh {
198 for x in 0..gw {
199 let shade = if (x + y) % 2 == 0 {
200 Color([0.06, 0.06, 0.07, 1.0])
201 } else {
202 Color([0.04, 0.04, 0.05, 1.0])
203 };
204 engine.set_background(x, y, shade);
205 }
206 }
207
208 // Draw every entity that has a Position and a Renderable.
209 //
210 // `query_multi` yields `(Entity, (&Position, &Renderable))`. We can
211 // also call `world.get::<Health>(entity)` inside the loop because both
212 // borrows are shared (`&`) and thus non-conflicting.
213 for (entity, (pos, rend)) in self.world.query_multi::<(Position, Renderable)>() {
214 // Dim the glyph proportionally to remaining health — pure data lookup.
215 let frac = self
216 .world
217 .get::<Health>(entity)
218 .map(|h| h.current as f32 / h.max as f32)
219 .unwrap_or(1.0)
220 .clamp(0.0, 1.0);
221
222 let dimmed = Color([
223 rend.color.0[0] * frac,
224 rend.color.0[1] * frac,
225 rend.color.0[2] * frac,
226 1.0,
227 ]);
228
229 // Solid black behind each entity so it stands out from the checker.
230 engine.set_background(pos.x, pos.y, Color::BLACK);
231 engine.set_foreground(pos.x, pos.y, rend.glyph, dimmed);
232 }
233
234 // ── UI overlay (always drawn on top of the world) ─────────────────────
235 let count = self.world.query::<Position>().count();
236 let sw = gw as f32 * engine.tile_width() as f32;
237
238 Panel::new(0.0, 0.0, sw, 30.0).with_color(Color([0.0, 0.0, 0.0, 0.85])).draw(engine);
239 engine.ui.ui_text(
240 20.0,
241 8.0,
242 &format!(
243 "Entities: {count:<3} | [Space] spawn [D] damage all [Esc] quit"
244 ),
245 Color::WHITE,
246 Color::TRANSPARENT, Some(14.0));
247 }Additional examples can be found in:
pub fn ui_text_wrapped( &mut self, x: f32, y: f32, max_w: f32, max_h: f32, text: &str, fg: Color, bg: Color, font_size: Option<f32>, )
Sourcepub fn ui_hline(&mut self, x: f32, y: f32, w: f32, thickness: f32, color: Color)
pub fn ui_hline(&mut self, x: f32, y: f32, w: f32, thickness: f32, color: Color)
Examples found in repository?
examples/ui_styling.rs (line 129)
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 }Sourcepub fn ui_vline(&mut self, x: f32, y: f32, h: f32, thickness: f32, color: Color)
pub fn ui_vline(&mut self, x: f32, y: f32, h: f32, thickness: f32, color: Color)
Examples found in repository?
examples/ui_styling.rs (line 120)
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 }pub fn ui_progress_bar( &mut self, x: f32, y: f32, w: f32, h: f32, pct: f32, filled: Color, empty: Color, )
Sourcepub fn debug_box(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color)
pub fn debug_box(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color)
Examples found in repository?
examples/particles.rs (line 354)
337 fn debug_render(&mut self, engine: &mut jEngine) -> Option<Box<dyn jengine::ui::widgets::Widget>> {
338 use jengine::ui::widgets::{VStack, TextWidget};
339 use jengine::ui::Alignment;
340
341 let fs = 12.0;
342 let tw = engine.tile_width() as f32;
343 let th = engine.tile_height() as f32;
344
345 // ── 1. World-Space Highlight & Standalone Hover Popup ──
346 let [mx, my] = engine.input.mouse_pos;
347 let [wx, wy] = engine.screen_to_world(mx, my);
348 let gx = (wx / tw).floor();
349 let gy = (wy / th).floor();
350
351 // Highlight tile
352 let [s_x, s_y] = engine.world_to_screen(gx * tw, gy * th);
353 let z = engine.camera_zoom();
354 engine.ui.debug_box(s_x, s_y, tw * z, th * z, Color::CYAN);
355
356 // List entities at hovered tile (pos is in pixel space; gx/gy are tile indices).
357 let mut hovered_entities = Vec::new();
358 for (entity, pos) in self.world.query::<Position>() {
359 if pos.x >= gx * tw && pos.x < (gx + 1.0) * tw
360 && pos.y >= gy * th && pos.y < (gy + 1.0) * th {
361 let components = self.world.components_for_entity(entity);
362 let short_names: Vec<String> = components.iter()
363 .map(|&full_name| full_name.split("::").last().unwrap_or(full_name).to_string())
364 .collect();
365 hovered_entities.push((entity, short_names));
366 }
367 }
368
369 // Draw standalone popup next to cursor using a styled VStack
370 if !hovered_entities.is_empty() {
371 let h_x = mx + 15.0;
372 let h_y = my + 15.0;
373 let panel_w = 220.0;
374
375 let mut popup = VStack::new(Alignment::Start)
376 .with_padding(Padding::all(5.0))
377 .with_bg(Color([0.05, 0.05, 0.1, 0.8]))
378 .with_border(BorderStyle::Thin, Color::CYAN);
379
380 for (entity, comps) in hovered_entities {
381 popup = popup.add(TextWidget {
382 text: format!("E{}: {}", entity.id(), comps.join(", ")),
383 size: Some(fs),
384 color: Some(Color::WHITE),
385 });
386 }
387 Widget::draw(&mut popup, engine, h_x, h_y, panel_w, None);
388 }
389
390 // ── 2. Build Draggable Content ──
391 let total_entities = self.world.entity_count();
392 let mut stack = VStack::new(Alignment::Start).with_spacing(2.0);
393
394 stack = stack.add(TextWidget {
395 text: format!("Entities (Total): {}", total_entities),
396 size: Some(fs),
397 color: Some(Color::DARK_GRAY),
398 });
399
400 stack = stack.add(TextWidget {
401 text: "--- ENTITY LIST ---".to_string(),
402 size: Some(fs),
403 color: Some(Color::CYAN),
404 });
405
406 // Fetch first 100 entities to ensure we have enough content to scroll
407 let entities = self.world.entities_debug_info_paginated(0, 100);
408 for (entity, components) in entities {
409 let mut short_comps = Vec::new();
410 for c in components {
411 short_comps.push(c.split("::").last().unwrap_or(c));
412 }
413 stack = stack.add(TextWidget {
414 text: format!("E{}: {}", entity.id(), short_comps.join(", ")),
415 size: Some(fs),
416 color: Some(Color::BLACK),
417 });
418 }
419
420 if total_entities > 10 {
421 stack = stack.add(TextWidget {
422 text: "... and more".to_string(),
423 size: Some(fs),
424 color: Some(Color([0.4, 0.4, 0.4, 1.0])),
425 });
426 }
427
428 Some(Box::new(stack))
429 }Auto Trait Implementations§
impl Freeze for UI
impl RefUnwindSafe for UI
impl Send for UI
impl Sync for UI
impl Unpin for UI
impl UnsafeUnpin for UI
impl UnwindSafe for UI
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.