Commit b5a3e43
20
files
+74
added
-14
deleted
@@ -31,6 +31,10 @@ fn press(p: Point) -> Event {
31
31
}
32
32
33
33
fn render_pixels(settings: &ThemeSettings, demo: &Demo, text: &TextEngine, size: Size) -> Vec<u8> {
34
+ // Headless: domknij trwające animacje, by klatka pokazywała stan ustalony.
35
+ let mut demo = demo.clone();
36
+ demo.tab_stack.advance(1.0, 1.0);
37
+ demo.toast.advance(1.0, 1.0);
34
38
let theme = settings.resolve_themed(false);
35
39
let mut scene = Scene::new();
36
40
ui::build_scene(
@@ -38,7 +42,7 @@ fn render_pixels(settings: &ThemeSettings, demo: &Demo, text: &TextEngine, size:
38
42
&theme,
39
43
text,
40
44
size,
41
- demo,
45
+ &demo,
42
46
settings.preference(),
43
47
settings.accent(),
44
48
);
@@ -72,6 +72,12 @@ fn main() -> Result<(), Box<dyn Error>> {
72
72
frame.request_frame();
73
73
}
74
74
75
+ // Animacja przejścia zakładek (`Stack` slide).
76
+ if demo.tab_stack.is_animating() {
77
+ demo.tab_stack.advance(frame.delta_time(), 0.25);
78
+ frame.request_frame();
79
+ }
80
+
75
81
// Motyw po zmianach (np. wybór stylu) — klatka od razu poprawna.
76
82
let theme = settings.resolve_themed(frame.system_dark());
77
83
ui::build_scene(
@@ -5,7 +5,7 @@
5
5
use libluna_core::event::ButtonState;
6
6
use libluna_core::style::{weight, Appearance};
7
7
use libluna_core::{
8
- Accent, Color, Event, Insets, KeyEvent, Point, PointerButton, Rect, Size, Theme,
8
+ Accent, Color, Easing, Event, Insets, KeyEvent, Point, PointerButton, Rect, Size, Theme,
9
9
ThemePreference, ThemeSettings,
10
10
};
11
11
use libluna_i18n::{tr, Locale};
@@ -14,8 +14,9 @@ use libluna_widgets::layout::{Layout as Tree, LayoutCtx, Node};
14
14
use libluna_widgets::{
15
15
ActionRow, BottomSheet, Button, Card, CheckButton, ControlsLayout, ControlsSide, DropDown,
16
16
Entry, HeaderBar, Item, ItemView, Menu, MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar,
17
- RadioGroup, ScrollView, SearchEntry, Slider, StatusPage, Stepper, StepperSide, Switch, Tab,
18
- TabBar, TabView, TextInput, Toast, ViewMode, ViewSwitcher, WindowControl,
17
+ RadioGroup, ScrollView, SearchEntry, Slider, Stack, StackTransition, StatusPage, Stepper,
18
+ StepperSide, Switch, Tab, TabBar, TabView, TextInput, Toast, ViewMode, ViewSwitcher,
19
+ WindowControl,
19
20
};
20
21
21
22
const STATUS_H: f64 = 28.0;
@@ -49,6 +50,7 @@ pub enum Popover {
49
50
}
50
51
51
52
/// Stan aplikacji demo, który nie należy do warstwy platformy ani do widgetów.
53
+#[derive(Clone)]
52
54
pub struct Demo {
53
55
pub option_a: bool,
54
56
pub volume: f64,
@@ -77,6 +79,10 @@ pub struct Demo {
77
79
/// Strony zakładek (tytuły to klucze i18n) oraz podświetlona zakładka.
78
80
pub tabs: TabView,
79
81
pub tabs_hover: Option<usize>,
82
+ /// Animowane przejście aktywnej strony zakładek (`Stack`: slide w poziomie).
83
+ pub tab_stack: Stack,
84
+ /// Tytuł strony wychodzącej w trakcie animacji przejścia.
85
+ pub tab_prev_title: Option<String>,
80
86
/// Dolny panel (modalny) i karuzela stron w jego zawartości.
81
87
pub sheet: BottomSheet,
82
88
pub pager: Pager,
@@ -137,6 +143,13 @@ impl Default for Demo {
137
143
tabs
138
144
},
139
145
tabs_hover: None,
146
+ tab_stack: {
147
+ let mut s = Stack::with_count(3);
148
+ s.set_transition(StackTransition::SlideHorizontal);
149
+ s.set_transition_easing(Easing::EaseOut);
150
+ s
151
+ },
152
+ tab_prev_title: None,
140
153
sheet: BottomSheet::new(),
141
154
pager: Pager::new(3),
142
155
pager_dragging: false,
@@ -784,6 +797,11 @@ pub fn handle_events(
784
797
.set_content_height(view.content_height(lay.items, theme));
785
798
demo.files_scroll.set_offset(view.scroll(), lay.items);
786
799
} else if let Some(i) = tabs_bar.index_at(lay.tabs, text, theme, p) {
800
+ if i != demo.tabs.selected() {
801
+ demo.tab_prev_title =
802
+ demo.tabs.selected_title().map(|s| s.to_owned());
803
+ demo.tab_stack.set_visible_index(i);
804
+ }
787
805
demo.tabs.select(i);
788
806
} else if demo.search.contains(lay.search, p) {
789
807
if demo.search.clear_at(lay.search, theme, p) {
@@ -1212,6 +1230,29 @@ fn draw_sidebar(paint: &mut PaintCtx, lay: &Layout, demo: &Demo) {
1212
1230
nav.paint(paint, sb);
1213
1231
}
1214
1232
1233
+/// Pojedyncza „strona” zakładki rysowana jako kapsuła (z kryciem dla animacji przejścia).
1234
+fn draw_tab_page(paint: &mut PaintCtx, rect: Rect, title: &str, alpha: f64) {
1235
+ let theme = *paint.theme;
1236
+ let a = alpha.clamp(0.0, 1.0) as f32;
1237
+ paint
1238
+ .scene
1239
+ .fill_rounded_rect(rect, theme.radius.small, theme.colors.shade.scale_alpha(a));
1240
+ paint.scene.stroke_rounded_rect(
1241
+ rect,
1242
+ theme.radius.small,
1243
+ 1.0,
1244
+ theme.colors.accent.scale_alpha(a),
1245
+ );
1246
+ paint.label_center(
1247
+ rect.center().x,
1248
+ rect.center().y,
1249
+ title,
1250
+ theme.typography.body.size,
1251
+ weight::MEDIUM,
1252
+ theme.colors.text.scale_alpha(a),
1253
+ );
1254
+}
1255
+
1215
1256
fn draw_content(
1216
1257
paint: &mut PaintCtx,
1217
1258
area: Rect,
@@ -1277,7 +1318,7 @@ fn draw_content(
1277
1318
// Wiersz 0: pasek zakładek (widget TabBar).
1278
1319
tab_bar(demo).paint(paint, lay.tabs);
1279
1320
1280
- // Wiersz 1: aktywna strona (model TabView).
1321
+ // Wiersz 1: aktywna strona (model TabView) z animowanym przejściem (`Stack`).
1281
1322
paint.label_left(
1282
1323
card3.x() + md,
1283
1324
lay.tabs_value.center().y,
@@ -1286,15 +1327,23 @@ fn draw_content(
1286
1327
weight::NORMAL,
1287
1328
theme.colors.text,
1288
1329
);
1289
- let active = demo.tabs.selected_title().map(tr).unwrap_or_default();
1290
- paint.label_right(
1291
- lay.tabs_value.right() - md,
1292
- lay.tabs_value.center().y,
1293
- &active,
1294
- body,
1295
- weight::MEDIUM,
1296
- theme.colors.text,
1330
+ let cur_title = demo.tabs.selected_title().map(tr).unwrap_or_default();
1331
+ let pill = Rect::from_xywh(
1332
+ lay.tabs_value.right() - md - 150.0,
1333
+ lay.tabs_value.y() + 2.0,
1334
+ 150.0,
1335
+ (lay.tabs_value.height() - 4.0).max(0.0),
1297
1336
);
1337
+ if demo.tab_stack.previous_index().is_some() {
1338
+ let (out, inc) = demo.tab_stack.frames(pill);
1339
+ let prev_title = demo.tab_prev_title.as_deref().map(tr).unwrap_or_default();
1340
+ paint.scene.push_clip(lay.tabs_value);
1341
+ draw_tab_page(paint, out.rect, &prev_title, out.alpha);
1342
+ draw_tab_page(paint, inc.rect, &cur_title, inc.alpha);
1343
+ paint.scene.pop_clip();
1344
+ } else {
1345
+ draw_tab_page(paint, pill, &cur_title, 1.0);
1346
+ }
1298
1347
1299
1348
// --- Karta 4: Pliki (PathBar + SearchEntry + ItemView) ---
1300
1349
let card4 = lay.card4;
1301
1350
Binary files a/docs/images/demo-montage.png and b/docs/images/demo-montage.png differ
1302
1351
Binary files a/docs/images/demo-step-1.png and b/docs/images/demo-step-1.png differ
1303
1352
Binary files a/docs/images/demo-step-10.png and b/docs/images/demo-step-10.png differ
1304
1353
Binary files a/docs/images/demo-step-11.png and b/docs/images/demo-step-11.png differ
1305
1354
Binary files a/docs/images/demo-step-12.png and b/docs/images/demo-step-12.png differ
1306
1355
Binary files a/docs/images/demo-step-13.png and b/docs/images/demo-step-13.png differ
1307
1356
Binary files a/docs/images/demo-step-14.png and b/docs/images/demo-step-14.png differ
1308
1357
Binary files a/docs/images/demo-step-15.png and b/docs/images/demo-step-15.png differ
1309
1358
Binary files a/docs/images/demo-step-16.png and b/docs/images/demo-step-16.png differ
1310
1359
Binary files a/docs/images/demo-step-2.png and b/docs/images/demo-step-2.png differ
1311
1360
Binary files a/docs/images/demo-step-3.png and b/docs/images/demo-step-3.png differ
1312
1361
Binary files a/docs/images/demo-step-4.png and b/docs/images/demo-step-4.png differ
1313
1362
Binary files a/docs/images/demo-step-5.png and b/docs/images/demo-step-5.png differ
1314
1363
Binary files a/docs/images/demo-step-6.png and b/docs/images/demo-step-6.png differ
1315
1364
Binary files a/docs/images/demo-step-7.png and b/docs/images/demo-step-7.png differ
1316
1365
Binary files a/docs/images/demo-step-8.png and b/docs/images/demo-step-8.png differ
@@ -67,7 +67,8 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
67
67
animacja; bez tego okno odświeża się tylko na zdarzenia).
68
68
- Demo używa widgetów zamiast ręcznego rysowania (pasek boczny, wybór motywu/widoku, menu `☰`,
69
69
przycisk „Opcje”, przełączniki, suwak, wybór języka, pasek tytułu, zakładki `TabBar`/`TabView`).
70
- Powiadomienie (`Toast`) w demo animuje się (wjazd + fade).
70
+ Powiadomienie (`Toast`) w demo animuje się (wjazd + fade), a zmiana zakładki
71
+ to animowane przejście (`Stack` slide w poziomie).
71
72
- **Galeria widgetów** headless: `cargo run -p luna-demo --bin gallery` → `docs/images/gallery.png`
72
73
(kontenery, tabela, kalendarz, chooser’y, kreator kroków, pasek stanu z cz. 5–12).
73
74
- Motywy: wariant (light/dark/auto) + akcent, z trwałością. **Component metrics**: rozmiary