GoFlow is a Flutter-inspired GUI framework for Go with built-in reactive state management through signals. Unlike Flutter, GoFlow simplifies state management by using signals instead of separate StatelessWidget and StatefulWidget classes.
- Signals-First: All state management is handled through the signals package
- Single Widget Type: No distinction between stateless and stateful widgets
- Declarative UI: Describe what you want, not how to build it
- Composition: Build complex UIs from simple, reusable widgets
- Reactive: UI automatically updates when signals change
┌──────────────────────────────────────┐
│ Application Layer │
│ (User code: MyApp, CounterApp, etc) │
├──────────────────────────────────────┤
│ Widget Layer │
│ (Text, Container, Column, etc) │
├──────────────────────────────────────┤
│ Framework Core │
│ (Widget, Element, BuildContext) │
├──────────────────────────────────────┤
│ Rendering Layer │
│ (RenderObject, Layout, Paint) │
├──────────────────────────────────────┤
│ Signals Layer │
│ (Reactive State Management) │
├──────────────────────────────────────┤
│ Platform Layer (Future) │
│ (WGPU, Window Management) │
└──────────────────────────────────────┘
Widgets are immutable descriptions of part of the UI.
type Widget interface {
Build(context BuildContext) Widget
CreateElement() Element
GetKey() Key
}Key Points:
- Widgets are configuration objects
- They describe what to show, not how to show it
- They are cheap to create and recreate
- A widget's Build() method returns another widget (or nil for leaf widgets)
Elements are mutable objects that manage the widget tree lifecycle.
type Element interface {
Mount(parent Element, slot interface{})
Update(newWidget Widget)
Unmount()
Rebuild()
GetRenderObject() RenderObject
// ... more methods
}Key Points:
- Elements live longer than widgets
- They manage state and the relationship between widgets and render objects
- One element per widget instance
- Elements form a tree parallel to the widget tree
RenderObjects handle layout, painting, and hit testing.
type RenderObject interface {
Layout(constraints *Constraints)
Paint(canvas Canvas, offset *Offset)
HitTest(position *Offset) bool
GetSize() *Size
// ... more methods
}Key Points:
- Do the actual layout calculations
- Perform painting to the canvas
- Handle hit testing for events
- Form a tree (subset of element tree)
Instead of StatefulWidget, use signals:
type CounterApp struct {
goflow.BaseWidget
counter *signals.Signal[int]
}
func NewCounterApp() *CounterApp {
return &CounterApp{
counter: signals.New(0),
}
}
func (a *CounterApp) Build(ctx goflow.BuildContext) goflow.Widget {
count := a.counter.Get() // Reactive!
return widgets.NewText(fmt.Sprintf("Count: %d", count))
}GoFlow maintains three parallel trees:
- Immutable configuration
- Created by calling Build() methods
- Cheap to recreate
- Example:
Text("Hello")→Container()→Column()
- Mutable objects
- Manages widget lifecycle
- Lives longer than widgets
- One-to-one with widget instances
- Example:
TextElement→ContainerElement→ColumnElement
- Subset of element tree
- Only elements with RenderObjects
- Performs layout and painting
- Example:
RenderText→RenderPadding→RenderColumn
GoFlow uses Flutter's box protocol for layout:
Parent passes constraints to children:
constraints := &Constraints{
MinWidth: 0,
MaxWidth: 800,
MinHeight: 0,
MaxHeight: 600,
}
child.Layout(constraints)Children return their size to parents:
func (r *RenderText) Layout(constraints *Constraints) {
size := r.ComputeSize(constraints)
r.SetSize(size)
}After layout, parents position children:
child.SetOffset(goflow.NewOffset(x, y))Widget.Build() → New Widget Tree → Element.Update()
- User code builds widget tree
- Framework updates element tree
- Minimal rebuilds (dirty tracking)
RenderObject.Layout() → Constraint System → Sizes Computed
- Constraints flow down
- Sizes flow up
- Parent positions children
RenderObject.Paint() → Canvas Commands → Visual Output
- Depth-first traversal
- Parent paints before children
- Respects clipping/transforms
// 1. User creates widget
app := &MyApp{}
// 2. RunApp builds element tree
goflow.RunApp(app)
→ app.CreateElement() → GenericElement
→ element.Mount(nil, nil)
→ element.Rebuild()
→ app.Build(ctx) → child widget
→ childElement.Mount(element, nil)
→ ... recursive build
// 3. Layout
→ findRenderObject(rootElement)
→ renderObject.Layout(constraints)
→ ... recursive layout
// 4. Paint
→ renderObject.Paint(canvas, offset)
→ ... recursive painttype TodoApp struct {
goflow.BaseWidget
todos *signals.SignalSlice[Todo]
}
func (a *TodoApp) Build(ctx goflow.BuildContext) goflow.Widget {
// Access signal - creates dependency
todoList := a.todos.Get()
return widgets.NewColumn(
// Build UI from signal data
buildTodoWidgets(todoList),
)
}// In a future version, elements could use effects:
dispose := signals.NewEffect(func() {
element.MarkNeedsBuild()
})GoFlow/
├── signals/ # Reactive state management
│ ├── signal.go
│ ├── computed.go
│ ├── effect.go
│ ├── batch.go
│ └── collections.go
├── goflow/ # Core framework
│ ├── widget.go # Widget interface
│ ├── element.go # Element system
│ ├── render_object.go # RenderObject system
│ ├── constraints.go # Layout constraints
│ ├── geometry.go # Size, Offset, Rect
│ ├── canvas.go # Painting abstraction
│ ├── key.go # Widget keys
│ └── app.go # RunApp entry point
├── widgets/ # Built-in widgets
│ ├── text.go
│ ├── container.go
│ ├── column.go
│ └── center.go
└── examples/ # Example apps
├── goflow-hello/
└── goflow-counter/
| Feature | Flutter | GoFlow |
|---|---|---|
| State Management | StatelessWidget + StatefulWidget | Single Widget + Signals |
| Language | Dart | Go |
| Build Method | Widget build(BuildContext) |
Widget Build(BuildContext) |
| State Updates | setState(() => ...) |
signal.Set(value) |
| Reactive | Through setState | Through signals |
| Hot Reload | Yes | Planned |
| Platform | Mobile, Web, Desktop | Desktop (planned) |
- No StatefulWidget: Use signals instead
- Explicit reactivity: Signal.Get() creates dependencies
- Go idioms: Interfaces, composition, explicit errors
- Simpler: One way to manage state (signals)
- Type-safe: Go's static typing + generics for signals
-
Platform Integration
- WGPU rendering backend
- Window management (GLFW)
- Event handling (mouse, keyboard)
-
More Widgets
- Row (horizontal flex)
- Stack (overlay)
- Gesture detection
- Input widgets
-
Advanced Features
- Hot reload
- Dev tools
- Performance profiling
- Animation system
-
Optimizations
- Render object reuse
- Layer caching
- Incremental layout
- Paint culling
- Flutter Architecture: https://flutter.dev/docs/resources/architectural-overview
- Signals Pattern: Preact Signals, Solid.js
- Box Layout: https://flutter.dev/docs/development/ui/layout
See CONTRIBUTING.md for development guidelines.
MIT License - see LICENSE file