← luna

Commit 45c3fa8

8
files
+278
added
-104
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, ScrollView, Stepper, CheckButton, RadioGroup
92 + libluna-widgets/ # widgety: ViewSwitcher, NavList, Menu, Button, Switch, Slider, DropDown, HeaderBar, TabBar/TabView, BottomSheet, Pager, PathBar, SearchEntry, ItemView, ScrollView, Stepper, CheckButton, RadioGroup, Card, ActionRow
93 93 apps/
94 94 luna-demo/ # demo aplikacji środowiska Luna; też render offline do PNG
95 95 templates/
@@ -226,7 +226,7 @@ w `~/.config/luna/theme.conf`.
226 226 Rozmiary i paddingi kontrolek nie są już stałymi w widgetach, lecz **tokenami**
227 227 (`Theme::metrics`) podzielonymi na sekcje per widget: `headerbar`, `button`, `switch`,
228 228 `slider`, `dropdown`, `menu`, `nav`, `segmented`, `tabs`, `sheet`, `pager`, `pathbar`, `entry`,
229 -`itemview`, `scrollview`, `stepper`, `check`, `radio`. Dzięki temu wygląd steruje **także
229 +`itemview`, `scrollview`, `stepper`, `check`, `radio`, `card`, `row`. Dzięki temu wygląd steruje **także
230 230 geometrią**, nie tylko kolorami — np. `theme.metrics.switch.width` albo
231 231 `theme.metrics.headerbar.height`. Widgety czytają te wartości w `paint`/`layout`, więc
232 232 przyszły, instalowany styl może zmienić np. wysokość paska tytułu czy rozmiar przełącznika
@@ -300,10 +300,11 @@ XDG_CONFIG_HOME="$PWD/examples" cargo run -p luna-demo --bin render -- --lang fr
300 300 `PathBar` (okruszki z separatorem `›`, wskaźnikiem `▾` i zwijaniem do `…`), `SearchEntry`
301 301 (pole wyszukiwania: lupka, kursor, czyszczenie), `ItemView` (pliki/katalogi w siatce lub
302 302 liście, z zaznaczaniem i przewijaniem) i `ScrollView` (pionowe przewijanie: rolka + „kciuk”)
303 - oraz `Stepper`, `CheckButton` i `RadioGroup` (krokowiec, pole wyboru, wybór jednokrotny)
304 - oraz wspólny `PaintCtx`.
303 + oraz `Stepper`, `CheckButton` i `RadioGroup` (krokowiec, pole wyboru, wybór jednokrotny),
304 + a także `Card` (karta z nagłówkiem) i `ActionRow` (wiersz ustawień) oraz wspólny `PaintCtx`.
305 305 - `luna-demo`: `HeaderBar` jako widget (tytuł + `[—][□][✕]` po prawej + treść: `◧`, `+`,
306 - „Opcje ▾”, `☰`); **popover `☰`**, chowany i rozciągany (drag) panel boczny (`NavList`),
306 + „Opcje ▾”, `☰`); karty i wiersze ustawień rysowane widgetami (`Card`, `ActionRow`);
307 + **popover `☰`**, chowany i rozciągany (drag) panel boczny (`NavList`),
307 308 chowany **pasek dolny**, wybór motywu/widoku (`ViewSwitcher`), `Switch`, `Slider`, `DropDown`,
308 309 **zakładki** (`TabBar` + `TabView`) oraz **dolny panel** (`BottomSheet` z karuzelą `Pager`,
309 310 otwierany z menu `☰` → „O aplikacji”), a także kartę **Pliki** (`PathBar` + `SearchEntry` +
@@ -11,14 +11,16 @@ use libluna_core::{
11 11 use libluna_i18n::{tr, Locale};
12 12 use libluna_render::TextEngine;
13 13 use libluna_widgets::{
14 - BottomSheet, Button, CheckButton, ControlsLayout, ControlsSide, DropDown, HeaderBar, Item,
15 - ItemView, Menu, MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar, RadioGroup, ScrollView,
16 - SearchEntry, Slider, Stepper, StepperSide, Switch, Tab, TabBar, TabView, ViewMode,
17 - ViewSwitcher, WindowControl,
14 + ActionRow, BottomSheet, Button, Card, CheckButton, ControlsLayout, ControlsSide, DropDown,
15 + HeaderBar, Item, ItemView, Menu, MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar,
16 + RadioGroup, ScrollView, SearchEntry, Slider, Stepper, StepperSide, Switch, Tab, TabBar,
17 + TabView, ViewMode, ViewSwitcher, WindowControl,
18 18 };
19 19
20 20 const STATUS_H: f64 = 28.0;
21 21 const PAD: f64 = 12.0;
22 +/// Wysokość wiersza ustawień w demo.
23 +const ROW_H: f64 = 56.0;
22 24 /// Indeks pozycji „O aplikacji” w menu „☰” (otwiera dolny panel).
23 25 const ABOUT_ENTRY: usize = 4;
24 26
@@ -379,8 +381,8 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
379 381 (area.width() - 2.0 * lg).max(0.0),
380 382 208.0,
381 383 );
382 - let header_h = 40.0;
383 - let row_h = 56.0;
384 + let header_h = theme.metrics.card.header;
385 + let row_h = ROW_H;
384 386 let row = |i: usize| {
385 387 Rect::from_xywh(
386 388 card.x(),
@@ -1048,63 +1050,28 @@ fn draw_content(
1048 1050 (area.width() - 2.0 * lg).max(0.0),
1049 1051 208.0,
1050 1052 );
1051 - draw_card(paint, card, &tr("Appearance"));
1053 + Card::new(tr("Appearance")).paint(paint, card);
1052 1054
1053 - paint.label_left(
1054 - card.x() + md,
1055 - lay.theme_switcher.center().y,
1056 - &tr("Theme"),
1057 - body,
1058 - weight::NORMAL,
1059 - theme.colors.text,
1060 - );
1055 + draw_row_label(paint, card, lay.theme_switcher, &tr("Theme"));
1061 1056 theme_switcher(preference, demo.theme_hover).paint(paint, lay.theme_switcher);
1062 1057
1063 - paint.label_left(
1064 - card.x() + md,
1065 - lay.accents[0].center().y,
1066 - &tr("Accent color"),
1067 - body,
1068 - weight::NORMAL,
1069 - theme.colors.text,
1070 - );
1058 + draw_row_label(paint, card, lay.accents[0], &tr("Accent color"));
1071 1059 draw_accent_dots(paint, &lay.accents, accent);
1072 1060
1073 - paint.label_left(
1074 - card.x() + md,
1075 - lay.language.center().y,
1076 - &tr("Language"),
1077 - body,
1078 - weight::NORMAL,
1079 - theme.colors.text,
1080 - );
1061 + draw_row_label(paint, card, lay.language, &tr("Language"));
1081 1062 let codes = libluna_i18n::languages();
1082 1063 language_dropdown(&codes).paint(paint, lay.language);
1083 1064
1084 1065 // --- Karta 2: Ustawienia ---
1085 1066 let card2 = Rect::from_xywh(card.x(), card.bottom() + lg, card.width(), 320.0);
1086 - draw_card(paint, card2, &tr("Settings"));
1067 + Card::new(tr("Settings")).paint(paint, card2);
1087 1068
1088 - paint.label_left(
1089 - card2.x() + md,
1090 - lay.option_a.center().y,
1091 - &tr("Option A"),
1092 - body,
1093 - weight::NORMAL,
1094 - theme.colors.text,
1095 - );
1069 + draw_row_label(paint, card2, lay.option_a, &tr("Option A"));
1096 1070 let mut sw = Switch::new(demo.option_a);
1097 1071 sw.set_hovered(demo.switch_hover);
1098 1072 sw.paint(paint, lay.option_a);
1099 1073
1100 - paint.label_left(
1101 - card2.x() + md,
1102 - lay.volume_track.center().y,
1103 - &tr("Volume level"),
1104 - body,
1105 - weight::NORMAL,
1106 - theme.colors.text,
1107 - );
1074 + draw_row_label(paint, card2, lay.volume_track, &tr("Volume level"));
1108 1075 Slider::new(demo.volume).paint(paint, lay.volume_track);
1109 1076 paint.label_right(
1110 1077 lay.volume_track.x() - 12.0,
@@ -1115,39 +1082,18 @@ fn draw_content(
1115 1082 theme.colors.text,
1116 1083 );
1117 1084
1118 - paint.label_left(
1119 - card2.x() + md,
1120 - lay.view_switcher.center().y,
1121 - &tr("View"),
1122 - body,
1123 - weight::NORMAL,
1124 - theme.colors.text,
1125 - );
1085 + draw_row_label(paint, card2, lay.view_switcher, &tr("View"));
1126 1086 view_switcher(demo.view_mode, demo.view_hover).paint(paint, lay.view_switcher);
1127 1087
1128 - paint.label_left(
1129 - card2.x() + md,
1130 - lay.bottom_bar.center().y,
1131 - &tr("Bottom bar"),
1132 - body,
1133 - weight::NORMAL,
1134 - theme.colors.text,
1135 - );
1088 + draw_row_label(paint, card2, lay.bottom_bar, &tr("Bottom bar"));
1136 1089 Switch::new(demo.show_bottom_bar).paint(paint, lay.bottom_bar);
1137 1090
1138 - paint.label_left(
1139 - card2.x() + md,
1140 - lay.sidebar_toggle.center().y,
1141 - &tr("Sidebar"),
1142 - body,
1143 - weight::NORMAL,
1144 - theme.colors.text,
1145 - );
1091 + draw_row_label(paint, card2, lay.sidebar_toggle, &tr("Sidebar"));
1146 1092 Switch::new(demo.sidebar_visible).paint(paint, lay.sidebar_toggle);
1147 1093
1148 1094 // --- Karta 3: Zakładki ---
1149 1095 let card3 = Rect::from_xywh(card.x(), card2.bottom() + lg, card.width(), 152.0);
1150 - draw_card(paint, card3, &tr("Tabs"));
1096 + Card::new(tr("Tabs")).paint(paint, card3);
1151 1097
1152 1098 // Wiersz 0: pasek zakładek (widget TabBar).
1153 1099 tab_bar(demo).paint(paint, lay.tabs);
@@ -1173,7 +1119,7 @@ fn draw_content(
1173 1119
1174 1120 // --- Karta 4: Pliki (PathBar + SearchEntry + ItemView) ---
1175 1121 let card4 = Rect::from_xywh(card.x(), card3.bottom() + lg, card.width(), 196.0);
1176 - draw_card(paint, card4, &tr("Files"));
1122 + Card::new(tr("Files")).paint(paint, card4);
1177 1123 path_bar(demo).paint(paint, lay.path);
1178 1124
1179 1125 let mut search = demo.search.clone();
@@ -1187,27 +1133,18 @@ fn draw_content(
1187 1133 scrollbar.paint_scrollbar(paint, lay.items);
1188 1134 }
1189 1135
1190 -fn draw_card(paint: &mut PaintCtx, card: Rect, title: &str) {
1136 +/// Rysuje etykietę wiersza ustawień (`ActionRow`) z miejscem na kontrolkę po prawej.
1137 +fn draw_row_label(paint: &mut PaintCtx, container: Rect, control: Rect, title: &str) {
1191 1138 let theme = *paint.theme;
1192 - paint
1193 - .scene
1194 - .fill_rounded_rect(card, theme.radius.large, theme.colors.card_bg);
1195 - paint
1196 - .scene
1197 - .stroke_rounded_rect(card, theme.radius.large, 1.0, theme.colors.border);
1198 - let header_h = 40.0;
1199 - paint.label_left(
1200 - card.x() + theme.spacing.md,
1201 - card.y() + header_h / 2.0,
1202 - title,
1203 - theme.typography.body.size,
1204 - weight::BOLD,
1205 - theme.colors.text,
1206 - );
1207 - paint.scene.fill_rect(
1208 - Rect::from_xywh(card.x(), card.y() + header_h, card.width(), 1.0),
1209 - theme.colors.border,
1139 + let md = theme.spacing.md;
1140 + let row = Rect::from_xywh(
1141 + container.x(),
1142 + control.center().y - ROW_H / 2.0,
1143 + container.width(),
1144 + ROW_H,
1210 1145 );
1146 + let suffix = (container.width() - (control.x() - container.x()) - md).max(0.0);
1147 + ActionRow::new(title).paint(paint, row, suffix);
1211 1148 }
1212 1149
1213 1150 fn draw_accent_dots(paint: &mut PaintCtx, dots: &[Rect; 9], accent: Accent) {
@@ -26,8 +26,9 @@ pub use event::{
26 26 pub use geometry::{Insets, Point, Rect, Size};
27 27 pub use scale::Scale;
28 28 pub use style::{
29 - BottomSheetMetrics, ButtonMetrics, CheckMetrics, DropDownMetrics, EntryMetrics,
30 - HeaderBarMetrics, ItemViewMetrics, MenuMetrics, Metrics, NavMetrics, PagerMetrics,
31 - PathBarMetrics, RadioMetrics, Radius, ScrollViewMetrics, SegmentedMetrics, SliderMetrics,
32 - Spacing, StepperMetrics, SwitchMetrics, TabsMetrics, Theme, Typography,
29 + ActionRowMetrics, BottomSheetMetrics, ButtonMetrics, CardMetrics, CheckMetrics,
30 + DropDownMetrics, EntryMetrics, HeaderBarMetrics, ItemViewMetrics, MenuMetrics, Metrics,
31 + NavMetrics, PagerMetrics, PathBarMetrics, RadioMetrics, Radius, ScrollViewMetrics,
32 + SegmentedMetrics, SliderMetrics, Spacing, StepperMetrics, SwitchMetrics, TabsMetrics, Theme,
33 + Typography,
33 34 };
@@ -429,6 +429,24 @@ pub struct RadioMetrics {
429 429 pub row_h: f64,
430 430 }
431 431
432 +/// Geometria karty ([`Card`](crate::style)).
433 +#[derive(Debug, Clone, Copy, PartialEq)]
434 +pub struct CardMetrics {
435 + /// Wysokość nagłówka karty.
436 + pub header: f64,
437 + /// Padding wewnętrzny (nagłówek).
438 + pub pad: f64,
439 +}
440 +
441 +/// Geometria wiersza akcji ([`ActionRow`](crate::style)).
442 +#[derive(Debug, Clone, Copy, PartialEq)]
443 +pub struct ActionRowMetrics {
444 + /// Padding poziomy (lewy i prawy).
445 + pub pad_x: f64,
446 + /// Odstęp między tytułem a podtytułem.
447 + pub gap: f64,
448 +}
449 +
432 450 /// Geometria kontrolek („component metrics”) — rozmiary i paddingi w pikselach logicznych.
433 451 ///
434 452 /// Widgety nie mają już własnych stałych rozmiarów: czytają je z motywu, dzięki czemu
@@ -454,6 +472,8 @@ pub struct Metrics {
454 472 pub stepper: StepperMetrics,
455 473 pub check: CheckMetrics,
456 474 pub radio: RadioMetrics,
475 + pub card: CardMetrics,
476 + pub row: ActionRowMetrics,
457 477 }
458 478
459 479 impl Metrics {
@@ -562,6 +582,14 @@ impl Metrics {
562 582 gap: 10.0,
563 583 row_h: 30.0,
564 584 },
585 + card: CardMetrics {
586 + header: 40.0,
587 + pad: 12.0,
588 + },
589 + row: ActionRowMetrics {
590 + pad_x: 12.0,
591 + gap: 8.0,
592 + },
565 593 }
566 594 }
567 595 }
568 596 new file mode 100644
@@ -0,0 +1,109 @@
1 +//! `ActionRow` — wiersz ustawień: tytuł (i podtytuł) z lewej, obszar na kontrolkę z prawej.
2 +//!
3 +//! Widget immediate: rysuje tekst wiersza i zwraca [`Rect`] na kontrolkę (`suffix`), którą
4 +//! umieszcza aplikacja. Padding pochodzi z tokenów motywu ([`ActionRowMetrics`](libluna_core::ActionRowMetrics)).
5 +
6 +use libluna_core::style::weight;
7 +use libluna_core::{Rect, Theme};
8 +
9 +use crate::ctx::PaintCtx;
10 +
11 +/// Wiersz akcji (ustawienie).
12 +#[derive(Debug, Clone, Default)]
13 +pub struct ActionRow {
14 + title: String,
15 + subtitle: Option<String>,
16 +}
17 +
18 +impl ActionRow {
19 + pub fn new(title: impl Into<String>) -> Self {
20 + Self {
21 + title: title.into(),
22 + subtitle: None,
23 + }
24 + }
25 +
26 + pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
27 + self.subtitle = Some(subtitle.into());
28 + self
29 + }
30 +
31 + pub fn title(&self) -> &str {
32 + &self.title
33 + }
34 +
35 + pub fn subtitle_text(&self) -> Option<&str> {
36 + self.subtitle.as_deref()
37 + }
38 +
39 + /// Obszar na kontrolkę (prawa strona) o zadanej szerokości.
40 + pub fn suffix_rect(&self, rect: Rect, theme: &Theme, suffix_width: f64) -> Rect {
41 + let pad = theme.metrics.row.pad_x;
42 + Rect::from_xywh(
43 + rect.right() - pad - suffix_width,
44 + rect.y(),
45 + suffix_width,
46 + rect.height(),
47 + )
48 + }
49 +
50 + /// Rysuje tytuł (i podtytuł); zwraca obszar na kontrolkę.
51 + pub fn paint(&self, ctx: &mut PaintCtx, rect: Rect, suffix_width: f64) -> Rect {
52 + let theme = *ctx.theme;
53 + let pad = theme.metrics.row.pad_x;
54 + let x = rect.x() + pad;
55 + match &self.subtitle {
56 + Some(subtitle) => {
57 + let gap = theme.metrics.row.gap;
58 + ctx.label_left(
59 + x,
60 + rect.center().y - gap / 2.0 - 2.0,
61 + &self.title,
62 + theme.typography.body.size,
63 + weight::NORMAL,
64 + theme.colors.text,
65 + );
66 + ctx.label_left(
67 + x,
68 + rect.center().y + gap / 2.0 + 5.0,
69 + subtitle,
70 + theme.typography.caption.size,
71 + weight::NORMAL,
72 + theme.colors.dim_text,
73 + );
74 + }
75 + None => ctx.label_left(
76 + x,
77 + rect.center().y,
78 + &self.title,
79 + theme.typography.body.size,
80 + weight::NORMAL,
81 + theme.colors.text,
82 + ),
83 + }
84 + self.suffix_rect(rect, &theme, suffix_width)
85 + }
86 +}
87 +
88 +#[cfg(test)]
89 +mod tests {
90 + use super::*;
91 +
92 + #[test]
93 + fn suffix_is_right_aligned_with_padding() {
94 + let theme = Theme::light();
95 + let row = ActionRow::new("Motyw");
96 + let rect = Rect::from_xywh(0.0, 0.0, 300.0, 56.0);
97 + let s = row.suffix_rect(rect, &theme, 120.0);
98 + assert!((s.right() - (rect.right() - theme.metrics.row.pad_x)).abs() < 1e-9);
99 + assert!((s.width() - 120.0).abs() < 1e-9);
100 + assert!((s.height() - rect.height()).abs() < 1e-9);
101 + }
102 +
103 + #[test]
104 + fn subtitle_builder() {
105 + let row = ActionRow::new("Sieć").subtitle("Włączone");
106 + assert_eq!(row.title(), "Sieć");
107 + assert_eq!(row.subtitle_text(), Some("Włączone"));
108 + }
109 +}
0 110 new file mode 100644
@@ -0,0 +1,91 @@
1 +//! `Card` — karta (powierzchnia podniesiona) z nagłówkiem i obszarem treści.
2 +//!
3 +//! Widget immediate: rysuje tło, obramowanie, nagłówek i separator, a [`Card::paint`] zwraca
4 +//! obszar zawartości, w którym aplikacja umieszcza własne wiersze.
5 +
6 +use libluna_core::style::weight;
7 +use libluna_core::{Rect, Theme};
8 +
9 +use crate::ctx::PaintCtx;
10 +
11 +/// Karta z nagłówkiem.
12 +#[derive(Debug, Clone, Default)]
13 +pub struct Card {
14 + title: String,
15 +}
16 +
17 +impl Card {
18 + pub fn new(title: impl Into<String>) -> Self {
19 + Self {
20 + title: title.into(),
21 + }
22 + }
23 +
24 + pub fn title(&self) -> &str {
25 + &self.title
26 + }
27 +
28 + /// Wysokość nagłówka (z metryk).
29 + pub fn header_height(&self, theme: &Theme) -> f64 {
30 + theme.metrics.card.header
31 + }
32 +
33 + /// Obszar zawartości (poniżej nagłówka i separatora).
34 + pub fn content_rect(&self, rect: Rect, theme: &Theme) -> Rect {
35 + let h = self.header_height(theme);
36 + Rect::from_xywh(
37 + rect.x(),
38 + rect.y() + h,
39 + rect.width(),
40 + (rect.height() - h).max(0.0),
41 + )
42 + }
43 +
44 + /// Rysuje kartę; zwraca obszar zawartości.
45 + pub fn paint(&self, ctx: &mut PaintCtx, rect: Rect) -> Rect {
46 + let theme = *ctx.theme;
47 + ctx.scene
48 + .fill_rounded_rect(rect, theme.radius.large, theme.colors.card_bg);
49 + ctx.scene
50 + .stroke_rounded_rect(rect, theme.radius.large, 1.0, theme.colors.border);
51 +
52 + let h = self.header_height(&theme);
53 + ctx.label_left(
54 + rect.x() + theme.metrics.card.pad,
55 + rect.y() + h / 2.0,
56 + &self.title,
57 + theme.typography.body.size,
58 + weight::BOLD,
59 + theme.colors.text,
60 + );
61 + ctx.scene.fill_rect(
62 + Rect::from_xywh(rect.x(), rect.y() + h, rect.width(), 1.0),
63 + theme.colors.border,
64 + );
65 + self.content_rect(rect, &theme)
66 + }
67 +}
68 +
69 +#[cfg(test)]
70 +mod tests {
71 + use super::*;
72 +
73 + #[test]
74 + fn content_is_below_header() {
75 + let theme = Theme::light();
76 + let card = Card::new("Wygląd");
77 + let rect = Rect::from_xywh(0.0, 0.0, 300.0, 200.0);
78 + let content = card.content_rect(rect, &theme);
79 + assert!((content.y() - (rect.y() + theme.metrics.card.header)).abs() < 1e-9);
80 + assert!((content.height() - (200.0 - theme.metrics.card.header)).abs() < 1e-9);
81 + }
82 +
83 + #[test]
84 + fn header_height_from_metrics() {
85 + let theme = Theme::light();
86 + assert_eq!(
87 + Card::new("X").header_height(&theme),
88 + theme.metrics.card.header
89 + );
90 + }
91 +}
@@ -26,6 +26,8 @@
26 26 //! - [`Stepper`] — krokowiec (`−` wartość `+`).
27 27 //! - [`CheckButton`] — pole wyboru (checkbox) z etykietą.
28 28 //! - [`RadioGroup`] — lista opcji z pojedynczym wyborem.
29 +//! - [`Card`] — karta z nagłówkiem i obszarem treści.
30 +//! - [`ActionRow`] — wiersz ustawień (tytuł/podtytuł + obszar na kontrolkę).
29 31 //!
30 32 //! ```no_run
31 33 //! use libluna_widgets::{PaintCtx, ViewSwitcher};
@@ -35,8 +37,10 @@
35 37 //! # }
36 38 //! ```
37 39
40 +pub mod action_row;
38 41 pub mod bottom_sheet;
39 42 pub mod button;
43 +pub mod card;
40 44 pub mod check_button;
41 45 pub mod ctx;
42 46 pub mod dropdown;
@@ -57,8 +61,10 @@ pub mod tab_bar;
57 61 pub mod tab_view;
58 62 pub mod view_switcher;
59 63
64 +pub use action_row::ActionRow;
60 65 pub use bottom_sheet::BottomSheet;
61 66 pub use button::{Button, ButtonVariant};
67 +pub use card::Card;
62 68 pub use check_button::CheckButton;
63 69 pub use ctx::PaintCtx;
64 70 pub use dropdown::DropDown;
@@ -39,7 +39,8 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
39 39 (zakładki: pasek + model stron), `BottomSheet` (dolny panel modalny), `Pager` (karuzela stron),
40 40 `PathBar` (okruszki), `SearchEntry` (pole wyszukiwania), `ItemView` (pliki/katalogi: siatka
41 41 lub lista) i `ScrollView` (przewijanie), oraz `Stepper`, `CheckButton` i `RadioGroup`
42 - (krokowiec, checkbox, wybór jednokrotny); wspólny `PaintCtx`; testy hit-testingu bez okna.
42 + (krokowiec, checkbox, wybór jednokrotny); do tego `Card` i `ActionRow` (karty i wiersze
43 + ustawień); wspólny `PaintCtx`; testy hit-testingu bez okna.
43 44 - `libluna-platform`: realne akcje okna z `Frame` (`close`/`minimize`/`toggle_maximize`).
44 45 - Demo używa widgetów zamiast ręcznego rysowania (pasek boczny, wybór motywu/widoku, menu `☰`,
45 46 przycisk „Opcje”, przełączniki, suwak, wybór języka, pasek tytułu, zakładki `TabBar`/`TabView`).
@@ -101,7 +102,7 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
101 102 | SearchEntry / SearchBar | `libluna-widgets` | Files |
102 103 | GridView / ListView | `libluna-widgets` (`ItemView`) | Files |
103 104 | ScrollView | `libluna-widgets` | obie |
104 -| Card + ActionRow | `libluna-adwaita` | Settings |
105 +| Card + ActionRow | `libluna-widgets` | Settings |
105 106 | Switch | `libluna-widgets` | Settings |
106 107 | Slider (+ etykieta %) | `libluna-widgets` | Settings |
107 108 | DropDown | `libluna-widgets` | Settings |