This document explains the complete workflow of GoFlow from installation to rendering, showing how all the pieces fit together.
- Overview
- CLI Tool
- Framework Packages
- Project Structure
- Development Workflow
- Complete Execution Flow
- Rendering Pipeline
- State Management Flow
GoFlow consists of multiple interconnected components:
┌─────────────────────────────────────────────────────────┐
│ GoFlow Ecosystem │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────────┐ │
│ │ CLI Tool │────────▶│ Framework Core │ │
│ │ (cmd/goflow) │ creates │ (goflow/, │ │
│ │ │ projects│ signals/, │ │
│ └───────────────┘ using │ widgets/) │ │
│ │ └──────────────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌───────────────────────────────────┼───────────┐ │
│ │ Generated Project │ │ │
│ │ ┌──────────┐ ┌───────────────┐ │ │ │
│ │ │ lib/ │ │ Platform │◀┘ │ │
│ │ │ main.go │◀─│ Runners │ │ │
│ │ └──────────┘ │ (macos/etc) │ │ │
│ │ └───────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
The GoFlow CLI (cmd/goflow/main.go) is a command-line tool for creating and managing GoFlow projects.
go install github.com/base-go/GoFlow/cmd/goflow@latestThis compiles the CLI tool and installs the goflow binary to $GOPATH/bin.
- Templates Management: Embeds project templates using Go's
embed.FS - Project Generation: Creates directory structure and files
- Platform Support: Generates platform-specific runners
- Configuration: Creates
goflow.yaml,go.mod, and other configs
User runs: goflow create myapp
↓
Parse command-line arguments
↓
Select template (default, minimal, material)
↓
Create directory structure
↓
Generate files from templates:
- lib/main.go (app code)
- platform/main.go (runners)
- goflow.yaml (config)
- go.mod (dependencies)
↓
Initialize git repository
↓
Print success message
GoFlow framework consists of multiple Go packages:
github.com/base-go/GoFlow/
├── goflow/ # Core framework (Widget, Element, RenderObject)
├── signals/ # Reactive state management
└── widgets/ # Built-in widgets (Text, Container, etc.)
Your generated project imports these packages:
import (
"github.com/base-go/GoFlow/pkg/core/framework"
"github.com/base-go/GoFlow/pkg/core/signals"
"github.com/base-go/GoFlow/pkg/core/widgets"
)GoFlow is both:
- CLI tool: Creates projects
- Framework library: Provides APIs for building UIs
Think of it like Flutter:
fluttercommand creates projects- Flutter SDK provides widgets, state management, etc.
When you run goflow create myapp:
myapp/
├── lib/ # Your application code (package lib)
│ └── main.go # Defines MyappApp struct and Run() function
│
├── macos/ # macOS platform (package main)
│ └── main.go # Imports lib and calls lib.Run()
│
├── linux/ # Linux platform (package main)
│ └── main.go # Imports lib and calls lib.Run()
│
├── windows/ # Windows platform (package main)
│ └── main.go # Imports lib and calls lib.Run()
│
├── go.mod # Go module definition
├── goflow.yaml # GoFlow project config
└── README.md
┌─────────────────────┐
│ macos/main.go │ package main
│ │
│ import "com.ex/ │
│ myapp/lib" │
│ │
│ func main() { │
│ lib.Run() │──┐
│ } │ │
└─────────────────────┘ │
│
▼
┌─────────────────────┐
│ lib/main.go │ package lib
│ │
│ type MyappApp │
│ struct {...} │
│ │
│ func Run() { │
│ app := New...() │
│ goflow.RunApp() │
│ } │
└─────────────────────┘
Key Point: Platform runners are entry points that call into shared lib/ code.
go install github.com/base-go/GoFlow/cmd/goflow@latestWhat happens:
- Go downloads GoFlow source
- Compiles
cmd/goflow/main.go - Installs binary to
$GOPATH/bin/goflow
goflow create myappWhat happens:
- CLI creates
myapp/directory - Generates all files from embedded templates
- Creates
go.modwith module pathcom.example/myapp - Initializes git repository
cd myapp
echo "replace github.com/base-go/GoFlow => /path/to/GoFlow" >> go.mod
go mod tidyWhat happens:
- Points to local GoFlow development copy
- Downloads and caches dependencies
- Creates
go.sumchecksum file
In production: Users would skip the replace directive and just run go mod tidy.
cd macos # or windows, or linux
go run main.goWhat happens:
- Go compiles
macos/main.go - Imports
com.example/myapp/lib - Calls
lib.Run() lib.Run()callsgoflow.RunApp(app)- GoFlow builds widget tree
- Prints output (currently no real rendering)
┌─────────────────────────────────────────────────────────────┐
│ 1. USER ACTION │
└─────────────────────────────────────────────────────────────┘
│
│ $ cd myapp/macos && go run main.go
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. PLATFORM RUNNER (macos/main.go) │
│ │
│ package main │
│ │
│ import "com.example/myapp/lib" │
│ │
│ func main() { │
│ // TODO: Initialize GLFW window │
│ // TODO: Initialize WGPU rendering │
│ // TODO: Set up event loop │
│ │
│ lib.Run() ◄─── Calls into shared lib │
│ } │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. APPLICATION CODE (lib/main.go) │
│ │
│ package lib │
│ │
│ type MyappApp struct { │
│ goflow.BaseWidget │
│ counter *signals.Signal[int] │
│ } │
│ │
│ func (app *MyappApp) Build(ctx BuildContext) Widget { │
│ count := app.counter.Get() // Reactive! │
│ return widgets.NewCenter(...) │
│ } │
│ │
│ func Run() { │
│ app := NewMyappApp() │
│ goflow.RunApp(app) ◄─── Enters framework │
│ } │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. GOFLOW FRAMEWORK (goflow.RunApp) │
│ │
│ func RunApp(app Widget) { │
│ // 1. Create root element │
│ element := app.CreateElement() │
│ │
│ // 2. Mount element tree │
│ element.Mount(nil, nil) │
│ │
│ // 3. Build widget tree │
│ element.Rebuild() │
│ └─▶ app.Build(ctx) ──┐ │
│ │ │
│ // 4. Layout render tree │ │
│ renderObject.Layout(...)─┤ │
│ │ │
│ // 5. Paint to canvas │ │
│ renderObject.Paint(...)──┘ │
│ } │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. WIDGET TREE (app.Build returns) │
│ │
│ Center │
│ └─ Column │
│ ├─ Text("Welcome") │
│ ├─ Container (spacer) │
│ └─ Text("Counter: 5") │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. ELEMENT TREE (parallel to widgets) │
│ │
│ CenterElement │
│ └─ ColumnElement │
│ ├─ TextElement │
│ ├─ ContainerElement │
│ └─ TextElement │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. RENDER TREE (subset with RenderObjects) │
│ │
│ RenderCenter │
│ └─ RenderColumn │
│ ├─ RenderText("Welcome") │
│ ├─ RenderPadding (spacer) │
│ └─ RenderText("Counter: 5") │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 8. LAYOUT PHASE │
│ │
│ RenderCenter.Layout(constraints) │
│ ├─ Passes constraints to child │
│ ▼ │
│ RenderColumn.Layout(constraints) │
│ ├─ Measures children │
│ ├─ Computes total height │
│ ├─ Returns size to parent │
│ ▼ │
│ Each child computes its size │
│ └─ Sizes bubble up │
│ ▲─ Constraints flow down │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 9. PAINT PHASE │
│ │
│ RenderCenter.Paint(canvas, offset) │
│ └─ Centers child, calls child.Paint() │
│ ▼ │
│ RenderColumn.Paint(canvas, offset) │
│ └─ Paints each child at computed position │
│ ├─ RenderText.Paint() → canvas.DrawText() │
│ ├─ RenderPadding.Paint() → (nothing) │
│ └─ RenderText.Paint() → canvas.DrawText() │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 10. CANVAS INTERFACE │
│ │
│ type Canvas interface { │
│ DrawRect(rect, paint) │
│ DrawText(text, offset, style) │
│ DrawCircle(center, radius, paint) │
│ // ... transform operations │
│ } │
│ │
│ Current: MockCanvas (testing only) │
│ Future: NativeCanvas (Core Graphics, Direct2D, Cairo) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 11. RENDERING BACKEND (Future) │
│ │
│ macOS: Core Graphics → GPU → Screen │
│ Windows: Direct2D → GPU → Screen │
│ Linux: Cairo → GPU → Screen │
│ │
│ OR: WGPU → Metal/DirectX/Vulkan → Screen │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 12. PIXELS ON SCREEN! 🎨 │
└─────────────────────────────────────────────────────────────┘
Widget Tree → Element Tree → RenderObject Tree → Canvas Interface
↓
MockCanvas
↓
(No actual rendering)
Widget Tree → Element Tree → RenderObject Tree → Canvas Interface
↓
┌─────────────┴──────────────┐
▼ ▼
macOS/Windows/Linux Native APIs
↓ ↓
CoreGraphicsCanvas Core Graphics
Direct2DCanvas OR Direct2D
CairoCanvas Cairo
↓ ↓
Platform GPU APIs Metal
↓ DirectX
Screen Vulkan
Widget Tree → Element Tree → RenderObject Tree → Canvas Interface
↓
WGPUCanvas
↓
wgpu-native
↓
┌─────────────┴──────────────┐
▼ ▼
Metal (macOS) DirectX (Windows)
↓ ↓
Screen Screen
▼
Vulkan (Linux)
↓
Screen
See RENDERING.md for detailed rendering architecture.
┌─────────────────────────────────────────────────────────────┐
│ 1. SIGNAL CREATION │
│ │
│ counter := signals.New(0) │
│ │
│ // Creates reactive container with: │
│ // - Value: 0 │
│ // - Subscribers: [] │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. BUILD PHASE (Creates Dependency) │
│ │
│ func (app *MyApp) Build(ctx BuildContext) Widget { │
│ count := app.counter.Get() ◄─ Reading signal │
│ // creates dependency │
│ return widgets.NewText(fmt.Sprintf("%d", count)) │
│ } │
│ │
│ After .Get(): │
│ counter.subscribers = [Build function] │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. STATE UPDATE │
│ │
│ counter.Set(5) │
│ ↓ │
│ counter.value = 5 │
│ ↓ │
│ Notify all subscribers │
│ ↓ │
│ For each subscriber: │
│ element.MarkNeedsBuild() ◄─ Mark for rebuild │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. REBUILD PHASE │
│ │
│ element.Rebuild() │
│ ↓ │
│ newWidget = app.Build(ctx) ◄─ Calls Build again │
│ ↓ │
│ count := counter.Get() // Now returns 5 │
│ ↓ │
│ element.Update(newWidget) │
│ ↓ │
│ Re-layout and re-paint if needed │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. UI UPDATES │
│ │
│ Screen shows new value: "Counter: 5" │
└─────────────────────────────────────────────────────────────┘
counter := signals.New(0)
doubled := signals.NewComputed(func() int {
return counter.Get() * 2 ◄─ Creates dependency
})
// Dependency graph:
counter ──▶ doubled ──▶ Build()
│
└──────────────────▶ Build() (if used directly)
// When counter.Set(5):
counter changes → doubled recomputes → Build() reruns → UI updates
dispose := signals.NewEffect(func() {
count := counter.Get() ◄─ Creates dependency
fmt.Printf("Count: %d\n", count)
})
// When counter.Set(5):
counter changes → effect reruns → prints "Count: 5"
// Cleanup:
defer dispose() // Removes subscription
Your Machine:
│
├─ GoFlow Source (/path/to/GoFlow/)
│ ├─ cmd/goflow/ ◄─ CLI tool source
│ ├─ goflow/ ◄─ Framework package
│ ├─ signals/ ◄─ Signals package
│ └─ widgets/ ◄─ Widgets package
│
├─ $GOPATH/bin/
│ └─ goflow ◄─ Compiled CLI binary
│
└─ Your Project (myapp/)
├─ lib/main.go ◄─ Imports GoFlow packages
├─ macos/main.go ◄─ Imports lib/
└─ go.mod ◄─ Points to GoFlow
└─ replace directive to /path/to/GoFlow/
1. User runs: cd myapp/macos && go run main.go
↓
2. Go compiles macos/main.go
↓
3. Resolves import: com.example/myapp/lib
↓
4. Resolves imports in lib/main.go:
- github.com/base-go/GoFlow/pkg/core/framework
- github.com/base-go/GoFlow/pkg/core/signals
- github.com/base-go/GoFlow/pkg/core/widgets
↓
5. Uses replace directive in go.mod
→ Loads from /path/to/GoFlow/
↓
6. Compiles everything into single binary
↓
7. Executes: main.main() → lib.Run() → goflow.RunApp()
↓
8. Builds widget tree, creates elements, renders
- CLI Tool (
goflow) creates projects with proper structure - Framework Packages (
goflow/,signals/,widgets/) provide APIs - Platform Runners bootstrap the app and call into
lib/ - Shared Code in
lib/is cross-platform - Three Trees (Widget → Element → RenderObject) power the UI
- Signals provide reactive state management
- Canvas Interface abstracts rendering backend
- Native Backends (future) render to actual pixels
goflow create myapp
↓
Edit lib/main.go (your app)
↓
cd platform && go run main.go
↓
Platform runner → lib.Run() → goflow.RunApp()
↓
Widget Tree → Element Tree → RenderObject Tree
↓
Layout → Paint → Canvas
↓
(Future) Native Backend → GPU → Screen
- Getting Started Guide - Step-by-step tutorial
- Architecture - Deep dive into three-tree system
- Rendering Architecture - Native vs WGPU backends
- CLI Reference - Complete CLI documentation
Now you understand the complete GoFlow workflow! 🎉