https://github.com/dborth/libgui (Under GPL License)
API documentation · Changelog · Latest build
libgui is a GUI library for the GameCube, Wii, and Wii U created to help structure the design of a complicated GUI interface, and to enable an author to create a sophisticated, feature-rich GUI. It was originally conceived and written after I started to design a GUI for Snes9x GX, and found libwiisprite and GRRLIB inadequate for the purpose.
libgui powers the GUI of several Nintendo homebrew emulators, including
Snes9x GX, FCE Ultra GX, and Visual Boy Advance GX. It is a single,
shared codebase that targets three different consoles from one source
tree - GameCube and Wii share one driver set, and Wii U runs on modern
GX2/GPU hardware through an entirely separate one, but the GuiElement/
GuiWindow/GuiButton/etc. class hierarchy your app is built from is
identical on all three. It was designed to be flexible and is easy to
modify - don't be afraid to change the way it works or expand it to suit
your GUI's purposes! If you do, and you think your changes might benefit
others, please share them so they might be added to the project!
- Three platforms, one codebase. GameCube, Wii, and Wii U builds all
compile from the same
source/libguiand application code - no#ifdefs in your GUI logic, no per-platform art or asset variants. - A real hardware abstraction layer. Every platform touchpoint (video, audio, input, storage, threading, logging) sits behind a small set of abstract driver interfaces (see Architecture below), so the core library never includes a platform header.
- A full widget set. Buttons, images, text, an on-screen keyboard, paged option and file browsers, windows/containers with focus and selection navigation, and a trigger-based input-condition system that auto-resolves "Accept"/"Cancel" semantics across button layouts.
- FreeType2-based text rendering with glyph shaping/caching per
pixel size, UTF-8 string tables loaded from a compact binary
.langblob, and bitmask text styling (justification, alignment, underline, strike). - PNG image decoding straight to a platform-native texture, with a caller-supplied scratch buffer so decode memory is sized once by the app rather than allocated per image.
- Audio via fixed one-shot PCM voices plus a single background OGG stream, decoded through an integer (Tremor) decoder shared by every platform, with separate global music and sound-effect volumes.
- Storage - SD, USB, DVD, and network shares behind one
FileSystemDriver, with full hot-plug detection: up to three simultaneous USB drives on Wii and Wii U, SD Gecko cards on GameCube, and FAT, exFAT, and NTFS volumes on Wii U USB. See Storage. - Network shares (SMB) through libsmb2 on all three consoles,
mounted as an ordinary
smb:/device so the rest of the app reads and writes it with plain POSIX calls. See Network shares. - Threading primitives (
Thread,Mutex,Cond) with a cooperative stop protocol and an app-exit safety net, so app and library code can run background work without touching a platform's raw threading API. See Threading. - A multi-backend logging framework with severity levels, runtime reconfiguration, and output to the console debug channel, UDP, a USB Gecko / USB serial adapter, or a log file - compiled out entirely when disabled. See Logging.
- Consistent input across every controller - GameCube pad, Wiimote (IR pointer, Nunchuk, Classic Controller), Wii U Pro Controller, and the Wii U GamePad (sticks, buttons, and touch) all arrive as the same per-channel input snapshot, with adaptive IR pointer smoothing and menu rumble feedback. See Input.
- A proper application lifecycle - a single
Platformobject reports running/paused/exiting state and hardware events (power and reset buttons, the Wii U HOME menu and close requests) the same way on every console. See Application lifecycle.
| Platform | Toolchain | Video | Audio | Input | Storage |
|---|---|---|---|---|---|
| GameCube | devkitPPC + libogc2 | GX | AESND | PAD | SD Gecko (slot A/B), SD2SP2, GC Loader, DVD, SMB (Broadband Adapter) |
| Wii | devkitPPC + libogc2 | GX | AESND | WPAD (Wiimote/Nunchuk/Classic/Pro), plus Wii U GamePad support | SD, up to 3 USB, DVD, SMB |
| Wii U | devkitPPC + wut + libwhb | GX2 | AX (sndcore2) | VPAD (GamePad), KPAD/WPAD (Wiimote/Nunchuk/Classic/Pro) | SD, up to 3 USB (FAT/exFAT/NTFS), SMB |
Wii U renders a separate, higher-resolution asset set (data/images_hd, selected in Makefile.wiiu)
libgui/ layout code still works in the one shared configurable coordinate space (eg: 640x480).
libgui's core (source/libgui/) never includes <gccore.h>, <gx2/*.h>,
<wpad/wpad.h>, or any other platform header. Everything platform-specific
sits behind a small set of abstract driver interfaces in source/drivers/,
composed by a single Platform object:
class Platform {
public:
virtual void init(const PlatformConfig& config) = 0; // constructs + initializes every driver
const PlatformConfig& getConfig() const; // the config this platform was init()'d with
virtual void requestExit() = 0; // shut down and leave the app; does not return
virtual AudioDriver* getAudio() = 0;
virtual VideoDriver* getVideo() = 0;
virtual InputDriver* getInput() = 0;
virtual FileSystemDriver* getFileSystem() = 0;
virtual ThreadDriver* getThread() = 0;
virtual Logger* getLogger() = 0; // nullptr when logging is compiled out
virtual SystemEvent getSystemEvent() = 0; // None / ShutdownRequested / ResetRequested
virtual Status getStatus() const = 0; // Running / Paused / Exiting
virtual void triggerExit() = 0; // move to Status::Exiting
bool shouldExit(); // Exiting, or a shutdown event was reported
};
extern Platform* platform; // single global instance, assigned by the appPlatformConfig carries the design canvas size and, on platforms with a
higher-resolution asset set, the scale factor between that asset set and
the design canvas:
struct PlatformConfig {
int canvasWidth;
int canvasHeight;
float assetScaleX = 1.0f;
float assetScaleY = 1.0f;
};There are three concrete Platform implementations - GameCubePlatform
and WiiPlatform (sharing GameCube/Wii's driver set), and WutPlatform
(Wii U) - selected once at startup based on the build:
#ifdef __WIIU__
static WutPlatform platformInstance;
#elif defined(HW_DOL)
static GameCubePlatform platformInstance;
#else
static WiiPlatform platformInstance;
#endif
Platform* platform = &platformInstance;That's the only platform branch an app needs to write. Everything else -
GuiElement::update(), GuiImage, GuiText, the file browser, and so
on - talks only to the interfaces below.
| Interface | Responsibility | GameCube/Wii (drivers/ogc) |
Wii U (drivers/wut) |
|---|---|---|---|
VideoDriver |
Frame lifecycle, screen size/refresh rate/delta time; hands out an ImageRenderer (textured quads and triangle meshes; a mesh that keeps being drawn is kept as a GX display list or as GX2 vertex buffers) and GlyphRenderer (glyph quads + solid rectangles) |
OgcVideoDriver - raw GX, double-buffered XFB |
WutVideoDriver - GX2 + libwhb's WHBGfx* helpers, backed by two small custom GX2 shaders (Texture2DShader, ColorShader). UI draws are recorded into a list and replayed once for the TV and once for the GamePad, instead of switching render targets on every individual draw |
AudioDriver |
Fixed one-shot PCM voices plus one background OGG stream | OgcAudioDriver - AESND |
WutAudioDriver - AX (sndcore2), 16 voice slots plus a ring-buffered stereo stream path, mixed to both the TV and the GamePad |
InputDriver |
Polls hardware and produces a per-channel InputPadData snapshot each frame, consumed by a persistent InputController per channel; controls rumble and Wiimote orientation |
OgcInputDriver - PAD (GameCube) / WPAD (Wiimote, Nunchuk, Classic, Wii U Pro Controller), plus Wii U GamePad via the vendored libwiidrc |
WutInputDriver - VPAD (GamePad stick/buttons/touch) and KPAD/WPAD for up to 4 Wiimotes/Nunchuks/Classic/Pro Controllers |
FileSystemDriver |
Storage device enumeration, mount/poll, hot-plug detection, and the network-share driver (StorageDevice, MountResult, SmbDriver) |
WiiFileSystemDriver / GameCubeFileSystemDriver, with OgcSmbDriver |
WutFileSystemDriver, with WutSmbDriver |
ThreadDriver |
Raw thread/mutex/condition-variable primitives | OgcThreadDriver - libogc's LWP |
WutThreadDriver - coreinit's OSThread/OSMutex/OSCondition |
The Logger (see Logging) is owned by Platform alongside
these drivers and fans each log line out to a set of LoggingDriver
backends.
The UI is laid out on a fixed design canvas (640x480 in the demo) that the video driver maps onto the output, so widget positions and sizes are expressed in the same units on every platform.
Platform gives an app one consistent way to find out that it should stop,
pause, or reset, regardless of console:
getStatus()returnsStatus::Running,Status::Paused, orStatus::Exiting. On Wii U the status isPausedwhenever the app is not in the foreground (for example while the HOME menu overlay is up); the video and audio drivers skip their work while paused, and the app keeps running. Once the OS asks the app to close, or the app callstriggerExit(), the status becomesExitingand stays there.getSystemEvent()reports hardware/OS events:ShutdownRequested(the power button on the console or a Wiimote on Wii; a close/standby request from Cafe OS on Wii U) andResetRequested(the reset button, Wii only - soft-reset and keep going). GameCube has no hardware event source and always reportsNone.shouldExit()is true once eitherStatus::Exitingis set or a shutdown event has been reported, so a main loop only needs to check one thing.requestExit()shuts every driver down and then ends the app the way the platform expects: Wii U asks Cafe OS to return to the system menu (or loader) and waits for the app to leave the foreground before exiting; Wii performs an IOS power-off when a power button started the exit, and otherwise exits normally; GameCube exits. It does not return.
Background threads must be stopped before requestExit() - see
Thread::JoinAll() under Threading. The demo's main()
shows the intended order:
PlatformConfig config;
config.canvasWidth = 640;
config.canvasHeight = 480;
#ifdef __WIIU__
config.assetScaleX = 3.0f; // data/images_hd in the demo is authored at 3x/2.25x the 640x480 canvas (ie: 1920x1080)
config.assetScaleY = 2.25f;
#endif
platform->init(config);
// ... set up fonts, audio, the device-checking thread, run the menus ...
Thread::JoinAll();
platform->requestExit();libgui/
├── Makefile[.wii|.gc|.wiiu] # per-platform build, dispatched by the top-level Makefile
├── doc/ # topic guides (also included in the doxygen docs)
├── data/ # images/fonts/sounds/lang - one shared asset set, bin2o'd for all 3 platforms
├── meta/ # Wii U .wuhb icon/splash assets
└── source/
├── demo.cpp / demo.h # entry point / app template
├── menu.cpp / menu.h # template menu screens (not part of the library itself)
├── filebrowser.cpp/.h # storage file browser built on GuiFileBrowser
│
├── drivers/ # ---- Platform Abstraction Layer ----
│ ├── Platform.h, VideoDriver.h, AudioDriver.h, InputDriver.h,
│ │ FileSystemDriver.h, ThreadDriver.h # the abstract interfaces
│ ├── SmbDriver.h # network-share interface (owned by FileSystemDriver)
│ ├── Logger.h/.cpp, LoggerFile.h/.cpp # logging framework + the generic file backend
│ ├── Thread.h/.cpp, Mutex.h/.cpp, Cond.h/.cpp # RAII wrappers app/core code uses directly
│ ├── InputController.h/.cpp # per-channel logical controller (repeat/orientation/scroll logic)
│ ├── InputData.h, Time.h, OneEuroFilter.h # input snapshot, monotonic timing, pointer smoothing filter
│ │
│ ├── ogc/ # GameCube + Wii implementation (video, audio, input, threads,
│ │ │ # SMB, and the SysReport/UDP/USB Gecko logging backends)
│ │ ├── wii/ # WiiPlatform, WiiFileSystemDriver, WiiUsbMulti, libwiidrc
│ │ └── gamecube/ # GameCubePlatform, GameCubeFileSystemDriver
│ └── wut/ # Wii U implementation (video, audio, input, threads, storage, SMB,
│ │ # USB hot-plug probe, libdvm glue, and the OSReport/UDP/USB serial
│ │ # logging backends)
│ └── shaders/ # the GX2 shader plumbing
│
└── libgui/ # ---- Platform-agnostic core UI library ----
├── Gui.h # umbrella header; alignment/state/scroll enums
├── GuiElement, GuiWindow, GuiButton, GuiImage, GuiImageData,
│ GuiText, GuiTextRenderer, GuiTextTranslator, GuiTrigger
├── GuiSound, GuiSoundOggPlayer
├── GuiFileBrowser, GuiOptionBrowser, GuiKeyboard
└── ...
menu.cpp/menu.h and filebrowser.cpp/.h build the actual template
screens (settings, storage file browser, network share) on top of the core
library and are explicitly not part of the library itself - they're a
recommended pattern to build from, not a hard dependency.
Topic guides, each readable here on GitHub and included in the API documentation:
- Building - Toolchain, dependencies (
libsmb2,libmocha,libdvm) and build steps for all three consoles - Storage - SD, USB, DVD and network devices, hot-plug and mounting
- Network shares (SMB) - Connecting to an SMB share as an ordinary
smb:/device - Input - Controllers, the input snapshot, rumble and pointer smoothing
- Audio - One-shot voices, the OGG stream and volume categories
- Threading - Thread, Mutex, Cond and the cooperative stop protocol
- Logging - The multi-backend logger and its configuration
libgui, Snes9x GX, FCE Ultra GX and Visual Boy Advance GX share one toolchain
and one set of dependencies, so there is a single build guide for all of them:
Building. It covers installing devkitPro and libogc2
(GameCube/Wii) or wut (Wii U), and building libsmb2 (all platforms),
libmocha and libdvm (Wii U) from the dborth forks.
Once the dependencies are installed, from the repository root:
make -f Makefile.wii # Wii -> libgui-wii-demo.dol
make -f Makefile.gc # GameCube -> libgui-gc-demo.dol
make -f Makefile.wiiu # Wii U -> .rpx / .wuhb
make # builds all threeEach Makefile builds the same source, source/drivers, and
source/libgui trees, adding only the directories for its platform:
drivers/ogc plus drivers/ogc/wii or drivers/ogc/gamecube for Wii and
GameCube, and drivers/wut plus drivers/wut/shaders for Wii U.
To enable logging, add -DLOGGING_ENABLED=1 to the build's compiler flags
(see Logging).
The GitHub Actions workflow builds all three platforms on every push, deploys
the doxygen documentation to GitHub Pages, and keeps a rolling pre-release
with the latest .dol, .rpx, and .wuhb builds. It is also the most
up-to-date reference for the build steps.
Start from the supplied template example (source/demo.cpp). It shows the
full startup and shutdown sequence: create the platform, size the image
decode scratch buffer, create the font renderer and string table, start audio
and the storage device-checking thread, run the menus, then join background
threads and exit. For more advanced uses, see the source code for Snes9x GX,
FCE Ultra GX, and Visual Boy Advance GX - all of which build their menu system
on top of libgui.
If you have any suggestions for the library or documentation, or want to contribute, please visit the libgui website: https://github.com/dborth/libgui
The API reference is generated with doxygen from the source and this README, and published at https://dborth.github.io/libgui/
This covers both the core GUI classes (source/libgui) and the platform
driver layer (source/drivers), including the GameCube/Wii (ogc) and
Wii U (wut) driver implementations.
This library was wholly designed and written by Tantric. Thanks
also to the authors of GRRLIB and libwiisprite for laying the foundations.
Thanks to mvit for the artwork and Peter de Man for the music used in the
template. Thanks to FIX94 for libwiidrc, letting a Wii app use a Wii U
GamePad.
See CHANGELOG.md for the full, version-by-version history.
[2.01 - October 1, 2026] batches Wii U UI drawing into one TV pass and one
GamePad pass per frame instead of switching render targets on every draw
(menu frame rate on Wii U roughly doubled in testing), adds a higher-
resolution Wii U asset set with PlatformConfig::assetScaleX/Y, fixes text
scrolling and Wiimote/GamePad pointer angle math, and improves UDP and USB
Gecko logging reliability. Platform::init() now takes a PlatformConfig
instead of separate width/height parameters - see Architecture above.
[2.00 - September 23, 2026] adds full Wii U support (GX2 video, AX audio, VPAD/ KPAD input, FSA-based SD storage, USB storage with libdvm), a GameCube build, network shares, multi-drive USB with full hot-plug, and a logging framework - all made possible by refactoring the library behind a proper hardware abstraction layer. See Architecture above and the changelog for the complete list of changes.