1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
//! [`Core`]: one window's runtime, the state machine a runner drives frame
//! by frame.
//!
//! A core owns everything that survives across frames and belongs to one
//! window: the per-frame tree and its builder state, interaction state
//! (hover, press, drag), focus, the scroll and edit stores, animations,
//! the glyph atlas and the finished [`DisplayList`]. What a window must
//! not duplicate — the font database, the resource registry, the audio
//! store — lives in the [`Session`] a core is constructed against;
//! [`Core::new`] makes a private one, so a single-window app never sees
//! it. The frame lifecycle, with an example, is on [`Core`].
//!
//! Builder state lives in the core itself rather than in a borrowing
//! wrapper so that flat C bindings can drive a frame through one opaque
//! pointer; the Rust [`Ui`] is a thin safe facade over it.
use std::rc::Rc;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::anim::{AnimStore, Slot, Track};
use crate::atlas::GlyphAtlas;
use crate::color::Color;
use crate::depart::{At, DepartStore, Ghost, GhostContent, Place, Playback, Replay};
use crate::diag::{Diagnostics, Warning};
use crate::display::{Clip, ClipId, DisplayList, NO_CLIP_ID, Quad, QuadKind};
use crate::edit::{EditOptions, EditStore};
use crate::env::{Env, SystemEnv};
use crate::geom::{Rect, Size, Vec2};
use crate::input::{
EditKey, HitRegion, InputEvent, Interaction, KeyCode, KeyMods, KeyPhase, KeyPress, MouseButton,
ScrollAxis, ScrollRegion, ScrollbarRegion, UiEvent,
};
use crate::key::{Key, LabelIndex};
use crate::keyframes;
use crate::layout::{self, TextMeasure};
use crate::line::Stroke;
use crate::resources::{FontId, Resources};
use crate::scroll::ScrollStore;
use crate::session::{
MAIN_WINDOW_NAME, Session, SharedAudio, SharedResources, WindowChange, WindowDecl,
};
use crate::spec::{NodeSpec, Sizing, TextStyle};
use crate::stats::FrameStats;
use crate::text::TextHit;
use crate::text::{Span, TextMetrics, TextSystem};
use crate::theme::{Theme, ThemeSource};
use crate::tree::{NIL, NodeContent, OriginId, Tree};
use crate::ui::Ui;
use crate::value::Value;
use crate::window::{WindowConfig, WindowId};
// `impl Core` continues in these, one concern per file (each opens with
// what it holds). Children of this module, so the fields stay private.
mod builder;
pub use builder::Content;
pub mod cause;
mod composites;
pub mod devtools;
mod dispatch;
mod emit;
mod fills;
mod focus;
pub mod follow;
mod gesture;
pub mod inspect;
mod menu_api;
pub(crate) use menu_api::MenuSurface;
mod menubar_api;
mod resources_api;
mod scrolling;
mod select_api;
mod windows;
/// Wheel line-delta to logical px.
const SCROLL_LINE_PX: f32 = 40.0;
/// What a `Core::focus_region` call asked to enter, held until the frame
/// finishes: the main ring,
/// a region by key, or one by the label its node declares — the spelling
/// a caller has for a node the last frame did not build.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum RegionTarget {
Main,
Key(Key),
Label(String),
}
/// One window's runtime: the state a runner drives frame by frame.
///
/// Construct one with [`Core::new`] (a private [`Session`]) or
/// [`Core::new_in`] (joining a session another window shares). The public
/// fields are the stores a runner or a binding reads directly:
/// `resources` and `audio` for registration and playback commands, `env`
/// for the host facts a driver pushes, `stats` for frame timing.
///
/// # One frame, in order
///
/// 1. [`Core::set_time`] with the frame clock, so transitions advance.
/// 2. [`Core::frame`] with the viewport (logical px) and the scale. It
/// begins the frame and returns the [`Ui`] builder; build the tree
/// through it.
/// 3. [`Ui::finish`] runs layout and emission. The draw data is now in
/// [`Core::output`]: the [`DisplayList`] and the [`GlyphAtlas`] a
/// renderer mirrors to a texture.
/// 4. [`Core::take_pending_events`] drains events the frame itself raised
/// (a `resize`, a hover change under a still pointer); route them like
/// any other.
/// 5. Between frames, feed input through [`Core::handle_input`]. Each
/// call returns the [`UiEvent`]s it resolved to, hit-tested against
/// the frame that finished.
/// 6. [`Core::take_warnings`] for misconfigurations the core noticed, and
/// [`Core::animating`] to decide whether to draw another frame without
/// waiting for input.
///
/// A headless core needs no window or GPU, so a test can drive it:
///
/// ```rust
/// use kui_core::{Color, Core, InputEvent, NodeSpec, QuadKind, Size, TextStyle, Vec2};
///
/// let mut core = Core::new();
///
/// // 1–3: a frame. `frame` begins it, `finish` lays it out and emits.
/// core.set_time(0.0);
/// let mut ui = core.frame(Size::new(400.0, 300.0), 1.0);
/// ui.with_keyed("toolbar", NodeSpec::row().pad(8.0).gap(8.0), |ui| {
/// // A clickable node needs an accessible name, or `take_warnings`
/// // reports `control-without-name`.
/// let button = NodeSpec::row().size(80.0, 32.0).bg(Color::hex(0x3366cc));
/// ui.leaf_keyed("save", button.on_click("save").label("Save"));
/// ui.text("Untitled", TextStyle::new(14.0));
/// });
/// ui.finish();
///
/// // 4: what the frame itself raised (nothing on a first frame).
/// assert!(core.take_pending_events().is_empty());
///
/// // The renderer's view of the frame: quads in physical pixels.
/// let (list, atlas) = core.output();
/// assert!(list.quads.iter().any(|q| q.kind == QuadKind::Solid));
/// atlas.dirty = false; // after uploading `atlas.pixels`
///
/// // 5: input, hit-tested against the frame that finished.
/// core.handle_input(InputEvent::CursorMoved(Vec2::new(20.0, 20.0)));
/// core.handle_input(InputEvent::mouse_down(1));
/// let events = core.handle_input(InputEvent::mouse_up());
/// assert_eq!(events.len(), 1);
/// assert_eq!(events[0].payload.as_str(), Some("save"));
///
/// // 6: diagnostics, and whether another frame is owed.
/// assert!(core.take_warnings().is_empty());
/// assert!(!core.animating());
/// ```
///
/// A windowed runner does the same with real time, real input and a
/// renderer consuming [`Core::output`]; `kui-native` is that runner.
pub struct Core {
/// The caches and registries this window shares with the rest of its
/// session. Everything a frame needs from it is borrowed inside a
/// method and dropped before it returns; see `session`'s module doc.
session: Session,
/// This window's shaped-text cache and rasterizer. Not the session's:
/// its cache entries are stamped with `atlas`'s epoch, and `TextId`
/// indexes its per-frame list.
pub text: TextSystem,
/// The frame's cell grids and their glyph tables.
pub cells: crate::cells::CellStore,
/// This window's glyph atlas — the CPU side of its renderer's texture,
/// handed out by `output`.
pub atlas: GlyphAtlas,
/// The session's resource registry. A font, image or sound registered
/// through it is registered for every window in the session.
pub resources: SharedResources,
/// The session's audio store: one device for the process, so playback
/// bookkeeping and the command queue are shared, not per window.
pub audio: SharedAudio,
/// The family names of the session's fonts as of this window's last
/// frame, so `font_family` can lend one out. Refreshed from
/// `SessionState::fonts_rev`.
font_names: FxHashMap<FontId, std::rc::Rc<str>>,
fonts_rev: u64,
/// The session's `weights_rev` this core's shaped text is of
/// (`sync_weights`).
weights_rev: u64,
/// The session's `images_rev` this core last checked its atlas
/// against (`sync_dropped`).
images_rev: u64,
pub interaction: Interaction,
pub scroll: ScrollStore,
pub edit: EditStore,
/// Transition tweens, keyed by node; see `anim`. Fed by `set_time`.
pub anim: AnimStore,
/// Subtrees the view stopped declaring, played out and then dropped;
/// see `depart`. Empty unless something declares `exit`.
pub depart: DepartStore,
/// Frame timing pushed by the frame driver; see `widgets::latency_graph`.
pub stats: FrameStats,
/// Host facts pushed by the frame driver (refresh rate, focus).
pub env: Env,
/// Where this window's palette comes from; see [`crate::theme`].
/// `Derived` unless the app said otherwise, so an app that never
/// mentions themes still follows the OS.
theme_source: ThemeSource,
/// `theme_source` resolved against `env.system`, re-resolved at the
/// start of every frame. Read by the stock widgets, by the core's own
/// chrome (ring, scrollbar, selection) and by any view that asks.
theme: Theme,
/// The sizes the stock widgets are built from: the
/// palette's other axis, set by the app or
/// [`Metrics::default`](crate::metrics::Metrics::default).
metrics: crate::metrics::Metrics,
/// The named colours and lengths each origin declared: the
/// host's under `OriginId::HOST`, an extension's under its own, so a
/// guest's declaration never replaces the host's palette. Read through
/// [`Core::token_lookup`], which puts the running origin's table over
/// the host's.
tokens: rustc_hash::FxHashMap<OriginId, crate::tokens::Tokens>,
/// Window title declared this frame (immediate-mode: cleared each
/// `begin_frame`; the driver diffs and applies). None = leave as-is.
window_title: Option<String>,
/// Whether this frame asked for the window to stay above every other
/// app's. The title's shape — cleared each `begin_frame`,
/// the driver diffs and applies on change — but a bool with a default
/// rather than an option, so a frame that stops asking is what lowers
/// the window again: the pin button an app draws for this is a
/// toggle, and the fact follows it.
always_on_top: bool,
/// Whether this frame asked for secure keyboard entry while its window
/// has the keyboard. `always_on_top`'s shape: cleared each
/// `begin_frame`, so a frame that stops asking is what turns it off.
secure_input: bool,
/// Which Option keys this frame asked to act as Alt on macOS.
/// `always_on_top`'s shape: cleared each `begin_frame`, so a
/// frame that stops declaring it gives the Option keys back to the
/// layout's composition.
option_as_alt: crate::input::OptionAsAlt,
/// Whether this frame asked for the platform's input method off in
/// its window. `always_on_top`'s shape: cleared each `begin_frame`,
/// so a frame that stops asking gives the window its IME back.
ime_off: bool,
/// Keyboard focus: the one node key input goes to — an editor (the
/// edit store mirrors it), an `on_key` sink, a control Tab landed on.
/// `set_focus` is
/// the only writer.
focus: Option<Key>,
/// Focus got there by keyboard or assistive technology, so it shows:
/// the default ring, or the node's `focus_bg`. A mouse press clears it.
focus_visible: bool,
/// Nodes that declared focus this frame and last (`set_key_focus`):
/// a declaration takes focus only when it starts, so a repeated one
/// does not clobber a Tab press.
declared_focus: Vec<Key>,
declared_focus_last: Vec<Key>,
/// Whether the app moved focus through `set_focus` since the last
/// frame began — the edge a closing modal's restore yields to, like a
/// `keyFocus` edge. Cleared by `begin_frame`.
focus_asked: bool,
/// The `.str`-keyed nodes this frame and last, with their labels
/// (`open_keyed`): what `key_of` resolves a name through. The same
/// swap-and-clear pair as the focus declarations, so a frame that
/// keys nothing costs two clears.
key_labels: LabelIndex,
key_labels_last: LabelIndex,
/// The slots this frame declared (`begin_slot`), by name: what the
/// `"root"` fill and the `unknown-slot` check read, and what makes a
/// second declaration of one name a `duplicate-slot`. Cleared each
/// frame; never compared to the last one, since a slot is a position
/// and not a declaration the core diffs.
slot_labels: LabelIndex,
/// The key namespace of the fill in progress:
/// while the node stack is exactly `ns_depth` deep, a child's key is
/// derived from `ns_key` instead of from the node it is opened under,
/// so an extension's nodes are keyed by the slot and the extension
/// rather than by whatever the host built around them. `usize::MAX`
/// when no fill is in progress — one compare on the auto-key path.
ns_depth: usize,
ns_key: Key,
/// Between `begin_frame` and `finish_frame`: `key_labels` is partial
/// and `key_labels_last` is the last whole frame, and `key_of` reads
/// both; outside a build `key_labels` is the whole last frame and is
/// the only one read.
building: bool,
/// Windows this frame and last declared (`declare_window`): the same
/// edge-triggered shape as the focus pair, one level up. The session's
/// registry diffs the union across every core at `finish_frame`; the
/// pair here is what makes a frame that changed nothing cost nothing.
declared_windows: Vec<WindowDecl>,
declared_windows_last: Vec<WindowDecl>,
/// The presses delivered to the current focus and not yet released,
/// in press order. A `KeyUp` is routed only when its press is in here
/// (so a sink never sees a release it did not see the press of), and
/// focus leaving synthesizes the missing releases from it.
keys_held: Vec<KeyPress>,
/// The modifiers of the `KeyDown` the next input event is the second
/// channel of (`KeyPress::edit_event`), so the `Key` and `Text` arms
/// ask the same chord question the raw press did. Set by a `KeyDown`, read
/// and cleared by whatever
/// comes next: a `Key` or `Text` with no press before it — a test
/// driving one channel, a host's editing-key door — has no chord to
/// agree with and falls back to what its own `Mods` say.
pressed_mods: Option<KeyMods>,
pub(crate) tree: Tree,
/// Whether the host asked for `finish_frame` to copy the frame into
/// `inspected` (see `runtime/inspect.rs`, `set_inspect`); off unless
/// it did. The panel's own need is `dt_inspect`, derived each frame
/// and kept apart, so neither ask can turn the other
/// off.
inspect: bool,
/// The devtools panel's need for the snapshot this frame: its tree
/// tab is showing, or it is picking.
dt_inspect: bool,
inspected: Vec<inspect::NodeInfo>,
/// The devtools' hold on this window's frame: the
/// tree index of the app container the host's tree is wrapped in
/// while the panel is docked, whether this core draws the panel's own
/// window, and — while the panel's theme override is in force — the
/// source the host had before it, beside the override applied, so a
/// source the host sets *under* the override is told from it.
dt_app: Option<usize>,
/// The dock the frame was wrapped for at `begin_frame`, `None` when
/// it was not: a panel turned on or re-docked mid-frame (an app's
/// `set_devtools` from its `view`) is built next frame, which is
/// asked for, rather than into a root laid out for something else.
dt_dock: Option<devtools::Dock>,
dt_window: bool,
dt_theme: Option<(ThemeSource, ThemeSource)>,
/// The menu mode the host had before the panel's override went on —
/// drawn or native, the bar with it — for the override's `platform`
/// choice to put back.
dt_menus: Option<(bool, bool)>,
/// The panel was built at `begin_frame` (a left dock precedes the
/// app's container in tree order), on this tab, so `finish` must not
/// build again — and a door that moved the panel, turned it off or
/// changed its tab since is the next frame's, which `finish` asks
/// for.
dt_built: Option<devtools::Shown>,
/// The devtools tabs declared this frame, in order: what
/// the panel's strip lists, moved into the session's state at the
/// end of the main window's frame. Empty on a frame nobody declares
/// one, which is what every other frame pays.
dt_tabs: Vec<devtools::TabDecl>,
/// The host's viewport in window coordinates: the whole window, or
/// what the dock leaves of it while the panel is docked.
/// What `Core::viewport` reports, what a `resize` is measured on,
/// what the host's viewport floats resolve against, and the origin
/// every coordinate the host is handed or hands in is relative to.
dt_area: Rect,
/// The previous frame's tree, kept only while a frame declares `exit`
/// — `begin_frame` swaps the two buffers instead of clearing one, so
/// the frame that notices a node gone still has the node. Empty (and
/// untouched) for every frame that declares no exit.
prev_tree: Tree,
/// The frame's strokes, indexed by the `line` nodes' `LineId`s; the
/// previous frame's kept alongside on the same terms as `prev_tree`.
pub(crate) lines: crate::line::LineStore,
/// The frame's paths, on the same terms as the strokes.
pub(crate) paths: crate::path::PathStore,
/// What each `path` key last declared and when its ops last changed,
/// and whether the key is animating: one whose ops changed twice
/// within `path::ANIMATING_WINDOW` frames, whose masks leave the atlas
/// until it has held still for `path::SETTLED_AFTER`.
pub(crate) path_motion: rustc_hash::FxHashMap<Key, crate::path::Motion>,
/// The ops of each `d` string a `path` key last declared, so a string
/// handed over every frame is parsed once.
pub(crate) path_parsed: rustc_hash::FxHashMap<Key, crate::path::Parsed>,
/// The masks drawn from a texture of their own rather than the atlas:
/// too big for a page, or animating.
pub(crate) path_textures: crate::path::PathTextures,
/// Whether [`Core::output`] has been asked for since the frame was
/// built: what says its `dropped_textures` and `dropped_fragments`
/// reached a backend. A frame nobody read hands them to the next.
output_read: bool,
pub(crate) fragments: crate::fragment::FragmentList,
/// The stock polygon fragment's handle, once a `polygon` node has
/// asked for it this session. Forgotten by
/// `remove_fragment` if a host removes it, so the next node registers
/// it again rather than drawing nothing — and not re-checked per node,
/// which was a session lock per polygon and cost more than the six
/// segment quads a closed stroke of the same outline emits.
pub(crate) stock_polygon: Option<crate::resources::FragmentId>,
/// The hit shapes the frame being emitted builds beside its regions,
/// handed to `interaction` with them at the end of
/// emission; the previous frame's buffers, cleared, in between.
pub(crate) hit_shapes: crate::input::HitShapes,
pub(crate) display: DisplayList,
pub(crate) viewport: Size,
pub(crate) scale: f32,
// Frame-builder state.
stack: Vec<u32>,
counters: Vec<u64>,
origin: OriginId,
/// Per-node inherited clip (logical), rebuilt each finish_frame.
/// The access tree cuts each node's rect to it, so what a
/// reader finds is what a pointer can hit.
clips: Vec<Clip>,
/// The `DisplayList::clips` index each of those became, so a node
/// whose clip is its parent's names the entry the parent already
/// interned instead of asking again. Parallel to `clips`.
clip_ids: Vec<ClipId>,
/// Per-node inherited group opacity (the product down the ancestors),
/// rebuilt each finish_frame and only materialized when something
/// actually fades.
opacity: Vec<f32>,
/// Per node, the layer it paints in: the index of its nearest floating
/// ancestor-or-self, `NIL` in flow. Only filled on a frame
/// that floats something.
float_root: Vec<u32>,
/// The float layers as the last frame painted them, bottom to top:
/// each root's key and its rank among that frame's float roots in
/// tree order. A root the next frame keeps stays where it is, one it
/// opens goes on top, one it closes leaves — so the stack is the
/// order the layers opened in. Bounded by the
/// frame's own float count; nothing to evict.
float_stack: Vec<(Key, u32)>,
/// The hover hints of the nodes open right now that declared a
/// `tooltip` (`Core::hint`), each with the stack depth it was opened
/// at, so `close` knows whose turn it is. Only nodes that declared one
/// are here: a frame without a tooltip pays one length check per close.
hints: Vec<(usize, Key, String)>,
/// The context menu this window has open, the keys the stock renderer
/// gave its rows (so their clicks can be told from the app's), and
/// what choosing one left for the host to do.
menu: Option<crate::menu::Menu>,
menu_actions: Vec<crate::menu::MenuAction>,
/// The editor that held focus when the menu opened, since the menu's
/// own rows take focus from it — what Cut, Copy and Select All act on.
menu_editor: Option<Key>,
/// Whether the host draws menus itself (`set_native_menus`).
native_menus: bool,
/// The submenus open in the drawn context menu and in the drawn bar's
/// open menu (backlog F128): retained, like `menu_bar_open`, because
/// the frame cannot derive which row the pointer last rested on.
menu_sub: menu_api::Submenus,
menu_bar_sub: menu_api::Submenus,
/// The application menu this frame has in force, and a count bumped
/// whenever it changes, so a driver diffs against one integer rather
/// than against a tree.
/// `None` is a declaration nobody has made; an empty `MenuBar` is one
/// that took the bar away.
menu_bar: Option<crate::menu::MenuBar>,
menu_bar_rev: u64,
/// Who declared it, and so who hears the events its items post — the
/// host, or the extension whose view declared the bar.
menu_bar_origin: OriginId,
/// Which of the drawn bar's menus is open, and the bar's root this
/// frame — where a menu-bar event lands. Its titles and rows are known
/// by their origin (`OriginId::MENU_BAR`), not by key.
menu_bar_open: Option<usize>,
menu_bar_root: Option<Key>,
/// The select fields this frame built, each with its menu's rows
/// (`widgets::select`); a click on one opens that menu.
selects: Vec<(Key, Vec<crate::menu::MenuItem>)>,
/// Whether the platform owns the menu bar (`set_native_menu_bar`), in
/// which case the drawn one draws nothing and the driver hands the
/// declaration over instead.
native_menu_bar: bool,
/// Whether the host can show a definition panel
/// (`set_lookup_available`).
lookup_available: bool,
/// The window's text selection outside an editor, and what the
/// frame resolved it to: `sel_ords` numbers the text nodes of the
/// selection's scope in emission order (`u32::MAX` for a node
/// outside it), and `sel_ends` is the pair of ends in reading order,
/// `None` when this frame builds neither end.
selection: Option<crate::select::Selection>,
/// The window's selection when it is in a `cells` grid instead of in
/// text. One selection per window: starting either clears the other,
/// which `Core::set_selection` / `set_cell_selection` enforce in one
/// place each.
cell_selection: Option<crate::select::CellSelection>,
/// Whether a `selectionrange` ask is outstanding.
awaiting_selection: bool,
/// Whether a paste ask is outstanding — queued, or taken by the
/// driver and not yet answered with a `Commit`. A
/// second ask while one is out is dropped, so a view that asks every
/// frame until the answer lands asks once.
awaiting_paste: bool,
/// The one file-dialog ask: queued, taken by the host,
/// or none.
file_ask: crate::dialog::FileAsk,
/// The drag a press is running through a selection scope, if any: set
/// on the press inside a scope, cleared on release. The counterpart of
/// `EditStore::dragging` for text nobody is editing.
select_dragging: Option<crate::select::SelectDrag>,
/// The held drag — a caret drag or a drag-select — following its
/// scroller: the pointer's last position, the scroller found for it,
/// and what that scroller was at when the live end was last placed.
/// Set with either drag, cleared with both.
drag_follow: Option<follow::DragFollow>,
/// The frame clock's reading at the last frame, for the edge drag's
/// rate (`follow::follow_drag`); `None` before a clock is set.
last_frame_time: Option<f64>,
/// The fraction of a line the last delta over an `on_scroll` grid —
/// a wheel notch or an edge step — did not cover, with the grid it
/// was over: the next delta on the same grid adds to it.
line_carry: Option<(Key, f32)>,
/// The targets the scroll gesture under way latched, per axis.
scroll_latch: gesture::ScrollLatch,
sel_ords: Vec<u32>,
sel_ends: Option<crate::select::Ends>,
/// Per-node innermost enclosing selection scope — the key of the
/// nearest ancestor (or the node itself) declaring `selectable`, and
/// `None` outside every scope. Filled only on a frame that declares
/// one at all (`Tree::any_selectable`), so an app that never selects
/// anything pays nothing for it.
scopes: Vec<Option<Key>>,
/// Per-node enclosing virtualised row index, filled beside `scopes`:
/// what places an endpoint whose own node is no longer built.
rows: Vec<Option<u64>>,
/// The frame's modal scope: the tree range `[i, subtree_end(i))` of the
/// last node declaring `modal`, and its key. Everything outside it is
/// inert and out of the Tab ring. Recomputed by `finish_frame`.
modal: Option<(usize, usize, Key)>,
/// The modals declared by the last finished frame, in tree order, each
/// with the focus it displaced: a modal that stops being declared gives
/// that focus back.
modal_focus: Vec<(Key, Option<Key>)>,
/// Per-node inherited opacity while a departing subtree is replayed
/// (`depart`), reused across ghosts and frames.
ghost_opacity: Vec<f32>,
/// Per-node inherited clip and painted rect while a departing subtree
/// is replayed: the clips its own clippers establish, since a ghost
/// draws outside every clip its ancestors held (`depart`).
ghost_clip: Vec<Clip>,
/// The interned index of each of those, as `clip_ids` is for `clips`.
ghost_clip_ids: Vec<ClipId>,
ghost_rect: Vec<Rect>,
/// A view asked for one more frame (`request_frame`); cleared by
/// `begin_frame`, reported through `animating`.
frame_requested: bool,
/// Why frames run: the reasons, and — traced — who held an owed one
/// and whether a frame changed anything (`runtime/cause.rs`).
trace: cause::Trace,
/// The focused editor's caret rect (logical, viewport coords) as of the
/// last finish_frame — where drivers should anchor the OS IME window.
ime_rect: Option<Rect>,
/// A custom editor's caret as of the last frame: the `line` under the
/// focused sink that declares `caret`, and the offset it declares.
/// What the blink clock is armed on when no stock
/// editor is focused; `None` with nothing to blink.
sink_caret: Option<(Key, u32)>,
/// Whether that line declared its caret `caret_solid` — a block caret
/// in a modal editor's normal mode: still the IME's anchor and the
/// access tree's caret, but not a caret to blink, so `has_caret`
/// leaves it out and an idle app draws no frame for it.
sink_caret_solid: bool,
/// Bumped whenever `sink_caret` changes between frames — the caret
/// moved, or focus came to or left a custom editor — so the driver
/// re-arms the blink solid, the way `EditStore::caret_stamp` does for
/// the stock editor. The two are summed in `caret_stamp`.
sink_caret_stamp: u64,
/// Events raised by the frame driver's own reports rather than by input
/// — a changed viewport becoming a `resize`. Drained alongside the
/// interaction's pending queue.
pending: Vec<UiEvent>,
/// Whether any frame has begun yet: the first one establishes the
/// viewport instead of resizing it.
framed: bool,
/// The OS settings the last frame was begun with. A driver pushes
/// them into `env` whenever it learns of a change, and the difference
/// between two frames is what becomes a `system` event — the same
/// bookkeeping `viewport` does for `resize`.
system_seen: SystemEnv,
/// The session's `system_fonts_rev` the last frame was begun with; the
/// difference is what becomes a `fonts` event.
system_fonts_seen: u64,
/// Frames begun so far; stamps the per-key stores below.
frame_no: u64,
/// The rect last reported for each `on_layout` node and the frame it
/// was seen: a different rect, or a node not seen last frame, posts a
/// `layout` event (see `emit_layout_events`).
layouts: FxHashMap<Key, (Rect, u64)>,
/// One-off announcements queued since the last drain (see
/// [`Self::announce`]). The fourth of
/// the four drained channels, and the same shape as the other three:
/// the core appends, a driver drains, a headless test asserts on what
/// it drained.
announcements: Vec<crate::access::Announcement>,
/// The last announcement's text, the frame it was queued on and the
/// [`Self::events_answered`] reading then: the same text on two
/// consecutive frames with no event handed to the app between them is
/// what an unguarded `ui.announce(...)` in a view looks like, and
/// `announcement-repeated` says so. The event count is what tells a
/// window that redraws only on input apart from one shouting every
/// frame — two Copy presses in a row are two consecutive frames there.
last_announcement: Option<(String, u64, u64)>,
/// How many times `handle_input` handed the app at least one event.
events_answered: u64,
/// The `reveal(key)`s waiting for a layout to resolve against, in the
/// order asked: the next `finish_frame` scrolls each node's scrolling
/// ancestor to show it, then clears them. Within one container the
/// last ask wins; asks aimed at different containers all land.
pending_reveal: Vec<Key>,
/// `reveal_label` and `set_scroll_label` asks, with the origin that
/// asked: resolved when the frame finishes, where a label named before
/// its node is declared — later in the same build, or by the next
/// frame — has a node to find.
pending_reveal_labels: Vec<(String, crate::tree::OriginId)>,
/// The `on_focus` nodes the focus was last reported inside, outermost
/// first, with each one's origin and tag — what `report_focus` diffs
/// the focus against.
focus_reported: Vec<(Key, crate::tree::OriginId, Value)>,
pending_scroll_labels: Vec<(String, crate::tree::OriginId, Vec2)>,
/// Type-ahead inside a composite: the
/// characters typed so far, and the frame clock reading of the last
/// keystroke. The buffer is cleared at the start of the first frame
/// more than [`TYPE_AHEAD_SECS`] after it, so input routing stays
/// timeless — the aging happens where time already lives. With no
/// clock set `type_ahead_at` is None and every keystroke starts a
/// fresh search, which is the useful half of type-ahead.
type_ahead: String,
type_ahead_at: Option<f64>,
/// Reused by the composite walks (the ring, arrow motion), so a frame
/// with composites in it pays one allocation rather than one each.
items_scratch: Vec<usize>,
/// A `focus_next` / `focus_prev` waiting for a frame to walk: `true`
/// forward. The Tab ring is the finished tree's, and a request made
/// while a frame is being built has no tree to walk yet (`begin_frame`
/// cleared it), so the step is held until `finish_frame` — after the
/// modal scope is resolved, which is what scopes the ring. Last writer
/// wins, and an applied step beats a `set_focus` from the same frame.
pending_focus_step: Option<bool>,
/// The focus region in effect — the key of the node whose subtree Tab
/// walks — or `None` for the main ring (the tree minus every region).
/// Follows focus: `set_focus` moves it to the region enclosing the
/// focused node, a press settles it on the region under the pointer,
/// and it is kept across a blur so Tab re-enters where the user was.
region: Option<Key>,
/// Whether a press settled `region` somewhere other than the focused
/// node's own region — dead space in the dock, focus on the root sink
/// the press bubbled to — so the region stops following that focus
/// until it moves (decision 3's second sentence). Cleared by any
/// `set_focus` that changes the focus.
region_held: bool,
/// The focus each region last held, main (`None`) included: what
/// `focus_region` lands on when entering it again, and what main gets
/// back when the region in effect stops being declared.
region_focus: Vec<(Option<Key>, Option<Key>)>,
/// A `focus_region` waiting for the frame to finish, for the reason
/// `pending_focus_step` waits: the ring it enters is the finished
/// tree's, and the caller may name a node the last frame did not have.
/// Last writer wins.
pending_region: Option<RegionTarget>,
/// Silent-misconfiguration detection; see `diag`.
diag: Diagnostics,
/// The access tree of the last finished frame, built on demand (see
/// `access_tree`) and stamped with the frame it was built from.
access: crate::access::AccessTree,
access_built: u64,
/// The hash of the inputs `self.access` was derived from, so a frame
/// whose access-relevant state is unchanged keeps it. `None` when the last
/// frame could not be hashed, which
/// forces the next derivation.
access_inputs: Option<u64>,
/// How many times the access tree has actually been derived, as against
/// asked for. Tests read it to tell a cache hit from a miss; nothing in
/// the library acts on it.
access_rebuilds: u64,
}
/// How long a type-ahead search buffer survives without a keystroke.
/// Aged at
/// the start of a frame, so a core with no clock never ages one.
const TYPE_AHEAD_SECS: f64 = 1.0;
/// One step along a list of `len` items from `at`, wrapping or clamping.
/// None = the step ran off the end of a list that clamps.
fn step(at: usize, delta: isize, len: usize, wrap: bool) -> Option<usize> {
let n = len as isize;
let p = at as isize + delta;
if (0..n).contains(&p) {
return Some(p as usize);
}
wrap.then(|| (((p % n) + n) % n) as usize)
}
/// What a frame left owed, by kind: [`Core::owed`]. `any()` is what
/// [`Core::animating`] answers; `beyond_cycles()` is the same with a
/// keyframe cycle — which never ends — left out.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Owed {
/// A finite transition — a leg or a spring — still mid-flight.
pub transition: bool,
/// A keyframe cycle running; it always is, while its node is drawn.
pub cycle: bool,
/// An exit animation (a ghost) still departing.
pub depart: bool,
/// A frame a view asked for: `request_frame`, or an `animate` row —
/// or one the session's fonts ask for, moved under the text this
/// window shaped through another window (a new fallback list, a
/// rescan).
pub requested: bool,
/// A held drag scrolling its container.
pub autoscroll: bool,
/// A scroll container easing a programmatic offset change — a
/// `reveal` or a `set_scroll` on a container with a `transition`.
pub scroll: bool,
}
impl Owed {
/// Anything at all: the driver's reading.
pub fn any(self) -> bool {
self.transition
|| self.cycle
|| self.depart
|| self.requested
|| self.autoscroll
|| self.scroll
}
/// Anything but a cycle: what a test waits on when the view has a
/// cycle that will never let `any()` clear. An eased scroll is a
/// finite leg like a transition, so it is in.
pub fn beyond_cycles(self) -> bool {
self.transition || self.depart || self.requested || self.autoscroll || self.scroll
}
}
impl Core {
/// This window's palette, as of this frame.
/// Resolved from [`Core::theme_source`] and `env.system` at the start
/// of every frame, so it is already right by the time a view runs.
///
/// Frame-stable on purpose: every widget in one frame paints from the
/// same palette, whatever the driver does to `env` while the view
/// runs. A host that writes `env.system` *directly* and wants the new
/// answer before its next frame calls [`Core::refresh_theme`]; every
/// env setter a binding exposes already does.
pub fn theme(&self) -> &Theme {
&self.theme
}
/// Re-resolve the palette from `env.system` now, rather than at the
/// start of the next frame. What an env setter calls after writing.
pub fn refresh_theme(&mut self) {
self.theme = self.theme_source.resolve(&self.env.system);
}
/// Writes what the user set in the OS and re-resolves the palette from
/// it, so a host that pushes the appearance and reads the theme back
/// before its next frame sees the answer. The one door for
/// a binding's env setter: writing `env.system` by hand and forgetting
/// the refresh was a decision each of them had to remember.
pub fn set_system(&mut self, system: SystemEnv) {
self.env.system = system;
self.refresh_theme();
}
/// The env reading's inputs (`schema::ENV_FIELDS`): the stored env and
/// the frame's own facts — viewport, scale, focus — that ride in the
/// same reading.
pub fn env_facts(&self) -> crate::schema::EnvFacts {
crate::schema::EnvFacts {
env: self.env,
// The frame's viewport, as its `ENV_FIELDS` row says: what the
// dock leaves, not the window it was begun with (backlog F43
// — this read `self.viewport`, and an app under `KUI_DEVTOOLS`
// sized its tiers to a window it did not have).
viewport: self.viewport(),
scale: self.scale,
focus: self.focus(),
focus_visible: self.focus_visible(),
region: self.region(),
caret_visible: self.caret_visible(),
}
}
/// Whether anyone actually *chose* the accent — the OS reported one,
/// or the app set or pinned one — as opposed to the palette falling
/// back to kui's own blue.
///
/// The question the `accent` row asks before it repaints anything:
/// that row has always meant "the accent colour where there is one,
/// the `bg` I declared where there is not", so a view can name its
/// own fallback and a host that knows nothing changes nothing. The
/// theme widened *where* the accent comes from without widening
/// *whether* there is one.
pub fn has_accent(&self) -> bool {
match self.theme_source {
ThemeSource::Derived => self.env.system.accent.is_some(),
ThemeSource::DerivedWithAccent(_) | ThemeSource::Pinned(_) => true,
}
}
/// Where the palette comes from. [`ThemeSource::Derived`] by default.
pub fn theme_source(&self) -> ThemeSource {
self.theme_source
}
/// Change where the palette comes from. Takes effect on the next
/// frame, and immediately for anything reading [`Core::theme`] after
/// this call, so a host may set it before its first frame or in a
/// handler and get the same answer either way.
pub fn set_theme_source(&mut self, source: ThemeSource) {
self.theme_source = source;
self.refresh_theme();
}
/// Pin a palette: this exact [`Theme`], following neither the OS's
/// appearance nor its accent. Shorthand for
/// [`ThemeSource::Pinned`].
pub fn set_theme(&mut self, theme: Theme) {
self.set_theme_source(ThemeSource::Pinned(theme));
}
/// Keep following the OS's light/dark, but paint this accent instead
/// of the OS's. Shorthand for [`ThemeSource::DerivedWithAccent`], and
/// what an app with a brand colour wants.
pub fn set_accent(&mut self, accent: Color) {
self.set_theme_source(ThemeSource::DerivedWithAccent(accent));
}
/// Go back to following the OS for both — the default.
pub fn derive_theme(&mut self) {
self.set_theme_source(ThemeSource::Derived);
}
/// The sizes the stock widgets are built from — the palette's other
/// axis ([`crate::metrics`]). [`Metrics::default`](crate::metrics::Metrics::default) until
/// the app sets one; nothing in the OS is followed.
pub fn metrics(&self) -> &crate::metrics::Metrics {
&self.metrics
}
/// Declare the tokens the running origin references by name:
/// the host's outside a
/// fill, the filling extension's inside one. Replaces that origin's
/// table whole, so an app whose lengths change with a viewport tier
/// declares again on `resize`. A name a role owns is dropped with a
/// `reserved-token` warning, once per name. A derived token whose
/// source did not resolve is dropped with `unknown-token`, naming both.
pub fn set_tokens(&mut self, tokens: crate::tokens::Tokens) {
for name in tokens.reserved() {
let w = Warning {
code: crate::diag::RESERVED_TOKEN,
key: Key::ROOT.str(crate::diag::RESERVED_TOKEN).str(name),
message: format!(
"`{name}` is a theme or metrics role, so the token is dropped: `${name}` \
always means the role's value, and an app does not shadow one"
),
};
self.diag.raise(w);
}
// A derived token whose source did not resolve (ADR 0028) is
// dropped the same way, and the warning names both ends: the
// token that is gone and the name that was not there for it.
for u in tokens.unresolved() {
let w = Warning {
code: crate::diag::UNKNOWN_TOKEN,
key: Key::ROOT.str(crate::diag::UNKNOWN_TOKEN).str(&u.token),
message: format!(
"`${}` is dropped: it derives from `${}`, which is no colour token \n declared before it and no theme role",
u.token, u.source
),
};
self.diag.raise(w);
}
self.tokens.insert(self.origin, tokens);
}
/// Whether `origin` has declared a table — what an extension asks
/// before declaring the one it was loaded with, so a per-frame `view`
/// declares once.
pub fn tokens_declared(&self, origin: OriginId) -> bool {
self.tokens.contains_key(&origin)
}
/// The running origin's own table, if it declared one.
pub fn tokens(&self) -> Option<&crate::tokens::Tokens> {
self.tokens.get(&self.origin)
}
/// What a `$name` in a prop resolves to this frame: the running
/// origin's table over the host's, the theme's and metrics' roles in
/// front of both. What every binding lowers a reference through.
pub fn token_lookup(&self) -> crate::tokens::TokenLookup<'_> {
let own = self.tokens.get(&self.origin);
let host = if self.origin == OriginId::HOST {
None
} else {
self.tokens.get(&OriginId::HOST)
};
crate::tokens::TokenLookup {
own,
host,
theme: &self.theme,
metrics: &self.metrics,
session: Some(&self.session),
}
}
/// A binding lowered a `family` that names nothing installed or
/// loaded: raise `unknown-family`, once per name. The text
/// shapes as sans, which is what the message says.
pub fn warn_unknown_family(&mut self, name: &str) {
self.diag.raise(Warning {
code: crate::diag::UNKNOWN_FAMILY,
key: Key::ROOT.str(crate::diag::UNKNOWN_FAMILY).str(name),
message: format!(
"`family` named {name:?}, and no installed or loaded font family has that name, \
so the text shapes as sans; `systemFonts()` lists the names there are, and \
`sans`, `serif` and `mono` are kui's own"
),
});
// A family a sibling prop registered in the same parse is listed
// by the next frame's sync; nothing to do here.
}
/// A binding lowered a reference that did not resolve: raise
/// `unknown-token`, once per name, saying which slot asked. The slot
/// keeps its default, which is what the message says.
pub fn warn_unknown_token(&mut self, err: &crate::tokens::TokenError) {
let name = match err {
crate::tokens::TokenError::Unknown(n)
| crate::tokens::TokenError::Kind { name: n, .. } => n,
};
let w = Warning {
code: crate::diag::UNKNOWN_TOKEN,
key: Key::ROOT.str(crate::diag::UNKNOWN_TOKEN).str(name),
message: err.to_string(),
};
self.diag.raise(w);
}
/// Makes `metrics` the frame's: every stock widget from the next node
/// on is built from it, and `ui.metrics()` reads it back. Logical px,
/// before `env.scale`; a density is the app's to choose
/// (`Metrics::compact`, `Metrics::scaled`).
pub fn set_metrics(&mut self, metrics: crate::metrics::Metrics) {
self.metrics = metrics;
}
/// A core with a session of its own — one window, nothing shared.
pub fn new() -> Self {
Self::new_in(&Session::new())
}
/// A core joining an existing session: it draws with the same fonts,
/// images, sounds, shaping caches and glyph atlas as every other core
/// constructed against `session`, and plays through the same audio
/// device. Everything else — tree, focus, scroll, viewport — is this
/// window's alone.
pub fn new_in(session: &Session) -> Self {
let mut core = Self {
session: session.clone(),
text: TextSystem::new(),
cells: crate::cells::CellStore::new(),
atlas: GlyphAtlas::new(),
resources: SharedResources::new(session),
audio: SharedAudio::new(session),
font_names: FxHashMap::default(),
fonts_rev: u64::MAX,
weights_rev: 0,
images_rev: 0,
interaction: Interaction::default(),
scroll: ScrollStore::default(),
edit: EditStore::default(),
anim: AnimStore::default(),
depart: DepartStore::default(),
stats: FrameStats::default(),
env: Env::default(),
theme_source: ThemeSource::Derived,
theme: Theme::default(),
tokens: Default::default(),
metrics: crate::metrics::Metrics::default(),
window_title: None,
always_on_top: false,
secure_input: false,
option_as_alt: crate::input::OptionAsAlt::None,
ime_off: false,
focus: None,
focus_visible: false,
declared_focus: Vec::new(),
declared_focus_last: Vec::new(),
focus_asked: false,
key_labels: LabelIndex::default(),
key_labels_last: LabelIndex::default(),
slot_labels: LabelIndex::default(),
ns_depth: usize::MAX,
ns_key: Key::ROOT,
dt_app: None,
dt_dock: None,
dt_window: false,
dt_theme: None,
dt_menus: None,
dt_built: None,
dt_tabs: Vec::new(),
dt_area: Rect::new(0.0, 0.0, 0.0, 0.0),
building: false,
declared_windows: Vec::new(),
declared_windows_last: Vec::new(),
keys_held: Vec::new(),
pressed_mods: None,
access: Default::default(),
access_built: 0,
access_inputs: None,
access_rebuilds: 0,
tree: Tree::new(),
inspect: false,
dt_inspect: false,
inspected: Vec::new(),
prev_tree: Tree::new(),
lines: Default::default(),
paths: Default::default(),
path_motion: Default::default(),
path_parsed: Default::default(),
path_textures: crate::path::PathTextures::new(session.clone()),
output_read: true,
fragments: Default::default(),
stock_polygon: None,
hit_shapes: Default::default(),
display: DisplayList::default(),
viewport: Size::ZERO,
scale: 1.0,
stack: Vec::new(),
counters: Vec::new(),
origin: OriginId::HOST,
clips: Vec::new(),
clip_ids: Vec::new(),
opacity: Vec::new(),
float_root: Vec::new(),
float_stack: Vec::new(),
hints: Vec::new(),
menu: None,
menu_actions: Vec::new(),
menu_editor: None,
native_menus: false,
menu_sub: Default::default(),
menu_bar_sub: Default::default(),
menu_bar: None,
menu_bar_rev: 0,
menu_bar_origin: OriginId::HOST,
menu_bar_open: None,
menu_bar_root: None,
selects: Vec::new(),
native_menu_bar: false,
lookup_available: false,
selection: None,
cell_selection: None,
awaiting_selection: false,
awaiting_paste: false,
file_ask: Default::default(),
select_dragging: None,
drag_follow: None,
last_frame_time: None,
line_carry: None,
scroll_latch: Default::default(),
sel_ords: Vec::new(),
sel_ends: None,
scopes: Vec::new(),
rows: Vec::new(),
modal: None,
modal_focus: Vec::new(),
type_ahead: String::new(),
type_ahead_at: None,
items_scratch: Vec::new(),
ghost_opacity: Vec::new(),
ghost_clip: Vec::new(),
ghost_clip_ids: Vec::new(),
ghost_rect: Vec::new(),
frame_requested: false,
trace: cause::Trace::default(),
pending_reveal: Vec::new(),
pending_reveal_labels: Vec::new(),
focus_reported: Vec::new(),
pending_scroll_labels: Vec::new(),
pending_focus_step: None,
region: None,
region_held: false,
region_focus: Vec::new(),
pending_region: None,
ime_rect: None,
sink_caret: None,
sink_caret_solid: false,
sink_caret_stamp: 0,
pending: Vec::new(),
framed: false,
system_seen: SystemEnv::default(),
system_fonts_seen: 0,
frame_no: 0,
layouts: FxHashMap::default(),
announcements: Vec::new(),
last_announcement: None,
events_answered: 0,
diag: Diagnostics::default(),
};
core.sync_font_names();
core
}
// -- Measurement ----------------------------------------------------
// The layout engine measures text every frame; these hand the same
// numbers to the view, so it never re-derives them by hand.
/// Measures `content` in `style` without adding a node: its unwrapped
/// size, or with `max_w` (logical px) its size once wrapped to that
/// width — what layout would give a text node with that content and
/// style, `wrap` / `max_lines` / `ellipsis` included. Logical px at
/// the scale of the current or last frame (1 before any frame).
/// Shapes through the text cache, so measuring a string and then
/// drawing it shapes once.
pub fn measure_text(
&mut self,
content: &str,
style: &TextStyle,
max_w: Option<f32>,
) -> TextMetrics {
let sess = &mut *self.session.state();
self.text
.measure(content, style, &sess.resources, &mut sess.fonts, max_w)
}
/// `measure_text` for a rich-text paragraph.
pub fn measure_rich_text(
&mut self,
spans: &[Span<'_>],
base: &TextStyle,
max_w: Option<f32>,
) -> TextMetrics {
let sess = &mut *self.session.state();
self.text
.measure_rich(spans, base, &sess.resources, &mut sess.fonts, max_w)
}
/// Where a point lands in the text node `key` drew: a byte offset into
/// its content and the visual row within that node — counted across
/// every run the key covers by where the rows sit, so a `line` row of
/// inline runs is one row and a wrapped run as many as it wrapped to;
/// not the ordinal `line` node a pointer event names
/// — or `None` for a key that is not a text node or was not drawn.
/// A `role="none"` subtree under the key (a gutter) is
/// not its text, as the access tree reads it. `point` is logical
/// viewport px — the `x`/`y` a click or drag event carries — so a
/// custom editor turns the event into a caret position with one call
/// instead of measuring prefixes or assuming a cell width. Answered
/// from the frame that finished: between frames that is the layout the
/// pointer was over, and during a build it is the last one, since the
/// node being declared has no layout yet. A wrapped node answers in
/// the width it was drawn at.
pub fn text_hit(&self, key: Key, point: Vec2) -> Option<TextHit> {
self.text
.hit_at(key, point.plus(self.dt_shift()), self.building)
}
/// The caret rect for byte `byte` of the text node `key` drew: logical
/// viewport px, zero wide, one line tall — where a caret, an IME
/// candidate window or a selection edge goes. `byte` past the content
/// is the end. Answered from the same frame `text_hit` is.
pub fn caret_rect(&self, key: Key, byte: usize) -> Option<Rect> {
let shift = self.dt_shift();
self.text
.caret_at(key, byte, self.building)
.map(|r| Rect::new(r.x - shift.x, r.y - shift.y, r.w, r.h))
}
// -- Announcements ---------------------------------------------------
/// Says something once, with no node behind it: "Saved", "3 results".
/// Queued for [`Self::take_announcements`], the way `play` queues an
/// audio command — an announcement is a consequence of an event, and
/// the frame's tree, which is a function of state, has no place to
/// keep one.
/// A region whose text changes on screen is the other half, and is
/// the `live` prop instead.
///
/// [`Live::Off`](crate::access::Live::Off) and an empty string are both no-ops — the first so a
/// caller can gate politeness without an `if`, the second because
/// every platform needs a name to say.
pub fn announce(&mut self, text: &str, live: crate::access::Live) {
if live == crate::access::Live::Off || text.is_empty() {
return;
}
if let Some((last, frame, answered)) = &self.last_announcement
&& last == text
&& *frame + 1 >= self.frame_no
&& *answered == self.events_answered
{
self.diag.raise(crate::diag::announcement_repeated(text));
}
self.last_announcement = Some((text.to_string(), self.frame_no, self.events_answered));
self.announcements.push(crate::access::Announcement {
text: text.to_string(),
live,
});
}
/// Drains the announcements queued since the last drain. Windowed
/// runners drain every frame whether or not assistive technology is
/// attached, and discard what they cannot deliver, so a real app never
/// accumulates and nothing is spoken minutes late; headless drivers
/// assert on what comes back.
pub fn take_announcements(&mut self) -> Vec<crate::access::Announcement> {
std::mem::take(&mut self.announcements)
}
/// Announcements queued and not yet drained (what `pending` is for
/// audio commands).
pub fn pending_announcements(&self) -> &[crate::access::Announcement] {
&self.announcements
}
// -- Diagnostics ----------------------------------------------------
/// Drains the warnings raised since the last drain (see [`crate::diag`]):
/// silent misconfigurations the core noticed while finishing frames,
/// each distinct (code, node) pair once. Windowed runners print them;
/// headless tests assert on them.
pub fn take_warnings(&mut self) -> Vec<Warning> {
// Handles of other sessions resolve where the registry can see
// them and this core cannot (shaping, the audio backend), so the
// registry keeps them and the core draining warnings reports them.
for f in self.session.state().resources.take_foreign() {
self.diag.raise(crate::diag::foreign_resource(&f));
}
if let Some(w) = crate::diag::size_expressions_full() {
self.diag.raise(w);
}
self.diag.take()
}
/// Every warning this core has raised, drained or not, oldest first —
/// for a reader that is not the driver. The runner drains
/// [`Self::take_warnings`] after every frame and prints them, so a
/// view that wants to *show* them (a development overlay) would
/// otherwise never see one; this is the log the drain leaves behind.
pub fn warnings_raised(&self) -> &[Warning] {
self.diag.raised()
}
/// Raises a warning a binding built (see [`crate::diag::unknown_prop`]):
/// a frontend sees declarations the tree walk cannot, because a prop
/// name nothing claims never becomes part of a node. Behind the same
/// [`Self::set_diagnostics`] gate and the same once-per-(code, key)
/// dedup as the checks, so a binding may raise one per node per frame.
pub fn warn(&mut self, warning: Warning) {
self.diag.raise(warning);
}
/// Turns the diagnostic checks on or off. A bare `Core` has them on;
/// drivers set them for the build they are in (the runner: debug on,
/// release off; Node loops: off under `NODE_ENV=production`; a
/// standalone C context: off until asked).
pub fn set_diagnostics(&mut self, on: bool) {
self.diag.enabled = on;
}
pub fn diagnostics(&self) -> bool {
self.diag.enabled
}
/// Wheel line-deltas (e.g. winit's LineDelta) to logical px.
pub fn lines_to_px(lines: f32) -> f32 {
lines * SCROLL_LINE_PX
}
/// Rasterizes outline glyphs as LCD subpixel coverage
/// (`QuadKind::GlyphSubpixel`) instead of alpha masks. Drivers set it
/// from what their renderer can blend per channel; flipping it drops
/// the glyph atlas so every glyph re-rasterizes in the new mode.
pub fn set_subpixel_text(&mut self, on: bool) {
if self.text.set_subpixel(on) {
self.atlas.clear();
}
}
pub fn subpixel_text(&self) -> bool {
self.text.subpixel()
}
/// The byte budget for the shaped-text cache: every
/// text a frame draws is shaped once and kept, and past this many
/// estimated bytes the least recently drawn entries go, down to three
/// quarters of it, at the start of the next frame. What the last
/// frame drew is never evicted, so a budget too small for one
/// screenful costs re-shaping nothing — it only stops keeping what
/// scrolled away. Default `DEFAULT_TEXT_CACHE_BYTES` (64 MB): a
/// terminal streaming new lines lowers it, a document viewer that
/// wants every page it showed to stay warm raises it. The clock that
/// empties an idle cache after 300 frames is unchanged.
pub fn set_text_cache_budget(&mut self, bytes: usize) {
self.text.set_budget(bytes);
}
pub fn text_cache_budget(&self) -> usize {
self.text.budget()
}
/// What the shaped-text cache holds, as the estimate the budget is
/// charged against (a fixed floor per entry plus a per-glyph rate,
/// calibrated against a counting allocator; see `text.rs`).
pub fn text_cache_bytes(&self) -> usize {
self.text.bytes()
}
/// How many shaped texts the cache holds (a long line's chunks each
/// count).
pub fn text_cache_len(&self) -> usize {
self.text.len()
}
/// How many long lines — no-wrap texts past `LONG_LINE_BYTES`, shaped
/// in chunks — are held.
pub fn long_lines(&self) -> usize {
self.text.long_lines()
}
/// The frame clock for transitions: monotonic seconds, any origin.
/// Drivers set it before every frame; a driver that never does gets
/// snapping instead of animation.
pub fn set_time(&mut self, now_secs: f64) {
self.anim.set_time(now_secs);
self.scroll.set_time(now_secs);
}
/// True when the last frame left a transition mid-flight, or a view
/// asked for another frame — drivers schedule one without waiting for
/// input. One bool over every source; [`owed`](Self::owed) is the
/// same reading by kind.
pub fn animating(&self) -> bool {
self.owed().any()
}
/// What the last frame left owed, by kind. To a driver
/// the kinds are one — it schedules the frame either way — but a
/// test that wants to know whether the *transitions* have run out
/// under a keyframe cycle that never will reads `cycle` apart from
/// the rest: [`Owed::beyond_cycles`] is that wait's predicate.
pub fn owed(&self) -> Owed {
let (transition, cycle) = self.anim.owes();
Owed {
transition,
cycle,
depart: self.depart.animating(),
requested: self.frame_requested
|| self.tree.any_animate
|| self.fonts_moved()
|| self.submenu_waiting(),
autoscroll: self.autoscrolling(),
scroll: self.scroll.animating(),
}
}
/// Whether the session's fonts moved under the text this window shaped
/// since its last frame began — a new fallback list, a rescan, a face of
/// a registered family come or gone, through whichever window of the
/// session — so the window owes a frame to shape it again (backlog
/// RG118). The window that made the change asks for its own frame;
/// this is how every other window hears, through the `animating` a
/// driver polls for each of its windows, with no list of the session's
/// windows to wake. A window that has not drawn has nothing to shape
/// again. `try_state`, since a driver may ask while the session is
/// borrowed, where it reads as nothing owed.
fn fonts_moved(&self) -> bool {
self.framed
&& self
.session
.try_state()
.is_some_and(|sess| sess.weights_rev != self.weights_rev)
}
/// Asks the driver for one more frame right after this one. A view
/// that sets up a transition by drawing a starting state (a new split
/// drawn collapsed so it can slide open) needs the next frame to come
/// without waiting for input — the starting state itself snaps, so
/// nothing is mid-flight yet to request it.
///
/// Traced ([`Self::set_frame_trace`]), the calling line is kept as
/// the frame's [`cause::FrameRequest`], which is why this tracks its
/// caller.
#[track_caller]
pub fn request_frame(&mut self) {
self.frame_requested = true;
self.trace_request();
}
/// Starts a frame. Build the tree through the returned `Ui` (or the
/// `Core` builder methods directly), then `Ui::finish` — the one door
/// out of a frame, which runs the extension fills, the devtools panel
/// and the open menu before layout. A driver holding a bare `Core`
/// mid-frame finishes through `Ui::wrap(core).finish()`.
pub fn frame(&mut self, viewport: Size, scale: f32) -> Ui<'_> {
self.begin_frame(viewport, scale);
Ui::new(self)
}
/// `frame` with something to fill the slots the view declares — the
/// runner's extension list (`[Box<dyn Extension>]` is a `Fill`), or a
/// test's stand-in. `Ui::slot` calls it in place, and `Ui::finish`
/// lets it fill `"root"` and report unknown slots before layout.
pub fn frame_with<'a>(
&'a mut self,
viewport: Size,
scale: f32,
filler: &'a mut dyn crate::slot::Fill,
) -> Ui<'a> {
self.begin_frame(viewport, scale);
Ui::with_filler(self, filler)
}
/// The session this core draws from. Hand it to `Core::new_in` to open
/// another window sharing its fonts, images, sounds and glyph atlas.
pub fn session(&self) -> &Session {
&self.session
}
/// Runs an edit-store operation against the session's font system —
/// the one the text cache shapes with, so an editor and a text node
/// measure the same. The session borrow lasts exactly the call.
fn edit_with_fonts<T>(
&mut self,
f: impl FnOnce(&mut EditStore, &mut cosmic_text::FontSystem) -> T,
) -> T {
let sess = &mut *self.session.state();
f(&mut self.edit, &mut sess.fonts)
}
/// The viewport (logical px) the current frame was begun with — the
/// window, less the devtools' dock while the panel is docked:
/// what the host lays out into. Changes to it
/// arrive as `resize` events (see `take_pending_events`), a dock
/// coming, going or resizing among them.
pub fn viewport(&self) -> Size {
Size::new(self.dt_area.w, self.dt_area.h)
}
/// The device pixel ratio the current frame was begun with.
pub fn scale(&self) -> f32 {
self.scale
}
/// The origin nodes opened right now are tagged with: `OriginId::HOST`
/// in the host's own view, the filling extension's inside a fill (see
/// `Core::fill`).
pub fn origin(&self) -> OriginId {
self.origin
}
/// The finished frame's draw data: display list plus the glyph atlas the
/// renderer mirrors (mutable so it can clear the dirty flag).
pub fn output(&mut self) -> (&DisplayList, &mut GlyphAtlas) {
self.output_read = true;
(&self.display, &mut self.atlas)
}
/// Begins a frame without handing out a [`Ui`]: what [`Core::frame`]
/// calls first. A binding that drives the builder methods on the core
/// directly starts here and ends with `Ui::wrap(core).finish()`.
pub fn begin_frame(&mut self, viewport: Size, scale: f32) {
// First, while the last frame's tree and every store's reading of
// it are still whole: why this frame runs, and who held it
// (backlog F111).
self.trace_begin_frame();
// Against the finished frame, before anything below clears it: a
// held drag re-places its live end where the last layout moved
// the text under the pointer, and steps its scroller when the
// pointer is past the edge (ADR 0029).
self.follow_drag();
self.age_type_ahead();
// A window that changed size is a fact the driver reports, so the
// core turns it into data like any other: `{kind="resize", width,
// height, scale}` on the root, pending for the driver to route
// after the frame. The first frame establishes the viewport rather
// than resizing it.
// The host's viewport is what the dock leaves of the window
// (ADR 0024), so a dock that comes, goes or is dragged is a
// resize too.
let area = self.devtools_area(viewport);
if self.framed
&& (area.w != self.dt_area.w || area.h != self.dt_area.h || scale != self.scale)
{
self.pending.push(UiEvent {
origin: OriginId::HOST,
window: WindowId::MAIN,
key: Key::ROOT,
payload: Value::map([
("kind", Value::str("resize")),
("width", Value::Float(area.w as f64)),
("height", Value::Float(area.h as f64)),
("scale", Value::Float(scale as f64)),
]),
slot: None,
});
}
self.dt_area = area;
// And what the user set in the OS — and whether assistive
// technology is listening, which rides in the same reading. A
// driver that learns of a change writes it into `env` and asks
// for a redraw — which is
// enough for a host whose view is a function the runner calls
// every frame, and nothing at all for one that retains the tree
// it was handed (Node, C, Lua): its `view` runs when a message
// changes the model, so the change has to *be* a message. The
// first frame establishes the reading rather than reporting it,
// the way the viewport does.
if self.framed && self.env.system != self.system_seen {
let sys = self.env.system;
self.pending.push(UiEvent {
origin: OriginId::HOST,
window: WindowId::MAIN,
key: Key::ROOT,
payload: Value::map([
("kind", Value::str("system")),
("appearance", Value::str(sys.appearance.name())),
(
"accent",
sys.accent
.map_or(Value::Null, |c| Value::Int(c.to_hex() as i64)),
),
("motion", Value::str(sys.motion.name())),
(
"locale",
sys.locale.map_or(Value::Null, |l| Value::str(l.as_str())),
),
("assistive", Value::str(sys.assistive.name())),
]),
slot: None,
});
}
self.system_seen = self.env.system;
// And the installed fonts, rescanned since the last frame and found
// changed (`reload_system_fonts`, which the winit runner calls when
// the OS says so): a message for the same reason as `system`, an
// app holding `systemFonts()` in its model having nothing else to
// re-read it on. The first frame establishes it, as above.
let fonts_rev = self.session.state().system_fonts_rev;
if self.framed && fonts_rev != self.system_fonts_seen {
self.pending.push(UiEvent {
origin: OriginId::HOST,
window: WindowId::MAIN,
key: Key::ROOT,
payload: Value::map([("kind", Value::str("fonts"))]),
slot: None,
});
}
self.system_fonts_seen = fonts_rev;
// The palette is a function of what the OS said and what the app
// asked for, so it is recomputed rather than invalidated: a
// couple of dozen float ops once a frame, against a cache that
// would have to be poked from every writer of `env.system`.
self.refresh_theme();
self.framed = true;
self.frame_no += 1;
// The layout rects a node reported, swept on the one cadence
// every by-last-use store sweeps on (`retain::sweep_cutoff`, AR45).
if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
self.layouts.retain(|_, (_, seen)| *seen >= cutoff);
}
self.viewport = viewport;
self.scale = scale;
self.window_title = None;
self.always_on_top = false;
self.secure_input = false;
self.option_as_alt = crate::input::OptionAsAlt::None;
self.ime_off = false;
// The drawn menu bar's root is this frame's: a view that stops
// calling `widgets::menu_bar` leaves nothing behind for the next
// event to land on. Re-recorded while the widget builds.
self.menu_bar_root = None;
// And the select fields, re-declared by the ones the view builds.
self.selects.clear();
// Last frame's focus declarations are what this frame's are
// compared against (see `set_key_focus`).
std::mem::swap(&mut self.declared_focus, &mut self.declared_focus_last);
self.declared_focus.clear();
// And the labels `key_of` resolves through, the same way.
std::mem::swap(&mut self.key_labels, &mut self.key_labels_last);
self.key_labels.clear();
// Slots are positions, not declarations to diff: one clear.
self.slot_labels.clear();
self.ns_depth = usize::MAX;
self.building = true;
// And the window declarations, which `finish_frame` diffs the same
// way (see `declare_window`).
std::mem::swap(&mut self.declared_windows, &mut self.declared_windows_last);
self.declared_windows.clear();
// A frame that declared an `exit` may be the last one some node is
// ever seen in, so it is kept whole: the two tree buffers swap
// roles instead of one being cleared, which costs an allocation
// that already existed and no copying. Nothing else keeps it — a
// frame with no exits empties the spare, so a stale tree can never
// be diffed against.
let keep_prev = self.tree.any_exit;
if keep_prev {
std::mem::swap(&mut self.tree, &mut self.prev_tree);
} else {
self.prev_tree.clear();
}
self.tree.clear();
// The drops a frame carried and no backend read - a frame built
// twice before one is drawn, a window that framed and did not
// render - ride on, or the texture a path animated out of would
// be the backend's for good (RG112). Bounded, for a core that is
// never read at all and so has no backend to tell.
let unread = (!self.output_read).then(|| {
const MOST: usize = 4096;
let mut t = std::mem::take(&mut self.display.dropped_textures);
let mut f = std::mem::take(&mut self.display.dropped_fragments);
t.drain(..t.len().saturating_sub(MOST));
f.drain(..f.len().saturating_sub(MOST));
(t, f)
});
self.display.clear();
if let Some((t, f)) = unread {
self.display.dropped_textures = t;
self.display.dropped_fragments = f;
}
self.output_read = false;
// Removed handles the backend has not heard of, and this window's
// atlas slots for removed images (AR8); kept on the session
// because a removal can land between frames, after the list was
// cleared, and through a window that never draws again.
self.sync_dropped();
// Before anything is emitted: the one point where the atlas may
// empty its page — one the last frame extended, or one about to
// fill — without a quad sampling what it dropped (F99).
self.atlas.begin_frame();
// An image's spare buffer outlives its stream by a few frames (W20).
self.session.state().resources.release_spares();
// Before the text store begins its frame, as a scale change is.
self.sync_weights();
// The text list goes with the tree: a kept frame's text nodes carry
// that frame's `TextId`s, and nothing else can resolve them.
self.text.begin_frame(
&mut self.session.state().fonts,
scale,
keep_prev,
self.frame_no,
);
// And the strokes, for the same reason: a kept frame's `line`
// nodes index that frame's list.
self.lines.begin_frame(keep_prev);
self.paths.begin_frame(keep_prev);
let cutoff = self.frame_no.saturating_sub(crate::path::ANIMATING_WINDOW);
if self.path_motion.len() > 1024 {
self.path_motion.retain(|_, m| m.seen >= cutoff);
}
if self.path_parsed.len() > 1024 {
self.path_parsed.retain(|_, p| p.seen >= cutoff);
}
self.fragments.begin_frame(keep_prev);
self.cells.begin_frame(scale);
self.sync_font_names();
self.anim.begin_frame(self.frame_no);
self.depart.begin_frame(self.frame_no);
// The two stores that keep state by key across a key's absence:
// they stamp this frame onto what it declares, and cap what it
// does not (backlog F26).
self.edit.begin_frame(self.frame_no);
self.scroll.begin_frame(self.frame_no);
self.tree.push(
NIL,
Key::ROOT,
OriginId::HOST,
NodeSpec::column().fill(),
NodeContent::Container,
);
self.stack.clear();
self.stack.push(0);
self.counters.clear();
self.counters.push(0);
// A frame that ended with nodes unclosed must not leak its hints
// into the next one.
self.hints.clear();
self.origin = OriginId::HOST;
self.frame_requested = false;
// And the lines that asked, with it: an ask the reset above
// forgets is not one the next frame was held by.
self.trace_forget_requests();
self.devtools_begin_frame();
}
}
impl Default for Core {
fn default() -> Self {
Self::new()
}
}
/// A frontend that draws into the shared tree each frame — the trait the
/// runner uses to host Lua (or any other) extensions without knowing what
/// they are. Origins are assigned by the runner.
///
/// Where it draws is a slot the host declared:
/// `slots` names
/// the ones it fills, `view` is called once per frame for each of them
/// with which one it is, and an extension naming none is called once
/// after the host's view for the reserved `"root"` slot — the sequence
/// every extension got before slots existed. `on_event` answers with
/// replies: values the runner hands to the host's own `on_event`, with
/// this extension's origin on them.
pub trait Extension {
fn name(&self) -> &str;
/// The slot names this extension fills; empty means `"root"`. The one
/// entry [`crate::slot::ANY_SLOT`] (`"*"`) means every name declared
/// under its namespace, for an extension whose slots are not known
/// when it loads — a Lua host whose `init.lua` registers views at
/// runtime, one slot per view — and it then gets no `unknown-slot`
/// warning, since there is no list to check against.
fn slots(&self) -> &[String] {
&[]
}
fn view(&mut self, slot: &crate::slot::Slot<'_>, ui: &mut Ui<'_>) -> Result<(), String>;
/// One of this extension's events; the values returned are replies
/// to the host, delivered in order.
fn on_event(&mut self, ev: &UiEvent) -> Vec<Value>;
}