← luna

Commit 9e898ed

20
files
+278
added
-12
deleted
@@ -89,7 +89,7 @@ libluna/
89 89 libluna-i18n/ # wielojęzyczność: każdy język w pliku i18n/<kod>.lang + przełączanie w runtime
90 90 libluna-render/ # graf sceny + backend software (tiny-skia + fontdue)
91 91 libluna-platform/ # Wayland: xdg-shell (CSD), wl_shm, wejście, pętla zdarzeń, motyw w Frame
92 - libluna-widgets/ # widgety: ViewSwitcher, NavList, Menu, Button, Switch, Slider, DropDown, HeaderBar, TabBar/TabView, BottomSheet, Pager, PathBar, SearchEntry, ItemView
92 + libluna-widgets/ # widgety: ViewSwitcher, NavList, Menu, Button, Switch, Slider, DropDown, HeaderBar, TabBar/TabView, BottomSheet, Pager, PathBar, SearchEntry, ItemView, ScrollView
93 93 apps/
94 94 luna-demo/ # demo aplikacji środowiska Luna; też render offline do PNG
95 95 templates/
@@ -196,7 +196,7 @@ cargo run -p libluna --example minimal
196 196 (`BottomSheet` z karuzelą stron `Pager`), zamykany klikiem w tło, przyciskiem `✕` lub
197 197 przeciągnięciem w dół. W karcie **Pliki** kliknij element **okruszków** (nawigacja „w górę”),
198 198 **pole wyszukiwania** (fokus) lub **plik/katalog** (zaznaczenie); przełącznik `Lista | Siatka`
199 - zmienia tryb `ItemView`. Klikaj segmenty `Jasny | Ciemny | Auto`, kropki
199 + zmienia tryb `ItemView`, a **rolka/przeciąganie paska** przewija listę. Klikaj segmenty `Jasny | Ciemny | Auto`, kropki
200 200 **koloru akcentu**, przełączniki OPCJA A / **Pasek dolny** / **Pasek boczny** i **przeciągaj**
201 201 suwak głośności oraz krawędź paska bocznego (150–460 px). Kontrolki okna `[—][□][✕]` działają
202 202 (minimalizacja/maksymalizacja/zamknięcie przez warstwę platformy).
@@ -224,7 +224,7 @@ w `~/.config/luna/theme.conf`.
224 224 Rozmiary i paddingi kontrolek nie są już stałymi w widgetach, lecz **tokenami**
225 225 (`Theme::metrics`) podzielonymi na sekcje per widget: `headerbar`, `button`, `switch`,
226 226 `slider`, `dropdown`, `menu`, `nav`, `segmented`, `tabs`, `sheet`, `pager`, `pathbar`, `entry`,
227 -`itemview`. Dzięki temu wygląd steruje **także
227 +`itemview`, `scrollview`. Dzięki temu wygląd steruje **także
228 228 geometrią**, nie tylko kolorami — np. `theme.metrics.switch.width` albo
229 229 `theme.metrics.headerbar.height`. Widgety czytają te wartości w `paint`/`layout`, więc
230 230 przyszły, instalowany styl może zmienić np. wysokość paska tytułu czy rozmiar przełącznika
@@ -296,8 +296,9 @@ XDG_CONFIG_HOME="$PWD/examples" cargo run -p luna-demo --bin render -- --lang fr
296 296 (pasek zakładek i model stron: wybór, dodawanie, zamykanie), `BottomSheet` (dolny panel
297 297 modalny z „grabberem”, przeciąganiem i zamykaniem), `Pager` (karuzela stron z kropkami),
298 298 `PathBar` (okruszki z separatorem `›`, wskaźnikiem `▾` i zwijaniem do `…`), `SearchEntry`
299 - (pole wyszukiwania: lupka, kursor, czyszczenie) i `ItemView` (pliki/katalogi w siatce lub
300 - liście, z zaznaczaniem i przewijaniem) oraz wspólny `PaintCtx`.
299 + (pole wyszukiwania: lupka, kursor, czyszczenie), `ItemView` (pliki/katalogi w siatce lub
300 + liście, z zaznaczaniem i przewijaniem) i `ScrollView` (pionowe przewijanie: rolka + „kciuk”)
301 + oraz wspólny `PaintCtx`.
301 302 - `luna-demo`: `HeaderBar` jako widget (tytuł + `[—][□][✕]` po prawej + treść: `◧`, `+`,
302 303 „Opcje ▾”, `☰`); **popover `☰`**, chowany i rozciągany (drag) panel boczny (`NavList`),
303 304 chowany **pasek dolny**, wybór motywu/widoku (`ViewSwitcher`), `Switch`, `Slider`, `DropDown`,
@@ -12,8 +12,8 @@ use libluna_i18n::{tr, Locale};
12 12 use libluna_render::TextEngine;
13 13 use libluna_widgets::{
14 14 BottomSheet, Button, ControlsLayout, ControlsSide, DropDown, HeaderBar, Item, ItemView, Menu,
15 - MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar, SearchEntry, Slider, Switch, Tab,
16 - TabBar, TabView, ViewMode, ViewSwitcher, WindowControl,
15 + MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar, ScrollView, SearchEntry, Slider,
16 + Switch, Tab, TabBar, TabView, ViewMode, ViewSwitcher, WindowControl,
17 17 };
18 18
19 19 const STATUS_H: f64 = 28.0;
@@ -78,6 +78,9 @@ pub struct Demo {
78 78 /// Widok elementów (pliki/katalogi) i podświetlony element.
79 79 pub items: ItemView,
80 80 pub items_hover: Option<usize>,
81 + /// Przewijanie listy plików i stan przeciągania paska.
82 + pub files_scroll: ScrollView,
83 + pub scroll_dragging: bool,
81 84 }
82 85
83 86 impl Default for Demo {
@@ -123,8 +126,16 @@ impl Default for Demo {
123 126 Item::new("Report").detail("PDF"),
124 127 Item::new("Notes").detail("TXT"),
125 128 Item::new("Budget").detail("XLSX"),
129 + Item::new("IMG_0001.JPG").detail("JPG"),
130 + Item::new("IMG_0002.JPG").detail("JPG"),
131 + Item::new("screenshot.png").detail("PNG"),
132 + Item::new("TODO.txt").detail("TXT"),
133 + Item::new("changelog.md").detail("MD"),
134 + Item::new("backup.tar").detail("TAR"),
126 135 ]),
127 136 items_hover: None,
137 + files_scroll: ScrollView::new(),
138 + scroll_dragging: false,
128 139 }
129 140 }
130 141 }
@@ -233,6 +244,7 @@ pub fn item_view(demo: &Demo) -> ItemView {
233 244 view.set_mode(demo.items.mode());
234 245 view.set_selected(demo.items.selected());
235 246 view.set_hovered(demo.items_hover);
247 + view.set_scroll(demo.items.scroll());
236 248 view
237 249 }
238 250
@@ -528,6 +540,9 @@ pub fn handle_events(
528 540 let header_rect = Rect::from_xywh(0.0, 0.0, size.width, theme.metrics.headerbar.height);
529 541 let header = header_bar();
530 542
543 + demo.files_scroll
544 + .set_content_height(demo.items.content_height(lay.items, theme));
545 +
531 546 for event in events {
532 547 match event {
533 548 Event::PointerButton(pe) if pe.button == PointerButton::Left => {
@@ -598,6 +613,10 @@ pub fn handle_events(
598 613 } else {
599 614 ViewMode::List
600 615 });
616 + demo.files_scroll
617 + .set_content_height(demo.items.content_height(lay.items, theme));
618 + demo.items.clamp_scroll(lay.items, theme);
619 + demo.files_scroll.set_offset(demo.items.scroll(), lay.items);
601 620 } else if let Some(i) = tabs_bar.index_at(lay.tabs, text, theme, p) {
602 621 demo.tabs.select(i);
603 622 } else if demo.search.contains(lay.search, p) {
@@ -607,6 +626,8 @@ pub fn handle_events(
607 626 demo.search.set_focused(true);
608 627 } else if let Some(i) = path.index_at(lay.path, text, theme, p) {
609 628 demo.path.truncate(i + 1);
629 + } else if demo.files_scroll.begin_drag(p, lay.items, theme) {
630 + demo.scroll_dragging = true;
610 631 } else if let Some(i) = items.index_at(lay.items, p, theme) {
611 632 demo.items.set_selected(Some(i));
612 633 } else if let Some(i) = nav.index_at(lay.sidebar, p, theme) {
@@ -635,6 +656,10 @@ pub fn handle_events(
635 656 demo.pager.end_drag();
636 657 demo.pager_dragging = false;
637 658 }
659 + if demo.scroll_dragging {
660 + demo.files_scroll.end_drag();
661 + demo.scroll_dragging = false;
662 + }
638 663 if demo.sheet.is_open() {
639 664 demo.sheet.end_drag(theme);
640 665 }
@@ -673,6 +698,10 @@ pub fn handle_events(
673 698 demo.tabs_hover = tabs_bar.index_at(lay.tabs, text, theme, position);
674 699 demo.path_hover = path.index_at(lay.path, text, theme, position);
675 700 demo.items_hover = items.index_at(lay.items, position, theme);
701 + if demo.scroll_dragging {
702 + demo.files_scroll.drag_to(position, lay.items, theme);
703 + demo.items.set_scroll(demo.files_scroll.offset());
704 + }
676 705 demo.search
677 706 .set_hover_clear(demo.search.clear_at(lay.search, theme, position));
678 707 demo.controls_hover = header.control_at(header_rect, theme, position);
@@ -690,10 +719,20 @@ pub fn handle_events(
690 719 demo.tabs_hover = None;
691 720 demo.path_hover = None;
692 721 demo.items_hover = None;
722 + demo.files_scroll.end_drag();
723 + demo.scroll_dragging = false;
693 724 demo.search.set_hover_clear(false);
694 725 demo.sheet.set_hover_close(false);
695 726 demo.controls_hover = None;
696 727 }
728 + Event::Scroll(se) => {
729 + if !demo.sheet.is_open() && lay.items.contains(se.position) {
730 + let step = theme.metrics.itemview.row_h;
731 + if demo.files_scroll.on_scroll(se.dy * step, lay.items) {
732 + demo.items.set_scroll(demo.files_scroll.offset());
733 + }
734 + }
735 + }
697 736 Event::Key(ke) if ke.state == ButtonState::Pressed => {
698 737 // Gdy pole wyszukiwania ma fokus, klawisze idą do niego.
699 738 if demo.search.is_focused() && handle_search_key(&mut demo.search, ke) {
@@ -1088,6 +1127,10 @@ fn draw_content(
1088 1127 search.paint(paint, lay.search);
1089 1128
1090 1129 item_view(demo).paint(paint, lay.items);
1130 +
1131 + let mut scrollbar = demo.files_scroll;
1132 + scrollbar.set_content_height(demo.items.content_height(lay.items, &theme));
1133 + scrollbar.paint_scrollbar(paint, lay.items);
1091 1134 }
1092 1135
1093 1136 fn draw_card(paint: &mut PaintCtx, card: Rect, title: &str) {
@@ -2,8 +2,8 @@
2 2 //! przez `ui::handle_events` i sprawdzają zmiany stanu.
3 3
4 4 use libluna_core::{
5 - Accent, ButtonState, Event, KeyEvent, Modifiers, Point, PointerButton, PointerEvent, Size,
6 - ThemePreference, ThemeSettings,
5 + Accent, ButtonState, Event, KeyEvent, Modifiers, Point, PointerButton, PointerEvent,
6 + ScrollEvent, Size, ThemePreference, ThemeSettings,
7 7 };
8 8 use libluna_render::TextEngine;
9 9 use libluna_widgets::ViewSwitcher;
@@ -165,6 +165,22 @@ fn clicking_file_item_selects_it() {
165 165 assert_eq!(demo.items.selected(), Some(1));
166 166 }
167 167
168 +#[test]
169 +fn scrolling_files_moves_offset() {
170 + let mut settings = ThemeSettings::default();
171 + let mut demo = Demo::default();
172 + let theme = settings.resolve(false);
173 + let text = TextEngine::system();
174 + let items = layout(&settings, &demo).items;
175 + let scroll = Event::Scroll(ScrollEvent {
176 + position: items.center(),
177 + dx: 0.0,
178 + dy: 3.0,
179 + });
180 + ui::handle_events(&mut settings, &[scroll], size(), &theme, &text, &mut demo);
181 + assert!(demo.items.scroll() > 0.0);
182 +}
183 +
168 184 #[test]
169 185 fn clicking_sidebar_icon_toggles_and_back() {
170 186 let mut settings = ThemeSettings::default();
@@ -28,5 +28,6 @@ pub use scale::Scale;
28 28 pub use style::{
29 29 BottomSheetMetrics, ButtonMetrics, DropDownMetrics, EntryMetrics, HeaderBarMetrics,
30 30 ItemViewMetrics, MenuMetrics, Metrics, NavMetrics, PagerMetrics, PathBarMetrics, Radius,
31 - SegmentedMetrics, SliderMetrics, Spacing, SwitchMetrics, TabsMetrics, Theme, Typography,
31 + ScrollViewMetrics, SegmentedMetrics, SliderMetrics, Spacing, SwitchMetrics, TabsMetrics, Theme,
32 + Typography,
32 33 };
@@ -387,6 +387,17 @@ pub struct ItemViewMetrics {
387 387 pub icon: f64,
388 388 }
389 389
390 +/// Geometria paska przewijania ([`ScrollView`](crate::style)).
391 +#[derive(Debug, Clone, Copy, PartialEq)]
392 +pub struct ScrollViewMetrics {
393 + /// Grubość paska.
394 + pub thickness: f64,
395 + /// Minimalna długość uchwytu („kciuka”).
396 + pub min_thumb: f64,
397 + /// Odstęp paska od krawędzi obszaru.
398 + pub margin: f64,
399 +}
400 +
390 401 /// Geometria kontrolek („component metrics”) — rozmiary i paddingi w pikselach logicznych.
391 402 ///
392 403 /// Widgety nie mają już własnych stałych rozmiarów: czytają je z motywu, dzięki czemu
@@ -408,6 +419,7 @@ pub struct Metrics {
408 419 pub pathbar: PathBarMetrics,
409 420 pub entry: EntryMetrics,
410 421 pub itemview: ItemViewMetrics,
422 + pub scrollview: ScrollViewMetrics,
411 423 }
412 424
413 425 impl Metrics {
@@ -497,6 +509,11 @@ impl Metrics {
497 509 row_h: 44.0,
498 510 icon: 28.0,
499 511 },
512 + scrollview: ScrollViewMetrics {
513 + thickness: 6.0,
514 + min_thumb: 24.0,
515 + margin: 2.0,
516 + },
500 517 }
501 518 }
502 519 }
@@ -22,6 +22,7 @@
22 22 //! - [`PathBar`] — pasek ścieżki (okruszki) z separatorami i zwijaniem w wielokropek.
23 23 //! - [`SearchEntry`] — pole tekstowe wyszukiwania (lupka, kursor, czyszczenie).
24 24 //! - [`ItemView`] — widok elementów (pliki/katalogi) w siatce lub liście.
25 +//! - [`ScrollView`] — przewijanie pionowe (rolka + „kciuk”).
25 26 //!
26 27 //! ```no_run
27 28 //! use libluna_widgets::{PaintCtx, ViewSwitcher};
@@ -42,6 +43,7 @@ pub mod menu;
42 43 pub mod nav_list;
43 44 pub mod pager;
44 45 pub mod path_bar;
46 +pub mod scroll_view;
45 47 pub mod search_entry;
46 48 pub mod slider;
47 49 pub mod switch;
@@ -59,6 +61,7 @@ pub use menu::{Menu, MenuEntry, MenuRow};
59 61 pub use nav_list::{NavEntry, NavList, NavRow};
60 62 pub use pager::Pager;
61 63 pub use path_bar::PathBar;
64 +pub use scroll_view::ScrollView;
62 65 pub use search_entry::SearchEntry;
63 66 pub use slider::Slider;
64 67 pub use switch::Switch;
65 68 new file mode 100644
@@ -0,0 +1,184 @@
1 +//! `ScrollView` — przewijanie zawartości (kołysko/„kciuk” + obsługa rolki i przeciągania).
2 +//!
3 +//! Widget immediate: trzyma przesunięcie i wysokość zawartości. Aplikacja rysuje swoją treść
4 +//! z uwzględnieniem [`ScrollView::offset`], przycina do obszaru, a widget rysuje pasek
5 +//! ([`ScrollView::paint_scrollbar`]) i obsługuje rolkę ([`ScrollView::on_scroll`]) oraz
6 +//! przeciąganie „kciuka” ([`ScrollView::begin_drag`] / [`ScrollView::drag_to`] / [`ScrollView::end_drag`]).
7 +
8 +use libluna_core::{Point, Rect, Theme};
9 +
10 +use crate::ctx::PaintCtx;
11 +
12 +/// Przewijanie pionowe.
13 +#[derive(Debug, Clone, Copy, Default)]
14 +pub struct ScrollView {
15 + offset: f64,
16 + content_height: f64,
17 + /// Przesunięcie wskaźnika (y) względem początku „kciuka” na starcie przeciągania.
18 + drag_grab: Option<f64>,
19 +}
20 +
21 +impl ScrollView {
22 + pub fn new() -> Self {
23 + Self::default()
24 + }
25 +
26 + pub fn content_height(&self) -> f64 {
27 + self.content_height
28 + }
29 +
30 + pub fn set_content_height(&mut self, height: f64) {
31 + self.content_height = height.max(0.0);
32 + }
33 +
34 + pub fn offset(&self) -> f64 {
35 + self.offset
36 + }
37 +
38 + /// Maksymalne przesunięcie (0, gdy wszystko się mieści).
39 + pub fn max_offset(&self, viewport: Rect) -> f64 {
40 + (self.content_height - viewport.height()).max(0.0)
41 + }
42 +
43 + pub fn is_scrollable(&self, viewport: Rect) -> bool {
44 + self.max_offset(viewport) > 0.5
45 + }
46 +
47 + /// Ustawia przesunięcie (przycięte do zakresu).
48 + pub fn set_offset(&mut self, offset: f64, viewport: Rect) {
49 + self.offset = offset.clamp(0.0, self.max_offset(viewport));
50 + }
51 +
52 + /// Przewija o `dy`; zwraca `true`, jeśli zawartość da się przewijać.
53 + pub fn on_scroll(&mut self, dy: f64, viewport: Rect) -> bool {
54 + if !self.is_scrollable(viewport) {
55 + return false;
56 + }
57 + self.set_offset(self.offset + dy, viewport);
58 + true
59 + }
60 +
61 + /// Prostokąt „kciuka” paska przewijania (albo `None`, gdy nie ma czego przewijać).
62 + pub fn thumb_rect(&self, viewport: Rect, theme: &Theme) -> Option<Rect> {
63 + if !self.is_scrollable(viewport) {
64 + return None;
65 + }
66 + let m = theme.metrics.scrollview;
67 + let track_h = viewport.height();
68 + let frac = (viewport.height() / self.content_height).clamp(0.0, 1.0);
69 + let thumb_h = (track_h * frac).clamp(m.min_thumb, track_h);
70 + let max = self.max_offset(viewport);
71 + let t = if max > 0.0 { self.offset / max } else { 0.0 };
72 + let y = viewport.y() + t * (track_h - thumb_h);
73 + Some(Rect::from_xywh(
74 + viewport.right() - m.margin - m.thickness,
75 + y,
76 + m.thickness,
77 + thumb_h,
78 + ))
79 + }
80 +
81 + /// Czy punkt trafia w „kciuk” (z tolerancją grubości).
82 + pub fn thumb_at(&self, viewport: Rect, theme: &Theme, p: Point) -> bool {
83 + self.thumb_rect(viewport, theme)
84 + .is_some_and(|t| t.inflate(luna_insets(theme)).contains(p))
85 + }
86 +
87 + /// Rozpoczyna przeciąganie „kciuka” (jeśli punkt w niego trafia).
88 + pub fn begin_drag(&mut self, p: Point, viewport: Rect, theme: &Theme) -> bool {
89 + match self.thumb_rect(viewport, theme) {
90 + Some(t) if t.contains(p) => {
91 + self.drag_grab = Some(p.y - t.y());
92 + true
93 + }
94 + _ => false,
95 + }
96 + }
97 +
98 + /// Aktualizuje przeciąganie „kciuka”.
99 + pub fn drag_to(&mut self, p: Point, viewport: Rect, theme: &Theme) {
100 + let Some(grab) = self.drag_grab else { return };
101 + let Some(thumb) = self.thumb_rect(viewport, theme) else {
102 + return;
103 + };
104 + let range = viewport.height() - thumb.height();
105 + if range > 0.0 {
106 + let t = ((p.y - grab - viewport.y()) / range).clamp(0.0, 1.0);
107 + self.offset = t * self.max_offset(viewport);
108 + }
109 + }
110 +
111 + /// Kończy przeciąganie.
112 + pub fn end_drag(&mut self) {
113 + self.drag_grab = None;
114 + }
115 +
116 + /// Rysuje pasek przewijania (jeśli jest co przewijać).
117 + pub fn paint_scrollbar(&self, ctx: &mut PaintCtx, viewport: Rect) {
118 + let theme = *ctx.theme;
119 + if let Some(t) = self.thumb_rect(viewport, &theme) {
120 + ctx.scene.fill_rounded_rect(
121 + t,
122 + t.width() / 2.0,
123 + theme.colors.dim_text.scale_alpha(0.45),
124 + );
125 + }
126 + }
127 +}
128 +
129 +fn luna_insets(theme: &Theme) -> libluna_core::Insets {
130 + libluna_core::Insets::new(0.0, theme.metrics.scrollview.thickness, 0.0, 4.0)
131 +}
132 +
133 +#[cfg(test)]
134 +mod tests {
135 + use super::*;
136 +
137 + fn viewport() -> Rect {
138 + Rect::from_xywh(0.0, 0.0, 200.0, 100.0)
139 + }
140 +
141 + #[test]
142 + fn not_scrollable_when_content_fits() {
143 + let mut s = ScrollView::new();
144 + s.set_content_height(80.0);
145 + assert!(!s.is_scrollable(viewport()));
146 + assert!(!s.on_scroll(50.0, viewport()));
147 + assert!(s.thumb_rect(viewport(), &Theme::light()).is_none());
148 + }
149 +
150 + #[test]
151 + fn offset_is_clamped() {
152 + let mut s = ScrollView::new();
153 + s.set_content_height(300.0);
154 + s.set_offset(999.0, viewport());
155 + assert_eq!(s.offset(), 200.0);
156 + s.set_offset(-5.0, viewport());
157 + assert_eq!(s.offset(), 0.0);
158 + }
159 +
160 + #[test]
161 + fn thumb_shrinks_with_content() {
162 + let theme = Theme::light();
163 + let mut s = ScrollView::new();
164 + s.set_content_height(200.0); // viewport 100 → kciuk ~ połowa toru
165 + let t = s.thumb_rect(viewport(), &theme).unwrap();
166 + assert!((t.height() - 50.0).abs() < 1e-9);
167 + // Na dole kciuk dojeżdża do końca toru.
168 + s.set_offset(100.0, viewport());
169 + let t2 = s.thumb_rect(viewport(), &theme).unwrap();
170 + assert!((t2.bottom() - viewport().bottom()).abs() < 1e-9);
171 + }
172 +
173 + #[test]
174 + fn drag_moves_thumb_to_bottom() {
175 + let theme = Theme::light();
176 + let mut s = ScrollView::new();
177 + s.set_content_height(200.0);
178 + let t = s.thumb_rect(viewport(), &theme).unwrap();
179 + assert!(s.begin_drag(t.center(), viewport(), &theme));
180 + s.drag_to(Point::new(t.center().x, 500.0), viewport(), &theme);
181 + assert_eq!(s.offset(), s.max_offset(viewport()));
182 + s.end_drag();
183 + }
184 +}
0 185 Binary files a/docs/images/demo-montage.png and b/docs/images/demo-montage.png differ
1 186 Binary files a/docs/images/demo-step-1.png and b/docs/images/demo-step-1.png differ
2 187 Binary files a/docs/images/demo-step-10.png and b/docs/images/demo-step-10.png differ
3 188 Binary files a/docs/images/demo-step-2.png and b/docs/images/demo-step-2.png differ
4 189 Binary files a/docs/images/demo-step-3.png and b/docs/images/demo-step-3.png differ
5 190 Binary files a/docs/images/demo-step-4.png and b/docs/images/demo-step-4.png differ
6 191 Binary files a/docs/images/demo-step-5.png and b/docs/images/demo-step-5.png differ
7 192 Binary files a/docs/images/demo-step-6.png and b/docs/images/demo-step-6.png differ
8 193 Binary files a/docs/images/demo-step-7.png and b/docs/images/demo-step-7.png differ
9 194 Binary files a/docs/images/demo-step-8.png and b/docs/images/demo-step-8.png differ
10 195 Binary files a/docs/images/settings-de-dark.png and b/docs/images/settings-de-dark.png differ
11 196 Binary files a/docs/images/settings-light.png and b/docs/images/settings-light.png differ
@@ -37,8 +37,8 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
37 37 `Menu` (popover `☰`), `Button` (warianty + chevron), `Switch`, `Slider`, `DropDown`
38 38 (rozwijany przez `Menu`), `HeaderBar` (pasek tytułu z kontrolkami okna), `TabBar`/`TabView`
39 39 (zakładki: pasek + model stron), `BottomSheet` (dolny panel modalny), `Pager` (karuzela stron),
40 - `PathBar` (okruszki), `SearchEntry` (pole wyszukiwania) i `ItemView` (pliki/katalogi: siatka
41 - lub lista), oraz wspólny `PaintCtx`; testy hit-testingu bez okna.
40 + `PathBar` (okruszki), `SearchEntry` (pole wyszukiwania), `ItemView` (pliki/katalogi: siatka
41 + lub lista) i `ScrollView` (przewijanie), oraz wspólny `PaintCtx`; testy hit-testingu bez okna.
42 42 - `libluna-platform`: realne akcje okna z `Frame` (`close`/`minimize`/`toggle_maximize`).
43 43 - Demo używa widgetów zamiast ręcznego rysowania (pasek boczny, wybór motywu/widoku, menu `☰`,
44 44 przycisk „Opcje”, przełączniki, suwak, wybór języka, pasek tytułu, zakładki `TabBar`/`TabView`).
@@ -99,6 +99,7 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
99 99 | PathBar (okruszki) | `libluna-widgets` | Files |
100 100 | SearchEntry / SearchBar | `libluna-widgets` | Files |
101 101 | GridView / ListView | `libluna-widgets` (`ItemView`) | Files |
102 +| ScrollView | `libluna-widgets` | obie |
102 103 | Card + ActionRow | `libluna-adwaita` | Settings |
103 104 | Switch | `libluna-widgets` | Settings |
104 105 | Slider (+ etykieta %) | `libluna-widgets` | Settings |