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