pub mod core_widgets;
pub mod event_types;
pub mod registry;
pub use event_types::{KeyEventData, MouseButtonKind, PluginEventContext, UiInputEvent};
pub use registry::{
EventResponse, TraversalBehavior, WidgetOverflow, WidgetRenderContext, WidgetTypeDescriptor,
WidgetTypeRegistry,
};
use crate::tree::UiTree;
use std::any::Any;
use std::marker::PhantomData;
pub trait UiPlugin: Any + 'static {
fn name(&self) -> &str;
fn register_widgets(&self, registry: &mut WidgetTypeRegistry);
fn handle_event(&mut self, _event: &UiInputEvent, _ctx: &mut PluginEventContext<'_>) -> bool {
false
}
fn post_layout(&mut self, _tree: &mut UiTree) {}
fn update(&mut self, _dt: f32, _tree: &mut UiTree) {}
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
pub struct PluginHandle<P: UiPlugin> {
_private: (),
_marker: PhantomData<P>,
}
impl<P: UiPlugin> Clone for PluginHandle<P> {
fn clone(&self) -> Self {
*self
}
}
impl<P: UiPlugin> Copy for PluginHandle<P> {}
impl<P: UiPlugin> std::fmt::Debug for PluginHandle<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginHandle")
.field("type", &std::any::type_name::<P>())
.finish()
}
}
pub struct PluginManager {
plugins: Vec<Box<dyn UiPlugin>>,
widget_registry: WidgetTypeRegistry,
}
impl PluginManager {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
widget_registry: WidgetTypeRegistry::new(),
}
}
pub fn add_plugin<P: UiPlugin>(&mut self, plugin: P) -> PluginHandle<P> {
let type_id = std::any::TypeId::of::<P>();
for existing in &self.plugins {
if existing.as_any().type_id() == type_id {
panic!(
"Plugin '{}' is already registered",
std::any::type_name::<P>()
);
}
}
plugin.register_widgets(&mut self.widget_registry);
self.plugins.push(Box::new(plugin));
PluginHandle {
_private: (),
_marker: PhantomData,
}
}
pub fn handle<P: UiPlugin>(&self) -> Option<PluginHandle<P>> {
if self.get::<P>().is_some() {
Some(PluginHandle {
_private: (),
_marker: PhantomData,
})
} else {
None
}
}
pub fn get<P: UiPlugin>(&self) -> Option<&P> {
let type_id = std::any::TypeId::of::<P>();
for plugin in &self.plugins {
if plugin.as_any().type_id() == type_id {
return plugin.as_any().downcast_ref::<P>();
}
}
None
}
pub fn get_mut<P: UiPlugin>(&mut self) -> Option<&mut P> {
let type_id = std::any::TypeId::of::<P>();
for plugin in &mut self.plugins {
if plugin.as_any().type_id() == type_id {
return plugin.as_any_mut().downcast_mut::<P>();
}
}
None
}
pub fn widget_registry(&self) -> &WidgetTypeRegistry {
&self.widget_registry
}
pub fn post_layout(&mut self, tree: &mut UiTree) {
for plugin in &mut self.plugins {
plugin.post_layout(tree);
}
}
pub fn update(&mut self, dt: f32, tree: &mut UiTree) {
for plugin in &mut self.plugins {
plugin.update(dt, tree);
}
}
pub fn handle_event(&mut self, event: &UiInputEvent, ctx: &mut PluginEventContext<'_>) -> bool {
for plugin in &mut self.plugins {
if plugin.handle_event(event, ctx) {
return true;
}
}
false
}
pub fn plugin_count(&self) -> usize {
self.plugins.len()
}
}
impl Default for PluginManager {
fn default() -> Self {
Self::new()
}
}
pub struct CorePlugin;
impl UiPlugin for CorePlugin {
fn name(&self) -> &str {
"core"
}
fn register_widgets(&self, registry: &mut WidgetTypeRegistry) {
use crate::widgets::*;
use core_widgets::*;
registry.register::<Container>(
WidgetTypeDescriptor::new("Container")
.with_render(render_container)
.with_overflow(container_overflow),
);
registry.register::<Text>(
WidgetTypeDescriptor::new("Text")
.with_render(render_text)
.with_caches_measurement(),
);
registry.register::<Button>(
WidgetTypeDescriptor::new("Button")
.with_render(render_button)
.with_on_hover(button_hover)
.with_on_press(button_press)
.with_on_click(button_click),
);
registry.register::<TextInput>(
WidgetTypeDescriptor::new("TextInput")
.with_on_click(text_input_click)
.with_on_key_input(text_input_key)
.with_on_char_input(text_input_char),
);
registry.register::<Image>(WidgetTypeDescriptor::new("Image").with_render(render_image));
registry.register::<Row>(WidgetTypeDescriptor::new("Row"));
registry.register::<Column>(WidgetTypeDescriptor::new("Column"));
registry
.register::<Tooltip>(WidgetTypeDescriptor::new("Tooltip").with_render(render_tooltip));
registry.register::<HScrollbar>(
WidgetTypeDescriptor::new("HScrollbar").with_render(render_hscrollbar),
);
registry.register::<VScrollbar>(
WidgetTypeDescriptor::new("VScrollbar").with_render(render_vscrollbar),
);
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestPlugin {
initialized: bool,
}
impl TestPlugin {
fn new() -> Self {
Self { initialized: true }
}
}
impl UiPlugin for TestPlugin {
fn name(&self) -> &str {
"test"
}
fn register_widgets(&self, registry: &mut WidgetTypeRegistry) {
registry.register::<TestPlugin>(WidgetTypeDescriptor::new("TestWidget"));
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[test]
fn test_plugin_manager_add_and_get() {
let mut manager = PluginManager::new();
let _handle = manager.add_plugin(TestPlugin::new());
let plugin = manager.get::<TestPlugin>().unwrap();
assert!(plugin.initialized);
assert_eq!(plugin.name(), "test");
}
#[test]
fn test_plugin_manager_get_mut() {
let mut manager = PluginManager::new();
let _handle = manager.add_plugin(TestPlugin::new());
let plugin = manager.get_mut::<TestPlugin>().unwrap();
plugin.initialized = false;
let plugin = manager.get::<TestPlugin>().unwrap();
assert!(!plugin.initialized);
}
#[test]
#[should_panic(expected = "already registered")]
fn test_plugin_manager_duplicate_panics() {
let mut manager = PluginManager::new();
manager.add_plugin(TestPlugin::new());
manager.add_plugin(TestPlugin::new()); }
#[test]
fn test_plugin_handle_is_copy() {
let mut manager = PluginManager::new();
let handle = manager.add_plugin(TestPlugin::new());
let _copy = handle; let _clone = handle; }
#[test]
fn test_core_plugin_registers_widgets() {
let mut manager = PluginManager::new();
manager.add_plugin(CorePlugin);
let registry = manager.widget_registry();
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Container>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Text>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Button>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::TextInput>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Image>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Row>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Column>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::Tooltip>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::HScrollbar>()));
assert!(registry.contains(std::any::TypeId::of::<crate::widgets::VScrollbar>()));
}
#[test]
fn test_scroll_plugin_registers_scroll_container() {
let mut manager = PluginManager::new();
manager.add_plugin(crate::scroll_plugin::ScrollPlugin::new());
let registry = manager.widget_registry();
assert!(registry.contains(std::any::TypeId::of::<
crate::widgets::scroll_container::ScrollContainer,
>()));
}
#[test]
fn test_widget_type_descriptor_builder() {
let desc = WidgetTypeDescriptor::new("Test").with_clips_children(|_| true);
assert_eq!(desc.name, "Test");
assert!(desc.clips_children.is_some());
assert!(desc.measure.is_none());
assert!(desc.traversal.is_none());
assert!(desc.scroll_offset.is_none());
}
#[test]
fn test_registry_len() {
let mut registry = WidgetTypeRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
registry.register::<TestPlugin>(WidgetTypeDescriptor::new("Test"));
assert!(!registry.is_empty());
assert_eq!(registry.len(), 1);
}
}