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
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT
// `LABEL_PROPERTY_NAMES` is used by the label accessors, which are only compiled
// where the property registry exists.
use crate::compat::String;
// `lock` and `MiniToString` are used by the included macro bodies below, which
// dereference the state mutex and build owned label strings. They are imported
// once here rather than in each `.in.rs` fragment, because `include!` splices
// those fragments into *this* module, so this is the scope they resolve in.
use crate::compat::{lock, MiniToString};
#[cfg(widgets_unstripped)]
use crate::control_backend::custom::LABEL_PROPERTY_NAMES;
use crate::control_backend::trait_def::ControlBackend;
use crate::control_backend::types::ControlBackendKind;
use crate::core::ObjectId;
use crate::platform::{WidgetTriggerEvent, WidgetTriggerKind};
use crate::widget::WidgetKind;
// Pull in per-category macros that define method bodies.
include!("create_widgets_base.in.rs");
include!("create_widgets_input.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_view.in.rs");
include!("create_widgets_container.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_dialog.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_menu.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_advanced.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_other.in.rs");
#[cfg(not(embedded_surface))]
include!("create_widgets_modern.in.rs");
include!("create_widgets_helpers.in.rs");
impl ControlBackend for super::CustomPaintControlBackend {
impl_base_widgets!();
impl_input_widgets!();
#[cfg(not(embedded_surface))]
impl_view_widgets!();
impl_container_widgets!();
#[cfg(not(embedded_surface))]
impl_dialog_widgets!();
#[cfg(not(embedded_surface))]
impl_menu_widgets!();
#[cfg(not(embedded_surface))]
impl_advanced_widgets!();
#[cfg(not(embedded_surface))]
impl_other_widgets!();
#[cfg(not(embedded_surface))]
impl_modern_widgets!();
/// Creates a widget by factory name, reaching every registered control.
///
/// # Why this override exists
///
/// The trait's default returns `0`, and the typed `create_*` methods only
/// cover the kinds somebody wrote a method for. A caller holding a *name* — a
/// binding layer, a config file, the capability matrix — had no way to reach
/// the other 130-odd controls. Resolving through `WidgetFactory` makes the name
/// list the single source of truth: registration is the only step needed to
/// make a control reachable, which is what
/// `tools/check_widget_registration_fidelity.sh` enforces.
///
/// The name is normalized by the factory (case- and separator-insensitive), so
/// `"TreeView"`, `"tree_view"` and `"treeview"` all resolve, and aliases such
/// as `"nav_breadcrumb"` work without a table here.
fn create_widget(
&self,
kind: &str,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
#[cfg(full_widgets)]
{
let factory = crate::widget::WidgetFactory::new_with_defaults();
let Some(capability) = factory.capability(kind) else {
log::warn!(
"custom backend: no control is registered under the name {kind:?}; returning 0"
);
return 0;
};
// Mount by the *matched name*, not by the kind. Several controls share
// a kind (`chart` / `timeline_widget` / `gantt_widget` all report
// `WidgetKind::Chart`), and `mount_widget_of_kind` resolves a kind back
// to a single canonical name — so going through the kind would build
// whichever of the siblings registered first, and the caller would get a
// different control than the one it named.
self.mount_named_widget(capability.canonical_name, parent, text, x, y, width, height)
}
#[cfg(not(full_widgets))]
{
let _ = (kind, parent, text, x, y, width, height);
log::warn!(
"custom backend: name-based creation is unavailable in this profile (no \
constructor registry is compiled in); returning 0"
);
0
}
}
// The route-matrix generator derives `create_qrcode` from `WidgetKind::QRCode`
// (camel-to-snake of "QRCode" yields "qrcode"), whereas the canonical API name
// is `create_qr_code`. Provide both; the alias delegates to the canonical one.
#[cfg(widgets_unstripped)]
fn create_qrcode(&self, parent: ObjectId, x: i32, y: i32, width: u32, height: u32) -> ObjectId {
self.create_qr_code(parent, x, y, width, height)
}
/// Adopts a widget box a caller built itself, mounting it under the id it asked for.
///
/// # Why the JSON layout loader needs this
///
/// The loader applies a document's whole property set to a control *before* the control
/// exists, so it cannot use the name-based `create_widget`. Without this method it had
/// nowhere to hand the box over, and the answer it settled on — index the widget's own
/// `id()` in `WidgetRegistry` and drop the box — produced a tree of controls the runtime
/// had never mounted. Every handle a caller resolved from the binding then answered with
/// the control's *defaults*, and every `on_*` handler the document declared was dropped,
/// because both paths reach the widget through `with_widget`/`with_widget_mut`.
///
/// Delegates to [`adopt_widget_box`](super::CustomPaintControlBackend::adopt_widget_box),
/// which is also the tail of the name-based path — one implementation of "mount this
/// box, record both directions of the parent relation, give a window its host surface".
///
/// # Why the body is `0` on `alloc_frugal`
///
/// That profile compiles `widget::runtime` out, so there is no registry to adopt the box
/// into. The trait's own default already answers `0` there, and it is the same honest answer a
/// backend with no host windows gives: no fabricated id.
#[cfg(not(alloc_frugal))]
fn mount_widget_box(
&self,
widget: crate::compat::Box<dyn crate::widget::Widget>,
parent: Option<ObjectId>,
declared_id: ObjectId,
) -> ObjectId {
self.adopt_widget_box(widget, parent, declared_id)
}
/// Drops every piece of host state this backend keeps for `widget_id`.
///
/// The widget object itself is released through
/// `widget::runtime::unregister`, which owns it; what remains here is
/// the IME policy, the accessible-name override and the last reported client size,
/// none of which a widget holds.
///
/// The client size **must** be dropped here, and that is why it is named explicitly:
/// it is keyed by widget id, and a destroyed id can be handed out again (the registry
/// counts ids per thread from a fixed start). Leaving the entry behind would make a
/// brand-new window open at the size of a *different*, long-gone window, because
/// `window_client_size` answers from this map before falling back to geometry.
fn destroy_widget(&self, widget_id: ObjectId) -> bool {
// A window has a host object the platform built for it in
// `attach_window_host_if_needed`; that object is not owned by the widget registry, so
// dropping the widget alone left it live. Resolve it *before* `unregister` removes the
// association, and tear it down through the same `Platform` entry the native control
// backend uses. Without this the platform kept one window per destroyed window forever:
// Android's one-Activity-window rule then refused every later `create_window`, and the
// unified diagnostics probe reported `display_of_window returned None` on-device.
#[cfg(not(alloc_frugal))]
if let Some(host) = crate::widget::runtime::host_window_for(widget_id) {
let _ = crate::platform::get_platform().destroy_widget(host);
}
// `widget::runtime` is compiled out of the alloc-frugal profile, which
// holds no widget objects by design; only the host-side maps remain there.
#[cfg(not(alloc_frugal))]
let released = crate::widget::runtime::unregister(widget_id);
#[cfg(alloc_frugal)]
let released = false;
let mut state = lock(&self.state);
// Each `remove` must run: `||` short-circuits, so writing this as one expression ran only
// the leftmost `remove` whose entry existed and skipped the rest. A widget registered with
// both an IME policy and an accessibility name therefore leaked the second map's entry on
// destroy, and a later destroy of a reused id could still report residual state. Evaluating
// every removal (with `|`, not `||`, or as separate statements) closes that gap.
let had_ime = state.ime_enabled.remove(&widget_id).is_some();
let had_a11y = state.accessibility_names.remove(&widget_id).is_some();
let had_client_size = state.window_client_sizes.remove(&widget_id).is_some();
released || had_ime || had_a11y || had_client_size
}
impl_helpers!();
}