A Window Layout System for Fidelity
Our Fidelity framework builds desktop applications with our Clef language, targeting native user interfaces across multiple platforms while keeping the functional programming model. The layout system is one hard part of that: it has to stay functional and still give developers the panel, grid, and stacking primitives that established UI frameworks offer.
This article describes how Fidelity can draw ideas from modern functional UI frameworks to build a pure Clef implementation of a window layout system without external dependencies, relying on Clef native code and the integrated low level LVGL and Skia libraries. We also examine how Fidelity is designed to address UI process prioritization through its compiler pipeline, which matters to developers familiar with platforms that use a modernized WPF design.
Note for .NET Developers: Unlike .NET frameworks that use concepts of threads and thread pools, Fidelity’s native compilation architecture uses a different approach to concurrency. Rather than thinking about “UI threads” and “background threads,” Fidelity operates with specialized processes and task scheduling. This article will explain the conceptual transition from the threading model to Fidelity’s process-based architecture.
Key Insights from Modern Functional UI Frameworks
Modern functional UI frameworks like WPF, Uno, and MAUI (as well as functional wrappers like Avalonia.FuncUI and Elmish.Uno) provide several valuable insights for Fidelity’s layout system:
- Functional DSL for UI Definition: A domain-specific language that makes UI creation declarative and composable
- Virtual DOM for Efficient Updates: A mechanism to minimize UI updates by comparing virtual representations
- Type-Safe Attributes: Strong typing for UI properties that catches errors at compile time
- MVU (Model-View-Update) Integration: Clean separation of state, view, and update logic
- Component System: Reusable, composable UI elements
Each of these can be adapted to Fidelity’s pure Clef approach with LVGL and Skia integration.
Core Layout Model for Fidelity
Panel-Based Layout System
Similar to Avalonia.FuncUI and Elmish.Uno (which borrowed from WPF), Fidelity can implement a panel-based layout system with panels like:
// Core panels in Fidelity
type Panel =
| Grid of GridProperties
| StackPanel of StackPanelProperties
| Canvas of CanvasProperties
| DockPanel of DockPanelProperties
| WrapPanel of WrapPanelPropertiesEach panel would have specific layout properties and behavior. For example, a Grid would define rows and columns, while a StackPanel would stack elements vertically or horizontally.
Layout Properties
Layout properties control how elements are positioned within panels:
// Example of layout properties for Grid
type GridProperties = {
RowDefinitions: RowDefinition list
ColumnDefinitions: ColumnDefinition list
}
type RowDefinition =
| Auto
| Pixel of float
| Star of float
type ColumnDefinition =
| Auto
| Pixel of float
| Star of float
// Attached properties for Grid children
type GridChildProperties = {
Row: int
Column: int
RowSpan: int
ColumnSpan: int
}These properties would be applied to controls to determine their position and size within the parent container.
Pure Clef DSL for Layout
One of Avalonia.FuncUI’s strengths is its DSL for defining UI elements. Fidelity aims to create a similar DSL, but with a pure Clef implementation that targets LVGL and Skia:
module Fidelity.UI
let window title content =
Window.create [
Window.title title
Window.content content
]
let grid rows columns children =
Grid.create [
Grid.rowDefinitions rows
Grid.columnDefinitions columns
Grid.children children
]
let stackPanel orientation children =
StackPanel.create [
StackPanel.orientation orientation
StackPanel.children children
]
// Usage example
let mainView =
window "Hello Fidelity" (
grid
[ RowDefinition.Auto; RowDefinition.Star ]
[ ColumnDefinition.Star ]
[
TextBlock.create [
Grid.row 0
TextBlock.text "Welcome to Fidelity"
]
StackPanel.create [
Grid.row 1
StackPanel.orientation Orientation.Vertical
StackPanel.children [
Button.create [ Button.content "Click me" ]
TextBox.create [ TextBox.watermark "Enter text..." ]
]
]
]
)Process-Aware Attribute System
Fidelity’s attribute system is designed to enhance the approach seen in modern functional UI frameworks with compile-time processing context:
type IAttr<'t> =
interface
abstract ProcessContext: ProcessContext
end
module AttrBuilder =
let createProperty<'value> name value processContext =
{ new IAttr<'t> with
member _.ProcessContext = processContext }
// For events - always require UI process
let createEvent name handler =
{ new IAttr<'t> with
member _.ProcessContext = ProcessContext.UIProcess }
module Button =
let create attrs = ViewBuilder.Create<Button>(attrs)
// Property can be set from any process
let content value =
AttrBuilder.createProperty "Content" value ProcessContext.AnyProcess
// Event handlers must be UI process
let onClick handler =
AttrBuilder.createEvent "Click" handlerThis process-aware attribute system is designed to let the compiler verify processing safety at compile time, removing the need for runtime checks while keeping a familiar programming model for developers coming from traditional .NET and Fable UI frameworks.
Compiler Integration and Process Coordination
Traditional UI frameworks handle thread marshaling at runtime. Fidelity is designed to address process coordination through its compilation pipeline instead, which would remove runtime overhead while coordinating UI operations and background tasks by construction.
Compiler-Aware Process Boundaries
The Fidelity compiler is designed to analyze code to detect UI operations versus background processing:
// The compiler recognizes UI operations that must happen in the LVGL process
let view model dispatch =
window "Example" (
grid [ Auto; Star ] [ Star ] [
button "Load Data" [ onClick (fun _ -> dispatch LoadData) ]
textBlock $"Items: {model.Items.Length}"
]
)
// The compiler identifies background processing
let loadData = coldStream {
let! data = fetchFromDatabase()
// Process boundary crossing detected here - compiler inserts coordination
do! Timeline.dispatchOnUI (DataLoaded data)
}For developers familiar with traditional WPF-style frameworks, this approach parallels how dispatcher mechanisms work, but instead of runtime checks, the Fidelity compiler statically analyzes the code and inserts appropriate process coordination where needed.
Layout System and Process Context
During compilation, the layout description is transformed into platform-specific LVGL code with automatic process context awareness:
// High-level layout code written by developer
let mainView =
window "Hello Fidelity" (
grid [ Auto; Star ] [ Star ] [
TextBlock.create [ Grid.row 0; text "Welcome" ]
Button.create [ Grid.row 1; content "Click"; onClick handleClick ]
]
)
// Simplified representation of compiler-generated LVGL code
let compiledMainView() =
// Process context ensured automatically
LVGL.ensureUIProcess()
// Layout instantiation with platform-specific optimizations
let container = lv_obj_create(null)
let grid = lv_obj_create(container)
lv_obj_set_layout(grid, LV_LAYOUT_GRID)
// Row and column setup
lv_grid_set_row_dsc(grid, [| LV_GRID_CONTENT; LV_GRID_FR(1) |])
lv_grid_set_col_dsc(grid, [| LV_GRID_FR(1) |])
// Elements with automatic process-safety
let btn = lv_btn_create(grid)
lv_obj_add_event_cb(btn, compileHandleClick, LV_EVENT_CLICKED, null)This compilation approach provides several advantages over runtime process coordination:
- Zero Runtime Overhead: No need to check process context at runtime
- Compile-Time Safety: Process boundary violations are caught during compilation
- Platform-Specific Optimization: Process coordination adapts to target platform capabilities
Platform-Adaptive Process Models
The compiler generates different process coordination code based on the target platform:
// Generated code for cooperative task scheduling (embedded systems)
// Process safety through task scheduling
let dispatchToUIProcess action =
LVGL.Task.scheduleOnNextTick action
// Generated code for preemptive processing (desktop systems)
// Process safety through message passing
let dispatchToUIProcess action =
if Process.currentId() = uiProcessId then
action()
else
uiProcessQueue.enqueue action
uiProcessEvent.signal()This would allow the same source code to work efficiently across the entire computing spectrum while maintaining proper process coordination.
Virtual DOM with Process Awareness
Many modern functional UI frameworks employ a virtual DOM approach to minimize UI updates. Fidelity aims to implement a similar system, but with process-awareness built into the diffing and patching process:
// Virtual DOM types with process context awareness
type IView = interface
abstract ViewType: Type
abstract Attrs: IAttr list
abstract ProcessContext: ProcessContext // Process requirements
end
type ProcessContext =
| UIProcess // Must run in UI process
| AnyProcess // Can run in any process
| WorkerProcess // Should run in background process
// Process-aware differ to detect changes
module VirtualDom =
let diff (oldView: IView option) (newView: IView option) =
// Compare old and new views to generate minimal updates
// Also track process context of each change
let patch (control: Control) (diffs: Diff list) =
// Apply minimal updates to actual UI elements
// Ensure each update runs in the appropriate process
let updateRoot (host: Control, oldView: IView option, newView: IView option) =
// Compiler inserts process context verification
LVGL.ensureUIProcess()
let diffs = diff oldView newView
patch host diffsWhere traditional virtual DOM implementations check process context at runtime, our Fidelity virtual DOM is designed to carry process awareness at compile time, which would remove the need for those runtime checks. The update mechanism stays similar, with the process analysis moved into compilation.
MVU Integration with Process-Aware Compiler
The Elmish Model-View-Update (MVU) pattern in Fidelity could benefit from compiler-assisted process coordination. Where the runtime approach is common in many WPF-inspired frameworks, our Fidelity compiler would establish process safety statically:
// traditional runtime process safety, handled in a dispatcher
let runWithSyncDispatch (arg: 'arg) (program : Program<'arg, 'model, 'msg, #IView>) =
// Syncs view changes from non-UI processes through a dispatcher
let syncDispatch (dispatch: Dispatch<'msg>) (msg: 'msg) =
if Dispatcher.UIThread.CheckAccess() // Runtime thread check
then dispatch msg
else Dispatcher.UIThread.Post (fun () -> dispatch msg)
Program.runWithDispatch syncDispatch arg program
// In Fidelity, the compiler automatically inserts process coordination
// without runtime overhead
let elmishApp =
{ init = init
update = update // Compiler analyzes update function
view = view } // View always runs in the UI processThis provides a familiar programming model but with improved performance and safety characteristics. The compiler is designed to ensure:
- View Runs in UI Process: All view code runs in the LVGL process
- Update Process Safety: Background operations in update are properly coordinated
- Command Process Context: Commands return to the UI process when needed
Timeline State Management with Process Awareness
Our Timeline reactive system is designed to extend the MVU pattern with built-in process awareness:
// Timeline signals automatically manage process context
let counterApp = elmishApp {
// Model is a timeline signal
let! model = Timeline.signal { Count = 0 }
// Update function with background operations
let update msg model =
match msg with
| Increment ->
{ model with Count = model.Count + 1 }
| LoadData ->
// Compiler detects background operation
Frosty.start (
coldStream {
let! data = fetchData() // Background process
// Compiler inserts process coordination here
do! Timeline.dispatchOnUI (DataLoaded data)
return data
}
)
model // Return current model while loading
| DataLoaded data ->
// This handler always runs in UI process
{ model with Data = data }
// View is always executed in UI process
let view model dispatch =
window "Counter" (
stackPanel [
textBlock $"{model.Count}"
button "+" [ onClick (fun _ -> dispatch Increment) ]
button "Load" [ onClick (fun _ -> dispatch LoadData) ]
]
)
return { Model = model; Update = update; View = view }
}This approach provides several advantages over runtime dispatching mechanisms in traditional frameworks:
- No Runtime Process Checks: Eliminates overhead of access checks
- No Accidental UI Process Violations: Compile-time errors prevent process coordination mistakes
- Platform-Specific Optimization: Process handling adapts to platform capabilities
Implementing Layout Measurement and Arrangement
A critical aspect of any layout system is the measurement and arrangement of elements. Fidelity aims to implement this with process-aware pure Clef:
// Layout process with process awareness
type LayoutContext = {
AvailableSize: Size
ScaleFactor: float
ProcessContext: ProcessContext // Process context information
}
// ILayoutable interface for elements that participate in layout
type ILayoutable =
abstract Measure: LayoutContext -> Size
abstract Arrange: Rect -> unit
abstract ProcessRequirement: ProcessContext // Process requirement
// Example implementation for StackPanel with process awareness
type StackPanel() =
// Process requirement - layout operations must run in UI process
member this.ProcessRequirement = ProcessContext.UIProcess
interface ILayoutable with
member this.Measure context =
// Compiler inserts process verification
LVGL.ensureUIProcess()
let mutable totalSize = Size(0, 0)
for child in this.Children do
let childSize = child.Measure(context)
if this.Orientation = Orientation.Vertical then
totalSize.Width <- max totalSize.Width childSize.Width
totalSize.Height <- totalSize.Height + childSize.Height
else
totalSize.Width <- totalSize.Width + childSize.Width
totalSize.Height <- max totalSize.Height childSize.Height
totalSize
member this.Arrange rect =
// Compiler inserts process verification
LVGL.ensureUIProcess()
let mutable offset = 0.0
for child in this.Children do
let childSize = child.DesiredSize
let childRect =
if this.Orientation = Orientation.Vertical then
Rect(rect.X, rect.Y + offset, rect.Width, childSize.Height)
else
Rect(rect.X + offset, rect.Y, childSize.Width, rect.Height)
child.Arrange(childRect)
if this.Orientation = Orientation.Vertical then
offset <- offset + childSize.Height
else
offset <- offset + childSize.Width
member this.ProcessRequirement = ProcessContext.UIProcessThis process-aware layout system would ensure that all layout operations happen in the appropriate process, with compile-time verification.
LVGL-Skia Rendering Integration
While many modern UI frameworks have their own rendering engines, Fidelity uses a hybrid approach with LVGL for UI components and Skia for custom rendering:
// Process-aware LVGL component wrapper
type LVGLControl = {
Id: int
Type: LVGLControlType
Properties: Map<string, obj>
Children: LVGLControl list
ProcessContext: ProcessContext // Process context information
}
// Process-aware converter from Fidelity view to LVGL
let toNativeLVGL (view: IView) : LVGLControl =
// Compiler ensures this runs in the UI process
LVGL.ensureUIProcess()
// Convert Fidelity view to LVGL control structure
// Process-aware Skia renderer for custom drawing
type SkiaCanvas = {
Surface: SkiaSurface
DrawOperations: DrawOperation list
ProcessContext: ProcessContext // Process context information
}
// Shared buffer for LVGL and Skia with process coordination
let createSharedRenderBuffer width height =
// Compiler ensures this runs in the UI process
LVGL.ensureUIProcess()
// Allocate buffer for both LVGL and Skia to use
// with proper process coordinationThis process-aware hybrid approach is designed to let Fidelity use both libraries while keeping process safety through compile-time verification.
Conclusion
By drawing from modern UI frameworks while focusing on LVGL and Skia integration, Fidelity is designed to provide a pure Clef layout system that pairs Clef’s functional model with the performance and native feel of platform-specific implementations.
The advantages of this approach include:
- Functional Purity: A fully functional approach to UI development that fits naturally with Clef
- Cross-Platform Consistency: The same programming model across all platforms
- Compile-Time Process Safety: Process coordination violations caught at compile time rather than runtime
- Performance: Direct compilation to native code with minimal process coordination overhead
- Type Safety: Strong typing that catches errors at compile time
- Composability: Reusable, composable components that follow functional programming principles
For developers familiar with traditional WPF-style frameworks and their functional variants, Fidelity is designed to keep a familiar programming model while moving process coordination to compile time. That coordination would happen during compilation rather than at runtime, removing the overhead of runtime checks while keeping the structural safety the compiler can verify.
From Threads to Processes: A Conceptual Bridge for .NET Developers
For developers coming from .NET platforms like WPF, Uno, or MAUI, it’s important to understand that Fidelity’s native compilation architecture operates differently from .NET’s threading model. In .NET, UI operations must happen on a specific UI thread, with background work delegated to thread pools.
In Fidelity’s native compiled architecture:
- Instead of “threads,” Fidelity deals with specialized processes and tasks
- Rather than thread marshaling, Fidelity uses process coordination and message passing
- The compiler, not the runtime, enforces process boundaries
- Async operations are handled through task scheduling rather than thread management
This conceptual shift provides significant performance advantages while maintaining the familiar programming model that .NET and Fable developers expect.
We want our Fidelity layout system to draw the ideas worth keeping from existing functional UI frameworks while staying a pure Clef implementation over libraries like LVGL and Skia, with process boundaries checked during compilation. That is the direction we will keep building toward as the rest of the framework comes into place.