ferroui_static_view_locator/lib.rs
1//! ferroui-static-view-locator
2//!
3//! View locators generated at build time: a locator maps the type of a view model to the view
4//! that shows it, through a table written by the build script of the crate, with no lookup of a
5//! type by its name and no scan of the types of the program at run time. A port of
6//! StaticViewLocator (<https://github.com/wieslawsoltes/StaticViewLocator>), a C# source
7//! generator; the plan and the deviations are in `docs/porting/static-view-locator.md`.
8//!
9//! Three crates make the port:
10//!
11//! - this crate: the attributes, the tables a generated locator holds ([`ViewTable`],
12//! [`MissingViewTable`]), the run-time type of a view model ([`RuntimeType`]) and what the
13//! generated adapters call;
14//! - `ferroui-static-view-locator-macros`: [`static_view_locator`] and [`static_view_mapping`],
15//! re-exported here;
16//! - `ferroui-static-view-locator-build`: the generator, which the build script of the crate
17//! runs (the counterpart of the incremental generator of the reference).
18//!
19//! # Usage
20//!
21//! ```toml
22//! [dependencies]
23//! ferroui-static-view-locator = "0.4"
24//!
25//! [build-dependencies]
26//! ferroui-static-view-locator-build = "0.4"
27//! ```
28//!
29//! ```ignore
30//! // build.rs
31//! fn main() {
32//! ferroui_static_view_locator_build::StaticViewLocatorBuild::from_env().run();
33//! }
34//! ```
35//!
36//! ```ignore
37//! use ferroui_static_view_locator::{static_view_locator, static_view_mapping};
38//!
39//! #[static_view_locator(generate_data_template = true, data_template_match_types = [ViewModelBase])]
40//! #[static_view_mapping(LoginViewModel, LogInView)]
41//! #[derive(Default)]
42//! pub struct ViewLocator;
43//! ```
44//!
45//! The attribute writes the struct and includes the file the build script generated for it:
46//! the tables and the members the options ask for, in an `impl` block of the struct (the
47//! `partial class` of the reference). By convention `crate::view_models::SettingsViewModel` is
48//! shown by `crate::views::SettingsView`, and `crate::settings_view_model::SettingsViewModel` by
49//! `crate::settings_view::SettingsView`; a view is a class of the object model that derives from
50//! `UserControl` or `Window` and has a constructor without parameters (`new()`).
51
52#![forbid(unsafe_code)]
53
54mod runtime_type;
55mod table;
56#[cfg(test)]
57mod tests;
58
59#[cfg(feature = "reactiveui")]
60pub mod reactiveui;
61
62pub use ferroui_static_view_locator_macros::{static_view_locator, static_view_mapping};
63pub use runtime_type::RuntimeType;
64pub use table::{NameTable, TypeEntry, TypeTable};
65
66use ferroui_base::{BoxedValue, ObjectType, Ref, Upcast};
67use ferroui_controls::{Control, TextBlock};
68
69/// The fallback precedence the generated adapters are compiled with
70/// (`generated_adapter_resolution_mode` of [`static_view_locator`]).
71///
72/// The generator flattens the precedence into the table of the views: a view model type of the
73/// crate that has no view of its own gets the view of its nearest mapped base class or of its
74/// mapped trait, in the order the mode states.
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
76pub enum ViewResolutionMode {
77 /// The exact run-time type only.
78 Exact,
79 /// The exact type, then the nearest mapped base class.
80 ExactThenBaseTypes,
81 /// The exact type, then the nearest mapped trait.
82 ExactThenInterfaces,
83 /// The exact type, the mapped base classes, then the mapped traits. The default.
84 #[default]
85 ExactThenBaseTypesThenInterfaces,
86 /// The exact type, the mapped traits, then the mapped base classes.
87 ExactThenInterfacesThenBaseTypes,
88}
89
90/// Creates the view of a mapping (`Func<Control>` of `s_views`).
91pub type ViewFactory = fn() -> Ref<Control>;
92
93/// The views of the view model types (`Dictionary<Type, Func<Control>> s_views`).
94pub type ViewTable = TypeTable<ViewFactory>;
95
96/// The messages of the view model types whose view is not found
97/// (`Dictionary<Type, string> s_missingViews`): `"Not Found: crate::views::MainWindowView"`.
98pub type MissingViewTable = TypeTable<&'static str>;
99
100/// A set of types (the resolved view model types a generated `match_` accepts).
101pub type TypeSet = TypeTable<()>;
102
103/// A view as a control (the conversion of `new View()` to `Control`).
104pub fn upcast<V: ObjectType + Upcast<Control>>(view: Ref<V>) -> Ref<Control> {
105 view.upcast()
106}
107
108/// A text block that shows `text` (`new TextBlock { Text = text }`): the view of a missing view
109/// and of an invalid view model.
110pub fn text_view(text: &str) -> Ref<Control> {
111 let text_block = TextBlock::new();
112 text_block.set_text(Some(text));
113 text_block.upcast()
114}
115
116/// Sets `data` as the data context of `control` (`control.DataContext = data`).
117pub fn set_data_context(control: &Ref<Control>, data: &BoxedValue) {
118 control.set_data_context(Some(data.clone()));
119}
120
121/// The text of a view model type whose view is not found and that has no entry in the table of
122/// missing views (`$"View for {viewModelType.FullName} is not found."`).
123pub fn view_not_found_text(view_model_type: &RuntimeType) -> String {
124 format!("View for {} is not found.", view_model_type.name())
125}
126
127/// The failure of the legacy `build` for a view model of a type without a view or a missing-view
128/// entry (`throw new Exception($"Unable to create view for type: {type}")`).
129#[track_caller]
130pub fn unable_to_create_view(view_model_type: &RuntimeType) -> ! {
131 panic!("Unable to create view for type: {}", view_model_type.name())
132}
133
134/// What the generated source names. Not a public API: the paths may change in any version.
135#[doc(hidden)]
136pub mod generated {
137 pub use ferroui_base::{BoxedValue, Ref};
138 pub use ferroui_controls::templates::{IDataTemplate, ITemplateWithParam};
139 pub use ferroui_controls::Control;
140 pub use std::any::{Any, TypeId};
141 pub use std::option::Option;
142 pub use std::rc::Rc;
143}