Skip to content

Repository files navigation

FishUI

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.

.NET 9.0LicenseNuGet FishUINuGet RaylibFishGfxDeepWiki

Uses the GWEN Skin atlas for theming.

Table of Contents

Overview

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

Screenshots

Animation & Particle System

Windows & DialogsWindows & Dialogs

AnimationsButton Variants

DataGridDatePicker

DropDownEditor Layout

Game Main MenuGauges

LayoutSystemLineChart

MenuBarMultiLineEditbox

PropertyGridScrollablePane

SpreadsheetGridGame Window Sample

Features

Controls (50+ Built-in)

CategoryControls
InputButton, Textbox, CheckBox, RadioButton, ToggleSwitch, Slider, NumericUpDown, MultiLineEditbox
SelectionListBox, DropDown (ComboBox), TreeView, SelectionBox, DatePicker, TimePicker
DisplayLabel, StaticText, ImageBox, AnimatedImageBox, ProgressBar, LineChart, Timeline, BigDigitDisplay, ToastNotification
ContainersPanel, Window, GroupBox, TabControl, ScrollablePane, StackLayout, FlowLayout, GridLayout
NavigationScrollBarV, ScrollBarH, MenuBar, ContextMenu, MenuItem
GaugesRadialGauge, BarGauge, VUMeter
DataDataGrid, SpreadsheetGrid, PropertyGrid, ItemListbox
EffectsParticleEmitter
UtilityTooltip, Titlebar, GameConsole

Framework Features

  • 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

Installation

NuGet Packages

FishUI is available on NuGet. Choose the package that fits your needs:

Option 1: Raylib Backend (Recommended for Quick Start)

If you're using Raylib for graphics/input, install the all-in-one package:

dotnet add package RaylibFishGfx

This includes:

  • FishUI - Core library with all controls
  • Raylib-cs - Raylib bindings
  • Pre-built IFishUIGfx and IFishUIInput implementations

Option 2: Core Library Only

If you're implementing your own graphics backend:

dotnet add package FishUI

Then implement IFishUIGfx and IFishUIInput interfaces for your graphics library.

Data Files

The NuGet packages automatically include theme files, fonts, and icons. These are copied to your output directory on build under the data/ folder.

Projects

ProjectDescription
FishUICore library - all controls and interfaces
RaylibFishGfxRaylib graphics/input backend (NuGet package)
FishUIEditorVisual layout editor for designing FishUI interfaces
FishUIDemosSample implementations using ISample interface
FishUISampleRaylib-based sample runner with GUI chooser

Quick Start

1. Implement Required Interfaces

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){}}

2. Initialize FishUI

// 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);

3. Add Controls

// 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);

4. Run the Update Loop

// 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();

Complete Minimal Example

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();}}

Control Examples

Button Variants

// 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};

DropDown with Features

// 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)=>{/* ... */};

ListBox Features

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));};

Window with Titlebar

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);

Quake-Style Game Console

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.

Gauges

// 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;

Layout & Positioning

Anchoring

// Anchor to edges (resizes with parent)Buttonbtn=newButton();btn.Anchor=FishUIAnchor.Left|FishUIAnchor.Right;// Stretches horizontallybtn.Anchor=FishUIAnchor.All;// Fills parent

Margins

control.Margin=newFishUIMargin(10,10,10,10);// Left, Top, Right, Bottom

StackLayout

StackLayoutstack=newStackLayout();stack.Orientation=StackOrientation.Vertical;stack.Spacing=5;stack.AddChild(newButton{Text="First"});stack.AddChild(newButton{Text="Second"});stack.AddChild(newButton{Text="Third"});

Theming

YAML Theme Files

# Theme file exampleAtlas: "gwen.png"Button.Normal:
X: 480Y: 0W: 31H: 31Left: 8Right: 8Top: 8Bottom: 8Button.Hovered:
X: 480Y: 32W: 31H: 31# ...

Color Overrides

// Per-control color customizationlabel.SetColorOverride("Text",newFishColor(255,0,0,255));button.SetColorOverride("Text",newFishColor(100,200,255,255));

Opacity

control.Opacity=0.5f;// 50% transparent (affects children)

Serialization

// Save UI layoutLayoutFormat.SerializeToFile(ui,"layout.yaml");// Load UI layoutLayoutFormat.DeserializeFromFile(ui,"layout.yaml");

Virtual Cursor (Gamepad/Keyboard Navigation)

// 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);}

Integrating with Existing Game Loops

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.

FishUIEditor - Visual Layout Designer

FishUI includes a visual layout editor for designing interfaces:

cd FishUIEditor
dotnet run

Features:

  • 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");

Running Samples

The FishUISample project includes a GUI-based sample chooser:

cd FishUISample
dotnet run

Or run a specific sample:

dotnet run -- --sample 0

Available Samples

  • 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

Documentation

Additional documentation is available in the docs/ folder:

Requirements

  • .NET 9.0
  • YamlDotNet (included via NuGet) - for layout/theme serialization

For the sample application:

  • Raylib-cs - graphics/input backend for demos

Project Structure

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

License

MIT License - see repository for details.

About

Dependency free, simple GUI

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages