#[cfg(full_widgets)]
use std::collections::HashMap;
#[cfg(full_widgets)]
use crate::core::Rect;
#[cfg(full_widgets)]
use super::{Widget, WidgetKind};
#[cfg(full_widgets)]
use crate::widget::base_widgets::toggle_button::ToggleButton;
#[cfg(full_widgets)]
use crate::widget::input_widgets::rich_edit::RichEdit;
#[cfg(full_widgets)]
use crate::widget::input_widgets::textedit::TextEdit;
#[cfg(full_widgets)]
use crate::widget::menu_toolbar::status_bar::StatusBar;
#[cfg(full_widgets)]
use crate::widget::menu_toolbar::tool_button::ToolButton;
#[cfg(full_widgets)]
use crate::widget::special_widgets::canvas::Canvas;
#[cfg(full_widgets)]
use crate::widget::special_widgets::chart::ChartWidget;
#[cfg(full_widgets)]
use crate::widget::special_widgets::code_editor::CodeEditor;
#[cfg(full_widgets)]
use crate::widget::special_widgets::gantt_widget::GanttWidget;
#[cfg(full_widgets)]
use crate::widget::special_widgets::map_view::MapView;
#[cfg(full_widgets)]
use crate::widget::special_widgets::media_player::MediaPlayer;
#[cfg(full_widgets)]
use crate::widget::special_widgets::segmented_control::SegmentedControl;
#[cfg(full_widgets)]
use crate::widget::special_widgets::snackbar::Snackbar;
#[cfg(full_widgets)]
use crate::widget::special_widgets::split_button::SplitButton;
#[cfg(full_widgets)]
use crate::widget::special_widgets::terminal_view::TerminalView;
#[cfg(full_widgets)]
use crate::widget::view_widgets::data_grid::DataGrid;
#[cfg(full_widgets)]
use crate::widget::view_widgets::table_widget::TableWidget;
#[cfg(full_widgets)]
use crate::widget::view_widgets::tree_table::TreeTable;
#[cfg(full_widgets)]
use crate::widget::view_widgets::tree_view::TreeView;
#[cfg(full_widgets)]
use crate::widget::view_widgets::virtual_table::VirtualTable;
#[cfg(full_widgets)]
use crate::widget::web_widgets::web_view::WebView;
pub mod types;
pub use types::*;
pub mod properties_trait;
pub use properties_trait::{
base_property_get, base_property_set, geometry_to_value, read_widget_property_by_name,
widget_property_get, widget_property_names, widget_property_set, write_widget_property_by_name,
WidgetProperties, BASE_PROPERTY_NAMES,
};
#[cfg(widgets_unstripped)]
pub use access::{read_widget_property_by_id, write_widget_property_by_id};
#[cfg(widgets_unstripped)]
pub(crate) fn kind_canonical_name(kind: crate::widget::WidgetKind) -> alloc::string::String {
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() {
let starts_word = index > 0
&& !debug
.chars()
.nth(index - 1)
.is_some_and(|previous| previous.is_ascii_uppercase());
if starts_word {
snake.push('_');
}
snake.push(ch.to_ascii_lowercase());
} else {
snake.push(ch);
}
}
snake
}
#[cfg(widgets_unstripped)]
pub fn factory_name_for_kind(kind: crate::widget::WidgetKind) -> &'static str {
#[cfg(not(full_widgets))]
{
return factory_name_for_kind_without_registry(kind);
}
#[cfg(full_widgets)]
{
let factory = WidgetFactory::new_with_defaults();
if let Some(capability) = factory.capability_by_kind(kind) {
return capability.canonical_name;
}
alias_factory_name(kind)
}
}
#[cfg(full_widgets)]
fn alias_factory_name(kind: crate::widget::WidgetKind) -> &'static str {
match kind {
crate::widget::WidgetKind::ActivityIndicator => "progress_bar",
crate::widget::WidgetKind::DoubleSpinBox => "spin_box",
crate::widget::WidgetKind::ColumnView => "tree_view",
crate::widget::WidgetKind::UndoView => "list_view",
crate::widget::WidgetKind::CheckListBox => "list_box",
crate::widget::WidgetKind::DirectoryDialog => "file_dialog",
crate::widget::WidgetKind::ContextMenu => "menu",
crate::widget::WidgetKind::Dialog => "popup_window",
crate::widget::WidgetKind::Wizard => "wizard_dialog",
other => {
log::error!(
"widget capability registry has no entry and the alias table no mapping for \
{other:?}; its constructor cannot be resolved"
);
""
}
}
}
#[cfg(all(widgets_unstripped, not(full_widgets)))]
fn factory_name_for_kind_without_registry(kind: crate::widget::WidgetKind) -> &'static str {
let name = kind_canonical_name(kind);
if let Some(alias) = alias_for_name(&name) {
return alias;
}
intern_kind_name(name)
}
#[cfg(all(widgets_unstripped, not(full_widgets)))]
fn alias_for_name(name: &str) -> Option<&'static str> {
Some(match name {
"activity_indicator" => "progress_bar",
"double_spin_box" => "spin_box",
"column_view" => "tree_view",
"undo_view" => "list_view",
"check_list_box" => "list_box",
"directory_dialog" => "file_dialog",
"context_menu" => "menu",
"dialog" => "popup_window",
"wizard" => "wizard_dialog",
_ => return None,
})
}
#[cfg(all(widgets_unstripped, not(full_widgets)))]
fn intern_kind_name(name: alloc::string::String) -> &'static str {
use crate::compat::Mutex;
static NAMES: crate::compat::OnceLock<
Mutex<alloc::collections::BTreeMap<alloc::string::String, &'static str>>,
> = crate::compat::OnceLock::new();
let names = NAMES.get_or_init(|| Mutex::new(alloc::collections::BTreeMap::new()));
let mut names = names.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
if let Some(existing) = names.get(&name) {
return existing;
}
let leaked: &'static str = alloc::boxed::Box::leak(name.clone().into_boxed_str());
names.insert(name, leaked);
leaked
}
pub mod coercion;
pub use coercion::*;
#[cfg(full_widgets)]
pub mod constructors;
#[cfg(full_widgets)]
pub use constructors::*;
#[cfg(full_widgets)]
pub mod properties;
#[cfg(full_widgets)]
pub(crate) use properties::*;
#[cfg(widgets_unstripped)]
pub mod access;
#[cfg(widgets_unstripped)]
pub use access::*;
#[cfg(full_widgets)]
pub use access::default_widget_property_default_value;
#[cfg(full_widgets)]
pub mod registration;
#[cfg(all(test, full_widgets))]
pub mod tests;
#[cfg(all(test, full_widgets))]
mod properties_tests;
#[cfg(full_widgets)]
impl Default for WidgetFactory {
fn default() -> Self {
Self::new_with_defaults()
}
}
#[cfg(full_widgets)]
impl WidgetFactory {
pub fn new() -> Self {
Self {
capabilities: Vec::new(),
key_to_index: HashMap::new(),
kind_to_index: HashMap::new(),
constructors: HashMap::new(),
}
}
#[cfg(full_widgets)]
pub fn new_with_defaults() -> Self {
let mut factory = Self::new();
factory.register_core_widgets();
factory
}
pub fn register(&mut self, capability: WidgetCapability, ctor: WidgetCtor) {
let idx = self.capabilities.len();
self.kind_to_index.entry(capability.kind).or_default().push(idx);
let canonical_key = normalize_key(capability.canonical_name);
self.key_to_index.insert(canonical_key.clone(), idx);
self.constructors.insert(canonical_key, ctor);
for alias in capability.aliases {
let key = normalize_key(alias);
self.key_to_index.insert(key.clone(), idx);
self.constructors.insert(key, ctor);
}
self.capabilities.push(capability);
}
pub fn create(
&self,
kind_or_name: &str,
geometry: Rect,
text: &str,
) -> Option<Box<dyn Widget>> {
let key = normalize_key(kind_or_name);
self.constructors.get(&key).map(|ctor| ctor(geometry, text))
}
pub fn create_by_kind(
&self,
kind: WidgetKind,
geometry: Rect,
text: &str,
) -> Option<Box<dyn Widget>> {
let capability = self.capability_by_kind(kind)?;
self.create(capability.canonical_name, geometry, text)
}
pub fn capability(&self, kind_or_name: &str) -> Option<&WidgetCapability> {
let key = normalize_key(kind_or_name);
let idx = self.key_to_index.get(&key).copied()?;
self.capabilities.get(idx)
}
pub fn capability_by_kind(&self, kind: WidgetKind) -> Option<&WidgetCapability> {
let indices = self.kind_to_index.get(&kind)?;
let expected = kind_canonical_name(kind);
indices
.iter()
.filter_map(|index| self.capabilities.get(*index))
.find(|capability| capability.canonical_name == expected)
.or_else(|| self.capabilities.get(indices[0]))
}
pub fn capabilities_for_kind(
&self,
kind: WidgetKind,
) -> impl Iterator<Item = &WidgetCapability> {
self.kind_to_index
.get(&kind)
.into_iter()
.flatten()
.filter_map(|index| self.capabilities.get(*index))
}
pub fn capabilities(&self) -> &[WidgetCapability] {
&self.capabilities
}
pub fn capability_for_kind_instance(&self, widget: &dyn Widget) -> Option<&WidgetCapability> {
self.capability_for_widget(widget)
}
pub fn widget_names(&self) -> Vec<&'static str> {
self.capabilities.iter().map(|capability| capability.canonical_name).collect()
}
pub fn read_property(
&self,
widget: &dyn Widget,
property_name: &str,
) -> Result<CapabilityValue, CapabilityAccessError> {
let capability =
self.capability_for_widget(widget).ok_or(CapabilityAccessError::UnknownWidget)?;
let normalized = normalize_key(property_name);
let Some(property) =
capability.properties.iter().find(|schema| normalize_key(schema.name) == normalized)
else {
return Err(CapabilityAccessError::UnknownProperty);
};
if !property.readable {
return Err(CapabilityAccessError::UnsupportedOnWidget);
}
read_widget_property_by_name(widget, property.name)
}
pub fn write_property(
&self,
widget: &mut dyn Widget,
property_name: &str,
value: CapabilityValue,
) -> Result<(), CapabilityAccessError> {
let capability =
self.capability_for_widget(widget).ok_or(CapabilityAccessError::UnknownWidget)?;
let normalized = normalize_key(property_name);
let Some(property) =
capability.properties.iter().find(|schema| normalize_key(schema.name) == normalized)
else {
return Err(CapabilityAccessError::UnknownProperty);
};
if !property.writable {
return Err(CapabilityAccessError::ReadOnlyProperty);
}
write_widget_property_by_name(widget, property.name, value)
}
fn capability_for_widget(&self, widget: &dyn Widget) -> Option<&WidgetCapability> {
let kind = widget.kind();
let indices = self.kind_to_index.get(&kind)?;
if indices.len() == 1 {
return self.capabilities.get(indices[0]);
}
for &idx in indices.iter() {
let cap = &self.capabilities[idx];
if self.widget_matches_capability(widget, cap.canonical_name) {
return Some(cap);
}
}
log::warn!(
"widget capability registry has no type-based tie-break for {kind:?}; \
falling back to an empty schema rather than another control's"
);
self.capabilities.iter().find(|cap| cap.kind == kind && cap.properties.is_empty())
}
#[cfg(all(not(full_widgets), not(embedded_surface)))]
fn widget_matches_capability(&self, _widget: &dyn Widget, _canonical_name: &str) -> bool {
true
}
#[cfg(full_widgets)]
fn widget_matches_capability(&self, widget: &dyn Widget, canonical_name: &str) -> bool {
match canonical_name {
"data_grid" => self::coercion::widget_as::<DataGrid>(widget).is_some(),
"virtual_table" => self::coercion::widget_as::<VirtualTable>(widget).is_some(),
"table_widget" => self::coercion::widget_as::<TableWidget>(widget).is_some(),
"tree_table" => self::coercion::widget_as::<TreeTable>(widget).is_some(),
"tree_view" => self::coercion::widget_as::<TreeView>(widget).is_some(),
"segmented_control" => self::coercion::widget_as::<SegmentedControl>(widget).is_some(),
"toggle_button" => self::coercion::widget_as::<ToggleButton>(widget).is_some(),
"text_edit" => self::coercion::widget_as::<TextEdit>(widget).is_some(),
"terminal_view" => self::coercion::widget_as::<TerminalView>(widget).is_some(),
"rich_edit" => self::coercion::widget_as::<RichEdit>(widget).is_some(),
"code_editor" => self::coercion::widget_as::<CodeEditor>(widget).is_some(),
"status_bar" => self::coercion::widget_as::<StatusBar>(widget).is_some(),
"snackbar" => self::coercion::widget_as::<Snackbar>(widget).is_some(),
"canvas" => self::coercion::widget_as::<Canvas>(widget).is_some(),
"map_view" => self::coercion::widget_as::<MapView>(widget).is_some(),
"chart" => self::coercion::widget_as::<ChartWidget>(widget).is_some(),
"gantt_widget" => self::coercion::widget_as::<GanttWidget>(widget).is_some(),
"web_view" => self::coercion::widget_as::<WebView>(widget).is_some(),
"media_player" => self::coercion::widget_as::<MediaPlayer>(widget).is_some(),
"tool_button" => self::coercion::widget_as::<ToolButton>(widget).is_some(),
"split_button" => self::coercion::widget_as::<SplitButton>(widget).is_some(),
_ => false,
}
}
pub fn default_property_value(
&self,
kind_or_name: &str,
property_name: &str,
) -> Result<CapabilityValue, CapabilityAccessError> {
let capability =
self.capability(kind_or_name).ok_or(CapabilityAccessError::UnknownWidget)?;
let normalized = normalize_key(property_name);
let Some(property) =
capability.properties.iter().find(|schema| normalize_key(schema.name) == normalized)
else {
return Err(CapabilityAccessError::UnknownProperty);
};
self.schema_default_value(capability.kind, property.name)
.ok_or(CapabilityAccessError::UnsupportedOnWidget)
}
#[cfg(full_widgets)]
fn schema_default_value(
&self,
kind: crate::widget::WidgetKind,
property_name: &str,
) -> Option<CapabilityValue> {
access::default_widget_property_default_value(kind, property_name)
}
#[cfg(not(full_widgets))]
fn schema_default_value(
&self,
_kind: crate::widget::WidgetKind,
_property_name: &str,
) -> Option<CapabilityValue> {
None
}
pub fn property_schema(
&self,
kind_or_name: &str,
property_name: &str,
) -> Result<PropertySchema, CapabilityAccessError> {
let capability =
self.capability(kind_or_name).ok_or(CapabilityAccessError::UnknownWidget)?;
let normalized = normalize_key(property_name);
let Some(property) =
capability.properties.iter().find(|schema| normalize_key(schema.name) == normalized)
else {
return Err(CapabilityAccessError::UnknownProperty);
};
Ok(*property)
}
pub fn capability_manifest(
&self,
kind_or_name: &str,
) -> Result<WidgetCapabilityManifest, CapabilityAccessError> {
let capability =
self.capability(kind_or_name).ok_or(CapabilityAccessError::UnknownWidget)?;
let mut properties = Vec::with_capacity(capability.properties.len());
for property in capability.properties {
let default_value = self
.schema_default_value(capability.kind, property.name)
.ok_or(CapabilityAccessError::UnsupportedOnWidget)?;
properties.push(CapabilityPropertyManifest { schema: *property, default_value });
}
Ok(WidgetCapabilityManifest {
kind: capability.kind,
canonical_name: capability.canonical_name,
aliases: capability.aliases.to_vec(),
properties,
events: capability.events.to_vec(),
commands: capability.commands.to_vec(),
})
}
}