A dependency-free, immediate-mode-inspired 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
- Dependency Free: Core library has no external dependencies except YamlDotNet for 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 |
- 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 rendering
public class MyGfx : IFishUIGfx
{
public void Init() { }
public void BeginDrawing(float dt) { }
public void EndDrawing() { }
public int GetWindowWidth() { /* ... */ }
public int GetWindowHeight() { /* ... */ }
public ImageRef LoadImage(string path) { /* ... */ }
public FontRef LoadFont(string path, int size) { /* ... */ }
public void DrawRectangle(Vector2 pos, Vector2 size, FishColor color) { /* ... */ }
public void DrawImage(ImageRef img, Vector2 pos, float rot, float scale, FishColor color) { /* ... */ }
public void DrawNPatch(NPatch patch, Vector2 pos, Vector2 size, FishColor color) { /* ... */ }
public void DrawText(FontRef font, string text, Vector2 pos, float size, float spacing, FishColor color) { /* ... */ }
public void BeginScissor(Vector2 pos, Vector2 size) { /* ... */ }
public void EndScissor() { /* ... */ }
// ... see IFishUIGfx for full interface
}
// Input handling
public class MyInput : IFishUIInput
{
public Vector2 GetMousePosition() { /* ... */ }
public bool IsMouseDown(FishMouseButton button) { /* ... */ }
public bool IsMousePressed(FishMouseButton button) { /* ... */ }
public bool IsKeyDown(FishKey key) { /* ... */ }
public bool IsKeyPressed(FishKey key) { /* ... */ }
public FishKey GetKeyPressed() { /* ... */ }
public int GetCharPressed() { /* ... */ }
public float GetMouseWheelMove() { /* ... */ }
// ... see IFishUIInput for full interface
}
// Event handling (optional - can use empty implementation)
public class MyEvents : IFishUIEvents
{
public void Broadcast(FishUI.FishUI ui, Control sender, string eventName, object[] args) { }
}// Using the Raylib backend (recommended)
using RaylibGfx = RaylibFishGfx.RaylibFishGfx;
using RaylibInput = RaylibFishGfx.RaylibInput;
FishUISettings settings = new FishUISettings();
RaylibGfx gfx = new RaylibGfx(800, 600, "My App");
gfx.UseBeginDrawing = false; // Set to false if managing draw calls yourself
IFishUIInput input = new RaylibInput();
IFishUIEvents events = new MyEvents(); // Or use a simple empty implementation
FishUI.FishUI ui = new FishUI.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 event
Button btn = new Button();
btn.Text = "Click Me";
btn.Position = new Vector2(100, 100);
btn.Size = new Vector2(150, 40);
btn.OnButtonPressed += (sender, mouseBtn, pos) => Console.WriteLine("Clicked!");
ui.AddControl(btn);
// Panel with children
Panel panel = new Panel();
panel.Position = new Vector2(10, 10);
panel.Size = new Vector2(300, 200);
ui.AddControl(panel);
CheckBox check = new CheckBox("Enable Feature");
check.Position = new Vector2(10, 10);
check.Size = new Vector2(20, 20); // Size for the checkbox icon
panel.AddChild(check);
// ListBox with items
ListBox list = new ListBox();
list.Position = new Vector2(10, 50);
list.Size = new Vector2(150, 120);
list.AlternatingRowColors = true;
for (int i = 0; i < 10; i++)
list.AddItem($"Item {i + 1}");
list.OnItemSelected += (lb, idx, item) => Console.WriteLine($"Selected: {item.Text}");
panel.AddChild(list);// Main game loop
while (!Raylib.WindowShouldClose())
{
float dt = Raylib.GetFrameTime();
// Handle window resize
if (Raylib.IsWindowResized())
ui.Resized(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.DarkGray);
// Update and render UI
ui.Tick(dt, (float)Raylib.GetTime());
Raylib.EndDrawing();
}
Raylib.CloseWindow();Here's a complete working example using the Raylib backend:
using FishUI;
using FishUI.Controls;
using Raylib_cs;
using System.Numerics;
using RaylibGfx = RaylibFishGfx.RaylibFishGfx;
using RaylibInput = RaylibFishGfx.RaylibInput;
// Simple event handler
class SimpleEvents : IFishUIEvents
{
public void Broadcast(FishUI.FishUI ui, Control ctrl, string name, object[] args) { }
}
class Program
{
static void Main()
{
// Setup
var settings = new FishUISettings();
var gfx = new RaylibGfx(800, 600, "FishUI Demo");
gfx.UseBeginDrawing = false;
var ui = new FishUI.FishUI(settings, gfx, new RaylibInput(), new SimpleEvents());
ui.Init();
settings.LoadTheme("data/themes/gwen.yaml", applyImmediately: true);
// Create a button
var button = new Button { Text = "Click Me!", Position = new Vector2(100, 100), Size = new Vector2(120, 40) };
button.OnButtonPressed += (btn, mouse, pos) => Console.WriteLine("Clicked!");
ui.AddControl(button);
// Main loop
while (!Raylib.WindowShouldClose())
{
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.DarkGray);
ui.Tick(Raylib.GetFrameTime(), (float)Raylib.GetTime());
Raylib.EndDrawing();
}
Raylib.CloseWindow();
}
}// Standard button
Button btn = new Button { Text = "Normal" };
// Image button (icon only)
Button imgBtn = new Button();
imgBtn.Icon = gfx.LoadImage("icon.png");
imgBtn.IsImageButton = true;
// Toggle button
Button toggleBtn = new Button { Text = "Toggle", IsToggle = true };
// Repeat button (fires while held)
Button repeatBtn = new Button { Text = "Hold Me", IsRepeat = true };// Searchable dropdown
DropDown searchable = new DropDown();
searchable.Searchable = true; // Type to filter
searchable.AddItem("Apple");
searchable.AddItem("Banana");
searchable.AddItem("Cherry");
// Multi-select dropdown
DropDown multi = new DropDown();
multi.MultiSelect = true;
multi.OnMultiSelectionChanged += (dd, indices) => { /* ... */ };ListBox list = new ListBox();
list.AlternatingRowColors = true;
list.EvenRowColor = new FishColor(200, 220, 255, 40);
list.MultiSelect = true; // Ctrl+click, Shift+click
// Custom item rendering
list.CustomItemHeight = 28;
list.CustomItemRenderer = (ui, item, index, pos, size, selected, hovered) =>
{
ui.Graphics.DrawRectangle(pos, new Vector2(12, 12), FishColor.Red);
ui.Graphics.DrawText(ui.Settings.FontDefault, item.Text, pos + new Vector2(16, 0));
};Window window = new Window();
window.Title = "My Window";
window.Position = new Vector2(100, 100);
window.Size = new Vector2(400, 300);
window.ShowCloseButton = true;
window.Resizable = true;
window.OnClosed += (wnd) => wnd.Visible = false;
ui.AddControl(window);
// Add content to window
Label content = new Label("Window content here");
content.Position = new Vector2(10, 10);
window.AddChild(content);// Radial gauge (speedometer style)
RadialGauge radial = new RadialGauge();
radial.Size = new Vector2(150, 150);
radial.MinValue = 0;
radial.MaxValue = 100;
radial.Value = 75;
// Bar gauge (linear)
BarGauge bar = new BarGauge();
bar.Size = new Vector2(200, 30);
bar.MinValue = 0;
bar.MaxValue = 100;
bar.Value = 60;
// VU Meter (audio level)
VUMeter vu = new VUMeter();
vu.Size = new Vector2(30, 100);
vu.Value = 0.7f;// Anchor to edges (resizes with parent)
Button btn = new Button();
btn.Anchor = FishUIAnchor.Left | FishUIAnchor.Right; // Stretches horizontally
btn.Anchor = FishUIAnchor.All; // Fills parentcontrol.Margin = new FishUIMargin(10, 10, 10, 10); // Left, Top, Right, BottomStackLayout stack = new StackLayout();
stack.Orientation = StackOrientation.Vertical;
stack.Spacing = 5;
stack.AddChild(new Button { Text = "First" });
stack.AddChild(new Button { Text = "Second" });
stack.AddChild(new Button { Text = "Third" });# Theme file example
Atlas: "gwen.png"
Button.Normal:
X: 480
Y: 0
W: 31
H: 31
Left: 8
Right: 8
Top: 8
Bottom: 8
Button.Hovered:
X: 480
Y: 32
W: 31
H: 31
# ...// Per-control color customization
label.SetColorOverride("Text", new FishColor(255, 0, 0, 255));
button.SetColorOverride("Text", new FishColor(100, 200, 255, 255));control.Opacity = 0.5f; // 50% transparent (affects children)// Save UI layout
LayoutFormat.SerializeToFile(ui, "layout.yaml");
// Load UI layout
LayoutFormat.DeserializeFromFile(ui, "layout.yaml");// Enable virtual cursor
ui.VirtualMouse.Enabled = true;
ui.VirtualMouse.Speed = 300f;
// In update loop, map gamepad to virtual cursor
if (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 implementation
public class MyRaylibGfx : IFishUIGfx
{
// Set to false to disable automatic BeginDrawing()/EndDrawing() calls
public bool UseBeginDrawing { get; set; } = false;
public void BeginDrawing(float dt)
{
if (UseBeginDrawing)
{
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.Gray);
}
// Alpha blending is still enabled
Raylib.BeginBlendMode(BlendMode.Alpha);
}
public void EndDrawing()
{
Raylib.EndBlendMode();
if (UseBeginDrawing)
{
Raylib.EndDrawing();
}
}
}Then in your game loop:
// Your existing game loop
while (!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 FishUIEditor
LayoutFormat.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
- 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
- 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
│ └── THEMING.md # Theme creation guide
└── screenshots/ # Screenshot gallery
MIT License - see repository for details.


















