Menu-Panel
This commit is contained in:
+93
-16
@@ -26,11 +26,11 @@ pub(crate) const CURSORS: usize = 3;
|
||||
pub(crate) const ORN: usize = 4;
|
||||
|
||||
/// Cursor-Indizes im 2×2-Raster (Pivot mittig je 16×16-Zelle).
|
||||
pub(crate) const CUR_MOUSE: usize = 0; // Maus, normal
|
||||
pub(crate) const CUR_HUD: usize = 2; // First-Person-Pointer, normal
|
||||
// Die „interagierbar"-Varianten (1, 3) kommen mit dem Hit-Testing in Stage 4.
|
||||
#[allow(dead_code)] pub(crate) const CUR_MOUSE_INTERACT: usize = 1;
|
||||
#[allow(dead_code)] pub(crate) const CUR_HUD_INTERACT: usize = 3;
|
||||
pub(crate) const CUR_MOUSE: usize = 0; // Maus, normal
|
||||
pub(crate) const CUR_MOUSE_INTERACT: usize = 1; // Maus, über Klickziel
|
||||
pub(crate) const CUR_HUD: usize = 2; // First-Person-Pointer, normal
|
||||
// Die interagierbare HUD-Variante (3) kommt mit dem Welt-Raycast.
|
||||
#[allow(dead_code)] pub(crate) const CUR_HUD_INTERACT: usize = 3;
|
||||
|
||||
const CURSOR_PX: f32 = 16.0;
|
||||
const ORN_CORNER: f32 = 8.0; // Eckgröße im Ornament-Atlas (und im Panel)
|
||||
@@ -180,27 +180,86 @@ fn cursor_uv(idx: usize) -> [f32; 4] {
|
||||
[x0 / 32.0, y0 / 32.0, (x0 + CURSOR_PX) / 32.0, (y0 + CURSOR_PX) / 32.0]
|
||||
}
|
||||
|
||||
/// Ergebnis eines Frames: die Overlay-Geometrie und — falls der Cursor über
|
||||
/// einem Klickziel steht — dessen Aktion (Eingabe-String für `Session::exec`).
|
||||
pub(crate) struct Screen {
|
||||
pub(crate) ui: Ui,
|
||||
pub(crate) hover_action: Option<String>,
|
||||
}
|
||||
|
||||
/// Punkt in Rechteck (x, y, w, h)?
|
||||
fn in_rect(p: [f32; 2], r: [f32; 4]) -> bool {
|
||||
p[0] >= r[0] && p[0] < r[0] + r[2] && p[1] >= r[1] && p[1] < r[1] + r[3]
|
||||
}
|
||||
|
||||
/// Ein klickbarer Button: Fläche + zentriertes Label. Steht der Cursor
|
||||
/// darüber, wird er hervorgehoben und seine `action` in `hover` vermerkt
|
||||
/// (der Klick wirkt dann auf genau diese Aktion).
|
||||
fn button(
|
||||
ui: &mut Ui, font: &Font, rect: [f32; 4], label: &str, action: &str,
|
||||
cur: [f32; 2], hover: &mut Option<String>,
|
||||
) {
|
||||
let hot = in_rect(cur, rect);
|
||||
if hot { *hover = Some(action.to_string()); }
|
||||
let bg = if hot { [0.34, 0.22, 0.46, 1.0] } else { [0.15, 0.12, 0.22, 1.0] };
|
||||
ui.fill(rect, bg);
|
||||
let tx = rect[0] + (rect[2] - font.text_width(label)) * 0.5;
|
||||
let ty = rect[1] + (rect[3] - font.glyph_h) * 0.5;
|
||||
ui.text(font, tx.round(), ty.round(), label, [0.95, 0.95, 1.0, 1.0]);
|
||||
}
|
||||
|
||||
/// Das Overlay für den aktuellen Frame bauen.
|
||||
//
|
||||
// Stage 3: Demo-Panel mit Ornamentrahmen + Text, plus der Cursor. Ab Stage 4
|
||||
// verzweigt das hier nach `session.mode` (Dialog-Panel, Menü, HUD).
|
||||
pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, cur: &Cursor) -> Ui {
|
||||
// Stage 4: klickbare Demo-Buttons (nur wenn die Maus frei ist). Als Nächstes
|
||||
// verzweigt das hier nach `session.mode` (Dialog-Panel, Menü) und liefert die
|
||||
// jeweiligen Aktionen.
|
||||
pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, cur: &Cursor) -> Screen {
|
||||
let mut ui = Ui::default();
|
||||
let mut hover: Option<String> = None;
|
||||
let f = &fonts.cga; // CGA 8×8 ist der UI-Standard-Font.
|
||||
|
||||
// Demo-Panel (opak) mit Ornamentrahmen + beide Fonts zum Vergleich.
|
||||
ui.panel([8.0, 8.0, 216.0, 58.0], [0.06, 0.05, 0.10, 1.0], [0.85, 0.75, 0.95, 1.0]);
|
||||
ui.text(&fonts.ega, 16.0, 16.0, "EGA 8x14 — Schöne Grüße!", [0.9, 0.9, 1.0, 1.0]);
|
||||
ui.text(&fonts.cga, 16.0, 34.0, "CGA 8x8 — abcABC 0123 äöüß", [0.6, 1.0, 0.7, 1.0]);
|
||||
// Im Flycam-Modus (Maus gefangen) ist die freie Sicht wichtiger → kein
|
||||
// Panel; sonst die klickbare Demo. Hit-Tests laufen nur bei freier Maus.
|
||||
if !cur.grabbed {
|
||||
let demo = [
|
||||
("Hilfe", "help"),
|
||||
("KV anzeigen", "kv"),
|
||||
("Flag setzen", "signal set demo_flag true"),
|
||||
("Beenden", "quit"),
|
||||
];
|
||||
// Innenmaße aus Inhalt: der Inhalt sitzt um `inset` (Rahmendicke +
|
||||
// etwas Padding) vom Panelrand weg. Panel daraus zentriert auf der
|
||||
// internen Auflösung.
|
||||
let (bw, bh, gap) = (112.0, 14.0, 4.0);
|
||||
let inset = ORN_CORNER + 3.0; // 8px Ornamentrahmen + 3px Luft
|
||||
let title_h = f.glyph_h;
|
||||
let body_h = title_h + gap + demo.len() as f32 * bh + (demo.len() as f32 - 1.0) * gap;
|
||||
let pw = bw + 2.0 * inset;
|
||||
let ph = body_h + 2.0 * inset;
|
||||
let px = ((internal[0] - pw) * 0.5).round();
|
||||
let py = ((internal[1] - ph) * 0.5).round();
|
||||
|
||||
// Cursor zuletzt → liegt über allem. Gefangen (Flycam) → HUD-Pointer
|
||||
// mittig; sonst Maus-Cursor an der Mausposition.
|
||||
ui.panel([px, py, pw, ph], [0.06, 0.05, 0.10, 1.0], [0.85, 0.75, 0.95, 1.0]);
|
||||
ui.text(f, px + inset, py + inset, "Klick-Demo", [0.85, 0.8, 0.6, 1.0]);
|
||||
|
||||
let bx = px + inset;
|
||||
let mut by = py + inset + title_h + gap;
|
||||
for (label, action) in demo {
|
||||
button(&mut ui, f, [bx, by, bw, bh], label, action, cur.pos, &mut hover);
|
||||
by += bh + gap;
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor zuletzt → über allem. Gefangen → HUD-Pointer mittig; sonst
|
||||
// Maus-Cursor, „interagierbar" wenn er über einem Klickziel steht.
|
||||
if cur.grabbed {
|
||||
ui.cursor(CUR_HUD, [internal[0] * 0.5, internal[1] * 0.5], [1.0; 4]);
|
||||
} else {
|
||||
ui.cursor(CUR_MOUSE, cur.pos, [1.0; 4]);
|
||||
let idx = if hover.is_some() { CUR_MOUSE_INTERACT } else { CUR_MOUSE };
|
||||
ui.cursor(idx, cur.pos, [1.0; 4]);
|
||||
}
|
||||
|
||||
ui
|
||||
Screen { ui, hover_action: hover }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -235,6 +294,24 @@ mod tests {
|
||||
assert_eq!(&k.rgba[4..8], &[255, 255, 255, 0]); // schwarz → transparent
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn button_registers_hover_only_when_cursor_inside() {
|
||||
// Leerer 256×64-Atlas → CGA-Font mit lauter leeren Glyphen; text_width
|
||||
// bleibt endlich, Button funktioniert trotzdem.
|
||||
let atlas = Image { width: 256, height: 64, rgba: vec![0; 256 * 64 * 4] };
|
||||
let font = Font::cga(FONT_CGA, &atlas);
|
||||
let rect = [10.0, 10.0, 100.0, 20.0];
|
||||
|
||||
let mut ui = Ui::default();
|
||||
let mut hover = None;
|
||||
button(&mut ui, &font, rect, "X", "do_x", [50.0, 15.0], &mut hover);
|
||||
assert_eq!(hover.as_deref(), Some("do_x"));
|
||||
|
||||
let mut outside = None;
|
||||
button(&mut ui, &font, rect, "X", "do_x", [200.0, 15.0], &mut outside);
|
||||
assert!(outside.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_uv_picks_quadrant() {
|
||||
// idx 3 = (col 1, row 1) → rechte untere 16×16-Zelle.
|
||||
|
||||
Reference in New Issue
Block a user