gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198630 Sutichaykuna

feat(mirada): marco de ventana — distinguir y resaltar el foco

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 <noreply@anthropic.com>

751416252f9d80204795c4be1e12c0e59e95a3c2 · ruwaq sergio · (5 killa ñawpaq) · tayta fb3091d99 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 8

crates/apps/mirada-compositor/src/drm_backend.rs

crates/apps/mirada-compositor/src/main.rs

5 qillqakuna · +81 −10

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=<cmd>` — lanza una app al arrancar (`MIRADA_STARTUP=foot`).
 - `MIRADA_DRM_TIMEOUT=<s>` — 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<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), 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<R> where R: ImportAll;
     Window = WaylandSurfaceRenderElement<R>,
-    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 `<linux/input-event-codes.h>`.
 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<Frame<GlesRenderer>> = {
             let mut out: Vec<Frame<GlesRenderer>> = Vec::new();
 
@@ -128,7 +170,7 @@ impl DrmState {
                 Point::<i32, Physical>::from((cx.round() as i32, cy.round() as i32)),
                 Size::<i32, Physical>::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::<WlSurface>::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:<comando> del keymap (Super+Shift+Return → spawn:foot por defecto).