Welcome! If you're here, you're about to discover one of the most enjoyable ways to build desktop apps. v-gui combines the simplicity of V with a refreshingly straightforward approach to UI development.
No boilerplate. No ceremony. Just describe what you want and watch it appear.
import gui
fn main() {
mut window := gui.window(
width: 300
height: 200
on_init: fn (mut w gui.Window) {
w.update_view(fn (window &gui.Window) gui.View {
return gui.text(text: 'Hello, v-gui!')
})
}
)
window.run()
}That's a complete, runnable app. Run it with v run hello.v and you've got a window with text.
v-gui follows one simple principle: your UI is just a function of your data.
State ---> View Function ---> Screen
(data) (you write) (v-gui does)
When something changes—a button click, new data arrives, user types—v-gui calls your view function again. You return what the UI should look like now. v-gui handles the rest.
No manual updates. No "set this label's text." No keeping track of what changed. Just return the current view based on current state.
Let's look at a proper app. Check out examples/get_started.v for the full code—here's the
essence:
// Your data
@[heap]
struct App {
pub mut:
clicks int
}
// Your view function
fn main_view(window &gui.Window) gui.View {
app := window.state[App]()
return gui.column(
h_align: .center
v_align: .middle
content: [
gui.text(text: 'Welcome to GUI'),
gui.button(
content: [gui.text(text: '${app.clicks} Clicks')]
on_click: fn (_, _, mut w gui.Window) {
mut app := w.state[App]()
app.clicks += 1
}
),
]
)
}Click the button → clicks increases → v-gui calls main_view again → button shows new
count. The loop handles itself.
Your app's data lives in a struct. Mark it @[heap] so it sticks around:
@[heap]
struct TodoApp {
pub mut:
items []string
input_text string
}Access it in your view with window.state[TodoApp](). Mutate it in event handlers with
window.state[TodoApp]() (yes, same call—V handles the mutability).
v-gui uses rows and columns for layout. Nest them to build any structure:
gui.column(content: [ // Vertical stack
gui.row(content: [ // Horizontal stack inside
gui.text(text: 'Left'),
gui.text(text: 'Right'),
]),
gui.text(text: 'Below'),
])┌─────────────────────────┐
│ ┌──────┐ ┌───────┐ │
│ │ Left │ │ Right │ │ ← row
│ └──────┘ └───────┘ │
├─────────────────────────┤
│ ┌───────────────────┐ │
│ │ Below │ │ ← text
│ └───────────────────┘ │
└─────────────────────────┘
↑
column
Every widget can size itself three ways per axis:
| Mode | Behavior |
|---|---|
fit |
Shrink to content |
fill |
Expand to available space |
fixed |
Use exact pixel value |
Combine them: sizing: gui.fit_fill means fit width, fill height.
Center things easily:
gui.column(
h_align: .center // Horizontal: .left, .center, .right
v_align: .middle // Vertical: .top, .middle, .bottom
content: [
gui.text(text: 'Centered!')
]
)Switch looks instantly:
window.set_theme(gui.theme_dark) // Clean dark
window.set_theme(gui.theme_light) // Clean light
window.set_theme(gui.theme_dark_bordered) // Dark with borders
window.set_theme(gui.theme_gruvbox_dark) // Retro vibes
window.set_theme(gui.theme_ocean_light) // Calm bluesOr build your own with the Theme Generator in the showcase.
v-gui comes with everything you need:
| Category | Widgets |
|---|---|
| Text | text, input, textarea |
| Buttons | button, toggle, switch, checkbox, radio |
| Selection | select, dropdown, listbox |
| Data | table, tree, markdown |
| Graphics | image, svg |
| Layout | row, column, container, scroll, canvas |
| Feedback | progress_bar, pulsar, tooltip, dialog |
| Navigation | menu, menubar, tabs, splitter |
All follow the same pattern: call the function, set some options, done.
Windows native support is being validated. Treat it as a native smoke-tested path, not a final support claim yet.
- Use the MSVC/vcpkg path in
WINDOWS.mdforvglyph, Pango and Freetype setup/preflight. - Use
WINDOWS_MANUAL_SMOKE.mdfor manual native Windows validation of dialogs, notifications, printing and D3D11 readback. - MSYS2/GCC, WSL and Wine are diagnostic aids only until native Windows smoke proves them as supported user paths.
No widget objects to manage. Traditional frameworks make you create button objects, store references, call methods on them later. v-gui? Just return what you want. Every time.
No data binding. Other frameworks need you to wire data to UI elements. Here, you read state and return views. That's the binding.
No threading headaches. Change state from anywhere. v-gui runs the view function at the right time, on the right thread.
Fast enough to not think about. Layout runs in microseconds. Thousands of elements? No problem. Regenerate the whole UI sixty times per second if you want.
The examples/ folder is full of working apps demonstrating every feature:
# The basics
v run examples/get_started.v
# See all widgets
v run examples/buttons.v
v run examples/inputs.v
v run examples/containers.v
# Cool stuff
v run examples/animations.v # Tweens, springs, hero transitions
v run examples/svg_demo.v # Vector icon rendering
v run examples/tiger.v # Complex SVG (Ghostscript Tiger)
v run examples/markdown.v # Markdown rendering
v run examples/numeric_input.v # Locale-aware numeric input + stepping
v run examples/dialogs.v # Custom + native dialogs
v run examples/text_transform.v # Rotated and affine text
v run examples/table_demo.v # Table widget demo
v run examples/snake.v # Yes, it's a gameRead the code. It's short. That's the point.
examples/get_started.v- Fully commented starter appARCHITECTURE.md- How v-gui works under the hoodLAYOUT_ALGORITHM.md- Deep dive into sizing and positioningANIMATIONS.md- Bring your UI to lifeMARKDOWN.md- Markdown renderingTABLES.md- Table widget and data display
That's it. You now know enough to build real apps. The best way to learn more is to open an example, change something, and see what happens. v-gui gets out of your way so you can focus on what matters: making something useful.
Happy building!