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:
sergio
2026-05-21 00:55:33 +00:00
parent 2dd8ff139e
commit 4719f7c9f9
12 changed files with 230 additions and 22 deletions
+13 -4
View File
@@ -82,6 +82,8 @@ struct ManagedWindow {
/// Esquina superior-izquierda en píxeles, según el Cerebro.
loc: (i32, i32),
visible: bool,
/// `true` si flota: se compone por encima de las teseladas.
floating: bool,
}
/// El estado global del compositor.
@@ -170,10 +172,11 @@ impl App {
/// Ejecuta una operación concreta sobre las superficies reales.
fn exec_op(&mut self, op: BodyOp) {
match op {
BodyOp::Configure { id, rect, visible } => {
BodyOp::Configure { id, rect, visible, floating } => {
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
w.loc = (rect.x, rect.y);
w.visible = visible;
w.floating = floating;
w.toplevel.with_pending_state(|s| {
s.size = Some((rect.w.max(1), rect.h.max(1)).into());
});
@@ -244,6 +247,7 @@ impl App {
surface,
loc: (0, 0),
visible: false,
floating: false,
});
let ev = self.body.open_surface(id, app_id, title);
self.brain_feed(ev);
@@ -684,10 +688,15 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(size);
{
let (renderer, mut framebuffer) = backend.bind().unwrap();
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = state
.windows
// Orden de pintado: la lista de elementos va front-to-back
// (índice 0 = encima), así que las flotantes —que deben
// quedar sobre las teseladas— se ordenan primero. `sort_by_key`
// es estable: dentro de cada grupo se respeta el orden de apertura.
let mut shown: Vec<&ManagedWindow> =
state.windows.iter().filter(|w| w.visible).collect();
shown.sort_by_key(|w| !w.floating);
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = shown
.iter()
.filter(|w| w.visible)
.flat_map(|w| {
render_elements_from_surface_tree(
renderer,
+1
View File
@@ -120,6 +120,7 @@ Acciones de mirada-ctl:
move-forward adelanta la ventana enfocada en el teselado
move-backward la atrasa
close-focused cierra la ventana enfocada
toggle-float alterna flotante / teselada la enfocada
cycle-layout pasa al siguiente modo de teselado
layout <modo> master-stack · centered-master · spiral
grid · columns · rows · monocle
+11 -5
View File
@@ -21,10 +21,10 @@
//! ```text
//! n abre una ventana tab / espacio cicla layout
//! w cierra la enfocada t m g c r d s layout directo
//! j / k foco siguiente/anterior h / l área maestra /+
//! Shift+j / k mueve la enfocada , / . nmaster /+
//! Enter promueve a maestra 1..9 ir a escritorio
//! Ctrl+1..9 enviar a escritorio
//! f flota / tesela h / l área maestra /+
//! j / k foco siguiente/anterior , / . nmaster /+
//! Shift+j / k mueve la enfocada 1..9 ir a escritorio
//! Enter promueve a maestra Ctrl+1..9 enviar a escritorio
//! ```
//!
//! Los pips de escritorio y las ventanas del lienzo son **clicables**, y
@@ -278,6 +278,7 @@ impl Mirada {
match ks.key.as_str() {
"n" if !connected => self.open_window(),
"w" => self.act(DesktopAction::CloseFocused),
"f" => self.act(DesktopAction::ToggleFloat),
"j" if shift => self.act(DesktopAction::MoveForward),
"k" if shift => self.act(DesktopAction::MoveBackward),
"j" => self.act(DesktopAction::FocusNext),
@@ -440,6 +441,11 @@ impl Render for Mirada {
let tb_bg = if p.focused { theme.accent } else { theme.bg_row_hover };
let tb_fg = if p.focused { on_accent } else { theme.fg_muted };
let pid = p.id;
let kind_label = if p.floating {
"· ventana flotante ·"
} else {
"· superficie del Cuerpo ·"
};
canvas = canvas.child(
div()
@@ -486,7 +492,7 @@ impl Render for Mirada {
.gap(px(4.))
.text_color(theme.fg_disabled)
.child(SharedString::from(app_id))
.child("· superficie del Cuerpo ·"),
.child(kind_label),
),
);
}