feat(mirada): ventanas flotantes — toggle-float
Una ventana puede salir del teselado y flotar: conserva su propio rectángulo y se compone por encima de las teseladas. - Workspace guarda las flotantes en un mapa aparte; layout() tesela sólo las no-flotantes y añade las flotantes al final (orden de pintado). set_floating / is_floating. - WindowPlacement y BodyOp::Configure llevan floating: bool. BodyState detecta el cambio de floating como cualquier otro reconfigure. - DesktopAction::ToggleFloat (Super+f): saca la enfocada a un rectángulo centrado al 60 % de la pantalla, o la devuelve al teselado. En Monocle, una flotante sigue visible. - mirada-compositor ordena las flotantes al frente de la lista front-to-back de elementos → se pintan encima. - HUD de mirada marca las flotantes; mirada-ctl toggle-float. Verificado end-to-end con headless-ctl. mirada-layout 30->32, mirada-protocol 9->10, mirada-body 13->14, mirada-brain 41->42. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,22 @@ impl Desktop {
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
DesktopAction::ToggleFloat => {
|
||||
let Some(id) = self.workspaces[self.active].focused() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let screen = self.screen();
|
||||
let ws = &mut self.workspaces[self.active];
|
||||
if ws.is_floating(id) {
|
||||
ws.set_floating(id, None);
|
||||
} else {
|
||||
let rect = screen
|
||||
.map(centered_float_rect)
|
||||
.unwrap_or_else(|| Rect::new(100, 100, 800, 600));
|
||||
ws.set_floating(id, Some(rect));
|
||||
}
|
||||
self.relayout()
|
||||
}
|
||||
DesktopAction::CycleLayout => {
|
||||
let next = self.workspaces[self.active].params().mode.next();
|
||||
self.workspaces[self.active].set_mode(next);
|
||||
@@ -306,6 +322,18 @@ impl Desktop {
|
||||
}
|
||||
}
|
||||
|
||||
/// El rectángulo flotante por defecto: 60 % de la pantalla, centrado.
|
||||
fn centered_float_rect(screen: Rect) -> Rect {
|
||||
let w = screen.w * 3 / 5;
|
||||
let h = screen.h * 3 / 5;
|
||||
Rect::new(
|
||||
screen.x + (screen.w - w) / 2,
|
||||
screen.y + (screen.h - h) / 2,
|
||||
w,
|
||||
h,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -409,6 +437,21 @@ mod tests {
|
||||
assert!(!w2.focused);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_float_marks_the_focused_window_and_floats_it_last() {
|
||||
let mut d = desktop_with_screen();
|
||||
open(&mut d, 1);
|
||||
open(&mut d, 2); // enfocada
|
||||
let cmds = d.apply(DesktopAction::ToggleFloat);
|
||||
let p = places(&cmds);
|
||||
assert!(p.iter().find(|x| x.id == 2).unwrap().floating);
|
||||
// La flotante va al final de la lista — orden de pintado.
|
||||
assert_eq!(p.last().unwrap().id, 2);
|
||||
// Alternar de nuevo la devuelve al teselado.
|
||||
let cmds = d.apply(DesktopAction::ToggleFloat);
|
||||
assert!(!places(&cmds).iter().find(|x| x.id == 2).unwrap().floating);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_a_screen_nothing_is_placed() {
|
||||
let mut d = Desktop::new();
|
||||
|
||||
Reference in New Issue
Block a user