Current source contracts and migration notes: Unicode, layout, and update hardening.
A backend-independent, retained-control GUI library for .NET applications with backend-agnostic rendering.
Uses the GWEN Skin atlas for theming.
- Overview
- Screenshots
- Features
- Installation
- Projects
- Quick Start
- Control Examples
- Layout & Positioning
- Theming
- Serialization
- Virtual Cursor
- Integrating with Existing Game Loops
- FishUIEditor
- Running Samples
- Documentation
- Requirements
- Project Structure
- License
FishUI is a flexible GUI framework that separates UI logic from rendering, allowing integration with any graphics library. It provides a comprehensive set of controls suitable for game development, tools, and applications.
Key Principles:
- Backend Agnostic: Implement your own graphics and input handlers via simple interfaces
- Small Core: YamlDotNet supplies theme and layout serialization
- Game-Ready: Designed for real-time applications with features like virtual cursor support
- Themeable: YAML-based theme system with atlas/9-slice support
| Category | Controls |
|---|---|
| Input | Button, Textbox, CheckBox, RadioButton, ToggleSwitch, Slider, NumericUpDown, MultiLineEditbox |
| Selection | ListBox, DropDown (ComboBox), TreeView, SelectionBox, DatePicker, TimePicker |
| Display | Label, StaticText, ImageBox, AnimatedImageBox, ProgressBar, LineChart, Timeline, BigDigitDisplay, ToastNotification |
| Containers | Panel, Window, GroupBox, TabControl, ScrollablePane, StackLayout, FlowLayout, GridLayout |
| Navigation | ScrollBarV, ScrollBarH, MenuBar, ContextMenu, MenuItem |
| Gauges | RadialGauge, BarGauge, VUMeter |
| Data | DataGrid, SpreadsheetGrid, PropertyGrid, ItemListbox |
| Effects | ParticleEmitter |
| Utility | Tooltip, Titlebar, GameConsole |
- Layout System: Absolute positioning, anchoring, margins/padding, StackLayout, FlowLayout, GridLayout
- Theme System: YAML themes with atlas regions, 9-slice/NPatch rendering, color overrides, inheritance
- Serialization: Save/load UI layouts to YAML files with event handler binding
- Animation: Built-in animation system with easing functions, tween helpers, and particle effects
- Input: Mouse, keyboard, touch, and virtual cursor (gamepad/keyboard navigation)
- Events: Control events, serializable event handlers, event broadcasting
- UI Scaling: Resolution-independent UI with configurable scale factor
FishUI is available on NuGet. Choose the package that fits your needs:
If you're using Raylib for graphics/input, install the all-in-one package:
dotnet add package RaylibFishGfxThis includes:
- FishUI - Core library with all controls
- Raylib-cs - Raylib bindings
- Pre-built
IFishUIGfxandIFishUIInputimplementations
If you're implementing your own graphics backend:
dotnet add package FishUIThen implement IFishUIGfx and IFishUIInput interfaces for your graphics library.
The NuGet packages automatically include theme files, fonts, and icons. These are copied to your output directory on build under the data/ folder.
| Project | Description |
|---|---|
| FishUI | Core library - all controls and interfaces |
| RaylibFishGfx | Raylib graphics/input backend (NuGet package) |
| FishUIEditor | Visual layout editor for designing FishUI interfaces |
| FishUIDemos | Sample implementations using ISample interface |
| FishUISample | Raylib-based sample runner with GUI chooser |
FishUI requires two interfaces for your graphics backend (or use the pre-built Raylib backend):
// Graphics renderingpublicclassMyGfx:IFishUIGfx{publicvoidInit(){}publicvoidBeginDrawing(floatdt){}publicvoidEndDrawing(){}publicintGetWindowWidth(){/* ... */}publicintGetWindowHeight(){/* ... */}publicImageRefLoadImage(stringpath){/* ... */}publicFontRefLoadFont(stringpath,intsize){/* ... */}publicvoidDrawRectangle(Vector2pos,Vector2size,FishColorcolor){/* ... */}publicvoidDrawImage(ImageRefimg,Vector2pos,floatrot,floatscale,FishColorcolor){/* ... */}publicvoidDrawNPatch(NPatchpatch,Vector2pos,Vector2size,FishColorcolor){/* ... */}publicvoidDrawText(FontReffont,stringtext,Vector2pos,floatsize,floatspacing,FishColorcolor){/* ... */}publicvoidBeginScissor(Vector2pos,Vector2size){/* ... */}publicvoidEndScissor(){/* ... */}// ... see IFishUIGfx for full interface}// Input handlingpublicclassMyInput:IFishUIInput{publicVector2GetMousePosition(){/* ... */}publicboolIsMouseDown(FishMouseButtonbutton){/* ... */}publicboolIsMousePressed(FishMouseButtonbutton){/* ... */}publicboolIsKeyDown(FishKeykey){/* ... */}publicboolIsKeyPressed(FishKeykey){/* ... */}publicFishKeyGetKeyPressed(){/* ... */}publicintGetCharPressed(){/* ... */}publicfloatGetMouseWheelMove(){/* ... */}// ... see IFishUIInput for full interface}// Event handling (optional - can use empty implementation)publicclassMyEvents:IFishUIEvents{publicvoidBroadcast(FishUI.FishUIui,Controlsender,stringeventName,object[]args){}}// Using the Raylib backend (recommended)usingRaylibGfx=RaylibFishGfx.RaylibFishGfx;usingRaylibInput=RaylibFishGfx.RaylibInput;FishUISettingssettings=newFishUISettings();RaylibGfxgfx=newRaylibGfx(800,600,"My App");gfx.UseBeginDrawing=false;// Set to false if managing draw calls yourselfIFishUIInputinput=newRaylibInput();IFishUIEventsevents=newMyEvents();// Or use a simple empty implementationFishUI.FishUIui=newFishUI.FishUI(settings,gfx,input,events);ui.Init();// Load a theme (required for proper rendering)settings.LoadTheme("data/themes/gwen.yaml",applyImmediately:true);// Simple button with eventButtonbtn=newButton();btn.Text="Click Me";btn.Position=newVector2(100,100);btn.Size=newVector2(150,40);btn.OnButtonPressed+=(sender,mouseBtn,pos)=>Console.WriteLine("Clicked!");ui.AddControl(btn);// Panel with childrenPanelpanel=newPanel();panel.Position=newVector2(10,10);panel.Size=newVector2(300,200);ui.AddControl(panel);CheckBoxcheck=newCheckBox("Enable Feature");check.Position=newVector2(10,10);check.Size=newVector2(20,20);// Size for the checkbox iconpanel.AddChild(check);// ListBox with itemsListBoxlist=newListBox();list.Position=newVector2(10,50);list.Size=newVector2(150,120);list.AlternatingRowColors=true;for(inti=0;i<10;i++)list.AddItem($"Item {i+1}");list.OnItemSelected+=(lb,idx,item)=>Console.WriteLine($"Selected: {item.Text}");panel.AddChild(list);// Main game loopwhile(!Raylib.WindowShouldClose()){floatdt=Raylib.GetFrameTime();// Handle window resizeif(Raylib.IsWindowResized())ui.Resized(Raylib.GetScreenWidth(),Raylib.GetScreenHeight());Raylib.BeginDrawing();Raylib.ClearBackground(Color.DarkGray);// Update and render UIui.Tick(dt,(float)Raylib.GetTime());Raylib.EndDrawing();}Raylib.CloseWindow();Here's a complete working example using the Raylib backend:
usingFishUI;usingFishUI.Controls;usingRaylib_cs;usingSystem.Numerics;usingRaylibGfx=RaylibFishGfx.RaylibFishGfx;usingRaylibInput=RaylibFishGfx.RaylibInput;// Simple event handlerclassSimpleEvents:IFishUIEvents{publicvoidBroadcast(FishUI.FishUIui,Controlctrl,stringname,object[]args){}}classProgram{staticvoidMain(){// Setupvarsettings=newFishUISettings();vargfx=newRaylibGfx(800,600,"FishUI Demo");gfx.UseBeginDrawing=false;varui=newFishUI.FishUI(settings,gfx,newRaylibInput(),newSimpleEvents());ui.Init();settings.LoadTheme("data/themes/gwen.yaml",applyImmediately:true);// Create a buttonvarbutton=newButton{Text="Click Me!",Position=newVector2(100,100),Size=newVector2(120,40)};button.OnButtonPressed+=(btn,mouse,pos)=>Console.WriteLine("Clicked!");ui.AddControl(button);// Main loopwhile(!Raylib.WindowShouldClose()){Raylib.BeginDrawing();Raylib.ClearBackground(Color.DarkGray);ui.Tick(Raylib.GetFrameTime(),(float)Raylib.GetTime());Raylib.EndDrawing();}Raylib.CloseWindow();}}// Standard buttonButtonbtn=newButton{Text="Normal"};// Image button (icon only)ButtonimgBtn=newButton();imgBtn.Icon=gfx.LoadImage("icon.png");imgBtn.IsImageButton=true;// Toggle buttonButtontoggleBtn=newButton{Text="Toggle",IsToggle=true};// Repeat button (fires while held)ButtonrepeatBtn=newButton{Text="Hold Me",IsRepeat=true};// Searchable dropdownDropDownsearchable=newDropDown();searchable.Searchable=true;// Type to filtersearchable.AddItem("Apple");searchable.AddItem("Banana");searchable.AddItem("Cherry");// Multi-select dropdownDropDownmulti=newDropDown();multi.MultiSelect=true;multi.OnMultiSelectionChanged+=(dd,indices)=>{/* ... */};ListBoxlist=newListBox();list.AlternatingRowColors=true;list.EvenRowColor=newFishColor(200,220,255,40);list.MultiSelect=true;// Ctrl+click, Shift+click// Custom item renderinglist.CustomItemHeight=28;list.CustomItemRenderer=(ui,item,index,pos,size,selected,hovered)=>{ui.Graphics.DrawRectangle(pos,newVector2(12,12),FishColor.Red);ui.Graphics.DrawText(ui.Settings.FontDefault,item.Text,pos+newVector2(16,0));};Windowwindow=newWindow();window.Title="My Window";window.Position=newVector2(100,100);window.Size=newVector2(400,300);window.ShowCloseButton=true;window.Resizable=true;window.OnClosed+=(wnd)=>wnd.Visible=false;ui.AddControl(window);// Add content to windowLabelcontent=newLabel("Window content here");content.Position=newVector2(10,10);window.AddChild(content);Add GameConsole as a root control. It opens from the top with Grave or Shift+Grave, focuses its command input, and exposes a capture signal for the game input layer.
GameConsoleconsole=newGameConsole();console.RegisterCommand("teleport", context =>{// Parse context.Arguments and update the game on the UI thread.context.Console.WriteLine("Teleport command received.");},"Teleports the player.","teleport <x> <y> <z>","tp");ui.AddControl(console);// GameConsole must be a root.Use split update/draw calls when gameplay must inspect keyboard capture in the same frame:
ui.TickUpdate(deltaTime,currentTime);if(!ui.WantsKeyboardCapture)game.ProcessInput();ui.TickDraw(deltaTime,currentTime);WriteLine and console.Logger are safe for background producers. Command registration, execution, opening, closing, and UI properties remain UI-thread operations. A matched text-consuming hotkey suppresses all generated characters remaining in that update frame.
// Radial gauge (speedometer style)RadialGaugeradial=newRadialGauge();radial.Size=newVector2(150,150);radial.MinValue=0;radial.MaxValue=100;radial.Value=75;// Bar gauge (linear)BarGaugebar=newBarGauge();bar.Size=newVector2(200,30);bar.MinValue=0;bar.MaxValue=100;bar.Value=60;// VU Meter (audio level)VUMetervu=newVUMeter();vu.Size=newVector2(30,100);vu.Value=0.7f;// Anchor to edges (resizes with parent)Buttonbtn=newButton();btn.Anchor=FishUIAnchor.Left|FishUIAnchor.Right;// Stretches horizontallybtn.Anchor=FishUIAnchor.All;// Fills parentcontrol.Margin=newFishUIMargin(10,10,10,10);// Left, Top, Right, BottomStackLayoutstack=newStackLayout();stack.Orientation=StackOrientation.Vertical;stack.Spacing=5;stack.AddChild(newButton{Text="First"});stack.AddChild(newButton{Text="Second"});stack.AddChild(newButton{Text="Third"});# Theme file exampleAtlas: "gwen.png"Button.Normal:
X: 480Y: 0W: 31H: 31Left: 8Right: 8Top: 8Bottom: 8Button.Hovered:
X: 480Y: 32W: 31H: 31# ...// Per-control color customizationlabel.SetColorOverride("Text",newFishColor(255,0,0,255));button.SetColorOverride("Text",newFishColor(100,200,255,255));control.Opacity=0.5f;// 50% transparent (affects children)// Save UI layoutLayoutFormat.SerializeToFile(ui,"layout.yaml");// Load UI layoutLayoutFormat.DeserializeFromFile(ui,"layout.yaml");// Enable virtual cursorui.VirtualMouse.Enabled=true;ui.VirtualMouse.Speed=300f;// In update loop, map gamepad to virtual cursorif(gamepad.LeftStick.X!=0||gamepad.LeftStick.Y!=0){ui.VirtualMouse.Move(gamepad.LeftStick*deltaTime);}When integrating FishUI into an existing game that already handles BeginDrawing()/EndDrawing() calls (e.g., in Raylib), you can disable FishUI's automatic drawing frame management:
// In your graphics backend implementationpublicclassMyRaylibGfx:IFishUIGfx{// Set to false to disable automatic BeginDrawing()/EndDrawing() callspublicboolUseBeginDrawing{get;set;}=false;publicvoidBeginDrawing(floatdt){if(UseBeginDrawing){Raylib.BeginDrawing();Raylib.ClearBackground(Color.Gray);}// Alpha blending is still enabledRaylib.BeginBlendMode(BlendMode.Alpha);}publicvoidEndDrawing(){Raylib.EndBlendMode();if(UseBeginDrawing){Raylib.EndDrawing();}}}Then in your game loop:
// Your existing game loopwhile(!Raylib.WindowShouldClose()){Raylib.BeginDrawing();Raylib.ClearBackground(Color.Black);// Draw your game content...DrawGameWorld();// Draw FishUI on top (it won't call BeginDrawing/EndDrawing)ui.Tick(deltaTime,currentTime);Raylib.EndDrawing();}This allows FishUI to render as an overlay on top of your existing game rendering without interfering with your drawing frame management.
FishUI includes a visual layout editor for designing interfaces:
cd FishUIEditor
dotnet runFeatures:
- Drag-and-drop control placement from toolbox
- Visual resize handles and selection
- PropertyGrid for editing control properties
- Layout hierarchy tree view
- Save/load layouts to YAML files
- Parent/child control relationships with reparenting
- Anchor and Z-ordering support
- Visual feedback for drop targets
- Container selection mode (Window/TabControl protect internal controls)
- Nested control resizing with proper parent offset calculation
Layouts created in the editor can be loaded in your application:
// Load a layout created in FishUIEditorLayoutFormat.DeserializeFromFile(ui,"data/layouts/my_layout.yaml");The FishUISample project includes a GUI-based sample chooser:
cd FishUISample
dotnet runOr run a specific sample:
dotnet run -- --sample 0- Basic Controls: Textbox, Slider, NumericUpDown, ProgressBar, ToggleSwitch
- Button Variants: Icon buttons, toggle, repeat, image buttons
- DropDown: Basic, searchable, multi-select, custom rendering
- ListBox: Alternating colors, multi-select, custom rendering
- ImageBox: Scale modes, filter modes, animated images
- Gauges: RadialGauge, BarGauge, VUMeter dashboard
- PropertyGrid: Reflection-based property editor
- MenuBar: Dropdown menus with submenus
- ScrollablePane: Virtual scrolling container
- Layout System: Anchoring, margins, StackLayout, FlowLayout, GridLayout
- Theme Switcher: Runtime theme switching
- Virtual Cursor: Keyboard/gamepad navigation
- Game Menu: Example game-style UI
- Windows 98 Notepad: Classic text editor clone with in-memory Open/Save, Find, clipboard commands, and Word Wrap
- Diagnostic Snapshot: Capture structured input, layout, rendering, and multiline scrolling state on the next draw
- Editor Layout: Load and display layouts from FishUIEditor
- Data Controls: DataGrid, SpreadsheetGrid, DatePicker, TimePicker
- Serialization: Layout save/load with event handler binding
Additional documentation is available in the docs/ folder:
- Custom Control Creation Guide - How to create your own controls
- Diagnostic Snapshot Guide - Capture lifecycle, privacy, framebuffer, and export contracts
- FishUI 2.0 Runtime Contracts - Lifecycle, frames, input, overlays, serialization, and resource ownership
- FishUI 2.0 Migration Guide - Required changes for 1.x applications and backends
- FishUI 2.0 Codebase Audit - Baseline defects, coverage, and verification scope
- Custom Backend Guide - Graphics, clipping, input, and backend resource ownership
- Theme Creation Guide - Creating custom themes with YAML
- .NET 9.0
- YamlDotNet (included via NuGet) - for layout/theme serialization
For the sample application:
- Raylib-cs - graphics/input backend for demos
FishUI/
├── FishUI/ # Core library (NuGet: FishUI)
│ ├── Controls/ # All UI controls (50+)
│ ├── FishUI.cs # Main UI manager
│ ├── FishUISettings.cs # Settings and theme loading
│ ├── IFishUIGfx.cs # Graphics interface
│ ├── IFishUIInput.cs # Input interface
│ ├── IFishUIEvents.cs # Event handling interface
│ ├── LayoutFormat.cs # YAML serialization
│ ├── build/ # NuGet build props/targets
│ └── data/ # Themes, fonts, icons
├── RaylibFishUI/ # Raylib backend (NuGet: RaylibFishGfx)
│ ├── RaylibFishGfx.cs # IFishUIGfx implementation
│ └── RaylibInput.cs # IFishUIInput implementation
├── FishUIEditor/ # Visual layout editor
│ ├── Controls/ # Editor-specific controls
│ └── FishUIEditor.cs # Editor application
├── FishUIDemos/ # Sample implementations
│ ├── Samples/ # ISample implementations
│ └── Forms/ # Designer form examples
├── FishUISample/ # Raylib-based sample runner
│ ├── Program.cs # Sample chooser entry point
│ └── SampleChooser.cs # GUI sample selector
├── NugetTest/ # NuGet package testing project
├── docs/ # Documentation
│ ├── CUSTOM_CONTROLS.md # Custom control creation guide
│ ├── DIAGNOSTICS.md # Diagnostic snapshot guide
│ ├── RUNTIME_2.0.md # FishUI 2.0 runtime contracts
│ ├── MIGRATING_TO_2.0.md # FishUI 2.0 migration guide
│ ├── CODEBASE_AUDIT.md # Baseline audit and verification evidence
│ └── THEMING.md # Theme creation guide
└── screenshots/ # Screenshot gallery
MIT License - see repository for details.


















