← PaganDE

Commit edd9e04

9
plików
+74
dodanych
-321
usuniętych
@@ -4,7 +4,7 @@
4 4
5 5 - **`pagan-compositor`** — kompozytor (serwer) na [Smithay 0.7](https://github.com/Smithay/smithay):
6 6 floating window manager, renderer OpenGL (`glow`), **wymuszone dekoracje serwerowe
7 - (SSD)** z menu aplikacji w pasku, cień pod oknami, doklejanie okien (Aero Snap).
7 + (SSD)** z menu aplikacji w pasku, płaski firmowy styl, doklejanie okien (Aero Snap).
8 8 - **`pagan-toolkit`** — własna biblioteka UI dla klientów: `wayland-client` + `memmap2`
9 9 (SHM) + `cairo-rs`. Bez Qt/GTK/SCTK/iced. Antyaliasing, zaokrąglone rogi, wspólny
10 10 pasek tytułu, lekka lokalizacja PL/EN.
@@ -73,7 +73,7 @@ WAYLAND_DISPLAY=wayland-1 PAGAN_LANG=pl cargo run --example demo
73 73 Powinno pojawić się okno 800×500 z ciemnym paskiem tytułu: „hamburger” (Menu) po
74 74 lewej i trzy okrągłe przyciski (minimalizuj / maksymalizuj / zamknij) po prawej —
75 75 **wszystkie w jednej linii** (`TITLEBAR_CONTROL_CENTER_Y = 20.0`), z zaokrąglonymi
76 -rogami i cieniem.
76 +rogami.
77 77
78 78 ## Doklejanie i kafelkowanie (Aero Snap)
79 79
@@ -106,23 +106,19 @@ Konfiguracja przez zmienne środowiskowe (czytane raz przy starcie):
106 106 | `PAGAN_SNAP_BOTTOM` | `0` | `1` włącza doklejanie do dolnej krawędzi (dolna połowa) |
107 107
108 108 Logika geometrii to czysta funkcja w `pagan-compositor/src/snap.rs` z testami:
109 -`cargo test --bin pagan-compositor` (23 testy).
109 +`cargo test --bins`.
110 110
111 111 ## Motyw kompozytora (kolory) — natychmiastowe przeładowanie
112 112
113 -Kompozytor sam rysuje tylko tło pulpitu, cień okien i awaryjny kursor — dlatego
114 -motyw steruje nimi. Konfiguracja: `~/.config/pagan/theme.conf` (lub `PAGAN_THEME`),
115 -tworzona z komentowanym szablonem przy pierwszym starcie:
113 +Kompozytor sam rysuje tylko tło pulpitu, pasek/menu SSD, „ducha” doklejania
114 +i awaryjny kursor — dlatego motyw steruje nimi. Konfiguracja:
115 +`~/.config/pagan/theme.conf` (lub `PAGAN_THEME`), tworzona z komentowanym
116 +szablonem przy pierwszym starcie:
116 117
117 118 ```conf
118 -background = #0e0f14 # tło pulpitu
119 -shadow_color = #000000 # kolor cienia
120 -shadow_opacity = 0.42
121 -shadow_blur = 24
122 -shadow_offset_y = 6
123 -shadow_radius = 12 # promień rogów sylwetki cienia
124 -cursor_fill = #f2f2f7 # awaryjny kursor (gdy brak motywu XCursor)
125 -cursor_border = #14141a
119 +background = #0e0f14 # tło pulpitu
120 +cursor_fill = #f2f2f7 # awaryjny kursor (gdy brak motywu XCursor)
121 +cursor_border = #14141a
126 122
127 123 # Dekoracje serwerowe (SSD) — płaski, firmowy pasek tytułu
128 124 ssd_bar = #1c202b # pasek okna AKTYWNEGO
@@ -145,7 +141,7 @@ ręcznie. Kolory to `#RRGGBB` / `#RRGGBBAA`. Implementacja: `src/theme.rs`
145 141
146 142 Kompozytor **wymusza** tryb dekoracji serwerowych (`xdg-decoration` → `ServerSide`)
147 143 dla okien najwyższego poziomu, które ten protokół znają (nasz toolkit, Qt, GTK4).
148 -Pasek jest płaski (bez gradientów), z zaokrąglonymi górnymi rogami i cieniem pod ramką.
144 +Pasek jest płaski (bez gradientów), z zaokrąglonymi górnymi rogami.
149 145
150 146 Dwa świadome wyjątki:
151 147
@@ -259,7 +255,7 @@ DISPLAY=:1 python3 tools/x11_probe.py find 1280 800 # origin okna klienta
259 255 | 1. Szkielet kompozytora (Smithay, winit, floating, move/resize, Z-order, xdg-decoration=server) | ✅ winit + ✅ udev | `pagan-compositor/src/` |
260 256 | 1b. Doklejanie do krawędzi/rogów (Aero Snap) + „wyskakiwanie” ze snapa | ✅ (`snap.rs`, 10 testów) | `pagan-compositor/src/snap.rs`, `grabs.rs` |
261 257 | 2. Szkielet toolkitu (Wayland, SHM/memmap2, Cairo, CSD, hit-testing) | ✅ | `pagan-toolkit/src/` |
262 -| 3. Shadery GLSL (zaokrąglenia + cień) | ✅ **cień na GPU** (pixel shader) | `pagan-compositor/src/shaders/`, `window.rs` |
258 +| 3. Shadery GLSL (zaokrąglone prostokąty: pasek, menu, duch snapu) | ✅ | `pagan-compositor/src/shaders/`, `window.rs` |
263 259 | 4. Wymuszone dekoracje serwerowe (SSD) + menu aplikacji w pasku | ✅ | `pagan-compositor/src/window.rs`, `text.rs`, `app_menu.rs` |
264 260 | 5. Własny protokół `pagan_app_menu_v1` (menu per aplikacja) | ✅ | `protocols/`, `pagan-compositor/src/app_menu.rs`, `pagan-toolkit/src/app_menu.rs` |
265 261 | 6. Wektorowe ikony paska + launcher aplikacji testowej w panelu | ✅ | `pagan-compositor/src/icons.rs`, `pagan-panel/src/main.rs` |
@@ -280,8 +276,9 @@ DISPLAY=:1 python3 tools/x11_probe.py find 1280 800 # origin okna klienta
280 276 `Ctrl+Alt+R` (przeładuj motyw).
281 277 - **Backend tty-udev**: kompiluje się (`--features udev`, `--tty-udev`), ale **nie był
282 278 uruchamiany na sprzęcie** (komentarze `// WERYFIKUJ:`).
283 -- **Cień** działa na GPU własnym shaderem (`shadow.frag`). `rounded_corners.frag` jest
284 - gotową maską rogów na przyszły tryb SSD; przy CSD jest zbędna (klient rysuje rogi).
279 +- **Bez cienia pod oknami** (świadomie): okna są płaskie, oddziela je od tła tylko
280 + zaokrąglony pasek i 1 px linia. `rounded_corners.frag` rysuje zaokrąglone
281 + prostokąty (pasek SSD, tło menu, „duch” doklejania).
285 282 - **Kursor** z motywu XCursor (`XCURSOR_THEME`/`XCURSOR_SIZE`), z zapasowym prostokątem.
286 283 **Ikona DnD** rysowana pod kursorem. Minimalizacja chowa okno z `Space`.
287 284 - **Brak animacji** (świadomie — najpierw działający system).
@@ -294,15 +291,15 @@ Całość **skompilowana i uruchomiona** na prawdziwym sprzęcie (NVIDIA + Mesa,
294 291 |---|---|---|
295 292 | Build kompozytora (winit) | `cargo build` | ✅ 0 błędów |
296 293 | Build kompozytora (udev) | `cargo build --features udev` | ✅ 0 błędów |
297 -| Testy jednostkowe | `cargo test --bin pagan-compositor` | ✅ 23/23 (snap + monitory + motyw) |
294 +| Testy jednostkowe | `cargo test --bins` | ✅ 43/43 (snap + monitory + motyw + SSD) |
298 295 | Build toolkitu | `cargo build --examples` | ✅ 0 błędów |
299 296 | Kompozytor | `./target/debug/pagan-compositor --winit` | ✅ socket `wayland-1`, output 1280×800 |
300 297 | Klient + CSD | `WAYLAND_DISPLAY=wayland-1 demo` | ✅ okno, klik/hover/maximize/move |
301 -| Cień (shader) | analiza pikseli klatki | ✅ miękki cień POD oknem |
298 +| Bez cienia | analiza pikseli klatki (ring wokół okna) | ✅ brak halo — treść styka się z pulpitem |
302 299 | Zrzut ekranu | `WAYLAND_DISPLAY=wayland-1 grim shot.png` | ✅ (nasz `wlr-screencopy` od zera) |
303 300 | Kursor z motywu | `grim -c shot.png` | ✅ widoczny kursor XCursor |
304 301 | Motyw — tło | zmiana `background` w `theme.conf` | ✅ nowy kolor w ~1 s (inotify) |
305 -| Motyw — cień | `shadow_color = #00ff00` | ✅ cień zielony (bbox wokół okna) |
302 +| Motyw — pasek | zmiana `ssd_bar` w `theme.conf` | ✅ nowy kolor w ~1 s (inotify) |
306 303 | XWayland | log startowy bez binarki `Xwayland` | ⚠️ miękka degradacja (WARN) — brak binarki w środowisku testowym |
307 304
308 305 Zmierzona geometria z `grim`: menu i trzy przyciski mają środek na **tej samej
@@ -313,9 +310,9 @@ osi Y**, odstęp dokładnie 32 px, rogi przezroczyste (promień 12 px).
313 310 ### Dwie pułapki API, które kosztowały najwięcej czasu (na przyszłość)
314 311
315 312 1. **Kolejność elementów renderowania**: Smithay oczekuje listy **od przodu do tyłu**
316 - (indeks 0 = na wierzchu; `render_output` rysuje od końca). Cień musi być **na końcu**
317 - listy (pod oknem), a **kursor i ikona DnD na samym początku** — inaczej kursor
318 - chowa się POD oknem. `space::render_output` w Smithayu robi dokładnie tak:
313 + (indeks 0 = na wierzchu; `render_output` rysuje od końca). Belka paska musi być
314 + **na końcu** listy (pod tekstem i glifami), a **kursor i ikona DnD na samym
315 + początku** — inaczej kursor chowa się POD oknem. `space::render_output` w Smithayu robi dokładnie tak:
319 316 najpierw elementy niestandardowe, potem zawartość przestrzeni.
320 317 2. **`PixelShaderElement`**: `varying v_coords` jest w **UV [0..1]**, nie w pikselach —
321 318 trzeba pomnożyć przez `uniform vec2 size`. Dodatkowo GLSL ES wymaga jawnego
@@ -1,17 +1,17 @@
1 -//! PaganDE — kompozytor Wayland (floating, hybrydowe CSD) na Smithay 0.7.
1 +//! PaganDE — kompozytor Wayland (floating, wymuszone dekoracje SSD) na Smithay 0.7.
2 2 //!
3 3 //! Wejście programu. Obsługujemy tylko backend developmentowy (winit); backend
4 4 //! produkcyjny (tty-udev) jest zaplanowany i opisany w README.
5 5 //!
6 6 //! Modularność (wymóg projektu):
7 7 //! - `state` – stan kompozytora + handlery protokołów,
8 -//! - `window` – reprezentacja okna w przestrzeni (z cieniem),
8 +//! - `window` – reprezentacja okna w przestrzeni (z dekoracją SSD),
9 9 //! - `input` – polityka myszy/klawiatury i Z-order,
10 10 //! - `grabs` – przeciąganie i zmiana rozmiaru,
11 11 //! - `render` – budowa elementów i rysowanie klatki,
12 -//! - `theme` – motyw (kolory tła/cienia/kursora) z natychmiastowym przeładowaniem,
12 +//! - `theme` – motyw (kolory tła/paska/kursora) z natychmiastowym przeładowaniem,
13 13 //! - `focus` – cele fokusa (mysz/klawiatura),
14 -//! - `shaders` – wbudowane shadery GLSL (zaokrąglenia + cień).
14 +//! - `shaders` – wbudowane shadery GLSL (zaokrąglone prostokąty).
15 15
16 16 mod app_menu;
17 17 mod cursor;
@@ -45,7 +45,6 @@ fn main() -> anyhow::Result<()> {
45 45 // martwym kodem; pełne podłączenie do potoku GL to kolejny krok).
46 46 info!(
47 47 rounded_corners_frag = shaders::ROUNDED_CORNERS_FRAG.len(),
48 - shadow_frag = shaders::SHADOW_FRAG.len(),
49 48 "Wbudowane shadery GLSL dostępne"
50 49 );
51 50
@@ -3,10 +3,10 @@
3 3 //! PO CO osobno od `state.rs`: zgodnie z wymaganiem modularności rozdzielamy
4 4 //! "co jest w świecie" (stan) od "jak to narysować" (render).
5 5 //!
6 -//! Typy renderowania są **specjalizowane do `GlesRenderer`**. Powód: cień rysujemy
7 -//! własnym pixel shaderem (`PixelShaderElement`), a to API Smithaya działa tylko
8 -//! z konkretnym rendererem. Używamy `GlesRenderer` w obu backendach (winit, udev),
9 -//! więc nie tracimy na tym nic, a zyskujemy prostsze typy.
6 +//! Typy renderowania są **specjalizowane do `GlesRenderer`**. Powód: pasek SSD
7 +//! i menu rysujemy własnym pixel shaderem (`PixelShaderElement`), a to API
8 +//! Smithaya działa tylko z konkretnym rendererem. Używamy `GlesRenderer` w obu
9 +//! backendach (winit, udev), więc nie tracimy na tym nic, a zyskujemy prostsze typy.
10 10
11 11 use std::{collections::HashMap, fmt, sync::Mutex, sync::OnceLock, time::Duration};
12 12
@@ -282,7 +282,7 @@ pub fn output_elements(
282 282 }
283 283 }
284 284
285 - // 4) Zawartość przestrzeni (okna razem z ich cieniami) — na spodzie.
285 + // 4) Zawartość przestrzeni (okna razem z ich dekoracjami SSD) — na spodzie.
286 286 elements.extend(space_elements.into_iter().map(OutputRenderElements::Space));
287 287
288 288 (elements, theme::current().background)
@@ -291,7 +291,7 @@ pub fn output_elements(
291 291 /// Cache programów „ducha” (shader zaokrągleń), kluczowany kontekstem EGL.
292 292 ///
293 293 /// PO CO klucz po kontekście: program GL żyje w konkretnym kontekście — ten sam
294 -/// powód co przy shaderze cienia (patrz `window.rs`).
294 +/// powód co przy shaderze paska SSD (patrz `window.rs`).
295 295 fn preview_programs() -> &'static Mutex<HashMap<usize, GlesPixelProgram>> {
296 296 static CACHE: OnceLock<Mutex<HashMap<usize, GlesPixelProgram>>> = OnceLock::new();
297 297 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
@@ -4,19 +4,13 @@
4 4 //! kolorowane w edytorze, łatwe do iterowania), a `include_str!` wkompilowuje
5 5 //! je w binarkę.
6 6 //!
7 -//! Oba shadery to **pixel shadery** Smithaya — kompilowane przez
7 +//! Shadery to **pixel shadery** Smithaya — kompilowane przez
8 8 //! `GlesRenderer::compile_custom_pixel_shader` i rysowane jako
9 9 //! `PixelShaderElement`. Zgodnie z konwencją Smithaya nie zawierają dyrektywy
10 10 //! `#version` (renderer dokleja `#version 100`).
11 11 //!
12 -//! - `SHADOW_FRAG` jest używany bezwarunkowo: rysuje miękki, zaokrąglony cień
13 -//! pod każdym oknem (`crate::window`).
14 -//! - `ROUNDED_CORNERS_FRAG` jest gotowym narzędziem do maski rogów (przyszły SSD
15 -//! / okna bez własnego CSD). W obecnej konfiguracji (CSD) nie jest nakładany —
16 -//! szczegóły i ograniczenia opisano w nagłówku pliku.
12 +//! - `ROUNDED_CORNERS_FRAG` maluje zaokrąglone prostokąty: belkę paska SSD,
13 +//! tło menu okna oraz „ducha” doklejania (Aero Snap).
17 14
18 -/// Pixel shader miękkiego cienia pod oknem. Używany produkcyjnie.
19 -pub const SHADOW_FRAG: &str = include_str!("shaders/shadow.frag");
20 -
21 -/// Pixel shader maski zaokrąglonych rogów (opcjonalny, patrz komentarz w pliku).
15 +/// Pixel shader zaokrąglonego prostokąta o jednolitym kolorze.
22 16 pub const ROUNDED_CORNERS_FRAG: &str = include_str!("shaders/rounded_corners.frag");
23 17 deleted file mode 100644
@@ -1,66 +0,0 @@
1 -// shadow.frag
2 -// -----------------------------------------------------------------------------
3 -// Miękki cień pod oknem (drop shadow) — WŁASNY PIXEL SHADER KOMPOZYTORA.
4 -//
5 -// Ten shader jest NAPRAWDĘ używany: kompilujemy go przez
6 -// `GlesRenderer::compile_custom_pixel_shader` i rysujemy nim cień pod każdym
7 -// oknem (`PixelShaderElement`, patrz `src/window.rs`).
8 -//
9 -// Uwaga na konwencję Smithaya: shader NIE MOŻE zawierać dyrektywy `#version`
10 -// (renderer sam dokleja `#version 100`). Otrzymuje automatycznie:
11 -// varying vec2 v_coords – pozycja piksela w ZNORMALIZOWANYM UV [0..1]
12 -// (mnożymy przez `size`, by dostać piksele)
13 -// uniform vec2 size – rozmiar obszaru elementu (okno + margines) w px
14 -// uniform float alpha – alpha przekazana przez renderer
15 -// dodatkowo (przez `additional_uniforms`):
16 -// uniform vec2 window_size – rozmiar samego okna w px
17 -// uniform float radius – promień zaokrąglenia okna (12.0)
18 -// uniform float pad – margines cienia wokół okna
19 -// uniform float offset – przesunięcie cienia w dół
20 -// uniform float strength – maksymalna nieprzezroczystość cienia
21 -// uniform vec3 shadow_color – kolor cienia (z motywu, domyślnie czarny)
22 -//
23 -// PO CO cień w kompozytorze, a nie w kliencie: klient nie musi rezerwować
24 -// marginesu wokół bufora, a cień jest spójny dla WSZYSTKICH okien.
25 -// Sylwetka cienia respektuje zaokrąglone rogi (signed distance), więc cień
26 -// pasuje do rogów rysowanych przez toolkit.
27 -//
28 -// WAŻNE: renderer dokleja tylko `#version 100` (GLES2). W GLSL ES fragment
29 -// shader NIE MA domyślnej precyzji dla `float` — bez poniższej deklaracji
30 -// kompilator odrzuca shader (błąd C7573). Wbudowane shadery Smithaya mają to
31 -// samo (patrz `implicit/solid.frag`).
32 -precision mediump float;
33 -
34 -varying vec2 v_coords;
35 -
36 -uniform vec2 size;
37 -uniform float alpha;
38 -
39 -uniform vec2 window_size;
40 -uniform float radius;
41 -uniform float pad;
42 -uniform float offset;
43 -uniform float strength;
44 -uniform vec3 shadow_color;
45 -
46 -// Odległość ze znakiem do zaokrąglonego prostokąta. Ujemna = wewnątrz.
47 -float sd_rounded_rect(vec2 p, vec2 half_size, float r) {
48 - vec2 d = abs(p) - (half_size - vec2(r));
49 - return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
50 -}
51 -
52 -void main() {
53 - // `v_coords` jest w UV [0..1]; skalujemy do [0..size] i przesuwamy do układu
54 - // względem środka elementu, który pokrywa się ze środkiem okna.
55 - vec2 p = (v_coords - vec2(0.5)) * size;
56 -
57 - // Przesunięcie próbki w górę sprawia, że sylwetka cienia wędruje w dół.
58 - float dist = sd_rounded_rect(vec2(p.x, p.y - offset), window_size * 0.5, radius);
59 -
60 - // exp(-d/blur) to tanie, naturalne przybliżenie rozmycia Gaussa.
61 - float falloff = exp(-max(dist, 0.0) / max(pad * 0.5, 1.0));
62 -
63 - // Kolor cienia pochodzi z motywu (`~/.config/pagan/theme.conf`), dzięki czemu
64 - // można dostać np. ciepły/czerwony cień albo całkiem go wyłączyć (strength=0).
65 - gl_FragColor = vec4(shadow_color, falloff * strength * alpha);
66 -}
@@ -519,7 +519,7 @@ impl MyCompositor {
519 519 }
520 520 }
521 521
522 - /// Zamienia surowe okno Smithaya na nasze okno z cieniem (`PaganWindow`).
522 + /// Zamienia surowe okno Smithaya na nasze okno z dekoracją SSD (`PaganWindow`).
523 523 ///
524 524 /// PO CO: fokus klawiatury trzyma `Window` (tak wymaga Smithay), a operacje
525 525 /// typu minimalizacja potrzebują wrappera `PaganWindow` obecnego w `space`.
@@ -1,8 +1,9 @@
1 -//! System motywów kompozytora: kolory tła, cienia i kursora zapasowego.
1 +//! System motywów kompozytora: kolory tła, paska SSD, menu i kursora zapasowego.
2 2 //!
3 3 //! PO CO osobny moduł: kompozytor sam maluje tylko kilka rzeczy (tło pulpitu,
4 -//! cień pod oknami, awaryjny kursor) — resztę rysują klienty (hybrydowe CSD).
5 -//! Trzymamy te kolory w jednym miejscu, żeby dały się zmieniać bez rekompilacji.
4 +//! pasek tytułu i menu okna, ducha doklejania, awaryjny kursor) — resztę rysują
5 +//! klienty. Trzymamy te kolory w jednym miejscu, żeby dały się zmieniać bez
6 +//! rekompilacji.
6 7 //!
7 8 //! Gdzie jest konfiguracja: `$PAGAN_THEME`, a domyślnie
8 9 //! `~/.config/pagan/theme.conf` (albo `$XDG_CONFIG_HOME/pagan/theme.conf`).
@@ -13,8 +14,8 @@
13 14 //! zmianie wczytujemy motyw od nowa. Dodatkowo `Ctrl+Alt+R` wymusza przeładowanie.
14 15 //!
15 16 //! PO CO globalny stan: `AsRenderElements` okna nie ma dostępu do stanu
16 -//! kompozytora, a to właśnie tam rysujemy cień. Globalny `RwLock` z motywem
17 -//! pozwala odczytać kolory z dowolnego miejsca bez plątania typów.
17 +//! kompozytora, a to właśnie tam rysujemy pasek i menu. Globalny `RwLock`
18 +//! z motywem pozwala odczytać kolory z dowolnego miejsca bez plątania typów.
18 19
19 20 use std::{
20 21 fs, io,
@@ -40,16 +41,6 @@ use tracing::{info, warn};
40 41 pub struct Theme {
41 42 /// Kolor tła pulpitu (czyszczenie bufora wyjścia).
42 43 pub background: Color32F,
43 - /// Kolor cienia okien (RGB, składowe 0..1).
44 - pub shadow_color: [f32; 3],
45 - /// Maksymalna nieprzezroczystość cienia (0..1).
46 - pub shadow_opacity: f32,
47 - /// Rozmycie cienia w pikselach (większe = bardziej rozlane).
48 - pub shadow_blur: f32,
49 - /// Przesunięcie cienia w dół (światło „z góry”).
50 - pub shadow_offset_y: f32,
51 - /// Promień zaokrąglenia sylwetki cienia — dopasuj do rogów okien klienta.
52 - pub shadow_radius: f32,
53 44 /// Kolor wypełnienia awaryjnego kursora (gdy brak motywu XCursor).
54 45 pub cursor_fill: Color32F,
55 46 /// Kolor obwódki awaryjnego kursora.
@@ -91,11 +82,6 @@ impl Default for Theme {
91 82 // żeby odczyt szablonu dawał motyw równy domyślnemu (test regresyjny).
92 83 Self {
93 84 background: hex(0x0e, 0x0f, 0x14),
94 - shadow_color: [0.0, 0.0, 0.0],
95 - shadow_opacity: 0.42,
96 - shadow_blur: 24.0,
97 - shadow_offset_y: 6.0,
98 - shadow_radius: 12.0,
99 85 cursor_fill: hex(0xf2, 0xf2, 0xf7),
100 86 cursor_border: hex(0x14, 0x14, 0x1a),
101 87 snap_preview: rgba(0x4e, 0xa1, 0xff, 0x66),
@@ -160,15 +146,6 @@ impl Theme {
160 146 fn apply(&mut self, key: &str, value: &str) -> Result<(), String> {
161 147 match key {
162 148 "background" => self.background = parse_color(value).ok_or_else(bad_color)?,
163 - "shadow_color" => {
164 - let color = parse_color(value).ok_or_else(bad_color)?;
165 - // Cień to składowa RGB; alpha (nieprzezroczystość) jest osobno.
166 - self.shadow_color = [color.r(), color.g(), color.b()];
167 - }
168 - "shadow_opacity" => self.shadow_opacity = parse_float(value)?,
169 - "shadow_blur" => self.shadow_blur = parse_float(value)?.max(1.0),
170 - "shadow_offset_y" => self.shadow_offset_y = parse_float(value)?,
171 - "shadow_radius" => self.shadow_radius = parse_float(value)?.max(0.0),
172 149 "cursor_fill" => self.cursor_fill = parse_color(value).ok_or_else(bad_color)?,
173 150 "cursor_border" => self.cursor_border = parse_color(value).ok_or_else(bad_color)?,
174 151 "snap_preview" => self.snap_preview = parse_color(value).ok_or_else(bad_color)?,
@@ -187,10 +164,9 @@ impl Theme {
187 164 "ssd_menu_min_width" => self.ssd_menu_min_width = parse_float(value)?.max(0.0) as i32,
188 165 other => {
189 166 return Err(format!(
190 - "nieznany klucz '{other}' (dostępne: background, shadow_color, \
191 - shadow_opacity, shadow_blur, shadow_offset_y, shadow_radius, \
192 - cursor_fill, cursor_border, snap_preview, snap_preview_radius, \
193 - ssd_bar, ssd_unfocused, ssd_border, ssd_text, ssd_text_unfocused, \
167 + "nieznany klucz '{other}' (dostępne: background, cursor_fill, \
168 + cursor_border, snap_preview, snap_preview_radius, ssd_bar, \
169 + ssd_unfocused, ssd_border, ssd_text, ssd_text_unfocused, \
194 170 ssd_icon_close, ssd_radius, ssd_font_px, ssd_icon_px, ssd_menu_min_width)"
195 171 ));
196 172 }
@@ -255,13 +231,6 @@ const TEMPLATE: &str = "\
255 231 # Tło pulpitu (obszar bez okien).
256 232 background = #0e0f14
257 233
258 -# Cień pod oknami.
259 -shadow_color = #000000
260 -shadow_opacity = 0.42
261 -shadow_blur = 24
262 -shadow_offset_y = 6
263 -shadow_radius = 12
264 -
265 234 # Awaryjny kursor (używany tylko, gdy brak motywu XCursor).
266 235 cursor_fill = #f2f2f7
267 236 cursor_border = #14141a
@@ -454,9 +423,9 @@ mod tests {
454 423 #[test]
455 424 fn domyslne_wartosci_sa_spojne_z_dotychczasowym_wygladem() {
456 425 let theme = Theme::default();
457 - assert_eq!(theme.shadow_color, [0.0, 0.0, 0.0]);
458 - assert_eq!(theme.shadow_opacity, 0.42);
459 - assert_eq!(theme.shadow_radius, 12.0);
426 + assert_eq!(theme.background, hex(0x0e, 0x0f, 0x14));
427 + assert_eq!(theme.ssd_bar, hex(0x1c, 0x20, 0x2b));
428 + assert_eq!(theme.ssd_radius, 12.0);
460 429 }
461 430
462 431 #[test]
@@ -472,17 +441,10 @@ mod tests {
472 441 }
473 442
474 443 #[test]
475 - fn kolor_cienia_odrzuca_kanal_alfa_ale_przyjmuje_rgb() {
476 - let (theme, warnings) = Theme::from_text("shadow_color = #112233\n");
444 + fn prog_burgera_jest_konfigurowalny() {
445 + let (theme, warnings) = Theme::from_text("ssd_menu_min_width = 700\n");
477 446 assert!(warnings.is_empty());
478 - assert_eq!(
479 - theme.shadow_color,
480 - [
481 - 0x11 as f32 / 255.0,
482 - 0x22 as f32 / 255.0,
483 - 0x33 as f32 / 255.0
484 - ]
485 - );
447 + assert_eq!(theme.ssd_menu_min_width, 700);
486 448 }
487 449
488 450 #[test]
@@ -516,10 +478,10 @@ mod tests {
516 478 }
517 479
518 480 #[test]
519 - fn rozmycie_nie_moze_zejsc_do_zera() {
520 - // Dzielenie przez `blur` w shaderze — zero dałoby artefakty.
521 - let (theme, _) = Theme::from_text("shadow_blur = 0\n");
522 - assert!(theme.shadow_blur >= 1.0);
481 + fn ujemny_promien_ducha_ucina_sie_do_zera() {
482 + // Promień nie może być ujemny — inaczej shader narysowałby odwrócone rogi.
483 + let (theme, _) = Theme::from_text("snap_preview_radius = -5\n");
484 + assert!(theme.snap_preview_radius >= 0.0);
523 485 }
524 486
525 487 #[test]
@@ -714,7 +714,7 @@ impl MyCompositor {
714 714 return;
715 715 };
716 716
717 - // Nasz renderer jest zbudowany na GlesRenderer (pixel shadery cienia), a
717 + // Nasz renderer jest zbudowany na GlesRenderer (pixel shadery paska SSD), a
718 718 // `MultiRenderer` udostępnia wewnętrzny renderer przez `AsMut`.
719 719 let gles: &mut GlesRenderer = renderer.as_mut();
720 720 let (elements, clear_color) = render::output_elements(
@@ -2,15 +2,15 @@
2 2 //!
3 3 //! PO CO istnieje ten wrapper, skoro Smithay ma już `Window`?
4 4 //!
5 -//! 1. **Cień per-okno z zachowaniem Z-order.** Cień musi być narysowany POD
6 -//! konkretnym oknem i NAD oknami leżącymi niżej. Smithay pozwala dołączyć
7 -//! własne elementy renderowania tylko do typu, który sam implementuje
8 -//! `AsRenderElements`. Owinięcie `Window` pozwala nam "wstrzyknąć" element
9 -//! cienia bezpośrednio przed elementami tego okna.
10 -//! 2. **Miejsce na przyszłe dekoracje serwerowe (SSD).**
5 +//! 1. **Dekoracje serwerowe (SSD) w Z-orderze okna.** Pasek tytułu, menu i treść
6 +//! muszą być narysowane razem i trafiać na wierzch stosu jako jedna grupa.
7 +//! Smithay pozwala dołączyć własne elementy renderowania tylko do typu, który
8 +//! sam implementuje `AsRenderElements`. Owinięcie `Window` pozwala nam
9 +//! "wstrzyknąć" elementy paska bezpośrednio przy elementach tego okna.
10 +//! 2. **Miejsce na logikę dekoracji** (hit-testy, menu, stan aktywności).
11 11 //!
12 -//! Cień rysujemy **własnym shaderem GLSL** (`shaders/shadow.frag`) przez
13 -//! `PixelShaderElement`. Dlatego typy renderowania są specjalizowane do
12 +//! Pasek i menu rysujemy **własnym shaderem GLSL** (`shaders/rounded_corners.frag`)
13 +//! przez `PixelShaderElement`. Dlatego typy renderowania są specjalizowane do
14 14 //! `GlesRenderer` (pixel shadery Smithaya działają tylko z konkretnym
15 15 //! rendererem) — to nie ogranicza nas w praktyce, bo używamy `GlesRenderer`
16 16 //! zarówno w backendzie winit, jak i udev.
@@ -18,13 +18,7 @@
18 18 //! Uwaga: w specyfikacji pojawiało się `Space<Window>` — używamy
19 19 //! `Space<PaganWindow>` z powodów opisanych wyżej (patrz README).
20 20
21 -use std::{
22 - collections::HashMap,
23 - sync::{
24 - atomic::{AtomicBool, Ordering},
25 - Mutex, OnceLock,
26 - },
27 -};
21 +use std::sync::atomic::{AtomicBool, Ordering};
28 22
29 23 use smithay::{
30 24 backend::renderer::{
@@ -32,10 +26,7 @@ use smithay::{
32 26 memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement,
33 27 AsRenderElements, Kind,
34 28 },
35 - gles::{
36 - element::PixelShaderElement, GlesPixelProgram, GlesRenderer, Uniform, UniformName,
37 - UniformType,
38 - },
29 + gles::{element::PixelShaderElement, GlesRenderer},
39 30 Color32F,
40 31 },
41 32 desktop::{space::SpaceElement, Window, WindowSurface},
@@ -53,7 +44,7 @@ use smithay::{
53 44 },
54 45 };
55 46
56 -use crate::{icons, render, shaders::SHADOW_FRAG, text, theme};
47 +use crate::{icons, render, text, theme};
57 48
58 49 // --- Dekoracje serwerowe (SSD) ------------------------------------------------
59 50 //
@@ -112,9 +103,8 @@ struct SsdState {
112 103 focused: AtomicBool,
113 104 }
114 105
115 -// Parametry cienia (margines, rozmycie, przesunięcie, siła, promień rogów)
116 -// pochodzą z motywu (`theme::Theme`) — patrz `~/.config/pagan/theme.conf`.
117 -// Zmiana motywu jest widoczna w następnej klatce, bo parametry są uniformami.
106 +// Styl paska i menu opisują stałe poniżej; kolory pochodzą z motywu
107 +// (`theme::Theme`) — patrz `~/.config/pagan/theme.conf`.
118 108
119 109 /// Marker w danych powierzchni: klient **negocjował** dekoracje
120 110 /// (`zxdg_toplevel_decoration_v1`).
@@ -195,7 +185,7 @@ impl PaganWindow {
195 185 self.0.wl_surface()
196 186 }
197 187
198 - /// Geometria okna w przestrzeni logicznej (bez cienia).
188 + /// Geometria okna w przestrzeni logicznej (bez ramki SSD).
199 189 pub fn geometry(&self) -> Rectangle<i32, Logical> {
200 190 SpaceElement::geometry(&self.0)
201 191 }
@@ -600,7 +590,7 @@ impl SpaceElement for PaganWindow {
600 590 }
601 591 }
602 592
603 -// Elementy renderowania okna: cień (własny pixel shader) + treść okna.
593 +// Elementy renderowania okna: pasek SSD (własny pixel shader), tekst i treść.
604 594 //
605 595 // Wariant `<=GlesRenderer>` mówi makru, że enum jest specjalizowany do
606 596 // konkretnego renderera — dzięki temu możemy trzymać w nim `PixelShaderElement`,
@@ -610,7 +600,7 @@ render_elements! {
610 600 Window=WaylandSurfaceRenderElement<GlesRenderer>,
611 601 // Tekst (tytuł, etykiety menu) rasteryzowany Cairo i wysłany jako tekstura.
612 602 Text=MemoryRenderBufferRenderElement<GlesRenderer>,
613 - // Element rysowany pixel shaderem: cień okna ORAZ beleczki/glify SSD.
603 + // Element rysowany pixel shaderem: beleczka paska i tło menu SSD.
614 604 // (Jedna wariant, bo makro generuje `From` per typ — dwa warianty tego samego
615 605 // typu kolidowałyby.)
616 606 Pixel=PixelShaderElement,
@@ -641,8 +631,8 @@ impl AsRenderElements<GlesRenderer> for PaganWindow {
641 631
642 632 // Kolejność ma znaczenie: Smithay oczekuje elementów OD PRZODU DO TYŁU
643 633 // (indeks 0 = rysowany na wierzchu; `render_output` rysuje listę od końca).
644 - // Dlatego menu okna i treść idą na początek listy, a cień na koniec
645 - // — czyli POD oknem.
634 + // Dlatego menu okna i treść idą na początek listy, a belka paska na koniec
635 + // — czyli POD tekstem i glifami.
646 636
647 637 let logical_loc = location.to_f64().to_logical(scale).to_i32_round::<i32>();
648 638 let bar = Rectangle::new(
@@ -853,20 +843,6 @@ impl AsRenderElements<GlesRenderer> for PaganWindow {
853 843 }
854 844 }
855 845
856 - // 2) Cień POD RAMĄ (pasek + treść) — ta sama grupa elementów = wspólny Z-order.
857 - let (shadow_location, shadow_size) = if self.wants_ssd() {
858 - let geometry = self.geometry().size;
859 - (
860 - Point::from((location.x, location.y - TITLEBAR_HEIGHT)),
861 - Size::from((geometry.w, geometry.h + TITLEBAR_HEIGHT)),
862 - )
863 - } else {
864 - (location, self.geometry().size)
865 - };
866 - if let Some(shadow) = shadow_element(renderer, shadow_location, scale, shadow_size) {
867 - elements.push(shadow);
868 - }
869 -
870 846 elements.into_iter().map(C::from).collect()
871 847 }
872 848 }
@@ -992,115 +968,6 @@ fn icon_for(button: TitlebarButton) -> icons::Icon {
992 968 }
993 969 }
994 970
995 -/// Cache skompilowanych programów cienia, kluczowany uchwytem kontekstu EGL.
996 -///
997 -/// PO CO klucz po kontekście: program GL żyje w konkretnym kontekście. Backend
998 -/// winit ma jeden kontekst, ale udev może mieć wiele GPU — kluczowanie chroni
999 -/// przed użyciem programu z niewłaściwego kontekstu.
1000 -fn shadow_programs() -> &'static Mutex<HashMap<usize, GlesPixelProgram>> {
1001 - static CACHE: OnceLock<Mutex<HashMap<usize, GlesPixelProgram>>> = OnceLock::new();
1002 - CACHE.get_or_init(|| Mutex::new(HashMap::new()))
1003 -}
1004 -
1005 -/// Kompiluje (raz na kontekst) i zwraca program cienia.
1006 -fn shadow_program(renderer: &mut GlesRenderer) -> Option<GlesPixelProgram> {
1007 - let key = renderer.egl_context().get_context_handle() as usize;
1008 -
1009 - if let Some(program) = shadow_programs()
1010 - .lock()
1011 - .expect("cache shaderów zatruty")
1012 - .get(&key)
1013 - {
1014 - return Some(program.clone());
1015 - }
1016 -
1017 - // Deklarujemy dodatkowe uniformy używane w shaderze (poza wbudowanymi
1018 - // `size` i `alpha`, które Smithay podłącza automatycznie).
1019 - let uniforms = [
1020 - UniformName::new("window_size", UniformType::_2f),
1021 - UniformName::new("radius", UniformType::_1f),
1022 - UniformName::new("pad", UniformType::_1f),
1023 - UniformName::new("offset", UniformType::_1f),
1024 - UniformName::new("strength", UniformType::_1f),
1025 - UniformName::new("shadow_color", UniformType::_3f),
1026 - ];
1027 -
1028 - match renderer.compile_custom_pixel_shader(SHADOW_FRAG, &uniforms) {
1029 - Ok(program) => {
1030 - shadow_programs()
1031 - .lock()
1032 - .expect("cache shaderów zatruty")
1033 - .insert(key, program.clone());
1034 - Some(program)
1035 - }
1036 - Err(err) => {
1037 - tracing::error!(
1038 - ?err,
1039 - "nie udało się skompilować shadera cienia (pomijam cień)"
1040 - );
1041 - None
1042 - }
1043 - }
1044 -}
1045 -
1046 -/// Buduje element cienia dla okna (o ile shader da się skompilować).
1047 -///
1048 -/// Obszar elementu = okno powiększone o margines cienia z każdej strony. Shader
1049 -/// liczy odległość od zaokrąglonego prostokąta okna i zamienia ją na miękką
1050 -/// alfę — patrz `shaders/shadow.frag`. Wszystkie parametry (kolor, rozmycie,
1051 -/// przesunięcie, siła) bierzemy z motywu, więc zmiana motywu działa od razu.
1052 -fn shadow_element(
1053 - renderer: &mut GlesRenderer,
1054 - location: Point<i32, Physical>,
1055 - scale: Scale<f64>,
1056 - window_size: Size<i32, Logical>,
1057 -) -> Option<WindowRenderElement> {
1058 - if window_size.w <= 0 || window_size.h <= 0 {
1059 - return None;
1060 - }
1061 -
1062 - let program = shadow_program(renderer)?;
1063 - let theme = theme::current();
1064 -
1065 - // Margines musi pomieścić rozmycie ORAZ przesunięcie sylwetki, inaczej cień
1066 - // zostałby obcięty na krawędzi elementu.
1067 - let margin = (theme.shadow_blur + theme.shadow_offset_y.abs()).ceil() as i32;
1068 - let margin = margin.max(1);
1069 -
1070 - // `PixelShaderElement` operuje w jednostkach logicznych — przeliczamy
1071 - // pozycję fizyczną na logiczną, a rozmiar okna już mamy logiczny.
1072 - let logical_loc = location.to_f64().to_logical(scale).to_i32_round::<i32>();
1073 - let area = Rectangle::new(
1074 - Point::from((logical_loc.x - margin, logical_loc.y - margin)),
1075 - Size::from((window_size.w + margin * 2, window_size.h + margin * 2)),
1076 - );
1077 -
1078 - let uniforms = vec![
1079 - Uniform::new("window_size", (window_size.w as f32, window_size.h as f32)),
1080 - Uniform::new("radius", theme.shadow_radius),
1081 - Uniform::new("pad", theme.shadow_blur),
1082 - Uniform::new("offset", theme.shadow_offset_y),
1083 - Uniform::new("strength", theme.shadow_opacity),
1084 - Uniform::new(
1085 - "shadow_color",
1086 - (
1087 - theme.shadow_color[0],
1088 - theme.shadow_color[1],
1089 - theme.shadow_color[2],
1090 - ),
1091 - ),
1092 - ];
1093 -
1094 - Some(WindowRenderElement::Pixel(PixelShaderElement::new(
1095 - program,
1096 - area,
1097 - None,
1098 - 1.0,
1099 - uniforms,
1100 - Kind::Unspecified,
1101 - )))
1102 -}
1103 -
1104 971 #[cfg(test)]
1105 972 mod tests {
1106 973 use super::*;