rust_widgets 2.1.0

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 60+ widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
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
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT

//! rust_widgets - cross-platform native GUI architecture in pure Rust.

// BLUE11 R9.6: Unsafe code audit — unsafe is required for platform FFI
// Note: Removed `#![allow(unsafe_code)]` — default is allow, no-op.
// `missing_docs` is denied rather than allowed: every public item in every profile
// is documented, and this lint is what keeps it that way. It is enforced on the
// `desktop`, `embedded`, `mini` and `--all-features` builds alike, and CI's clippy
// job runs with `-D warnings`, so a new undocumented public item fails the build.
#![deny(missing_docs)]
// BLUE11: Clippy lints enabled for quality enforcement.
// Individual allows are placed next to their specific violations.
#![cfg_attr(test, allow(clippy::needless_pass_by_value, clippy::unwrap_used))]
// Required unconditionally — `alloc` is available in both std (re-exported)
// and no_std contexts. `core` is always available via `extern crate std` under
// std, but we need direct `alloc::` paths in `compat` for no_std builds.
extern crate alloc;

// ── BLUE13 Phase 3: Alloc bridge — unified imports for std and no_std ──
// All crate files import from `compat` instead of directly from std.
pub mod compat;

/// Action/command system.
pub mod action;
/// Desktop-only: Generic asset file watcher.
#[cfg(feature = "desktop")]
pub mod asset;
/// Audio module — format detection, decoding, encoding, sample processing, and normalization.
#[cfg(feature = "audio")]
pub mod audio;
/// C ABI and language bindings (C / Java-JNI / etc.).
///
/// Available on the `desktop` profile and on any profile that exposes an
/// FFI consumer (`jni` for Java, `mobile-api` for the mobile runtime). The
/// `mini` profile excludes it because it is `alloc`-free / no-std oriented.
#[cfg(all(any(feature = "desktop", feature = "jni", feature = "mobile-api"), not(alloc_frugal)))]
pub mod bindings;
/// Clipboard helpers.
pub mod clipboard;
/// Control backend abstraction for native/custom control implementations.
pub mod control_backend;
/// Core types and shared contracts.
pub mod core;
/// Reactive data binding system — Model → View automatic synchronization.
pub mod data_binding;
/// Embedded system optimizations and support.
#[cfg(embedded_surface)]
pub mod embedded;
/// Unified error system (ErrorId, RwError, c_try!).
pub mod error;
/// Event types and dispatch helpers.
pub mod event;
/// Gesture recognizer system (gated behind `touch` feature).
#[cfg(feature = "touch")]
pub mod gesture;
/// Hardware-adaptive GPU management.
pub mod gpu;
/// Internationalization module.
#[cfg(feature = "i18n")]
pub mod i18n;
/// Image module — format detection, decoding, encoding, transform, and color conversion.
///
/// **Decoding** is real for: PNG (all bit depths, scanline filters, palette), JPEG,
/// BMP, QOI, Farbfeld, PNM (P5/P6). GIF/WebP/TIFF/AVIF/ICO/SVG decode returns an
/// explicit `Err` rather than fabricated pixels until a codec lands.
#[cfg(feature = "image")]
pub mod image;
/// Declarative JSON window engine (QML-like).
#[cfg(all(any(feature = "desktop", feature = "tablet", feature = "mobile"), widgets_unstripped))]
pub mod json;
/// Layout managers.
pub mod layout;
/// Memory management utilities.
pub mod memory;
/// Advanced widgets (gated behind `advanced-widgets` feature).
#[cfg(feature = "advanced-widgets")]
pub mod menu_config;
/// Object tree and object utilities.
pub mod object;
/// Performance monitoring and optimization.
pub mod performance;
/// Platform abstraction and backend adapters.
pub mod platform;
/// Quality management for adaptive rendering.
pub mod quality;
/// Rendering traits and primitives.
pub mod render;
/// Runtime render-engine abstraction.
pub mod render_engine;
/// Global shortcut system for keyboard shortcuts.
pub mod shortcut;
/// Signal-slot utilities.
pub mod signal;
/// Style system primitives.
pub mod style;
/// Test infrastructure and utilities.
pub mod test;
/// Desktop-only: Theme management.
#[cfg(feature = "desktop")]
pub mod theme;
/// Undo/Redo framework for undoable commands and cross-widget undo/redo.
pub mod undo;
/// Generic utility modules (asset watcher, helpers, etc.).
/// Video module — container format detection, frame extraction, metadata, and playback.
#[cfg(feature = "video")]
pub mod video;
/// Web view and engine components.
#[cfg(widgets_unstripped)]
pub mod web;
/// Optional WGPU GPU acceleration backend (gated behind `gpu-wgpu` feature).
#[cfg(feature = "gpu-wgpu")]
pub mod wgpu_backend;
/// Widget definitions and widget helpers.
pub mod widget;
// Re-export all widget types for convenience
pub use widget::*;
// NOTE: there is no top-level `chart` module. The chart *engine* (layout, axes,
// ticks, SVG context, adapter) and the chart *widgets* live together under
// `crate::widget::chart_widgets`, because they are two layers of one feature.
// The engine is reachable as `rust_widgets::widget::chart_widgets::charts`
// (and `::types`/`::layout`/`::svg`/`::adapter`).
/// Translates a message key — the no-`i18n` fallback spelling.
///
/// This macro exists so that code which calls `tr!` still compiles when the `i18n`
/// feature is off. It performs **no translation**: it logs a warning naming the key
/// and returns the key itself. That makes a missing translation loud during
/// development instead of quietly rendering an empty string, but it also means the
/// returned text is a message *key*, not user-facing copy — a build without `i18n`
/// must not be shipped as a localized one.
///
/// Accepts the same three arities as the real macro (`$key`, `$key, $count`, and
/// `$key, $context, $count`) so call sites need no `cfg` of their own; the plural
/// and context arguments are ignored here.
#[cfg(not(feature = "i18n"))]
#[macro_export]
macro_rules! tr {
    ($key:expr) => {{
        log::warn!("i18n tr! called but the i18n feature is disabled, key={}", $key);
        $key.to_string()
    }};
    ($key:expr, $count:expr) => {{
        log::warn!("i18n tr! called but the i18n feature is disabled, key={}", $key);
        $key.to_string()
    }};
    ($key:expr, $context:expr, $count:expr) => {{
        log::warn!("i18n tr! called but the i18n feature is disabled, key={}", $key);
        $key.to_string()
    }};
}
/// Application lifecycle wrapper and type-safe widget handles (not available in mini mode).
#[cfg(all(any(feature = "desktop", feature = "tablet", feature = "mobile"), widgets_unstripped))]
pub mod app;
/// Index-based widget registry for runtime lookup.
pub mod index;
#[cfg(feature = "pdf")]
/// PDF rendering/export support.
pub mod pdf;
#[cfg(feature = "print")]
/// Print and preview support.
pub mod print;
/// Initialize global platform and i18n subsystems.
///
/// One function for every profile (BLUE15 rule #58): the branch that used to be
/// a `cfg`-gated pair now asks [`platform::profile`], so adding a profile does
/// not mean adding a copy of this function.
pub fn init() {
    trace_runtime_route("init");
    platform::profile::runtime_init();
    platform::profile::init_optional_subsystems();
}
/// Run platform main event loop.
pub fn run() {
    trace_runtime_route("run");
    platform::profile::runtime_run();
}
/// Request platform event loop shutdown.
pub fn quit() {
    trace_runtime_route("quit");
    platform::profile::runtime_quit();
}
/// Logs the resolved profile/backend/route when `RUST_WIDGETS_TRACE_RUNTIME=1`.
///
/// # Why this writes to stderr as well as the log
///
/// `log::info!` goes to the `log` facade, and this crate installs no logger on
/// desktop builds (`src/platform/android_jni.rs` does, via logcat, and that is the
/// only one). A program that has not installed one therefore saw **nothing** when it
/// asked for the trace, so the runtime audit BLUE15 #55 requires could not actually
/// be performed: `RUST_WIDGETS_TRACE_RUNTIME=1` printed an empty line and the route
/// stayed unverified. Writing the same record to stderr when the variable is set
/// makes the audit work out of the box, while the `log` record keeps the event
/// available to a host that does install a logger. The stderr write is opt-in — the
/// variable has to be exactly `1` — so a normal run prints nothing.
pub(crate) fn trace_runtime_route(stage: &str) {
    if std::env::var("RUST_WIDGETS_TRACE_RUNTIME").ok().as_deref() != Some("1") {
        return;
    }

    let line = format!(
        "[rust_widgets.runtime] stage={} profile={} backend={} route={} host={}",
        stage,
        platform::profile::profile_name(),
        platform::platform_facts().backend_name(),
        platform::profile::route_name(),
        platform::profile::host_name()
    );
    log::info!("{line}");
    eprintln!("{line}");
}
// Convenient wrapper functions for platform operations
// Users can call these directly without manually getting a platform instance

/// Resolve the backend that should create a widget of this kind.
///
/// # Why this exists
///
/// A widget kind may map onto a real platform primitive (a `Button` on Win32/
/// AppKit/GTK) or have no primitive at all (`Chart`, `CodeEditor`, …), in which
/// case the platform layer supplies the surface. **Which of the two happens is a
/// backend decision and must not leak to callers**: this function is the single
/// place the creation path asks for it, so the choice cannot drift between call
/// sites (see `control_backend::dispatcher`).
///
/// On a profile without an OS runtime (`mini`, `embedded`) the custom state
/// backend answers instead, so the same call works everywhere.
#[cfg(not(alloc_frugal))]
fn backend_for_kind(kind: widget::WidgetKind) -> &'static dyn control_backend::ControlBackend {
    control_backend::get_control_backend_for_widget(kind)
}

// ── Kinds that reduced profiles compile out ──
//
// `embedded` drops these `WidgetKind` variants, but the `create_*` functions that
// route on them must stay callable in **every** profile (the public API surface
// must not vary — principle #53). These aliases name the variant when it exists
// and substitute the closest always-present kind when it does not, so the call
// site stays a single unconditional expression.
//
// `Panel` is the stand-in: it exists in every profile, it is a container surface,
// and the backends that run reduced profiles implement it.

/// `WidgetKind::MenuBar` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(all(not(embedded_surface), feature = "desktop"))]
const KIND_MENU_BAR: widget::WidgetKind = widget::WidgetKind::MenuBar;
#[cfg(not(alloc_frugal))]
#[cfg(not(all(not(embedded_surface), feature = "desktop")))]
const KIND_MENU_BAR: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::Menu` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(all(not(embedded_surface), feature = "desktop"))]
const KIND_MENU: widget::WidgetKind = widget::WidgetKind::Menu;
#[cfg(not(alloc_frugal))]
#[cfg(not(all(not(embedded_surface), feature = "desktop")))]
const KIND_MENU: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::ToolBar` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(all(not(embedded_surface), feature = "desktop"))]
const KIND_TOOL_BAR: widget::WidgetKind = widget::WidgetKind::ToolBar;
#[cfg(not(alloc_frugal))]
#[cfg(not(all(not(embedded_surface), feature = "desktop")))]
const KIND_TOOL_BAR: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::StatusBar` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(all(not(embedded_surface), feature = "desktop"))]
const KIND_STATUS_BAR: widget::WidgetKind = widget::WidgetKind::StatusBar;
#[cfg(not(alloc_frugal))]
#[cfg(not(all(not(embedded_surface), feature = "desktop")))]
const KIND_STATUS_BAR: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::ListView` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(all(not(embedded_surface), feature = "desktop"))]
const KIND_LIST_VIEW: widget::WidgetKind = widget::WidgetKind::ListView;
#[cfg(not(alloc_frugal))]
#[cfg(not(all(not(embedded_surface), feature = "desktop")))]
const KIND_LIST_VIEW: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::MessageBox` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(full_widgets)]
const KIND_MESSAGE_BOX: widget::WidgetKind = widget::WidgetKind::MessageBox;
#[cfg(not(alloc_frugal))]
#[cfg(not(full_widgets))]
const KIND_MESSAGE_BOX: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::FileDialog` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(full_widgets)]
const KIND_FILE_DIALOG: widget::WidgetKind = widget::WidgetKind::FileDialog;
#[cfg(not(alloc_frugal))]
#[cfg(not(full_widgets))]
const KIND_FILE_DIALOG: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::ColorDialog` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(full_widgets)]
const KIND_COLOR_DIALOG: widget::WidgetKind = widget::WidgetKind::ColorDialog;
#[cfg(not(alloc_frugal))]
#[cfg(not(full_widgets))]
const KIND_COLOR_DIALOG: widget::WidgetKind = widget::WidgetKind::Panel;

/// `WidgetKind::FontDialog` where available, else the always-present fallback.
#[cfg(not(alloc_frugal))]
#[cfg(full_widgets)]
const KIND_FONT_DIALOG: widget::WidgetKind = widget::WidgetKind::FontDialog;
#[cfg(not(alloc_frugal))]
#[cfg(not(full_widgets))]
const KIND_FONT_DIALOG: widget::WidgetKind = widget::WidgetKind::Panel;

/// Create a top-level window with specified title and geometry.
///
/// # Example
/// ```
/// let window_id = rust_widgets::create_window("My App", 100, 100, 800, 600);
/// ```
#[cfg(not(alloc_frugal))]
pub fn create_window(
    title: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::Window).create_window(title, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Create a button control as a child of specified parent.
///
/// The backend decides whether this becomes a platform button or is painted by
/// the platform's custom surface; callers get a handle either way.
pub fn create_button(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::Button).create_button(parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a check-box. The backend decides whether it is a platform control
/// or is painted by the platform's custom surface.
pub fn create_checkbox(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::CheckBox)
        .create_checkbox(parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a single-line text editor. The backend chooses how it is hosted.
pub fn create_line_edit(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::LineEdit)
        .create_line_edit(parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a read-only text label. The backend chooses how it is hosted.
pub fn create_label(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::Label).create_label(parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a radio button. The backend chooses how it is hosted.
pub fn create_radio_button(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::RadioButton)
        .create_radio_button(parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a slider. The backend chooses how it is hosted.
pub fn create_slider(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::Slider).create_slider(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a progress bar. The backend chooses how it is hosted.
pub fn create_progress_bar(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::ProgressBar)
        .create_progress_bar(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a combo box. The backend chooses how it is hosted.
pub fn create_combo_box(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::ComboBox).create_combo_box(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a list box. The backend chooses how it is hosted.
pub fn create_list_box(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::ListBox).create_list_box(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a panel (a container surface). The backend chooses how it is hosted.
pub fn create_panel(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    // `WidgetKind::Panel` is the closest always-available kind; on profiles where
    // a dedicated panel kind exists the routing table answers for it.
    backend_for_kind(widget::WidgetKind::Panel).create_panel(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Create a message box dialog as a child of specified parent.
///
/// Creates a message box. The backend chooses how it is hosted.
pub fn create_message_box(
    parent: crate::core::ObjectId,
    title: &str,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_MESSAGE_BOX).create_message_box(parent, title, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Create a file dialog as a child of specified parent.
///
/// Creates a file dialog. The backend chooses how it is hosted.
pub fn create_file_dialog(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_FILE_DIALOG).create_file_dialog(parent, "", x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Create a color dialog as a child of specified parent.
pub fn create_color_dialog(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_COLOR_DIALOG).create_color_dialog(parent, "", x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Create a font dialog as a child of specified parent.
pub fn create_font_dialog(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_FONT_DIALOG).create_font_dialog(parent, "", x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a spin box. The backend chooses how it is hosted.
pub fn create_spin_box(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::SpinBox).create_spin_box(parent, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
/// Creates a list view. The backend chooses how it is hosted.
pub fn create_list_view(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_LIST_VIEW).create_list_view(parent, x, y, width, height)
}
/// Creates a scroll area. The backend chooses how it is hosted.
#[cfg(not(alloc_frugal))]
pub fn create_scroll_area(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(widget::WidgetKind::ScrollArea).create_scroll_area(parent, x, y, width, height)
}
/// Mounts a widget onto a surface supplied by the host.
///
/// The host platform supplies a window and a drawing surface; the library paints
/// the widget through [`widget::Draw`]. Which surface hosts it — a child window,
/// a drawing area, a view — is decided inside `src/platform/` and never named
/// here; callers only need to know whether the backend can host widgets, which
/// [`supports_surfaces`] answers.
///
/// `id` must already be registered in `widget::runtime`. Prefer the
/// higher-level `app::WindowHandle::mount_surface`, which performs the
/// registration for you and reports failures as a `Result`.
///
/// Returns `false` when the backend has no surface to offer, or when it refuses
/// this particular mount. Backends that cannot host widgets log why.
#[cfg(not(alloc_frugal))]
pub fn mount_surface(
    parent: crate::core::ObjectId,
    id: crate::core::ObjectId,
    rect: crate::core::Rect,
) -> bool {
    platform::get_platform().mount_surface(parent, id, rect)
}

/// Moves and resizes a mounted surface.
#[cfg(not(alloc_frugal))]
pub fn resize_surface(id: crate::core::ObjectId, rect: crate::core::Rect) -> bool {
    platform::get_platform().resize_surface(id, rect)
}

/// Unmounts a surface from its window.
#[cfg(not(alloc_frugal))]
pub fn unmount_surface(id: crate::core::ObjectId) -> bool {
    platform::get_platform().unmount_surface(id)
}

/// Marks a mounted surface as needing a repaint.
///
/// Returns `false` when the id is not a surface mounted on the active backend.
#[cfg(not(alloc_frugal))]
pub fn invalidate_surface(id: crate::core::ObjectId) -> bool {
    platform::get_platform().invalidate_surface(id)
}

/// Returns `true` when the active backend can host library-painted widgets.
#[cfg(not(alloc_frugal))]
pub fn supports_surfaces() -> bool {
    platform::get_platform().supports_surfaces()
}

/// Mounts a widget object on the platform's surface, or reports why it cannot.
///
/// This is the **single implementation** of the register → mount → roll back on
/// failure sequence, used by both [`create_widget_of_kind`] and
/// [`app::WindowHandle::mount_surface`]. Keeping one
/// copy is what stops the two paths from disagreeing about ownership: on any
/// failure the widget is unregistered, so the registry never holds a widget the
/// backend is not showing.
///
/// # Resolving the parent
///
/// `parent` may be either a **window widget** (what [`create_window`] returns and
/// what `App::new_window` hands out) or a **host window** (what
/// `Platform::create_window` returns). `mount_surface` is a `Platform` method, so
/// it can only resolve the latter — the platform knows nothing of the widget
/// registry. Translating here, at the one place that already owns both the widget
/// registry and the platform handle, is what lets a caller mount controls on the
/// window it created. Without it the library's own windows could not carry the
/// library's own controls, because the two id spaces never met.
///
/// Returns `Ok(id)` with the widget live in the registry, or `Err(reason)`.
#[cfg(not(alloc_frugal))]
fn mount_widget_object(
    parent: crate::core::ObjectId,
    widget: Box<dyn widget::Widget>,
    rect: crate::core::Rect,
) -> Result<crate::core::ObjectId, widget::runtime::SurfaceMountError> {
    use widget::runtime::SurfaceMountError;

    // Register first: the backend looks the widget up by id on every repaint.
    let id = widget::runtime::register(widget).ok_or(SurfaceMountError::NoRegistryOnThread)?;
    widget::runtime::set_geometry(id, rect);

    let host_parent = widget::runtime::host_window_for(parent).unwrap_or(parent);

    if !platform::get_platform().mount_surface(host_parent, id, rect) {
        // Do not leave a widget stranded when the backend refused to show it.
        widget::runtime::unregister(id);
        if !supports_surfaces() {
            return Err(SurfaceMountError::UnsupportedByBackend(backend_name()));
        }
        return Err(SurfaceMountError::RejectedByBackend(backend_name()));
    }
    Ok(id)
}

/// Creates a widget of any kind — **the single, mechanism-free creation entry**.
///
/// # How this differs from the per-kind `create_*` functions
///
/// The `create_*` functions are the typo-safe spelling for the common widgets
/// (`create_button`, `create_slider`, …). This function is the general form: hand
/// Creates a widget of `kind`, painted by the library.
///
/// # What this does now
///
/// Every kind is painted by the library (BLUE15 rule #55), so there is a single
/// path: build (or accept) the `Box<dyn Widget>` and hand it to the host surface
/// through `widget::runtime`. The `Platform` layer supplies a surface to paint on,
/// not a control to map onto, so no branch here asks which mechanism to use.
///
/// # The `widget` argument
///
/// A caller may supply a pre-built object so that a widget with constructor
/// arguments the factory cannot express (`CodeEditor` with initial text, a chart
/// with series) reaches this path without a bespoke branch. When it is `None` the
/// widget factory is asked; that factory is the single place that knows every
/// kind's constructor (rule #65).
///
/// Returns `0` — a truthful "not created" — when the kind has no constructor and
/// the caller supplied no object. Callers already handle `0` for the per-kind
/// functions, so this needs no separate error channel.
#[cfg(not(alloc_frugal))]
pub fn create_widget_of_kind(
    kind: widget::WidgetKind,
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
    widget: Option<Box<dyn widget::Widget>>,
) -> crate::core::ObjectId {
    let rect = crate::core::Rect::new(x, y, width, height);

    // Prefer the caller's object; otherwise build from the factory, which is the
    // only component that knows every kind's constructor.
    let widget = widget.or_else(|| {
        #[cfg(any(feature = "desktop", feature = "tablet", feature = "mobile"))]
        {
            widget::WidgetFactory::new_with_defaults().create(&kind_name(kind), rect, text)
        }
        #[cfg(not(any(feature = "desktop", feature = "tablet", feature = "mobile")))]
        {
            let _ = (text, rect);
            None
        }
    });

    let Some(widget) = widget else {
        log::warn!(
            "create_widget_of_kind: no constructor for {kind:?} and no widget object was \
             supplied; returning 0"
        );
        return 0;
    };

    match mount_widget_object(parent, widget, rect) {
        Ok(id) => id,
        Err(error) => {
            log::warn!("create_widget_of_kind: cannot host {kind:?}: {error}");
            0
        }
    }
}

/// The canonical factory name for a [`WidgetKind`].
///
/// Derived from the widget capability registry (the same table the factory
/// dispatches on) rather than from `Debug` output, so the two can never disagree.
/// Only the registry-backed arm exists where the registry does; every other build
/// derives the name from the variant, which is the same convention the registry
/// uses.
#[cfg(all(not(alloc_frugal), full_widgets))]
fn kind_name(kind: widget::WidgetKind) -> alloc::string::String {
    use alloc::string::ToString;
    if let Some(capability) = widget::WidgetFactory::new_with_defaults().capability_by_kind(kind) {
        return capability.canonical_name.to_string();
    }
    widget::capability::factory_name_for_kind(kind).to_string()
}

/// See the `full_widgets` definition: the capability registry is gated with the
/// full widget set, so without it there is nothing to consult. Deriving the name
/// from the variant's own spelling follows the same convention the registry uses,
/// which is what keeps the two in step.
#[cfg(all(not(alloc_frugal), not(full_widgets)))]
#[allow(dead_code)]
fn kind_name(kind: widget::WidgetKind) -> alloc::string::String {
    use alloc::string::ToString;
    let debug = alloc::format!("{kind:?}");
    let mut snake = alloc::string::String::with_capacity(debug.len() + 4);
    for (index, ch) in debug.chars().enumerate() {
        if ch.is_ascii_uppercase() {
            // A run of capitals (`QRCode`) is one word, so only a capital after a
            // lowercase letter or digit starts a new one.
            let starts_word =
                index > 0 && !debug.chars().nth(index - 1).is_some_and(|p| p.is_ascii_uppercase());
            if starts_word {
                snake.push('_');
            }
            snake.push(ch.to_ascii_lowercase());
        } else {
            snake.push(ch);
        }
    }
    snake.to_string()
}

/// Stub for mini mode (no platform runtime, no windows).
#[cfg(alloc_frugal)]
pub fn mount_surface(
    _parent: crate::core::ObjectId,
    _id: crate::core::ObjectId,
    _rect: crate::core::Rect,
) -> bool {
    false
}

/// Stub for mini mode.
#[cfg(alloc_frugal)]
pub fn resize_surface(_id: crate::core::ObjectId, _rect: crate::core::Rect) -> bool {
    false
}

/// Stub for mini mode.
#[cfg(alloc_frugal)]
pub fn unmount_surface(_id: crate::core::ObjectId) -> bool {
    false
}

/// Stub for mini mode.
#[cfg(alloc_frugal)]
pub fn invalidate_surface(_id: crate::core::ObjectId) -> bool {
    false
}

/// Stub for mini mode.
#[cfg(alloc_frugal)]
pub fn supports_surfaces() -> bool {
    false
}

/// Show a widget by its object id.
///
/// Routed through the control backend, which is the one mechanism that owns the
/// control (BLUE15 #55): the platform supplies the surface, not the control.
#[cfg(not(alloc_frugal))]
pub fn show_widget(widget_id: crate::core::ObjectId) {
    control_backend::get_control_backend().set_widget_visible(widget_id, true);
}
/// Hide a widget by its object id.
#[cfg(not(alloc_frugal))]
pub fn hide_widget(widget_id: crate::core::ObjectId) {
    control_backend::get_control_backend().set_widget_visible(widget_id, false);
}
/// Set geometry of a widget.
#[cfg(not(alloc_frugal))]
pub fn set_widget_geometry(
    widget_id: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) {
    control_backend::get_control_backend().set_widget_geometry(widget_id, x, y, width, height);
}
/// Read a widget's rectangle, or `None` when the id addresses nothing.
#[cfg(not(alloc_frugal))]
pub fn widget_geometry(widget_id: crate::core::ObjectId) -> Option<(i32, i32, u32, u32)> {
    control_backend::get_control_backend().get_widget_geometry(widget_id)
}
/// Set text of a widget.
#[cfg(not(alloc_frugal))]
pub fn set_widget_text(widget_id: crate::core::ObjectId, text: &str) {
    control_backend::get_control_backend().set_widget_text(widget_id, text);
}
/// Get text of a widget.
#[cfg(not(alloc_frugal))]
pub fn get_widget_text(widget_id: crate::core::ObjectId) -> String {
    control_backend::get_control_backend().get_widget_text(widget_id)
}
/// Set enabled state of a widget.
#[cfg(not(alloc_frugal))]
pub fn set_widget_enabled(widget_id: crate::core::ObjectId, enabled: bool) {
    control_backend::get_control_backend().set_widget_enabled(widget_id, enabled);
}
/// Check if a widget is enabled.
#[cfg(not(alloc_frugal))]
pub fn is_widget_enabled(widget_id: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().is_widget_enabled(widget_id)
}
/// Set visibility of a widget.
#[cfg(not(alloc_frugal))]
pub fn set_widget_visible(widget_id: crate::core::ObjectId, visible: bool) {
    control_backend::get_control_backend().set_widget_visible(widget_id, visible);
}
/// Check if a widget is visible.
#[cfg(not(alloc_frugal))]
pub fn is_widget_visible(widget_id: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().is_widget_visible(widget_id)
}

/// Set a widget's primary numeric value (slider, progress bar, spin box, ...).
///
/// Returns `false` when the widget exposes no numeric value, so callers never
/// mistake "unsupported" for "set to 0".
#[cfg(not(alloc_frugal))]
pub fn set_widget_value(widget_id: crate::core::ObjectId, value: f64) -> bool {
    widget::capability::widget_access::write_number(widget_id, &["value"], value)
}

/// Read a widget's primary numeric value.
#[cfg(not(alloc_frugal))]
pub fn widget_value(widget_id: crate::core::ObjectId) -> Option<f64> {
    widget::capability::widget_access::read_number(widget_id, &["value"])
}

/// Set a widget's `(min, max)` range.
#[cfg(not(alloc_frugal))]
pub fn set_widget_range(widget_id: crate::core::ObjectId, min: f64, max: f64) -> bool {
    let names: &[&str] = &["minimum", "min", "min_value"];
    let max_names: &[&str] = &["maximum", "max", "max_value"];
    widget::capability::widget_access::write_number(widget_id, names, min)
        && widget::capability::widget_access::write_number(widget_id, max_names, max)
}

/// Read a widget's `(min, max)` range.
#[cfg(not(alloc_frugal))]
pub fn widget_range(widget_id: crate::core::ObjectId) -> Option<(f64, f64)> {
    let min = widget::capability::widget_access::read_number(
        widget_id,
        &["minimum", "min", "min_value"],
    )?;
    let max = widget::capability::widget_access::read_number(
        widget_id,
        &["maximum", "max", "max_value"],
    )?;
    Some((min, max))
}

/// Set a widget's selection index (combo box, list box, tab widget).
///
/// `None` clears the selection, which is what these controls store as `Null` —
/// distinct from selecting index 0.
#[cfg(not(alloc_frugal))]
pub fn set_widget_selected_index(widget_id: crate::core::ObjectId, index: Option<usize>) -> bool {
    let names: &[&str] = &["selected_index", "current_index", "current_row", "active_index"];
    let value = match index {
        Some(index) => crate::widget::capability::CapabilityValue::UInt(index as u64),
        None => crate::widget::capability::CapabilityValue::Null,
    };
    widget::capability::widget_access::write_first(widget_id, names, &value)
}

/// Read a widget's selection index.
#[cfg(not(alloc_frugal))]
pub fn widget_selected_index(widget_id: crate::core::ObjectId) -> Option<usize> {
    widget::capability::widget_access::read_index(
        widget_id,
        &["selected_index", "current_index", "current_row", "active_index"],
    )
}

/// Set a widget's checked state (check box, radio button, toggle button).
#[cfg(not(alloc_frugal))]
pub fn set_widget_checked(widget_id: crate::core::ObjectId, checked: bool) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["checked"],
        &crate::widget::capability::CapabilityValue::Bool(checked),
    )
}

/// Read a widget's checked state, or `None` when it is not checkable.
#[cfg(not(alloc_frugal))]
pub fn is_widget_checked(widget_id: crate::core::ObjectId) -> Option<bool> {
    widget::capability::widget_access::read_flag(widget_id, &["checked"])
}

/// Set a widget's increment step (slider, spin box, scroll bar).
#[cfg(not(alloc_frugal))]
pub fn set_widget_step(widget_id: crate::core::ObjectId, step: f64) -> bool {
    widget::capability::widget_access::write_number(widget_id, &["single_step", "step"], step)
}

/// Read a widget's increment step.
#[cfg(not(alloc_frugal))]
pub fn widget_step(widget_id: crate::core::ObjectId) -> Option<f64> {
    widget::capability::widget_access::read_number(widget_id, &["single_step", "step"])
}

/// Set a progress-style widget's indeterminate (busy) state.
#[cfg(not(alloc_frugal))]
pub fn set_widget_indeterminate(widget_id: crate::core::ObjectId, indeterminate: bool) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["indeterminate"],
        &crate::widget::capability::CapabilityValue::Bool(indeterminate),
    )
}

/// Read a progress-style widget's indeterminate state.
#[cfg(not(alloc_frugal))]
pub fn is_widget_indeterminate(widget_id: crate::core::ObjectId) -> Option<bool> {
    widget::capability::widget_access::read_flag(widget_id, &["indeterminate"])
}

/// Set a text-entry widget's read-only state.
#[cfg(not(alloc_frugal))]
pub fn set_widget_read_only(widget_id: crate::core::ObjectId, read_only: bool) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["read_only"],
        &crate::widget::capability::CapabilityValue::Bool(read_only),
    )
}

/// Read a text-entry widget's read-only state.
#[cfg(not(alloc_frugal))]
pub fn is_widget_read_only(widget_id: crate::core::ObjectId) -> Option<bool> {
    widget::capability::widget_access::read_flag(widget_id, &["read_only"])
}

/// Set a text-entry widget's maximum accepted length.
#[cfg(not(alloc_frugal))]
pub fn set_widget_max_length(widget_id: crate::core::ObjectId, max_length: u32) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["max_length"],
        &crate::widget::capability::CapabilityValue::UInt(max_length as u64),
    )
}

/// Read a text-entry widget's maximum accepted length.
#[cfg(not(alloc_frugal))]
pub fn widget_max_length(widget_id: crate::core::ObjectId) -> Option<u32> {
    widget::capability::widget_access::read_index(widget_id, &["max_length"])
        .and_then(|v| u32::try_from(v).ok())
}

/// Apply or clear a window state (maximised, minimised, full-screen, ...).
///
/// Returns `false` when the id is not a window or the backend cannot honour the
/// state on its toolkit.
#[cfg(not(alloc_frugal))]
pub fn set_window_state(
    widget_id: crate::core::ObjectId,
    flag: platform::WindowStateFlag,
    on: bool,
) -> bool {
    platform::get_platform().set_window_state(widget_id, flag, on)
}

/// Read a window state, or `None` when the id is not a window.
#[cfg(not(alloc_frugal))]
pub fn is_window_in_state(
    widget_id: crate::core::ObjectId,
    flag: platform::WindowStateFlag,
) -> Option<bool> {
    platform::get_platform().is_window_in_state(widget_id, flag)
}

/// Set a window's minimum content size.
#[cfg(not(alloc_frugal))]
pub fn set_window_min_size(widget_id: crate::core::ObjectId, width: u32, height: u32) -> bool {
    platform::get_platform().set_window_min_size(widget_id, width, height)
}

/// Read a window's minimum content size.
#[cfg(not(alloc_frugal))]
pub fn window_min_size(widget_id: crate::core::ObjectId) -> Option<(u32, u32)> {
    platform::get_platform().window_min_size(widget_id)
}

/// Set a window's icon from a file path.
#[cfg(not(alloc_frugal))]
pub fn set_window_icon(widget_id: crate::core::ObjectId, path: &str) -> bool {
    platform::get_platform().set_window_icon(widget_id, path)
}

/// Read a window's icon path, if one was set.
#[cfg(not(alloc_frugal))]
pub fn window_icon(widget_id: crate::core::ObjectId) -> Option<String> {
    platform::get_platform().window_icon(widget_id)
}

/// Set a text entry's selection range as `(start, end)` character offsets.
///
/// # Current support
///
/// No control publishes a selection range as a property (the text controls track
/// it internally and expose `cursor_position`), so this reports `false` rather
/// than pretending the range was applied. Routing it through the property
/// contract means publishing the property on the text controls is the only change
/// needed to make it work.
#[cfg(not(alloc_frugal))]
pub fn set_widget_selection(widget_id: crate::core::ObjectId, start: u32, end: u32) -> bool {
    let names: &[&str] = &["selection"];
    // The pair travels as the historical "start,end" spelling used by the text
    // controls' own setter, so a control that publishes `selection` parses one
    // string rather than needing two properties kept in step.
    widget::capability::widget_access::write_first(
        widget_id,
        names,
        &crate::widget::capability::CapabilityValue::String(format!("{start},{end}")),
    )
}

/// Read a text entry's selection range, or `None` when nothing is selected.
#[cfg(not(alloc_frugal))]
pub fn widget_selection(widget_id: crate::core::ObjectId) -> Option<(u32, u32)> {
    let raw = widget::capability::widget_access::read_text(widget_id, &["selection"])?;
    let (start, end) = raw.split_once(',')?;
    Some((start.trim().parse().ok()?, end.trim().parse().ok()?))
}

/// Set a text entry's placeholder (cue) text.
#[cfg(not(alloc_frugal))]
pub fn set_widget_placeholder(widget_id: crate::core::ObjectId, text: &str) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["placeholder_text", "placeholder"],
        &crate::widget::capability::CapabilityValue::String(text.to_string()),
    )
}

/// Read a text entry's placeholder text.
#[cfg(not(alloc_frugal))]
pub fn widget_placeholder(widget_id: crate::core::ObjectId) -> Option<String> {
    widget::capability::widget_access::read_text(widget_id, &["placeholder_text", "placeholder"])
}

/// Set a text entry's echo mode.
///
/// # Current support
///
/// No control publishes an echo property yet, so this reports `false` on every
/// widget rather than pretending the mode was applied. It is routed through the
/// property contract — the place a control would express it — so that adding the
/// property to the text controls is the only change needed to make it work.
#[cfg(not(alloc_frugal))]
pub fn set_widget_echo_mode(widget_id: crate::core::ObjectId, mode: platform::EchoMode) -> bool {
    let token = match mode {
        platform::EchoMode::Normal => "normal",
        platform::EchoMode::Password => "password",
        platform::EchoMode::NoEcho => "no_echo",
    };
    widget::capability::widget_access::write_first(
        widget_id,
        &["echo_mode"],
        &crate::widget::capability::CapabilityValue::String(token.to_string()),
    )
}

/// Read a text entry's echo mode.
#[cfg(not(alloc_frugal))]
pub fn widget_echo_mode(widget_id: crate::core::ObjectId) -> Option<platform::EchoMode> {
    match widget::capability::widget_access::read_text(widget_id, &["echo_mode"])?.as_str() {
        "normal" => Some(platform::EchoMode::Normal),
        "password" => Some(platform::EchoMode::Password),
        "no_echo" => Some(platform::EchoMode::NoEcho),
        _ => None,
    }
}

/// Apply a slider's creation-time orientation.
#[cfg(not(alloc_frugal))]
pub fn set_slider_orientation(
    widget_id: crate::core::ObjectId,
    orientation: crate::core::Orientation,
) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["orientation"],
        &crate::widget::capability::CapabilityValue::String(
            widget::capability::orientation_to_str(orientation).to_string(),
        ),
    )
}

/// Read a slider's orientation.
#[cfg(not(alloc_frugal))]
pub fn slider_orientation(widget_id: crate::core::ObjectId) -> Option<crate::core::Orientation> {
    let token = widget::capability::widget_access::read_text(widget_id, &["orientation"])?;
    widget::capability::expect_orientation(crate::widget::capability::CapabilityValue::String(
        token,
    ))
    .ok()
}

/// Set a checkable control's tri-state mode.
#[cfg(not(alloc_frugal))]
pub fn set_widget_tristate(widget_id: crate::core::ObjectId, enabled: bool) -> bool {
    widget::capability::widget_access::write_first(
        widget_id,
        &["tristate_enabled"],
        &crate::widget::capability::CapabilityValue::Bool(enabled),
    )
}

/// Read a checkable control's tri-state mode.
#[cfg(not(alloc_frugal))]
pub fn is_widget_tristate(widget_id: crate::core::ObjectId) -> Option<bool> {
    widget::capability::widget_access::read_flag(widget_id, &["tristate_enabled"])
}

/// Put a radio button into a named mutually-exclusive group.
///
/// An empty group clears the membership, which these controls store as `Null`.
#[cfg(not(alloc_frugal))]
pub fn set_widget_group(widget_id: crate::core::ObjectId, group: &str) -> bool {
    let value = if group.is_empty() {
        crate::widget::capability::CapabilityValue::Null
    } else {
        crate::widget::capability::CapabilityValue::String(group.to_string())
    };
    widget::capability::widget_access::write_first(widget_id, &["group_id", "group"], &value)
}

/// Read a radio button's group name.
#[cfg(not(alloc_frugal))]
pub fn widget_group(widget_id: crate::core::ObjectId) -> Option<String> {
    widget::capability::widget_access::read_text(widget_id, &["group_id", "group"])
}

/// Set a scrollable container's scroll offset.
#[cfg(not(alloc_frugal))]
pub fn set_widget_scroll_position(widget_id: crate::core::ObjectId, x: i32, y: i32) -> bool {
    let x_ok = widget::capability::widget_access::write_number(
        widget_id,
        &["scroll_position_x"],
        x as f64,
    );
    let y_ok = widget::capability::widget_access::write_number(
        widget_id,
        &["scroll_position_y"],
        y as f64,
    );
    x_ok && y_ok
}

/// Read a scrollable container's scroll offset.
#[cfg(not(alloc_frugal))]
pub fn widget_scroll_position(widget_id: crate::core::ObjectId) -> Option<(i32, i32)> {
    let x = widget::capability::widget_access::read_number(widget_id, &["scroll_position_x"])?;
    let y = widget::capability::widget_access::read_number(widget_id, &["scroll_position_y"])?;
    Some((x as i32, y as i32))
}
// ComboBox operations
/// Appends an item with the given text to a combo box.
///
/// Returns `false` when the backend refuses — which includes being handed an id
/// that is not a combo box.
#[cfg(not(alloc_frugal))]
pub fn combo_box_add_item(combo_box: crate::core::ObjectId, text: &str) -> bool {
    control_backend::get_control_backend().combo_box_add_item(combo_box, text)
}
/// Removes every item from a combo box, leaving it empty and with nothing
/// selected. Returns `false` when the backend refuses.
#[cfg(not(alloc_frugal))]
pub fn combo_box_clear_items(combo_box: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().combo_box_clear_items(combo_box)
}
/// Selects the combo box item at `index`.
///
/// Routed through the property contract as the `current_index` property, so this
/// reports `false` when the widget does not publish that property. An
/// out-of-range `index` is **not** reliably reported as a failure — read
/// [`combo_box_current_index`] back to confirm what took effect.
#[cfg(not(alloc_frugal))]
pub fn combo_box_set_current_index(combo_box: crate::core::ObjectId, index: usize) -> bool {
    widget::capability::widget_access::write_first(
        combo_box,
        &["current_index"],
        &crate::widget::capability::CapabilityValue::UInt(index as u64),
    )
}
/// The combo box's selected index, or `None` when nothing is selected or the id
/// is unknown.
#[cfg(not(alloc_frugal))]
pub fn combo_box_current_index(combo_box: crate::core::ObjectId) -> Option<usize> {
    widget::capability::widget_access::read_index(combo_box, &["current_index"])
}
/// How many items a combo box holds.
///
/// Reports `0` for an unknown id as well as for a genuinely empty list, so it
/// cannot be used to tell "empty" from "not a combo box".
#[cfg(not(alloc_frugal))]
pub fn combo_box_item_count(combo_box: crate::core::ObjectId) -> usize {
    widget::capability::widget_access::read_index(combo_box, &["item_count"]).unwrap_or(0)
}
/// The text of the combo box item at `index`, or `None` when the index is out of
/// range or the id is unknown.
#[cfg(not(alloc_frugal))]
pub fn combo_box_item_text(combo_box: crate::core::ObjectId, index: usize) -> Option<String> {
    control_backend::get_control_backend().combo_box_item_text(combo_box, index)
}
// ListBox operations
/// Appends an item with the given text to a list box. Returns `false` when the
/// backend refuses.
#[cfg(not(alloc_frugal))]
pub fn list_box_add_item(list_box: crate::core::ObjectId, text: &str) -> bool {
    control_backend::get_control_backend().list_box_add_item(list_box, text)
}
/// Removes the list box item at `index`, shifting later items down.
///
/// Returns `false` when the backend refuses. As with the combo box, an
/// out-of-range index is not reliably distinguished from a refusal — re-read
/// [`list_box_item_count`] to check.
#[cfg(not(alloc_frugal))]
pub fn list_box_remove_item(list_box: crate::core::ObjectId, index: usize) -> bool {
    control_backend::get_control_backend().list_box_remove_item(list_box, index)
}
/// Removes every item from a list box. Returns `false` when the backend refuses.
#[cfg(not(alloc_frugal))]
pub fn list_box_clear_items(list_box: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().list_box_clear_items(list_box)
}
/// Selects the list box item at `index`.
///
/// Tries the `current_row` property first and falls back to `current_index`,
/// because both spellings are published by list controls in this library.
/// Returns `false` when neither is published; an out-of-range `index` is not
/// reliably reported, so read [`list_box_current_index`] back to confirm.
#[cfg(not(alloc_frugal))]
pub fn list_box_set_current_index(list_box: crate::core::ObjectId, index: usize) -> bool {
    widget::capability::widget_access::write_first(
        list_box,
        &["current_row", "current_index"],
        &crate::widget::capability::CapabilityValue::UInt(index as u64),
    )
}
/// The list box's selected index, or `None` when nothing is selected or the id
/// is unknown.
#[cfg(not(alloc_frugal))]
pub fn list_box_current_index(list_box: crate::core::ObjectId) -> Option<usize> {
    widget::capability::widget_access::read_index(list_box, &["current_row", "current_index"])
}
/// How many items a list box holds. Reports `0` for an unknown id as well as for
/// an empty list.
#[cfg(not(alloc_frugal))]
pub fn list_box_item_count(list_box: crate::core::ObjectId) -> usize {
    widget::capability::widget_access::read_index(list_box, &["item_count"]).unwrap_or(0)
}
/// The text of the list box item at `index`, or `None` when the index is out of
/// range or the id is unknown.
#[cfg(not(alloc_frugal))]
pub fn list_box_item_text(list_box: crate::core::ObjectId, index: usize) -> Option<String> {
    control_backend::get_control_backend().list_box_item_text(list_box, index)
}
// Event polling
/// Takes the id of the next widget that has been activated, or `None` when no
/// activation is queued.
///
/// This is a queue, not a state: each successful call removes one event, so a
/// loop calling it until `None` drains the pending activations. Use
/// [`poll_widget_trigger_event`] when the kind of activation matters.
#[cfg(not(alloc_frugal))]
pub fn poll_widget_triggered() -> Option<crate::core::ObjectId> {
    control_backend::get_control_backend().poll_widget_triggered()
}
/// Takes the next queued widget activation together with what kind it was, or
/// `None` when the queue is empty. Drains the same queue as
/// [`poll_widget_triggered`].
#[cfg(not(alloc_frugal))]
pub fn poll_widget_trigger_event() -> Option<WidgetTriggerEvent> {
    control_backend::get_control_backend().poll_widget_trigger_event()
}
/// Queues an activation of `widget_id` as if the user had performed it, for
/// tests and for driving the UI from outside the event loop.
///
/// Returns `false` when the backend will not accept the injected event, in
/// which case nothing is queued and no later poll will report it.
#[cfg(not(alloc_frugal))]
pub fn inject_widget_trigger_event(
    widget_id: crate::core::ObjectId,
    kind: WidgetTriggerKind,
) -> bool {
    control_backend::get_control_backend().inject_widget_trigger_event(widget_id, kind)
}
// Clipboard
/// Replaces the platform clipboard's text contents.
///
/// Returns `false` when the clipboard could not be written — including when
/// another application holds it, which is a normal, transient condition. This
/// function takes only text; see [`platform_clipboard`] for the rich backend.
#[cfg(not(alloc_frugal))]
pub fn set_clipboard_text(text: &str) -> bool {
    platform::get_platform().set_clipboard_text(text)
}
/// The platform clipboard's text contents.
///
/// Returns an empty string when the clipboard holds no text, holds a non-text
/// format, or cannot be read — the three cases are **not** distinguished here,
/// so an empty result does not mean the copy succeeded with empty text.
#[cfg(not(alloc_frugal))]
pub fn get_clipboard_text() -> String {
    platform::get_platform().get_clipboard_text()
}
/// Returns the platform's rich clipboard backend, if available.
#[cfg(not(alloc_frugal))]
pub fn platform_clipboard() -> Option<&'static dyn crate::platform::clipboard::RichClipboardBackend>
{
    platform::get_platform().clipboard_backend()
}
// Menu operations
/// Creates a native menu bar as a child of `parent`.
///
/// On macOS the bar only becomes the application's main menu once it is attached
/// with [`attach_menu_bar_to_window`]. Add menus with [`create_menu`] and items
/// with [`menu_add_item`].
#[cfg(not(alloc_frugal))]
pub fn create_menu_bar(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_MENU_BAR).create_menu_bar(parent, x, y, width, height)
}
/// Adds a top-level menu to a menu bar.
///
/// `parent` must be the **menu bar**, not the window: a menu's parent is
/// structurally its bar and several backends silently do nothing when given a
/// window instead.
#[cfg(not(alloc_frugal))]
pub fn create_menu(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_MENU).create_menu(parent, text, x, y, width, height)
}
/// Attaches a menu bar to a window.
///
/// Returns `false` when the backend cannot attach it.
#[cfg(not(alloc_frugal))]
pub fn attach_menu_bar_to_window(
    window: crate::core::ObjectId,
    menu_bar: crate::core::ObjectId,
) -> bool {
    control_backend::get_control_backend().attach_menu_bar_to_window(window, menu_bar)
}
/// Adds an item to a menu, returning the item's id.
///
/// `shortcut` is **display text** and is neither parsed nor registered as an
/// accelerator — use [`format_shortcut`] to render a platform-correct string
/// from a typed shortcut declaration. The returned id is what
/// [`poll_menu_triggered`] reports once the item is chosen.
#[cfg(not(alloc_frugal))]
pub fn menu_add_item(
    parent_menu: crate::core::ObjectId,
    text: &str,
    shortcut: Option<&str>,
) -> crate::core::ObjectId {
    control_backend::get_control_backend().menu_add_item(parent_menu, text, shortcut)
}
/// Renders a shortcut the way the current operating system writes it.
///
/// macOS returns `⌘⇧Z`-style glyphs; Windows and Linux return
/// `Ctrl+Shift+Z`. Use this for any label the user reads (menu text, tooltips,
/// the shortcut column of a command palette) so one shortcut declaration reads
/// natively on every platform.
///
/// For the host-independent notation (useful in serialized keymaps and tests),
/// use [`crate::shortcut::Shortcut::format_shortcut`] together with
/// [`crate::shortcut::format_shortcut_for_platform`] instead.
///
/// ```
/// use rust_widgets::shortcut::{Key, Shortcut};
///
/// let shown = rust_widgets::format_shortcut(&Shortcut::primary(Key::Z));
/// #[cfg(target_os = "macos")]
/// assert_eq!(shown, "⌘Z");
/// #[cfg(not(target_os = "macos"))]
/// assert_eq!(shown, "Ctrl+Z");
/// ```
#[cfg(not(alloc_frugal))]
pub fn format_shortcut(shortcut: &crate::shortcut::Shortcut) -> String {
    platform::get_platform().format_shortcut(shortcut)
}
/// Takes the id of the next menu item the user activated, or `None` when none is
/// queued.
///
/// Like the widget version this drains a queue, so a loop calling it until
/// `None` collects every pending activation. The id returned is the one
/// [`menu_add_item`] handed back.
#[cfg(not(alloc_frugal))]
pub fn poll_menu_triggered() -> Option<crate::core::ObjectId> {
    control_backend::get_control_backend().poll_menu_triggered()
}
/// Returns the accelerator text bound to a menu item, if any.
///
/// Read from the menu item's own `shortcut` property, so the answer describes the
/// item the library drew rather than a host-side copy that could drift.
#[cfg(not(alloc_frugal))]
pub fn menu_item_shortcut(menu_item: crate::core::ObjectId) -> Option<String> {
    control_backend::get_control_backend().menu_item_shortcut(menu_item)
}
/// Returns the backend's native handle for a widget, when it has one.
///
/// The value is opaque: it is the platform's own object pointer or handle (an
/// `NSView*` on macOS, an `HWND` on Windows, ...), and its meaning is entirely
/// backend-specific. It exists so host code and integration tests can reach the
/// underlying control for things the cross-platform API does not model.
///
/// Returns `None` when the widget is unknown, or when the backend created it in
/// state-only mode (for example off the UI thread) and therefore has no native
/// object to return.
#[cfg(not(alloc_frugal))]
pub fn native_handle(widget: crate::core::ObjectId) -> Option<usize> {
    platform::get_platform().get_native_handle(widget)
}
/// Queues activation of a menu item as if the user had chosen it, for tests.
///
/// Returns `false` when the backend will not accept the injected event.
#[cfg(not(alloc_frugal))]
pub fn inject_menu_trigger(menu_item_id: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().inject_menu_trigger(menu_item_id)
}
// ToolBar and StatusBar
/// Creates a tool bar strip as a child of `parent`.
///
/// The returned id can be wrapped in an `app::ToolBarHandle` for the
/// typed operations (adding actions, changing orientation).
#[cfg(not(alloc_frugal))]
pub fn create_tool_bar(
    parent: crate::core::ObjectId,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_TOOL_BAR).create_tool_bar(parent, x, y, width, height)
}
/// Creates a status bar as a child of `parent`, with `text` as its message.
#[cfg(not(alloc_frugal))]
pub fn create_status_bar(
    parent: crate::core::ObjectId,
    text: &str,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
) -> crate::core::ObjectId {
    backend_for_kind(KIND_STATUS_BAR).create_status_bar(parent, text, x, y, width, height)
}
// Drag and Drop
/// Starts a drag of `payload` out of `source_widget_id`, advertising it as
/// `mime`.
///
/// `payload` is copied into the platform's drag object and is not retained by
/// this library, so the caller may free it once this returns. Returns `false`
/// when the backend cannot begin a drag — for instance when a drag is already in
/// progress — in which case no drop event will follow.
#[cfg(not(alloc_frugal))]
pub fn begin_drag(source_widget_id: crate::core::ObjectId, mime: &str, payload: &[u8]) -> bool {
    platform::get_platform().begin_drag(source_widget_id, mime, payload)
}
/// Takes the next completed drop, or `None` when none is pending.
///
/// Polling a queue: each successful call removes one event. A drop is only
/// reported after the user has released, so a drag in progress yields `None`.
#[cfg(not(alloc_frugal))]
pub fn poll_drop_event() -> Option<DropEvent> {
    platform::get_platform().poll_drop_event()
}
/// Queues a drop event as if the user had performed it, for tests.
///
/// Returns `false` when the backend will not accept the injected event, in which
/// case a later poll will not report it.
#[cfg(not(alloc_frugal))]
pub fn inject_drop_event(event: DropEvent) -> bool {
    platform::get_platform().inject_drop_event(event)
}
// IME and Accessibility
/// Enables or disables input-method (IME) input for a text-entry widget.
///
/// Returns `false` when the backend refuses — typically because the id is not a
/// text-entry control, or because the platform has no IME support to switch.
#[cfg(not(alloc_frugal))]
pub fn set_widget_ime_enabled(widget_id: crate::core::ObjectId, enabled: bool) -> bool {
    control_backend::get_control_backend().set_widget_ime_enabled(widget_id, enabled)
}
/// Whether input-method input is enabled for a widget.
///
/// Reports `false` both for "enabled is off" and for "this id is unknown or not
/// a text control", so it cannot distinguish the two.
#[cfg(not(alloc_frugal))]
pub fn is_widget_ime_enabled(widget_id: crate::core::ObjectId) -> bool {
    control_backend::get_control_backend().is_widget_ime_enabled(widget_id)
}
/// Returns the platform's IME bridge, if available.
#[cfg(not(alloc_frugal))]
pub fn platform_ime_bridge() -> Option<&'static dyn crate::platform::ime::ImeBridge> {
    platform::get_platform().ime_bridge()
}
/// Sets the accessible name of a widget, as reported to assistive technology.
///
/// This is the label a screen reader announces — distinct from the visible text,
/// which is often too terse or ambiguous to read aloud. Returns `false` when the
/// backend refuses.
#[cfg(not(alloc_frugal))]
pub fn set_widget_accessibility_name(widget_id: crate::core::ObjectId, name: &str) -> bool {
    control_backend::get_control_backend().set_widget_accessibility_name(widget_id, name)
}
/// The accessible name set by [`set_widget_accessibility_name`].
///
/// Returns an empty string when no name was set, when the id is unknown, or when
/// the backend refused — the cases are not distinguished, so an empty result
/// does not prove the widget is nameless to assistive technology.
#[cfg(not(alloc_frugal))]
pub fn get_widget_accessibility_name(widget_id: crate::core::ObjectId) -> String {
    control_backend::get_control_backend().get_widget_accessibility_name(widget_id)
}
// Re-exports from platform module for convenience
#[cfg(not(alloc_frugal))]
pub use platform::{
    backend_name, capabilities, dpi_scale_factor, get_platform, init as platform_init,
    quit as platform_quit, run as platform_run, runtime_gui_mode, runtime_gui_mode_for,
};
pub use platform::{
    CapabilityContract, DesktopBackend, DropEvent, EmbeddedCapabilityContract, MobileBackend,
    NativeCapabilityContract, PlatformCapabilities, RuntimeGuiMode, WidgetTriggerEvent,
    WidgetTriggerKind, WindowStateFlag,
};

// ═══════════════════════════════════════════════════════
// Deprecated aliases kept for API compatibility (principle #21)
// ═══════════════════════════════════════════════════════

/// Deprecated spellings of the surface API.
///
/// These names said "custom", which described a rendering mechanism — "painted
/// by the library rather than mapped onto an OS control". That distinction no
/// longer exists: the host platform supplies only a window and a drawing surface
/// and the library paints every widget, so the mechanism vocabulary has no
/// referent. Use the `*_surface` names instead.
///
/// The forwards are re-exported at the crate root below, so an existing
/// `rust_widgets::mount_custom_widget(...)` call keeps compiling and only gains a
/// deprecation warning — the caller is told the name changed rather than being
/// broken by it (principle #21).
#[allow(deprecated)]
pub mod deprecated {
    /// Deprecated alias of [`crate::mount_surface`].
    #[deprecated(
        note = "renamed to `mount_surface`; 'custom' named a mechanism that no longer exists"
    )]
    pub fn mount_custom_widget(
        parent: crate::core::ObjectId,
        id: crate::core::ObjectId,
        rect: crate::core::Rect,
    ) -> bool {
        crate::mount_surface(parent, id, rect)
    }

    /// Deprecated alias of [`crate::resize_surface`].
    #[deprecated(
        note = "renamed to `resize_surface`; 'custom' named a mechanism that no longer exists"
    )]
    pub fn resize_custom_widget(id: crate::core::ObjectId, rect: crate::core::Rect) -> bool {
        crate::resize_surface(id, rect)
    }

    /// Deprecated alias of [`crate::unmount_surface`].
    #[deprecated(
        note = "renamed to `unmount_surface`; 'custom' named a mechanism that no longer exists"
    )]
    pub fn unmount_custom_widget(id: crate::core::ObjectId) -> bool {
        crate::unmount_surface(id)
    }

    /// Deprecated alias of [`crate::invalidate_surface`].
    #[deprecated(
        note = "renamed to `invalidate_surface`; 'custom' named a mechanism that no longer exists"
    )]
    pub fn request_custom_repaint(id: crate::core::ObjectId) -> bool {
        crate::invalidate_surface(id)
    }

    /// Deprecated alias of [`crate::supports_surfaces`].
    #[deprecated(
        note = "renamed to `supports_surfaces`; 'custom' named a mechanism that no longer exists"
    )]
    pub fn supports_custom_widgets() -> bool {
        crate::supports_surfaces()
    }
}

// Old crate-root paths keep resolving, so a rename is a warning rather than a
// break. Exported here rather than relying on callers reaching into `deprecated`.
#[allow(deprecated)]
pub use deprecated::{
    mount_custom_widget, request_custom_repaint, resize_custom_widget, supports_custom_widgets,
    unmount_custom_widget,
};

#[cfg(test)]
mod docs_paths_tests;

#[cfg(test)]
mod compat_path_tests {
    /// The old crate-root spellings must keep resolving after the rename.
    ///
    /// The rename is a *deprecation*, not a break (principle #21): existing callers
    /// must still compile. Referring to the functions here fails the build if a
    /// re-export is ever dropped, which is otherwise invisible because nothing else
    /// in the crate uses the old names.
    #[test]
    #[allow(deprecated)]
    fn old_surface_names_still_resolve_at_the_crate_root() {
        let _: fn(crate::core::ObjectId, crate::core::ObjectId, crate::core::Rect) -> bool =
            crate::mount_custom_widget;
        let _: fn(crate::core::ObjectId, crate::core::Rect) -> bool = crate::resize_custom_widget;
        let _: fn(crate::core::ObjectId) -> bool = crate::unmount_custom_widget;
        let _: fn(crate::core::ObjectId) -> bool = crate::request_custom_repaint;
        let _: fn() -> bool = crate::supports_custom_widgets;
    }

    /// The deprecated forwards must reach the same function, not a stale copy.
    #[test]
    #[allow(deprecated)]
    fn deprecated_aliases_delegate_to_the_surface_api() {
        assert_eq!(crate::supports_custom_widgets(), crate::supports_surfaces());
    }
}