Skip to main content

UI

Struct UI 

Source
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: TextLayer

Implementations§

Source§

impl UI

Source

pub fn new(tile_w: u32, tile_h: u32) -> Self

Source

pub fn clear(&mut self)

Source

pub fn push_scissor(&mut self, rect: Rect)

Source

pub fn pop_scissor(&mut self)

Source

pub fn set_layer(&mut self, layer: UILayer)

Source

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    }
Source

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    }
Source

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, )

Source

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
Hide additional examples
examples/perf_bench.rs (line 78)
77    fn render(&mut self, engine: &mut jEngine) {
78        engine.ui.ui_rect(10.0, 10.0, 300.0, 100.0, Color([0.0, 0.0, 0.0, 0.8]));
79        let stats = format!("Entities: {}", self.entity_count);
80        let fps_str = format!("FPS: {:.1}", self.fps);
81        let mode = if self.auto_mode { "AUTO (Space to toggle)" } else { "MANUAL (Hold W to spawn)" };
82        
83        engine.ui.ui_text(20.0, 20.0, "PERFORMANCE BENCHMARK", Color::CYAN, Color::TRANSPARENT, None);
84        engine.ui.ui_text(20.0, 40.0, &stats, Color::WHITE, Color::TRANSPARENT, None);
85        engine.ui.ui_text(20.0, 60.0, &fps_str, Color::YELLOW, Color::TRANSPARENT, None);
86        engine.ui.ui_text(20.0, 80.0, mode, Color::GRAY, Color::TRANSPARENT, None);
87    }
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    }
Source

pub fn ui_box( &mut self, x: f32, y: f32, w: f32, h: f32, style: BorderStyle, fg: Color, bg: Color, )

Source

pub fn ui_pattern( &mut self, x: f32, y: f32, w: f32, h: f32, color: Color, scale: f32, )

Source

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
Hide additional examples
examples/perf_bench.rs (line 83)
77    fn render(&mut self, engine: &mut jEngine) {
78        engine.ui.ui_rect(10.0, 10.0, 300.0, 100.0, Color([0.0, 0.0, 0.0, 0.8]));
79        let stats = format!("Entities: {}", self.entity_count);
80        let fps_str = format!("FPS: {:.1}", self.fps);
81        let mode = if self.auto_mode { "AUTO (Space to toggle)" } else { "MANUAL (Hold W to spawn)" };
82        
83        engine.ui.ui_text(20.0, 20.0, "PERFORMANCE BENCHMARK", Color::CYAN, Color::TRANSPARENT, None);
84        engine.ui.ui_text(20.0, 40.0, &stats, Color::WHITE, Color::TRANSPARENT, None);
85        engine.ui.ui_text(20.0, 60.0, &fps_str, Color::YELLOW, Color::TRANSPARENT, None);
86        engine.ui.ui_text(20.0, 80.0, mode, Color::GRAY, Color::TRANSPARENT, None);
87    }
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    }
Source

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>, )

Source

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    }
Source

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    }
Source

pub fn ui_progress_bar( &mut self, x: f32, y: f32, w: f32, h: f32, pct: f32, filled: Color, empty: Color, )

Source

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

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>

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)

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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,