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::*;