feat(mirada): nmaster, promover a maestra y smart gaps (estilo dwm)

Tanda de funciones de tiling WM, toda pura (mirada-layout/brain), sin
tocar el protocolo:

- nmaster: LayoutParams.master_count — cuántas ventanas van en el área
  maestra. MasterStack y CenteredMaster apilan N maestras; sin pila, las
  maestras llenan la pantalla. Acciones inc-master/dec-master (Super+,
  Super+.), acotadas 1..9.
- Promover a maestra: Workspace::promote_focused lleva la ventana
  enfocada al puesto 0. Acción promote-to-master (Super+Return).
- Smart gaps: una sola ventana se tesela a sangre, sin margen.

combo_string del compositor canoniza ahora teclas con nombre (Return,
Tab, F5, flechas…) vía xkb::keysym_get_name, no sólo caracteres
imprimibles — sin eso Super+Return no sería un atajo expresable.

Cableado en keymap por defecto, HUD de mirada y mirada-ctl. Verificado
end-to-end con headless-ctl. mirada-layout 26->30, mirada-brain 39->41.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 00:45:47 +00:00
parent 8821d34bd5
commit 2dd8ff139e
11 changed files with 228 additions and 42 deletions
@@ -190,6 +190,12 @@ impl Desktop {
}
DesktopAction::GrowMaster => self.nudge_master(0.05),
DesktopAction::ShrinkMaster => self.nudge_master(-0.05),
DesktopAction::IncMaster => self.nudge_master_count(1),
DesktopAction::DecMaster => self.nudge_master_count(-1),
DesktopAction::PromoteToMaster => {
self.workspaces[self.active].promote_focused();
self.relayout()
}
DesktopAction::SwitchWorkspace(n) => {
if n < self.workspaces.len() && n != self.active {
self.active = n;
@@ -224,6 +230,14 @@ impl Desktop {
self.relayout()
}
/// Ajusta `nmaster` del escritorio activo, acotado a `1..=9`.
fn nudge_master_count(&mut self, delta: i32) -> Vec<BrainCommand> {
let ws = &mut self.workspaces[self.active];
let n = (ws.params().master_count as i32 + delta).clamp(1, 9) as usize;
ws.set_master_count(n);
self.relayout()
}
/// Recalcula la geometría del escritorio activo y la empaqueta en un
/// [`BrainCommand::Place`]. Sin salida conectada, no hay nada que
/// colocar.
@@ -468,6 +482,32 @@ mod tests {
assert!((d.active_workspace().params().master_ratio - r0).abs() < 1e-6);
}
#[test]
fn inc_and_dec_master_adjust_nmaster() {
let mut d = desktop_with_screen();
for id in [1, 2, 3] {
open(&mut d, id);
}
assert_eq!(d.active_workspace().params().master_count, 1);
d.apply(DesktopAction::IncMaster);
assert_eq!(d.active_workspace().params().master_count, 2);
d.apply(DesktopAction::DecMaster);
d.apply(DesktopAction::DecMaster); // no baja de 1
assert_eq!(d.active_workspace().params().master_count, 1);
}
#[test]
fn promote_to_master_brings_the_focused_window_to_the_front() {
let mut d = desktop_with_screen();
for id in [1, 2, 3] {
open(&mut d, id);
}
d.apply(DesktopAction::FocusWindow(3));
d.apply(DesktopAction::PromoteToMaster);
assert_eq!(d.active_workspace().windows()[0], 3);
assert_eq!(d.focused_window(), Some(3));
}
#[test]
fn master_ratio_stays_within_bounds() {
let mut d = desktop_with_screen();