From 751416252f9d80204795c4be1e12c0e59e95a3c2 Mon Sep 17 00:00:00 2001 From: sergio Date: Thu, 21 May 2026 04:10:32 +0000 Subject: [PATCH] =?UTF-8?q?feat(mirada):=20marco=20de=20ventana=20?= =?UTF-8?q?=E2=80=94=20distinguir=20y=20resaltar=20el=20foco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sin decoración, las ventanas se confundían entre sí. Ahora el backend DRM dibuja un marco fino alrededor de cada ventana: azul la que tiene el foco del teclado, gris las demás. - `ManagedWindow` gana `focused: bool` (lo fija `exec_op` al atender `BodyOp::Focus`/`Unfocus`) y `borders: [SolidColorBuffer; 4]` — un búfer por lado, cada uno con su `Id` estable para el seguimiento de daño; `SolidColorBuffer` sube su contador sólo si tamaño o color cambian, así un marco quieto no fuerza recomposición. - El enum `Frame` pasa de `Cursor` a `Solid`: una variante de color sólido que sirve para el cursor y para los marcos (dos variantes con el mismo tipo chocarían en el `From` que genera `render_elements!`). - `render()` en dos pasos: refresca los búferes (tamaño = contenido, color = foco) y luego arma los elementos. El marco va metido hacia adentro, sobre el borde de la superficie, así no pisa al vecino. Co-Authored-By: Claude Opus 4.7 --- crates/apps/mirada-compositor/README.md | 3 +- .../apps/mirada-compositor/src/drm_backend.rs | 69 +++++++++++++++++-- crates/apps/mirada-compositor/src/main.rs | 14 +++- crates/modules/mirada/SDD.md | 4 +- vamos.txt | 1 + 5 files changed, 81 insertions(+), 10 deletions(-) diff --git a/crates/apps/mirada-compositor/README.md b/crates/apps/mirada-compositor/README.md index 5091fe1..39a8d0d 100644 --- a/crates/apps/mirada-compositor/README.md +++ b/crates/apps/mirada-compositor/README.md @@ -43,7 +43,8 @@ Lleva teclado y ratón por `libinput`: el ratón mueve un cursor de software, el foco sigue al puntero y los clics y la rueda llegan a la ventana que tienes debajo. **`Super`+arrastre** con el botón izquierdo mueve una ventana, con el derecho la redimensiona — al arrastrarla, la -ventana pasa a flotar. +ventana pasa a flotar. Cada ventana lleva un marco fino: azul la que +tiene el foco, gris las demás. - `MIRADA_STARTUP=` — lanza una app al arrancar (`MIRADA_STARTUP=foot`). - `MIRADA_DRM_TIMEOUT=` — cierra el compositor solo tras N segundos diff --git a/crates/apps/mirada-compositor/src/drm_backend.rs b/crates/apps/mirada-compositor/src/drm_backend.rs index 2c6c964..cf94d9c 100644 --- a/crates/apps/mirada-compositor/src/drm_backend.rs +++ b/crates/apps/mirada-compositor/src/drm_backend.rs @@ -65,11 +65,11 @@ type Compositor = DrmCompositor, GbmFramebufferExporter, (), DrmDeviceFd>; render_elements! { - /// Lo que el backend DRM compone en un cuadro: las superficies de los - /// clientes y, encima de todo, el cursor de software. + /// Lo que el backend DRM compone en un cuadro: superficies de cliente + /// y rectángulos de color sólido (el cursor y los marcos de ventana). Frame where R: ImportAll; Window = WaylandSurfaceRenderElement, - Cursor = SolidColorRenderElement, + Solid = SolidColorRenderElement, } /// Color de fondo del escritorio cuando no hay nada que lo tape. @@ -84,6 +84,30 @@ const CURSOR_COLOR: [f32; 4] = [0.95, 0.95, 0.97, 1.0]; /// Lado mínimo de una ventana al redimensionarla con el ratón. const MIN_WINDOW: i32 = 120; +/// Grosor del marco de una ventana, en píxeles. +const BORDER_WIDTH: i32 = 2; + +/// Color del marco de la ventana enfocada — un azul que resalta. +const BORDER_FOCUS: [f32; 4] = [0.36, 0.56, 0.92, 1.0]; + +/// Color del marco de las ventanas sin foco — gris discreto. +const BORDER_NORMAL: [f32; 4] = [0.22, 0.22, 0.27, 1.0]; + +/// Los 4 rectángulos `(x, y, w, h)` del marco de una ventana cuyo +/// contenido ocupa `(sx, sy, sw, sh)`. El marco va *hacia adentro* (pisa +/// el borde de la superficie), así nunca se solapa con el de la ventana +/// vecina: arriba, abajo, izquierda, derecha. +fn border_rects(sx: i32, sy: i32, sw: i32, sh: i32) -> [(i32, i32, i32, i32); 4] { + let bw = BORDER_WIDTH; + let side_h = (sh - 2 * bw).max(0); + [ + (sx, sy, sw, bw), + (sx, sy + sh - bw, sw, bw), + (sx, sy + bw, bw, side_h), + (sx + sw - bw, sy + bw, bw, side_h), + ] +} + /// Códigos de botón de ``. const BTN_LEFT: u32 = 0x110; const BTN_RIGHT: u32 = 0x111; @@ -118,8 +142,26 @@ impl DrmState { if self.pending_flip { return; // aún esperamos el VBlank del cuadro anterior } - // Elementos a pintar — lista front-to-back (índice 0 = encima): - // primero el cursor, luego las flotantes, luego las teseladas. + + // Paso 1 · refresca los búferes del marco de cada ventana — su + // tamaño (sigue al contenido) y su color (según el foco). Cada + // `SolidColorBuffer` sube su contador de daño sólo si algo cambió. + for w in &mut self.app.windows { + if !w.visible { + continue; + } + let (x, y) = crate::render_loc(w); + let (sw, sh) = crate::surface_px_size(w).unwrap_or(w.size); + let color = if w.focused { BORDER_FOCUS } else { BORDER_NORMAL }; + let rects = border_rects(x, y, sw, sh); + for (buf, (_, _, bw, bh)) in w.borders.iter_mut().zip(rects) { + buf.update((bw, bh), color); + } + } + + // Paso 2 · arma los elementos — lista front-to-back (índice 0 = + // encima): el cursor, y por cada ventana su marco sobre su + // superficie. Las flotantes van antes que las teseladas. let elements: Vec> = { let mut out: Vec> = Vec::new(); @@ -128,7 +170,7 @@ impl DrmState { Point::::from((cx.round() as i32, cy.round() as i32)), Size::::from((CURSOR_SIZE, CURSOR_SIZE)), ); - out.push(Frame::Cursor(SolidColorRenderElement::new( + out.push(Frame::Solid(SolidColorRenderElement::new( self.cursor_id.clone(), cursor_rect, CommitCounter::default(), @@ -139,10 +181,23 @@ impl DrmState { let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect(); shown.sort_by_key(|w| !w.floating); for w in &shown { + let (x, y) = crate::render_loc(w); + let (sw, sh) = crate::surface_px_size(w).unwrap_or(w.size); + let rects = border_rects(x, y, sw, sh); + // El marco, encima de la propia superficie de la ventana. + for (buf, (bx, by, _, _)) in w.borders.iter().zip(rects) { + out.push(Frame::Solid(SolidColorRenderElement::from_buffer( + buf, + (bx, by), + 1.0, + 1.0, + Kind::Unspecified, + ))); + } for el in render_elements_from_surface_tree( &mut self.renderer, &w.surface, - crate::render_loc(w), + (x, y), 1.0, 1.0, Kind::Unspecified, diff --git a/crates/apps/mirada-compositor/src/main.rs b/crates/apps/mirada-compositor/src/main.rs index 4525152..339b09b 100644 --- a/crates/apps/mirada-compositor/src/main.rs +++ b/crates/apps/mirada-compositor/src/main.rs @@ -24,6 +24,7 @@ use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent}; use smithay::backend::renderer::element::surface::{ render_elements_from_surface_tree, WaylandSurfaceRenderElement, }; +use smithay::backend::renderer::element::solid::SolidColorBuffer; use smithay::backend::renderer::element::Kind; use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::utils::{ @@ -99,6 +100,11 @@ struct ManagedWindow { visible: bool, /// `true` si flota: se compone por encima de las teseladas. floating: bool, + /// `true` si tiene el foco del teclado — pinta el marco resaltado. + focused: bool, + /// Búferes de los 4 lados del marco (arriba, abajo, izq., der.) — + /// cada uno con su `Id` estable para el seguimiento de daño. + borders: [SolidColorBuffer; 4], } /// Un arrastre de ratón en curso: mueve o redimensiona una ventana. @@ -232,8 +238,9 @@ impl App { } BodyOp::Focus(id) => { let mut target = None; - for w in &self.windows { + for w in &mut self.windows { let active = w.id == id; + w.focused = active; if active { target = Some(w.surface.clone()); } @@ -251,6 +258,9 @@ impl App { } } BodyOp::Unfocus => { + for w in &mut self.windows { + w.focused = false; + } if let Some(kb) = self.keyboard.clone() { kb.set_focus(self, Option::::None, SERIAL_COUNTER.next_serial()); } @@ -297,6 +307,8 @@ impl App { size: (0, 0), visible: false, floating: false, + focused: false, + borders: std::array::from_fn(|_| SolidColorBuffer::default()), }); let ev = self.body.open_surface(id, app_id, title); self.brain_feed(ev); diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md index f853b6b..d9a0574 100644 --- a/crates/modules/mirada/SDD.md +++ b/crates/modules/mirada/SDD.md @@ -210,7 +210,9 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET` (`BodyEvent::PointerEntered`) y clics y rueda van a la ventana debajo. `Super`+arrastre mueve/redimensiona: el Cuerpo calcula el rectángulo y emite `BodyEvent::WindowFloatTo { id, rect }`; el Cerebro hace flotar - la ventana ahí (`Workspace::set_floating`). + la ventana ahí (`Workspace::set_floating`). Cada ventana lleva un marco + de 2 px (4 `SolidColorBuffer` por ventana, `Id` estable): azul si tiene + el foco, gris si no. **Pendiente** — refinamientos del Cuerpo: diff --git a/vamos.txt b/vamos.txt index df19b11..d128f1f 100644 --- a/vamos.txt +++ b/vamos.txt @@ -1002,6 +1002,7 @@ cargo run -p mirada-compositor -- --drm # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar) Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link. En --drm el ratón pinta un cursor de software, el foco sigue al puntero y clics/rueda van a la ventana debajo. + Cada ventana lleva un marco fino: azul la enfocada, gris las demás. Super+arrastre mueve la ventana (botón izq.) o la redimensiona (der.) — al arrastrarla pasa a flotar. Fuerza xdg-decoration ServerSide y no dibuja marco: las ventanas teseladas van sin barra de título. Lanzar programas: acción spawn: del keymap (Super+Shift+Return → spawn:foot por defecto).