#[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::dialog::popup_window::PopupWindow;
#[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::command_palette::CommandPalette;
#[cfg(full_widgets)]
use crate::widget::special_widgets::diff_viewer::DiffViewer;
#[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::markdown_editor::MarkdownEditor;
#[cfg(full_widgets)]
use crate::widget::special_widgets::media_player::MediaPlayer;
#[cfg(full_widgets)]
use crate::widget::special_widgets::notification_center::NotificationCenter;
#[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::special_widgets::timeline_widget::TimelineWidget;
#[cfg(full_widgets)]
use crate::widget::special_widgets::toast::ToastStack;
#[cfg(full_widgets)]
use crate::widget::view_widgets::data_grid::DataGrid;
#[cfg(full_widgets)]
use crate::widget::view_widgets::list_view::ListView;
#[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::{
append_widget_list_item, base_property_get, base_property_set, clear_widget_list_items,
geometry_to_value, read_widget_property_by_name, widget_list_item, widget_list_item_count,
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(full_widgets)]
pub(crate) fn kind_canonical_name(kind: crate::widget::WidgetKind) -> alloc::string::String {
let mut name = alloc::string::String::new();
kind_canonical_name_into(kind, &mut name);
name
}
#[cfg(all(not(alloc_frugal), not(embedded_surface)))]
pub(crate) fn kind_canonical_name_into(
kind: crate::widget::WidgetKind,
out: &mut alloc::string::String,
) {
use core::fmt::Write as _;
let start = out.len();
let _ = write!(out, "{kind:?}");
let segment = out[start..].to_ascii_lowercase();
let original: alloc::string::String = out[start..].into();
out.truncate(start);
for (index, ch) in original.chars().enumerate() {
if ch.is_ascii_uppercase() {
let previous_is_upper = index > 0
&& original.chars().nth(index - 1).is_some_and(|p| p.is_ascii_uppercase());
if index > 0 && !previous_is_upper {
out.push('_');
}
}
if let Some(lower) = segment.chars().nth(index) {
out.push(lower);
}
}
}
#[cfg(widgets_unstripped)]
pub fn factory_name_for_kind(kind: crate::widget::WidgetKind) -> &'static str {
#[cfg(not(full_widgets))]
{
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",
crate::widget::WidgetKind::DockPanel => "dock_widget",
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 mut buffer = alloc::string::String::new();
kind_canonical_name_into(kind, &mut buffer);
if let Some(alias) = alias_for_name(&buffer) {
return alias;
}
canonical_name_for_kind(kind)
}
#[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",
"dock_panel" => "dock_widget",
_ => return None,
})
}
#[cfg(all(widgets_unstripped, not(full_widgets)))]
fn canonical_name_for_kind(kind: crate::widget::WidgetKind) -> &'static str {
match kind {
crate::widget::WidgetKind::Button => "button",
crate::widget::WidgetKind::Label => "label",
crate::widget::WidgetKind::CheckBox => "check_box",
crate::widget::WidgetKind::RadioButton => "radio_button",
crate::widget::WidgetKind::LineEdit => "line_edit",
crate::widget::WidgetKind::Slider => "slider",
crate::widget::WidgetKind::ProgressBar => "progress_bar",
crate::widget::WidgetKind::ComboBox => "combo_box",
crate::widget::WidgetKind::ListBox => "list_box",
crate::widget::WidgetKind::Panel => "panel",
crate::widget::WidgetKind::GroupBox => "group_box",
crate::widget::WidgetKind::ScrollArea => "scroll_area",
crate::widget::WidgetKind::ScrollBar => "scroll_bar",
crate::widget::WidgetKind::Splitter => "splitter",
crate::widget::WidgetKind::TabWidget => "tab_widget",
crate::widget::WidgetKind::StatusBar => "status_bar",
crate::widget::WidgetKind::ToolBar => "tool_bar",
crate::widget::WidgetKind::MenuBar => "menu_bar",
_ => "",
}
}
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;
pub(crate) mod widget_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(),
"diff_viewer" => self::coercion::widget_as::<DiffViewer>(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(),
"list_view" => self::coercion::widget_as::<ListView>(widget).is_some(),
"command_palette" => self::coercion::widget_as::<CommandPalette>(widget).is_some(),
"notification_center" => {
self::coercion::widget_as::<NotificationCenter>(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(),
"markdown_editor" => self::coercion::widget_as::<MarkdownEditor>(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(),
"timeline_widget" => self::coercion::widget_as::<TimelineWidget>(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(),
"popup_window" => self::coercion::widget_as::<PopupWindow>(widget).is_some(),
"toast_stack" => self::coercion::widget_as::<ToastStack>(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(),
})
}
}