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 |