wimpysworld/nix-config/using-guigui
Claude Code and agent skill by wimpysworld/nix-config
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Install
npx vskill@latest install wimpysworld/nix-config/using-guiguiSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
Using Guigui
Guigui is a GUI framework for Go on top of Ebitengine. Its design is inspired by immediate-mode GUIs, but it is not immediate-mode. Widgets are retained Go structs that you own and keep across ticks. The framework reconstructs their child tree and presentation when it runs Build again after a relevant state change. Compose and SwiftUI run a view function again in the same way. You keep the state in struct fields. The framework decides when to rebuild, lay out, and redraw.
Read this whole file before you write widget code. Most errors come from the lifecycle rules below.
Freshness. Upstream verified this guidance against Guigui at commitc1ffb301(2026-07-10).Guigui is alpha, and its API can change. If this guidance disagrees with the source, the source wins.Trust the*.gofiles in the module root and the programs underexample/over this file.Report drift to the user. Do not edit the installed skill.
References
All reference paths in this skill are relative to the skill root. Load a reference when the task needs it.
| Need | Reference | |
|---|---|---|
LinearLayout, a shared layout() helper, built-in widgets, wrappers, text input edits, custom list item colours | references/layout-and-widgets.md | |
Env, event keys, WidgetSlice, adding only the items in view, resetting cached widget state | references/env-events-and-lists.md | |
A keyless widget with a stale Draw, auditing a widget for missing state key coverage | references/repaint-audits.md |
The mental model
- A widget is a struct that embeds
guigui.DefaultWidgetand overrides the methods that it needs.
DefaultWidget supplies no-op defaults for every method, so override only the methods that matter.
- A widget must work from its zero value. Declare children as plain (non-pointer) fields and refer to them as
&w.child.
Do not allocate them in a constructor. var w Foo must be usable immediately.
- The framework runs two separate cycles.
- Each tick is one Ebitengine
Update, at the app's ticks per second (TPS). The default is 60 per second.
A tick first settles the tree. It can run Build (reconstruct the child tree), Layout (position the children), and the `Handle*Input` methods. Then it calls Tick exactly once.
- A state change during input can cause another Build and Layout pass in the same tick before
Tickruns. - Rely only on these guarantees: Build comes before Layout in a pass, input sees the current layout, and Tick runs once, after input handling.
Do not assume a fixed count of Build or Layout passes in each tick.
- Each frame is one Ebitengine
Draw, at the display refresh rate, and it runs Draw.
Ticks and frames are not one-to-one. There can be more or fewer frames than ticks. Put per-step logic in Tick, never in Draw.
- Never call the framework-driven methods of a widget yourself (
Build,Layout,Tick,Draw, theHandle*Inputmethods,Env, and others).
Set fields and register handlers, and the framework calls back. The one exception is `Measure`. A parent or composite widget calls the Measure of a child directly to size it. LinearLayout and the shared layout() helper do this.
Minimal application
guigui.Run(root, *RunOptions) starts the app. RunOptions holds Title, WindowSize, WindowMinSize, WindowMaxSize, WindowFloating, AppScale, and an optional RunGameOptions that goes to Ebitengine unchanged.
The Widget interface
Embed guigui.DefaultWidget, then override the methods that you need.
| Method | Signature | Override when | |
|---|---|---|---|
Build | Build(*Context, *ChildAdder) error | Almost always. Declare and configure children. | |
Layout | Layout(*Context, *WidgetBounds, *ChildLayouter) | The widget has children to position, which is almost always. | |
Measure | Measure(*Context, Constraints) image.Point | The widget has an intrinsic preferred size (list rows, leaf widgets). | |
HandlePointingInput | HandlePointingInput(*Context, *WidgetBounds) HandleInputResult | Custom mouse or touch handling. | |
HandleButtonInput | HandleButtonInput(*Context, *WidgetBounds) HandleInputResult | Custom keyboard or gamepad handling. Delivery needs focus or receptiveness (see "Handling input directly"). | |
Env | Env(*Context, EnvKey, *EnvSource) (any, bool) | The widget provides shared values to descendants. | |
WriteStateKey | WriteStateKey(*Context, *StateKeyWriter) | A state change outside input or events must trigger a rebuild (see below). | |
Tick | Tick(*Context, *WidgetBounds) error | Per-tick updates (animation, timers). It runs at the app's TPS. | |
Draw | Draw(*Context, *WidgetBounds, *ebiten.Image) | Custom rendering. Most widgets compose children instead. | |
CursorShape | CursorShape(*Context, *WidgetBounds) (ebiten.CursorShapeType, bool) | The widget wants a cursor that is not the default. |
Build and Layout: the most important rule
- `Build` runs before the framework knows any bounds.
Use it to add children (adder.AddWidget(&w.child)) and to push the current state into them (SetText, SetEnabled, handler registration). Build runs again on every rebuild, so it must be idempotent. Write it as "configure my children from my current state", not "do this once".
- The usual order in `Build` is add first, configure second. Make all the
adder.AddWidget(...)calls that you need. Then push state into the children withSet*andOn*.
Configuring a child that you did not add causes no harm. You do not need to put each setter behind the same condition as its AddWidget. Unconditional configuration is simple and idiomatic.
- Add only the children that this rebuild uses.
Do not call AddWidget for a child that is not visible now, for example the contents of a closed popup, a collapsed panel, or a hidden tab. The framework does not lay out, draw, or send input to a widget that is not added. This costs less than adding the widget and hiding it. popup is the canonical example. It adds its background, shadow, and content only while it opens or is open, but it still runs their setters unconditionally after that.
- `Layout` runs after `Build`, when the bounds are known.
Use it only to position children with layouter.LayoutWidget(child, bounds) or a layout helper. Do not read bounds in Build. Do not configure widget content in Layout.
widgetBounds.Bounds() gives the rectangle of this widget in Layout, input, Tick, and Draw. A WidgetBounds describes only the widget that it goes to. No API gives the bounds of another widget. When a parent must size or place a child relative to a sibling, do it through the layout (LinearLayout or layouter). Do not try to read the rectangle of the sibling.
widgetBounds.VisibleBounds() gives the part that remains after ancestor clipping. Use Bounds() for layout coordinates, and use VisibleBounds() to cull custom drawing. For ordinary cursor hit tests, use widgetBounds.IsHitAtCursor(). It includes clipping and occlusion by higher layers. Use VisibleBounds() as geometry only for an additional custom-shape check or for a point that is not the cursor. In Draw, dst is a clipped subimage that still uses screen coordinates. Draw at Bounds().Min or at other screen-space coordinates, not at an assumed (0, 0) origin.
State changes and when the screen updates
This is the second common source of errors. A tree or content change needs a rebuild. A paint-only change needs only a redraw.
Rebuilds occur when:
- an input handler or event handler runs (the framework assumes that the handler can change state),
- the state key of a widget changes (see
WriteStateKey), or - you call
guigui.RequestRebuild()explicitly.
A rebuild is always whole-tree, never scoped. Each of these triggers runs Build again on every widget from the root. There is no rebuild for one widget or one subtree. RequestRebuild() takes no argument. A changed state key also rebuilds the whole tree, not only the widget that owns the key. So put a WriteStateKey on the widget that owns the state that changes by itself. A different widget that renders from that state still runs its own Build again and shows the change. The key does not need to be on the widget that displays the state.
A rebuild runs `Build` again. It does not repaint by itself. Repainted pixels come from state key changes (these repaint the region of the changed widget automatically), from differences in the tree or layout, or from an explicit RequestRedraw. A bare RequestRebuild() adds no redraw region. For a paint-only change that alters the appearance of a widget, but not its bounds or a state key, also call guigui.RequestRedraw(widget).
So a counter that changes inside the OnUp of a button updates with no extra work. But a field that changes outside a handler will not repaint unless you do one of the steps below. Examples are a change from Tick, a goroutine result that the main goroutine applies, or an ancestor that reacts to an external model.
- Implement `WriteStateKey` to hash the state that must drive rebuilds.
The framework hashes again after each build and rebuilds when the hash changes:
``go func (w *Foo) WriteStateKey(context *guigui.Context, s *guigui.StateKeyWriter) { s.WriteInt(w.count) s.WriteBool(w.open) s.WriteString(w.title) } ``
StateKeyWriter has WriteBool, WriteInt and WriteUint (and sized variants), WriteFloat32/64, WriteString, WriteWidget, and raw Write. A widget that writes nothing opts out. This is the default.
- Call `guigui.RequestRebuild()` at the mutation site when the key mechanism cannot observe the change because of its structure.
The framework checks keys only for widgets that are in the tree now, so you need this call in these cases:
- The owning widget can be out of the tree when the change occurs.
The framework evaluates no key for a widget that the Build of its parent leaves out. A transition that must bring the widget back, for example reopening a closed popup, needs an explicit rebuild.
- The change has no owner widget. For example, app-level code changes a shared model that no widget hashes.
A key or generation counter on the root, or on the widget that owns the model, is usually the better fix.
- The state is too expensive to hash at every checkpoint, and a cheap generation counter is not possible.
RequestRebuild runs Build again but does not repaint by itself. When the appearance also changes in unchanged bounds, also call guigui.RequestRedraw(widget). For a change that is only paint, do not rebuild. Call RequestRedraw alone.
guigui.RequestRedraw(widget) forces a repaint without a rebuild. Use it only when the tree structure did not change, for example for an animation frame.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckinghome-manager/_mixins/agentic/assistants/skills/using-guigui/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/wimpysworld/nix-config/using-guigui)