Phase 6-07a: Fensterlokale Control-Menüs und Windows-Testkorrekturen
Control-Menüs öffnen für alle Fensterarten am [≡]-Symbol des aktiven Fensters: eigener Auswahlzustand statt Kopplung an die Menüleiste, bevorzugt unter der Titelzeile, bei Platzmangel nach oben aufgeklappt, am rechten Rand nur so weit nach links wie nötig. Regressionstest über TestBackend für Code-, Projekt-, Output-, Immediate-, Debug- und Hilfefenster, maximiert, minimiert, Rand und Maus. Nebenbefunde der Windows-Testausführung: Temporärdatei vor sync_all schreibend öffnen (Zugriff verweigert), relative Projektverweise immer mit / schreiben, zeilenendenneutrale Vergleiche in Referenzmatrix-, Kompatibilitäts- und MAK-Beispieltests, DriveListBox-Zeichenbild plattformneutral. Korpus-Sollausgaben per .gitattributes auf LF. Specs ide-oberflaeche und ide-projekte synchronisiert, Change archiviert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -302,7 +302,7 @@ fn windows_restore_geometry_and_resize_without_document_loss() {
|
||||
assert!(r.right() <= 80 && r.bottom() <= 24);
|
||||
}
|
||||
key(&mut app, K::Char('-'), M::ALT);
|
||||
assert!(app.control_menu);
|
||||
assert!(app.control_menu.is_some());
|
||||
plain(&mut app, K::Esc);
|
||||
key(&mut app, K::F(4), M::CONTROL);
|
||||
assert_eq!(app.windows.len(), 2);
|
||||
@@ -1180,3 +1180,182 @@ fn theme_windows_erase_desktop_and_underlying_windows_on_every_redraw() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sucht die Zelle, an der `text` in der Ausgabe beginnt.
|
||||
fn find_text(b: &ratatui::buffer::Buffer, text: &str) -> Option<(u16, u16)> {
|
||||
let chars: Vec<String> = text.chars().map(|c| c.to_string()).collect();
|
||||
for y in 0..b.area.height {
|
||||
for x in 0..b.area.width.saturating_sub(chars.len() as u16) {
|
||||
if chars
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, c)| b[(x + i as u16, y)].symbol() == c)
|
||||
{
|
||||
return Some((x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Öffnet das Control-Menü per Alt+Minus und prüft die Anheftregel: linke Spalte gleich
|
||||
/// Symbolspalte (oder so weit links, dass es passt und das Symbol überdeckt), Rahmen grenzt
|
||||
/// unten oder oben an die Titelzeile, vollständig in der Arbeitsfläche, kein Menüleistentitel
|
||||
/// hervorgehoben. Liefert das Rechteck des Aufklappbereichs.
|
||||
fn assert_control_menu_attached(app: &mut App, context: &str) -> ratatui::layout::Rect {
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
let area = app.area();
|
||||
key(app, K::Char('-'), M::ALT);
|
||||
assert!(app.control_menu.is_some(), "{context}: Control-Menü offen");
|
||||
assert_eq!(
|
||||
app.menu, None,
|
||||
"{context}: Menüleiste darf nicht ausgewählt sein"
|
||||
);
|
||||
let (_, b) = draw(app);
|
||||
assert_eq!(
|
||||
(b[(1, 0)].fg, b[(1, 0)].bg),
|
||||
(tb_ide::render::dos(0), tb_ide::render::dos(7)),
|
||||
"{context}: „File“ darf nicht hervorgehoben sein"
|
||||
);
|
||||
let (rx, ry) = find_text(&b, "Restore").unwrap_or_else(|| panic!("{context}: Restore"));
|
||||
let (_, cy) = find_text(&b, "Close").unwrap_or_else(|| panic!("{context}: Close"));
|
||||
let (x, top, bottom) = (rx - 1, ry - 1, cy + 1);
|
||||
let mut right = x + 1;
|
||||
while right < b.area.width && b[(right, top)].symbol() != "┐" {
|
||||
right += 1;
|
||||
}
|
||||
let popup = ratatui::layout::Rect::new(x, top, right + 1 - x, bottom + 1 - top);
|
||||
assert_eq!(b[(x, top)].symbol(), "┌", "{context}: linke obere Ecke");
|
||||
assert_eq!(b[(x, bottom)].symbol(), "└", "{context}: linke untere Ecke");
|
||||
assert!(
|
||||
popup.y >= area.y && popup.bottom() <= area.bottom() && popup.right() <= area.right(),
|
||||
"{context}: Popup {popup:?} außerhalb der Arbeitsfläche {area:?}"
|
||||
);
|
||||
assert!(
|
||||
popup.x <= r.x && r.x < popup.right(),
|
||||
"{context}: Popup {popup:?} überdeckt die Symbolspalte von {r:?} nicht"
|
||||
);
|
||||
if r.x + popup.width <= area.right() {
|
||||
assert_eq!(popup.x, r.x, "{context}: linke Spalte gleich Symbolspalte");
|
||||
} else {
|
||||
assert_eq!(
|
||||
popup.right(),
|
||||
area.right(),
|
||||
"{context}: nur so weit links wie nötig"
|
||||
);
|
||||
}
|
||||
if popup.height <= area.bottom() - (r.y + 1) {
|
||||
assert_eq!(popup.y, r.y + 1, "{context}: direkt unter der Titelzeile");
|
||||
} else {
|
||||
assert_eq!(
|
||||
popup.bottom(),
|
||||
r.y,
|
||||
"{context}: nach oben aufgeklappt bis an die Titelzeile"
|
||||
);
|
||||
}
|
||||
popup
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_menus_attach_to_the_symbol_of_every_window() {
|
||||
let t = Temp::new();
|
||||
let mut app = t.app();
|
||||
let area = app.area();
|
||||
let code = app.active;
|
||||
let p = assert_control_menu_attached(&mut app, "Code");
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
assert_eq!((p.x, p.y), (r.x, r.y + 1));
|
||||
plain(&mut app, K::Esc);
|
||||
assert!(app.control_menu.is_none());
|
||||
assert_eq!(app.active, code);
|
||||
|
||||
plain(&mut app, K::F(6));
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Project);
|
||||
assert_control_menu_attached(&mut app, "Project");
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
menu(&mut app, Command::Output);
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Output);
|
||||
assert_control_menu_attached(&mut app, "Output");
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
menu(&mut app, Command::Immediate);
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Immediate);
|
||||
assert_control_menu_attached(&mut app, "Immediate");
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
menu(&mut app, Command::Debug);
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Debug);
|
||||
assert_control_menu_attached(&mut app, "Debug");
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
menu(&mut app, Command::HelpWindow);
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Help);
|
||||
assert_control_menu_attached(&mut app, "Help");
|
||||
plain(&mut app, K::Esc);
|
||||
assert_eq!(app.active_window().unwrap().kind, WindowKind::Help);
|
||||
menu(&mut app, Command::Immediate);
|
||||
|
||||
key(&mut app, K::F(10), M::CONTROL);
|
||||
let p = assert_control_menu_attached(&mut app, "Maximized");
|
||||
assert_eq!((p.x, p.y), (area.x, area.y + 1));
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
key(&mut app, K::F(9), M::CONTROL);
|
||||
assert_eq!(app.active_window().unwrap().state, WindowState::Minimized);
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
let p = assert_control_menu_attached(&mut app, "Minimized");
|
||||
assert_eq!(
|
||||
(p.x, p.bottom()),
|
||||
(r.x, r.y),
|
||||
"minimiertes Fenster klappt nach oben"
|
||||
);
|
||||
plain(&mut app, K::Esc);
|
||||
key(&mut app, K::F(5), M::CONTROL);
|
||||
|
||||
key(&mut app, K::F(8), M::CONTROL);
|
||||
for _ in 0..20 {
|
||||
key(&mut app, K::Left, M::CONTROL);
|
||||
}
|
||||
plain(&mut app, K::Enter);
|
||||
key(&mut app, K::F(7), M::CONTROL);
|
||||
for _ in 0..30 {
|
||||
key(&mut app, K::Right, M::CONTROL);
|
||||
}
|
||||
plain(&mut app, K::Enter);
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
assert_eq!(r.right(), area.right());
|
||||
let p = assert_control_menu_attached(&mut app, "Rechter Rand");
|
||||
assert!(p.x < r.x && p.right() == area.right());
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
// Normales Fenster am unteren Rand: zu wenig Platz unter der Titelzeile, klappt nach oben.
|
||||
key(&mut app, K::F(8), M::CONTROL);
|
||||
for _ in 0..20 {
|
||||
key(&mut app, K::Up, M::CONTROL);
|
||||
}
|
||||
plain(&mut app, K::Enter);
|
||||
key(&mut app, K::F(7), M::CONTROL);
|
||||
for _ in 0..30 {
|
||||
key(&mut app, K::Down, M::CONTROL);
|
||||
}
|
||||
plain(&mut app, K::Enter);
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
assert_eq!(r.bottom(), area.bottom());
|
||||
let p = assert_control_menu_attached(&mut app, "Unterer Rand");
|
||||
assert_eq!(p.bottom(), r.y, "klappt nach oben");
|
||||
plain(&mut app, K::Esc);
|
||||
|
||||
// Maus: Klick auf [≡] öffnet identisch, Klick auf einen Eintrag führt ihn aus.
|
||||
key(&mut app, K::F(5), M::CONTROL);
|
||||
draw(&mut app);
|
||||
let r = app.rect(app.active_window().unwrap());
|
||||
click(&mut app, r.x, r.y);
|
||||
assert!(app.control_menu.is_some());
|
||||
assert_eq!(app.menu, None);
|
||||
let (_, b) = draw(&mut app);
|
||||
let (mx, my) = find_text(&b, "Minimize").unwrap();
|
||||
click(&mut app, mx, my);
|
||||
assert!(app.control_menu.is_none());
|
||||
assert_eq!(app.active_window().unwrap().state, WindowState::Minimized);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user