Back to Azul

Azul GUI Framework Architecture

scripts/ARCHITECTURE.md

0.0.714.8 KB
Original Source

Azul GUI Framework Architecture

This document provides an architectural overview of the Azul GUI framework, detailing the flow from user input to final display, intended for new maintainers seeking a high-level understanding of the system components.

Crate Overview

Azul is organized into several key crates:

CratePathPurpose
corecore/Core data structures: Dom, RefAny, StyledDom, events, resources
csscss/CSS parsing, properties (Css, CssProperty), and styling
layoutlayout/Layout engine (solver3), text shaping (text3), managers, and LayoutWindow
dlldll/Platform shells (Windows, macOS, X11, Wayland), WebRender integration
webrenderwebrender/Fork of Mozilla's WebRender GPU rendering engine

Entry Points


1. Architecture of the Application: The State Graph

Azul employs a reactive, declarative paradigm where the User Interface (UI) is a pure function of the application state (UI = f(data)). This architecture fundamentally separates the application's data structure from its visual manifestation, addressing the inherent conflict between the Visual Tree and the State Graph.

Data Access and State Management

  1. Application Data (RefAny): The core application state (data models, database connections, application configuration) is encapsulated in an implementation-agnostic, reference-counted pointer called RefAny. This design choice enables C-compatibility.

  2. Visual Structure (Dom): The user defines the UI structure using a Document Object Model (Dom), which is highly similar to valid XHTML. The Dom is constructed in a pure function (layout callback) that takes the application state (RefAny) and returns a structured, unstyled hierarchy.

  3. Synchronization: Unlike immediate mode GUIs (IMGUI), the UI is only regenerated when a callback explicitly returns Update::RefreshDom. This explicit update control makes the render cycle efficient and predictable.

  4. Inter-Component Communication (Backreferences): Complex interactions between logically related but visually distant components are managed using backreferences. A lower-level component holds a direct RefAny reference and a callback to its specific higher-level logical controller (the State Graph), bypassing the visual hierarchy when passing information back up.


2. Event Handling: Two Complementary Systems

Azul uses a unified V2 event processing pipeline across all supported platforms (Windows, macOS, X11, Wayland). Event handling is split into two complementary systems:

  1. Window State Diffing – For simple, user-modifiable state (mouse position, button status, window size). The system diffs current_window_state vs previous_window_state each frame.

  2. Manager-Based Accumulation – For complex external events that require temporal context (multitouch gestures, drag operations, text input composition). Managers accumulate events over time until they can determine the correct high-level action.

The goal is compartmentalization: isolating the inherently messy interaction logic into dedicated managers rather than trying to eliminate complexity entirely.

Event Processing Flow

  1. OS Input & State Capture: The platform's event loop (e.g., WndProc on Windows, NSApplication on macOS) receives raw input and updates the internal FullWindowState (including mouse position, button status, keyboard state, etc.).

  2. State Diffing (Simple Events): For user-modifiable state, the system compares current_window_state against previous_window_state to detect changes (mouse moved, button flipped, window resized). These generate SyntheticEvents immediately.

  3. Manager Processing (Complex Events): For events requiring temporal context, specialized managers accumulate and interpret input:

    • GestureManager – Tracks touch events with timestamps until a gesture pattern (pinch, swipe, etc.) is recognized
    • ScrollManager – Handles scroll momentum, inertia, and programmatic scrolling
    • CursorManager – Manages text cursor state across frames during editing
  4. Event Dispatch: The SyntheticEvents are filtered and routed via the event processing pipeline, using hit-testing to determine which DomNodeId receives each event.

  5. Callback Invocation: The associated RefAny data and the callback function (CallbackType / Update return type) attached to the targeted node are invoked.

  6. Result Processing: The Update result from the callback determines the next steps: re-render, update display list, modify window properties, or perform recursive event re-entry if Update::RefreshDom was returned.

OS-Specific Input Mechanisms

The platform layer's primary job is to keep FullWindowState synchronized:

OSEvent ModelKey Input MechanismWindow Decoration
WindowsMessage Pump (WndProc, WM_LBUTTONDOWN)Direct Windows messages (WM_CHAR, WM_IME_COMPOSITION)Native (DWM) or custom (CSD)
macOSNSApplication event loop (NSEvent)NSTextInputClient protocol (IME)Native (AppKit) or custom (CSD)
X11Direct XNextEvent pollingXIM (X Input Method) for complex charactersNative (WM-managed) or custom (CSD)
WaylandProtocol-based listeners (asynchronous, compositor-driven)Text-Input protocol (or GTK fallback)Mandatory CSD (Wayland has no native decoration protocol)

3. Manager Interaction

The core runtime environment, encapsulated within the window's LayoutWindow structure, holds specialized managers responsible for specific UI concerns.

ManagerLocationResponsibility
LayoutCachesolver3/cache.rsStores the entire layout tree and absolute node positions across frames for incremental layout
FontManagertext3/cache.rsLoads, caches, and resolves font definitions and fallback chains
ScrollManagermanagers/scroll_state.rsTracks scroll positions and offsets for all scrollable nodes
FocusManagermanagers/focus_cursor.rsManages keyboard focus (which node has focus)
SelectionManagermanagers/selection.rsManages text selection ranges across different DOMs
CursorManagermanagers/cursor.rsManages cursor position and text editing state
IFrameManagermanagers/iframe.rsTracks nested DOMs (IFrames) and manages lazy loading/virtualization
GpuStateManagermanagers/gpu_state.rsManages GPU-accelerated state (transform, opacity, scrollbar fades)

4. How the Layout Engine Works (Roughly)

The core logic resides in the layout/src/solver3/ directory and implements a modern CSS layout pipeline.

  1. Input: The layout_document function takes a complete, fully styled DOM tree (StyledDom) and the current viewport boundaries.

  2. Preparation: Fonts are resolved and loaded by the FontManager based on the requested font-family chains.

  3. Layout Pass: The engine traverses the DOM, establishing Formatting Contexts (FC) for each node:

    • Block/Inline Contexts: Handled by the custom CSS engine (BFC and IFC implementations). IFC delegates complex text handling to text3.
    • Flex/Grid Contexts: Delegated to the underlying Taffy layout library for robust modern layout calculation.
  4. Text Layout (text3): For text nodes (within an IFC), the specialized text3 engine performs sophisticated typesetting: bidirectional (BIDI) analysis, font shaping via allsorts, line breaking (including hyphenation/Knuth-Plass algorithm), and glyph positioning.

  5. Output: The process generates a Layout Tree (storing box metrics, size, format context) and a map of final, absolute screen positions for all nodes.


5. How the CSS System Works (Key Structs)

The CSS system handles parsing, resolving cascading conflicts, and property storage.

StructLocationPurpose
Csscss/src/css.rsTop-level container for all parsed stylesheets in a document
Stylesheetcss/src/css.rsRepresents one complete stylesheet (e.g., loaded from a file)
CssRuleBlockcss/src/css.rsA single block of rules (CSS ruleset) associated with a specific selector path
CssDeclarationcss/src/css.rsRepresents a key: value pair, classified as either Static or Dynamic
CssPropertycss/src/props/property.rsAn enum representing a specific CSS property (e.g., LayoutWidth, StyleTextColor)
CssPropertyCachePtrcore/src/prop_cache.rsPointer to the centralized cache holding final computed values after inheritance and cascading

6. Font and Image Loading

Resource loading is tightly integrated with caching and cross-platform handling.

Font Loading

  1. Definition: Fonts are represented by FontRef, a reference-counted structure pointing to the parsed font data (ParsedFont).

  2. Resolution: The FontManager uses the operating system's Fontconfig cache (FcFontCache from rust_fontconfig) to resolve CSS font-family stacks into concrete font fallback chains.

  3. Loading: The bytes for required font IDs are loaded from disk and parsed using allsorts to extract font metrics (FontMetrics), glyph outlines, and shaping tables (GSUB/GPOS).

  4. Caching: Loaded fonts are inserted into the FontManager's internal cache for sharing and quick lookup by hash.

Image and SVG Loading

  • Azul provides Rust utilities for decoding and encoding images and handling SVG assets.
  • SVG files undergo parsing via resvg, simplification (usvg), and GPU-optimized vector triangulation (lyon) during the load process, with the resulting data stored in the ImageCache.
  • Images are referenced using unique keys (ImageKey) and managed by the RendererResources structure, which tracks GPU texture usage.

7. Rendering Pipeline: Display List to Screen

The actual rendering is handled by the high-performance, GPU-accelerated Mozilla WebRender engine.

  1. Display List Generation: After layout is complete, the LayoutWindow calls layout_and_generate_display_list() to produce a highly optimized, linear array of drawing primitives (DisplayList). This list contains items like Rect, Border, Text, Image, and PushStackingContext.

  2. WebRender Translation: This DisplayList is passed to the compositor2 module, which translates Azul's custom primitives into the formats understood by WebRender (e.g., converting text/font hashes into WebRender FontKey and FontInstanceKeys).

  3. Resource Synchronization: The translation process generates ResourceUpdate messages (AddFont, AddImage, etc.) required by WebRender's internal resource cache. These inform the GPU thread about new assets that need uploading.

  4. Transaction Submission: All display commands, resource updates, and scroll offsets are packaged into a WebRender Transaction. This transaction is sent from the application thread to the WebRender Render Backend thread.

  5. Scene Building & GPU Work: The Render Backend thread processes the transaction, builds the complex stacking context tree and scene primitives, and eventually signals the main thread via a notifier when the frame is ready in the backbuffer.

  6. Presentation: The main thread (typically triggered by a native OS event like WM_PAINT or drawRect:) commands the GPU to present the finalized frame by performing a buffer swap, making the computed scene visible on the screen. GPU-accelerated properties (opacity, transform) can update the frame efficiently without requiring a full layout pass.


8. Open Questions for New Maintainers

These are common questions that may require further exploration:

  1. How do I add a new CSS property? – The CssProperty enum and parsing logic need to be extended.

  2. Where are the platform entry points? – Look in dll/src/desktop/shell2/{platform}/mod.rs for each platform.

  3. How do IFrames and virtualization work? – See IFrameManager and the IFrameCallback system.

  4. When does solver3 vs taffy_bridge handle layout? – Block/Inline contexts use the custom engine; Flex/Grid containers delegate to Taffy.

  5. How are animations handled? – GPU-accelerated properties go through GpuStateManager; see also core/src/animation.rs.

  6. What is text3? – The third iteration of the text layout engine, supporting advanced typography features.

  7. How do I debug layout issues? – Check LayoutDebugMessage and the debug server in shell2/common/debug_server.rs.

  8. What's the testing strategy? – See tests/ directories in each crate and doc/src/reftest/ for visual regression tests.