← luna

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