# AssortedWidgets Architecture
> **Last Updated:** 2025-12-24
> **Status:** Phase 4.0 - Architectural Refactor (Widget System Unification)
This document describes the current architecture of AssortedWidgets and planned future features.
## Table of Contents
1. [Core Principles](#core-principles)
2. [Widget System Architecture](#widget-system-architecture)
3. [Event System](#event-system)
4. [Layout System](#layout-system)
5. [Rendering Architecture](#rendering-architecture)
6. [Text Rendering](#text-rendering)
7. [Animation System](#animation-system)
8. [Theme System](#theme-system)
9. [Multi-Window Architecture](#multi-window-architecture)
10. [Memory Management](#memory-management)
11. [Performance Considerations](#performance-considerations)
---
## Core Principles
### 1. Flexibility over Convenience
Provide low-level WebGPU access alongside high-level abstractions. Simple apps use primitives, complex apps access the render pass directly.
### 2. Compile-Time Safety over Runtime Flexibility
Event queue with direct ownership instead of RefCell/Rc. No runtime borrow checking panics.
### 3. Cross-Platform Consistency
Manual event loop control works the same on macOS, Windows, and Linux.
### 4. Clean Developer API
Internal complexity hidden behind minimal, intuitive public APIs. Developers work with widgets, not internal data structures.
---
## Widget System Architecture
**Philosophy:** Developers should only work with a clean widget API. Internal data structures (WidgetManager, WidgetTree, LayoutManager) are implementation details and completely hidden.
### Three-System Architecture
AssortedWidgets maintains three coordinated systems internally:
```mermaid
graph TB
A[Window API Public Interface] --> B[WidgetManager Storage]
A --> C[WidgetTree Hierarchy]
A --> D[LayoutManager Taffy Integration]
B -->|Stores| E[HashMap WidgetId → Widget]
C -->|Maintains| F[Tree WidgetId Parent-Child]
D -->|Manages| G[Taffy Tree Layout Calculation]
style A fill:#90EE90
style B fill:#FFE4B5
style C fill:#FFE4B5
style D fill:#FFE4B5
```
#### 1. WidgetManager (Storage)
```rust
// Internal - not exposed to developers
struct WidgetManager {
widgets: HashMap>,
}
```
**Purpose:** Store widget data (O(1) lookup by ID)
#### 2. WidgetTree (Hierarchy)
```rust
// Internal - not exposed to developers
struct WidgetTree {
root: Option,
}
struct TreeNode {
widget_id: WidgetId,
children: Vec,
}
```
**Purpose:** Maintain parent-child relationships for ALL widgets (normal + floating)
**Why needed:**
- Event bubbling (child → parent → grandparent)
- Z-order / paint order (tree traversal)
- Parent context (tooltips know their parent button)
#### 3. LayoutManager (Taffy Integration)
```rust
// Internal - not exposed to developers
struct LayoutManager {
taffy: Taffy,
widget_to_node: HashMap,
}
```
**Purpose:** Layout calculation for layout-participating widgets only
**Note:** Floating widgets (tooltips, modals) are NOT in Taffy, only in WidgetTree.
---
### WidgetId vs NodeId Design Decision
**Question:** Why not use Taffy's `NodeId` directly as `WidgetId`?
**Answer:** They serve different purposes and must remain separate.
#### Problems with using NodeId as WidgetId:
1. **Not Serializable**
- `NodeId` is tied to a specific Taffy instance
- Can't save/restore widget IDs across sessions
- Can't share widget IDs between contexts
2. **Taffy-Owned Generation**
- Only Taffy can create `NodeId`s
- Can't create widgets without immediately adding to layout
- Couples widget lifecycle to layout participation
3. **Floating Widgets**
- Tooltips, modals, popups don't participate in layout
- Would need fake Taffy nodes with `Display::None`
- Awkward and error-prone
4. **Lifecycle Independence**
- Widgets should exist before being added to layout
- Widgets can be removed from layout but kept alive
- Widget identity shouldn't depend on layout state
#### Benefits of separate WidgetId:
- ✅ **Decoupling:** Widget identity independent of layout system
- ✅ **Flexibility:** Create widgets before layout, remove from layout but keep alive
- ✅ **Floating elements:** Tooltips/modals don't need layout nodes
- ✅ **Serialization:** Can save/restore widget IDs
- ✅ **Simplicity:** Mapping is trivial (`HashMap`)
#### Floating Widget Support:
Taffy supports `Display::None`, but for truly floating widgets (modals, tooltips, drag previews), we simply don't create Taffy nodes for them. They exist in:
- ✅ WidgetManager (storage)
- ✅ WidgetTree (hierarchy for events)
- ❌ LayoutManager (no layout participation)
---
### Clean Developer API
**Design Principle:** Developers NEVER manipulate internal data structures directly. All operations go through a minimal Window API.
```rust
// Public API - simple and intuitive
impl Window {
/// Add a root widget to the window
/// Automatically registers in all three internal systems
pub fn add_root(&mut self, widget: Box, style: Style) -> WidgetId;
/// Add a child widget under a parent
/// Automatically registers in all three internal systems
pub fn add_child(&mut self, parent: WidgetId, widget: Box, style: Style) -> WidgetId;
/// Add a floating widget (tooltip, modal, popup)
/// Registers in WidgetManager and WidgetTree, but NOT in layout
pub fn add_floating(&mut self, parent: WidgetId, widget: Box) -> WidgetId;
/// Remove a widget and all its children
/// Automatically removes from all internal systems
pub fn remove(&mut self, widget_id: WidgetId);
/// Get immutable widget reference
pub fn get(&self, widget_id: WidgetId) -> Option<&dyn Widget>;
/// Get mutable widget reference
pub fn get_mut(&mut self, widget_id: WidgetId) -> Option<&mut dyn Widget>;
}
```
**Internal Implementation (hidden from developers):**
```rust
// Inside Window::add_child() - developers never see this
fn add_child(&mut self, parent: WidgetId, widget: Box, style: Style) -> WidgetId {
let widget_id = widget.id();
// 1. Add to WidgetManager (storage)
self.widget_manager.add(widget);
// 2. Add to WidgetTree (hierarchy)
self.widget_tree.add_child(parent, widget_id);
// 3. Add to LayoutManager if has layout style (Taffy)
if style.display != Display::None {
self.layout_manager.create_node(widget_id, style);
self.layout_manager.add_child(parent, widget_id);
}
widget_id
}
```
**Key Benefits:**
- ✅ Single point of truth (Window API)
- ✅ No manual synchronization needed
- ✅ Impossible to create inconsistent state
- ✅ Compile-time safety (can't access internal structures)
- ✅ Simple mental model for developers
---
### Terminology Update
**Old naming (inconsistent):**
- `Element` trait - confusing, borrowed from HTML
- `ElementManager` - but widgets have `WidgetId` (??)
- Project called "AssortedWidgets" but no Widget concept
**New naming (consistent):**
- `Widget` trait - clear, matches domain
- `WidgetManager` - manages widgets with `WidgetId` ✅
- Aligns with "AssortedWidgets" branding ✅
**Rationale:** HTML uses "element" because it's a document model (nested `
`, ``, etc.). We're building a GUI framework where widgets paint themselves. No need for sub-widget granularity. Widget is the atomic unit.
---
## Event System
**Architecture:** Event Queue + Manual RunLoop
```mermaid
graph TB
A[Platform Callbacks] -->|Push| B[Event Queue Arc Mutex VecDeque]
C[Manual RunLoop] -->|Poll NSApp/Wayland/Win32| C
C -->|Drain Queue| B
B --> D[Event Processing]
D --> E[Layout]
E --> F[Rendering]
```
**Key Components:**
- Platform callbacks push events to `Arc>>`
- Main loop polls platform events and drains queue with direct mutable access
- No RefCell - compile-time borrow checking only
**Event Bubbling via WidgetTree:**
- Click on child widget → bubble to parent → bubble to grandparent
- WidgetTree provides parent-child relationships for bubbling
- Works for both normal and floating widgets (tooltips bubble to their parent)
**IME Support:**
- Marked text ranges for candidate selection
- Composition events sent to focused widget
- Platform-specific IME coordinate conversions
---
## Layout System
**Architecture:** Taffy 0.9 Integration with Measure Functions
### Bidirectional Layout Flows
**Flow 1: Window Resize (Root → Leaves)**
```
Window Resize → Taffy compute_layout() → Update Widget Bounds
```
**Flow 2: Content Change (Leaves → Root)**
```
Widget.mark_needs_layout() → Set Dirty Flag → Taffy Recompute → Update Bounds
```
### Measure Functions
Widgets with intrinsic size (text, images) implement measure functions:
```rust
fn measure(
&self,
known_dimensions: taffy::Size