← luna

Commit 5d6765c

4
files
+65
added
-52
deleted
@@ -10,6 +10,7 @@ use libluna_core::{
10 10 };
11 11 use libluna_i18n::{tr, Locale};
12 12 use libluna_render::TextEngine;
13 +use libluna_widgets::layout::{Layout as Tree, LayoutCtx, Node};
13 14 use libluna_widgets::{
14 15 ActionRow, BottomSheet, Button, Card, CheckButton, ControlsLayout, ControlsSide, DropDown,
15 16 Entry, HeaderBar, Item, ItemView, Menu, MenuEntry, NavEntry, NavList, Pager, PaintCtx, PathBar,
@@ -175,6 +176,11 @@ pub struct Layout {
175 176 pub sidebar: Rect,
176 177 pub sidebar_handle: Rect,
177 178 pub area: Rect,
179 + /// Karty obszaru treści (ułożone silnikiem układu `measure`/`allocate`).
180 + pub card: Rect,
181 + pub card2: Rect,
182 + pub card3: Rect,
183 + pub card4: Rect,
178 184 pub sidebar_button: Rect,
179 185 pub sidebar_visible: bool,
180 186 pub options_button: Rect,
@@ -440,27 +446,43 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
440 446 control,
441 447 );
442 448
443 - // Karta 1: Wygląd (3 wiersze).
444 - let card = Rect::from_xywh(
445 - area.x() + lg,
446 - area.y() + lg,
447 - (area.width() - 2.0 * lg).max(0.0),
448 - 208.0,
449 - );
449 + // Karty i wiersze układane przez silnik `measure`/`allocate` (retencyjne drzewo).
450 + let ctx_text = TextEngine::none();
451 + let ctx = LayoutCtx {
452 + theme,
453 + text: &ctx_text,
454 + };
455 + let cards = Tree::new(Node::padding(
456 + Insets::splat(lg),
457 + Node::vbox(vec![
458 + Node::fixed(0.0, 208.0),
459 + Node::fixed(0.0, 320.0),
460 + Node::fixed(0.0, 152.0),
461 + Node::fixed(0.0, 196.0),
462 + ])
463 + .gap(lg),
464 + ))
465 + .fit(area, &ctx);
466 + let (card, card2, card3, card4) = (cards[0], cards[1], cards[2], cards[3]);
467 +
450 468 let header_h = theme.metrics.card.header;
451 469 let row_h = ROW_H;
452 - let row = |i: usize| {
453 - Rect::from_xywh(
470 + // Wiersze karty: pionowy stos `count` pozycji (silnik).
471 + let row_rects = |card: Rect, count: usize| -> Vec<Rect> {
472 + let inner = Rect::from_xywh(
454 473 card.x(),
455 - card.y() + header_h + i as f64 * row_h,
474 + card.y() + header_h,
456 475 card.width(),
457 - row_h,
458 - )
476 + (card.height() - header_h).max(0.0),
477 + );
478 + let tree = Node::vbox((0..count).map(|_| Node::fixed(0.0, row_h)).collect());
479 + Tree::new(tree).fit(inner, &ctx)
459 480 };
481 + let rows1 = row_rects(card, 3);
460 482
461 483 // Wiersz 0: tryb (jasny/ciemny/auto).
462 484 let seg_total = 240.0;
463 - let r0 = row(0);
485 + let r0 = rows1[0];
464 486 let theme_switcher = Rect::from_xywh(
465 487 r0.right() - md - seg_total,
466 488 r0.center().y - 16.0,
@@ -472,7 +494,7 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
472 494 let dot = 22.0;
473 495 let gap = 10.0;
474 496 let dots_w = Accent::ALL.len() as f64 * dot + (Accent::ALL.len() - 1) as f64 * gap;
475 - let r1 = row(1);
497 + let r1 = rows1[1];
476 498 let dots_x = r1.right() - md - dots_w;
477 499 let accents = std::array::from_fn(|i| {
478 500 Rect::from_xywh(
@@ -484,7 +506,7 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
484 506 });
485 507
486 508 // Wiersz 2: język.
487 - let r2 = row(2);
509 + let r2 = rows1[2];
488 510 let dd_h = theme.metrics.dropdown.height;
489 511 let language = Rect::from_xywh(
490 512 r2.right() - md - 170.0,
@@ -494,15 +516,7 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
494 516 );
495 517
496 518 // Karta 2: Ustawienia (5 wierszy).
497 - let card2 = Rect::from_xywh(area.x() + lg, card.bottom() + lg, card.width(), 320.0);
498 - let row2 = |i: usize| {
499 - Rect::from_xywh(
500 - card2.x(),
501 - card2.y() + header_h + i as f64 * row_h,
502 - card2.width(),
503 - row_h,
504 - )
505 - };
519 + let rows2 = row_rects(card2, 5);
506 520 let switch = |r: Rect| {
507 521 Rect::from_xywh(
508 522 r.right() - md - switch_m.width,
@@ -511,8 +525,8 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
511 525 switch_m.height,
512 526 )
513 527 };
514 - let option_a = switch(row2(0));
515 - let srow = row2(1);
528 + let option_a = switch(rows2[0]);
529 + let srow = rows2[1];
516 530 let track_h = theme.metrics.slider.track_height;
517 531 let volume_track = Rect::from_xywh(
518 532 srow.right() - md - 200.0,
@@ -520,40 +534,31 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
520 534 200.0,
521 535 track_h,
522 536 );
523 - let vrow = row2(2);
537 + let vrow = rows2[2];
524 538 let view_switcher = Rect::from_xywh(
525 539 vrow.right() - md - 160.0,
526 540 vrow.center().y - 16.0,
527 541 160.0,
528 542 32.0,
529 543 );
530 - let bottom_bar = switch(row2(3));
531 - let sidebar_toggle = switch(row2(4));
544 + let bottom_bar = switch(rows2[3]);
545 + let sidebar_toggle = switch(rows2[4]);
532 546
533 547 // Karta 3: Zakładki (pasek + wiersz z aktywną stroną).
534 - let card3 = Rect::from_xywh(area.x() + lg, card2.bottom() + lg, card.width(), 152.0);
535 - let row3 = |i: usize| {
536 - Rect::from_xywh(
537 - card3.x(),
538 - card3.y() + header_h + i as f64 * row_h,
539 - card3.width(),
540 - row_h,
541 - )
542 - };
548 + let rows3 = row_rects(card3, 2);
543 549 let tabs_h = theme.metrics.tabs.height;
544 550 let tabs_w = 420.0;
545 - let trow = row3(0);
551 + let trow = rows3[0];
546 552 let tabs = Rect::from_xywh(
547 553 trow.right() - md - tabs_w,
548 554 trow.center().y - tabs_h / 2.0,
549 555 tabs_w,
550 556 tabs_h,
551 557 );
552 - let tabs_value = row3(1);
558 + let tabs_value = rows3[1];
553 559
554 560 // Karta 4: Pliki (PathBar + SearchEntry + ItemView).
555 - let card4 = Rect::from_xywh(area.x() + lg, card3.bottom() + lg, card.width(), 196.0);
556 - let row4 = Rect::from_xywh(card4.x(), card4.y() + header_h, card4.width(), row_h);
561 + let row4 = row_rects(card4, 1)[0];
557 562 let path_h = theme.metrics.pathbar.height;
558 563 let path = Rect::from_xywh(
559 564 card4.x() + md,
@@ -579,6 +584,10 @@ pub fn layout(size: Size, theme: &Theme, demo: &Demo) -> Layout {
579 584 sidebar,
580 585 sidebar_handle,
581 586 area,
587 + card,
588 + card2,
589 + card3,
590 + card4,
582 591 sidebar_button,
583 592 sidebar_visible: demo.sidebar_visible,
584 593 options_button,
@@ -1214,17 +1223,12 @@ fn draw_content(
1214 1223 let theme = *paint.theme;
1215 1224 paint.scene.fill_rect(area, theme.colors.view_bg);
1216 1225
1217 - let lg = theme.spacing.lg;
1218 1226 let md = theme.spacing.md;
1219 1227 let body = theme.typography.body.size;
1220 1228
1221 1229 // --- Karta 1: Wygląd (3 wiersze) ---
1222 - let card = Rect::from_xywh(
1223 - area.x() + lg,
1224 - area.y() + lg,
1225 - (area.width() - 2.0 * lg).max(0.0),
1226 - 208.0,
1227 - );
1230 + // Karty pochodzą z silnika układu (`lay`).
1231 + let card = lay.card;
1228 1232 Card::new(tr("Appearance")).paint(paint, card);
1229 1233
1230 1234 draw_row_label(paint, card, lay.theme_switcher, &tr("Theme"));
@@ -1238,7 +1242,7 @@ fn draw_content(
1238 1242 language_dropdown(&codes).paint(paint, lay.language);
1239 1243
1240 1244 // --- Karta 2: Ustawienia ---
1241 - let card2 = Rect::from_xywh(card.x(), card.bottom() + lg, card.width(), 320.0);
1245 + let card2 = lay.card2;
1242 1246 Card::new(tr("Settings")).paint(paint, card2);
1243 1247
1244 1248 draw_row_label(paint, card2, lay.option_a, &tr("Option A"));
@@ -1267,7 +1271,7 @@ fn draw_content(
1267 1271 Switch::new(demo.sidebar_visible).paint(paint, lay.sidebar_toggle);
1268 1272
1269 1273 // --- Karta 3: Zakładki ---
1270 - let card3 = Rect::from_xywh(card.x(), card2.bottom() + lg, card.width(), 152.0);
1274 + let card3 = lay.card3;
1271 1275 Card::new(tr("Tabs")).paint(paint, card3);
1272 1276
1273 1277 // Wiersz 0: pasek zakładek (widget TabBar).
@@ -1293,7 +1297,7 @@ fn draw_content(
1293 1297 );
1294 1298
1295 1299 // --- Karta 4: Pliki (PathBar + SearchEntry + ItemView) ---
1296 - let card4 = Rect::from_xywh(card.x(), card3.bottom() + lg, card.width(), 196.0);
1300 + let card4 = lay.card4;
1297 1301 Card::new(tr("Files")).paint(paint, card4);
1298 1302 path_bar(demo).paint(paint, lay.path);
1299 1303
@@ -42,6 +42,15 @@ impl TextEngine {
42 42 Self { regular, bold }
43 43 }
44 44
45 + /// Silnik bez fontów (przybliżone metryki, brak rysowania) — do pomiaru drzew układu,
46 + /// które nie zawierają etykiet. Tani w tworzeniu.
47 + pub fn none() -> Self {
48 + Self {
49 + regular: None,
50 + bold: None,
51 + }
52 + }
53 +
45 54 /// Czy jakikolwiek font został wczytany.
46 55 pub fn has_font(&self) -> bool {
47 56 self.regular.is_some()
48 57 Binary files a/docs/images/demo-montage.png and b/docs/images/demo-montage.png differ
49 58 Binary files a/docs/images/demo-step-16.png and b/docs/images/demo-step-16.png differ