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>
This commit is contained in:
@@ -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
|
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
|
ventana que tienes debajo. **`Super`+arrastre** con el botón izquierdo
|
||||||
mueve una ventana, con el derecho la redimensiona — al arrastrarla, la
|
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_STARTUP=<cmd>` — lanza una app al arrancar (`MIRADA_STARTUP=foot`).
|
||||||
- `MIRADA_DRM_TIMEOUT=<s>` — cierra el compositor solo tras N segundos
|
- `MIRADA_DRM_TIMEOUT=<s>` — cierra el compositor solo tras N segundos
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ type Compositor =
|
|||||||
DrmCompositor<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), DrmDeviceFd>;
|
DrmCompositor<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), DrmDeviceFd>;
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
/// Lo que el backend DRM compone en un cuadro: las superficies de los
|
/// Lo que el backend DRM compone en un cuadro: superficies de cliente
|
||||||
/// clientes y, encima de todo, el cursor de software.
|
/// y rectángulos de color sólido (el cursor y los marcos de ventana).
|
||||||
Frame<R> where R: ImportAll;
|
Frame<R> where R: ImportAll;
|
||||||
Window = WaylandSurfaceRenderElement<R>,
|
Window = WaylandSurfaceRenderElement<R>,
|
||||||
Cursor = SolidColorRenderElement,
|
Solid = SolidColorRenderElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Color de fondo del escritorio cuando no hay nada que lo tape.
|
/// 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.
|
/// Lado mínimo de una ventana al redimensionarla con el ratón.
|
||||||
const MIN_WINDOW: i32 = 120;
|
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>`.
|
/// Códigos de botón de `<linux/input-event-codes.h>`.
|
||||||
const BTN_LEFT: u32 = 0x110;
|
const BTN_LEFT: u32 = 0x110;
|
||||||
const BTN_RIGHT: u32 = 0x111;
|
const BTN_RIGHT: u32 = 0x111;
|
||||||
@@ -118,8 +142,26 @@ impl DrmState {
|
|||||||
if self.pending_flip {
|
if self.pending_flip {
|
||||||
return; // aún esperamos el VBlank del cuadro anterior
|
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 elements: Vec<Frame<GlesRenderer>> = {
|
||||||
let mut out: Vec<Frame<GlesRenderer>> = Vec::new();
|
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)),
|
Point::<i32, Physical>::from((cx.round() as i32, cy.round() as i32)),
|
||||||
Size::<i32, Physical>::from((CURSOR_SIZE, CURSOR_SIZE)),
|
Size::<i32, Physical>::from((CURSOR_SIZE, CURSOR_SIZE)),
|
||||||
);
|
);
|
||||||
out.push(Frame::Cursor(SolidColorRenderElement::new(
|
out.push(Frame::Solid(SolidColorRenderElement::new(
|
||||||
self.cursor_id.clone(),
|
self.cursor_id.clone(),
|
||||||
cursor_rect,
|
cursor_rect,
|
||||||
CommitCounter::default(),
|
CommitCounter::default(),
|
||||||
@@ -139,10 +181,23 @@ impl DrmState {
|
|||||||
let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect();
|
let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect();
|
||||||
shown.sort_by_key(|w| !w.floating);
|
shown.sort_by_key(|w| !w.floating);
|
||||||
for w in &shown {
|
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(
|
for el in render_elements_from_surface_tree(
|
||||||
&mut self.renderer,
|
&mut self.renderer,
|
||||||
&w.surface,
|
&w.surface,
|
||||||
crate::render_loc(w),
|
(x, y),
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
Kind::Unspecified,
|
Kind::Unspecified,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent};
|
|||||||
use smithay::backend::renderer::element::surface::{
|
use smithay::backend::renderer::element::surface::{
|
||||||
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
||||||
};
|
};
|
||||||
|
use smithay::backend::renderer::element::solid::SolidColorBuffer;
|
||||||
use smithay::backend::renderer::element::Kind;
|
use smithay::backend::renderer::element::Kind;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::backend::renderer::utils::{
|
use smithay::backend::renderer::utils::{
|
||||||
@@ -99,6 +100,11 @@ struct ManagedWindow {
|
|||||||
visible: bool,
|
visible: bool,
|
||||||
/// `true` si flota: se compone por encima de las teseladas.
|
/// `true` si flota: se compone por encima de las teseladas.
|
||||||
floating: bool,
|
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.
|
/// Un arrastre de ratón en curso: mueve o redimensiona una ventana.
|
||||||
@@ -232,8 +238,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
BodyOp::Focus(id) => {
|
BodyOp::Focus(id) => {
|
||||||
let mut target = None;
|
let mut target = None;
|
||||||
for w in &self.windows {
|
for w in &mut self.windows {
|
||||||
let active = w.id == id;
|
let active = w.id == id;
|
||||||
|
w.focused = active;
|
||||||
if active {
|
if active {
|
||||||
target = Some(w.surface.clone());
|
target = Some(w.surface.clone());
|
||||||
}
|
}
|
||||||
@@ -251,6 +258,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BodyOp::Unfocus => {
|
BodyOp::Unfocus => {
|
||||||
|
for w in &mut self.windows {
|
||||||
|
w.focused = false;
|
||||||
|
}
|
||||||
if let Some(kb) = self.keyboard.clone() {
|
if let Some(kb) = self.keyboard.clone() {
|
||||||
kb.set_focus(self, Option::<WlSurface>::None, SERIAL_COUNTER.next_serial());
|
kb.set_focus(self, Option::<WlSurface>::None, SERIAL_COUNTER.next_serial());
|
||||||
}
|
}
|
||||||
@@ -297,6 +307,8 @@ impl App {
|
|||||||
size: (0, 0),
|
size: (0, 0),
|
||||||
visible: false,
|
visible: false,
|
||||||
floating: false,
|
floating: false,
|
||||||
|
focused: false,
|
||||||
|
borders: std::array::from_fn(|_| SolidColorBuffer::default()),
|
||||||
});
|
});
|
||||||
let ev = self.body.open_surface(id, app_id, title);
|
let ev = self.body.open_surface(id, app_id, title);
|
||||||
self.brain_feed(ev);
|
self.brain_feed(ev);
|
||||||
|
|||||||
@@ -210,7 +210,9 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
|
|||||||
(`BodyEvent::PointerEntered`) y clics y rueda van a la ventana debajo.
|
(`BodyEvent::PointerEntered`) y clics y rueda van a la ventana debajo.
|
||||||
`Super`+arrastre mueve/redimensiona: el Cuerpo calcula el rectángulo y
|
`Super`+arrastre mueve/redimensiona: el Cuerpo calcula el rectángulo y
|
||||||
emite `BodyEvent::WindowFloatTo { id, rect }`; el Cerebro hace flotar
|
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:
|
**Pendiente** — refinamientos del Cuerpo:
|
||||||
|
|
||||||
|
|||||||
@@ -1002,6 +1002,7 @@
|
|||||||
cargo run -p mirada-compositor -- --drm # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar)
|
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.
|
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.
|
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.
|
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.
|
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).
|
Lanzar programas: acción spawn:<comando> del keymap (Super+Shift+Return → spawn:foot por defecto).
|
||||||
|
|||||||
Reference in New Issue
Block a user