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
//! Register Rust-backed XAML classes (`<myns:Foo>`) with Noesis from Bevy systems.
//!
//! The resulting [`ClassRegistration`] is owned by the Bevy resource lifecycle:
//! it drops at app teardown, before [`noesis_runtime::shutdown`] runs via
//! `NoesisShutdownGuard`.
//!
//! The class machinery (`ClassBuilder`, `ClassRegistration`, `Instance`,
//! `PropertyChangeHandler`, `PropertyValue`) lives in [`noesis_runtime::classes`]
//! and is re-exported here. The Bevy side adds two pieces:
//! * [`NoesisClassPlugin`] installs the registry resource.
//! * [`NoesisClassRegistry`] owns the live `ClassRegistration` instances. Bevy
//! 0.18 runs resource cleanup before the `!Send` `NoesisShutdownGuard` Drop,
//! so registrations release before Noesis shuts down.
//!
//! # Property-change threading
//!
//! Callbacks fire from inside Noesis's property pump, on the **main thread**
//! that drives the View. The handler runs while Noesis (and the
//! `NoesisRenderState` that owns it) is borrowed, so it must not reenter the
//! Bevy `World`; queue any ECS mutations for a later system. For purely-derived
//! properties (e.g. `NineSlicer` computing viewbox rects from `SliceThickness`),
//! the handler can do the math and call `Instance::set_*` inline. Handlers are
//! `Send`-bound by the FFI.
//!
//! # Usage
//!
//! ```ignore
//! use bevy::prelude::*;
//! use noesis_bevy::classes::{
//! ClassBase, ClassBuilder, NoesisClassRegistry, PropType,
//! PropertyChangeHandler, PropertyValue, Instance,
//! };
//!
//! struct NineSlicerHandler { source_idx: u32, thickness_idx: u32 /* ... */ }
//! impl PropertyChangeHandler for NineSlicerHandler {
//! fn on_changed(&mut self, instance: Instance, idx: u32, value: PropertyValue<'_>) {
//! if idx == self.thickness_idx {
//! // Recompute derived properties and write back via instance.set_rect(...)
//! }
//! }
//! }
//!
//! fn register(mut registry: NonSendMut<NoesisClassRegistry>) {
//! let mut b = ClassBuilder::new("AOR.NineSlicer", ClassBase::ContentControl,
//! NineSlicerHandler { /* ... */ });
//! b.add_property("Source", PropType::ImageSource);
//! b.add_property("SliceThickness", PropType::Thickness);
//! // ...
//! if let Some(reg) = b.register() {
//! registry.add(reg);
//! }
//! }
//! ```
use *;
pub use ;
pub use ;
/// Owns the live [`ClassRegistration`] instances for the app lifetime.
/// Insert finished registrations from a `Startup` system; the resource
/// drops them at app teardown, before [`noesis_runtime::shutdown`] runs.
///
/// Add registrations BEFORE any XAML referencing them is loaded: a `Startup`
/// system ordered after [`crate::NoesisPlugin`] initialization (Bevy's default
/// startup order suffices unless you override it).
///
/// Non-send resource: [`ClassRegistration`] holds `!Send`/`!Sync` Noesis handles,
/// so it is stored via `init_non_send_resource` and accessed through `NonSendMut`.
/// Class registration is a main-thread, startup-time concern anyway, since Noesis
/// is thread-affine.
/// Plugin that installs [`NoesisClassRegistry`]. Add **after**
/// [`crate::NoesisPlugin`] so [`noesis_runtime::init`] has already run by the
/// time consumers register classes from `Startup` systems.
///
/// The plugin itself is intentionally minimal: registration is a startup-time
/// concern and class definitions are consumer-specific, so the plugin's only
/// job is to give consumers a well-known place to stash their registrations.
;