### Complete Minimal Hello World GUI Example Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started A full example demonstrating how to create a basic GUI window with a clickable button and text display. Ensure all necessary namespaces are imported. ```csharp using System; using Gui; using Gui.Widgets; using Gui.Rendering.Text; using OpenTK.Mathematics; using Vintagestory.API.Client; public class HelloWorldGui : GuiBase { public HelloWorldGui(ICoreClientAPI capi) : base(capi) { } protected override WindowConfig CreateWindowConfig() => new() { Size = new Vector2(300, 180), Draggable = true, Resizable = true, }; protected override Widget Build() => new WindowFrame( title: "Hello World", onClose: () => TryClose(), child: new _Content() ); class _Content : StatefulWidget { public override State CreateState() => new _State(); } class _State : State<_Content> { int _clicks = 0; public override Widget Build(BuildContext ctx) => new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.Center, spacing: 12, children: [ new Text($"Clicked {_clicks} times", new TextStyle { FontSize = 18, Color = Vector4.One }), new GestureDetector( onTap: _ => SetState(() => _clicks++), child: new Container( style: new BoxStyle { Color = new Vector4(0.2f, 0.5f, 0.9f, 1), CornerRadius = new Vector4(6), Padding = EdgeInsets.Symmetric(vertical: 8, horizontal: 20) }, child: new Text("Click me", new TextStyle { FontSize = 14 }) ) ) ] ) ); } } ``` -------------------------------- ### Complete MyToolWindow Example Source: https://github.com/ripls56/vslibgui/wiki/Windowing A comprehensive example of creating a tool window using WindowFrame. It includes window configuration such as size, draggable, resizable properties, and defines the window's content with padding and text. ```csharp public class MyToolWindow : GuiBase { public MyToolWindow(ICoreClientAPI capi) : base(capi) { } protected override WindowConfig CreateWindowConfig() => new() { Size = new Vector2(400, 300), Draggable = true, Resizable = true, MinSize = new Vector2(200, 100), }; protected override Widget Build() => new WindowFrame( title: "Tool Window", onClose: () => TryClose(), child: new Padding( EdgeInsets.All(12), child: new Text("Window content here") ) ); } ``` -------------------------------- ### EdgeInsets Examples Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Provides examples of creating EdgeInsets objects for defining padding and margins. Includes Zero, All, Only, Symmetric, and LTRB constructors. ```csharp EdgeInsets.Zero EdgeInsets.All(16) EdgeInsets.Only(left: 8, top: 4, right: 8, bottom: 4) EdgeInsets.Symmetric(vertical: 12, horizontal: 20) EdgeInsets.LTRB(left, top, right, bottom) ``` -------------------------------- ### Stack Layout Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Layers children on top of each other. Sizes to unpositioned children; fills parent if all children are positioned. ```csharp new Stack( [ new Container(style: new BoxStyle { Color = Colors.Background }), new Positioned(right: 8, top: 8, child: new CloseButton()), ]) ``` -------------------------------- ### Column Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Layout Creates a vertical layout with configurable spacing and alignment. Use for stacking items. ```csharp // Column — vertical flex new Column( spacing: 12, mainAxisAlignment: MainAxisAlignment.Start, crossAxisAlignment: CrossAxisAlignment.Stretch, children: [ ... ] ) ``` -------------------------------- ### Padding Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Adds inset space around its child. Can apply padding to all sides or specific sides. ```csharp new Padding(EdgeInsets.All(16), child: new Text("Padded")) ``` ```csharp new Padding(EdgeInsets.Only(left: 12, top: 8), child: new Text("...")) ``` -------------------------------- ### Alignment Examples Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Demonstrates the use of the Alignment class for positioning widgets. Includes predefined constants like Center and TopLeft, and custom Alignment instances. ```csharp Alignment.Center Alignment.TopLeft new Alignment(0.5f, -1f) // top edge, 75% from left ``` -------------------------------- ### TabView Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A TabView widget for creating tabbed panels. It manages its own active tab index internally. ```csharp new TabView( tabs: [ new TabItem { Label = "Overview", Content = new OverviewPanel() }, new TabItem { Label = "Settings", Content = new SettingsPanel() }, ], position: TabPosition.Top, // Top (default) or Left initialIndex: 0 ) ``` -------------------------------- ### FlatItemSlot Minimal Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A basic FlatItemSlot with default styling from the theme. It requires a SlotController and a slot definition. ```csharp var controller = new SlotController(capi); controller.WatchInventory(hotbarInv); // auto-update on external inventory changes // Minimal — all colors from theme: new FlatItemSlot(slot: inventory[0], controller: controller) ``` -------------------------------- ### ItemSlotGestureLayer Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Manually building a FlatItemSlot equivalent using ItemSlotGestureLayer, defining the visual box style and item overlay. ```csharp // FlatItemSlot equivalent built manually: new ItemSlotGestureLayer( slot: inventory[0], controller: controller, childBuilder: () => new Container( new BoxStyle { Width = 48, Height = 48, Color = new Vector4(0, 0, 0, 0.4f) }, new ItemSlotOverlay(inventory[0], size: 48f) ) ) ``` -------------------------------- ### ItemSlotOverlay Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Renders the item icon, stack size, and durability bar within an inventory slot. It automatically reads hover animation data. ```csharp new ItemSlotOverlay(slot: inventory[0], size: 48f) ``` ```csharp // Optional overrides: new ItemSlotOverlay(slot, size: 48f, hoverColor: new Vector4(1), padding: EdgeInsets.All(4)) ``` -------------------------------- ### EdgeInsets Factory Methods Source: https://github.com/ripls56/vslibgui/wiki/Layout Provides examples of using EdgeInsets factory methods to define various padding configurations. ```csharp EdgeInsets.All(16) // 16px on all sides EdgeInsets.Only(left: 8, top: 4, right: 8, bottom: 4) EdgeInsets.Symmetric(vertical: 12, horizontal: 20) EdgeInsets.Ltrb(left, top, right, bottom) EdgeInsets.Zero // no padding ``` -------------------------------- ### Row Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Layout Creates a horizontal layout with configurable spacing and alignment. Use for arranging items side-by-side. ```csharp // Row — horizontal flex new Row( spacing: 8, mainAxisAlignment: MainAxisAlignment.SpaceBetween, crossAxisAlignment: CrossAxisAlignment.Center, children: [ ... ] ) ``` -------------------------------- ### Container with BoxStyle Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A visual box with background, border, corner radii, optional fixed size, and optional child. Supports gradients, textures, and hit-test behavior. ```csharp new Container( style: new BoxStyle { Color = new Vector4(0.1f, 0.1f, 0.1f, 0.95f), Color2 = new Vector4(0.15f, 0.15f, 0.15f, 0.95f), // gradient end (optional) CornerRadius = new Vector4(8), // uniform radius; or per-corner Vector4 BorderThickness = 1f, BorderColor = new Vector4(1, 1, 1, 0.3f), Padding = EdgeInsets.All(12), Width = 300f, // optional fixed width Height = 200f, // optional fixed height Texture = myBitmap, // optional SKBitmap background ClipBehavior = ClipBehavior.AntiAlias, HitTestBehavior = HitTestBehavior.Defer }, child: new Text("Content") ) ``` -------------------------------- ### DrawImage Example Source: https://github.com/ripls56/vslibgui/wiki/Rendering Draws a bitmap using pre-computed source and destination rectangles, clipped to a rounded rectangle. BoxFit logic computes the rects. ```csharp context.DrawImage(bitmap, pos, size, srcRect, dstRect, radii, border, borderColor); ``` -------------------------------- ### SizedBox Examples Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Imposes explicit size constraints. With no child, acts as a spacer. Used for fixed sizing or creating vertical/horizontal spacing. ```csharp new SizedBox(width: 200, height: 100, child: new Text("Fixed size")) ``` ```csharp new SizedBox(height: 16) // 16px vertical spacer in Column ``` ```csharp new SizedBox(width: 8) // 8px horizontal spacer in Row ``` -------------------------------- ### SingleChildScrollView Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A non-virtualized scrollable container widget. Use when the content is not excessively large. ```csharp new SingleChildScrollView( child: new Column(children: manyWidgets) ) ``` -------------------------------- ### Stateless Widget Composition Example Source: https://github.com/ripls56/vslibgui/wiki/Custom-Widgets Define a reusable stateless widget for displaying text with a background color. Use this for static UI elements derived from constructor arguments. ```csharp public class Badge : StatelessWidget { public string Text { get; } public Vector4 Background { get; } public Badge(string text, Vector4 background) { Text = text; Background = background; } public override Widget Build(BuildContext context) => new Container( style: new BoxStyle { Padding = EdgeInsets.Symmetric(horizontal: 8, vertical: 2), Color = Background, CornerRadius = new Vector4(4), }, child: new Text(Text)); } ``` -------------------------------- ### DrawText Example Source: https://github.com/ripls56/vslibgui/wiki/Rendering Draws text with optional glow and outline effects. The drawing order is glow, then outline, then fill, creating a layered appearance. ```csharp context.DrawText( text: "Hello", pos: new Vector2(0, fontSize), // Y is baseline fontSize: 14f, color: Vector4.One, fontFamily: "sans-serif", weight: FontWeight.Normal, boldness: 0.1f, outlineWidth: 0.05f, outlineColor: new Vector4(0, 0, 0, 1), glowWidth: 0.3f, glowColor: new Vector4(0.5f, 0.8f, 1, 0.5f) ); ``` -------------------------------- ### Stateless Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A widget that describes a part of the user interface which can depend on configuration in the constructor and the widget tree. Override Build to describe the UI. ```csharp class MyButton : StatelessWidget { public string Label { get; } public Action? OnPressed { get; } public MyButton(string label, Action? onPressed = null) { Label = label; OnPressed = onPressed; } public override Widget Build(BuildContext ctx) => new GestureDetector( onTap: _ => OnPressed?.Invoke(), child: new Container( style: new BoxStyle { Padding = EdgeInsets.All(8) }, child: new Text(Label) ) ); } ``` -------------------------------- ### Inherited Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference An InheritedWidget that propagates data down the widget tree. Use Of() to access inherited data and register for updates. ```csharp class MyConfig : InheritedWidget { public string Locale { get; } public MyConfig(string locale, Widget child) : base(child) { Locale = locale; } public override bool UpdateShouldNotify(InheritedWidget oldWidget) { return Locale != ((MyConfig)oldWidget).Locale; } public static string Of(BuildContext context) { var w = context.DependOnInheritedWidgetOfExactType(); return w?.Locale ?? "en"; } } ``` -------------------------------- ### NineSliceItemSlot Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A NineSliceItemSlot using a bitmap texture for its background, with specified texture slicing and size. Requires a SlotController and slot definition. ```csharp // Load bitmap once (e.g., in GuiBase.OnScreenOpen): var slotTex = SKBitmap.Decode(asset.Data); new NineSliceItemSlot( texture: slotTex, slice: EdgeInsets.All(16), // fixed corner insets for nine-slice size: 48f, slot: inventory[0], controller: controller ) ``` -------------------------------- ### Custom RenderObjectWidget Example Source: https://github.com/ripls56/vslibgui/wiki/Custom-Widgets Inherit from RenderObjectWidget to create a custom widget. Override CreateRenderObject and UpdateRenderObject to manage the associated render object. ```csharp public class Dot : RenderObjectWidget { public Vector4 Color { get; } public float Radius { get; } public Dot(Vector4 color, float radius) { Color = color; Radius = radius; } public override RenderObject CreateRenderObject() => new _RenderDot { Color = Color, Radius = Radius }; public override void UpdateRenderObject(RenderObject ro) { var dot = (_RenderDot)ro; dot.Color = Color; dot.Radius = Radius; } } ``` -------------------------------- ### Wrap Layout Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Lays out children in runs, wrapping to the next line when a child would overflow. Handles overflow by creating new runs instead of clipping. ```csharp new Wrap( direction: FlexDirection.Horizontal, // or Vertical spacing: 8, // gap between children in a run runSpacing: 4, // gap between runs mainAxisAlignment: MainAxisAlignment.Start, crossAxisAlignment: CrossAxisAlignment.Start, runAlignment: MainAxisAlignment.Start, // how runs are distributed children: [ new Chip("Tag1"), new Chip("Tag2"), new Chip("LongTag3"), // wraps to next line if no room ] ) ``` -------------------------------- ### Layout in Scroll Widgets: Corrected Example Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Shows the correct way to manage layout within scrollable widgets by providing explicit heights for children, as `Expanded` widgets require bounded height constraints which scroll views do not provide. ```csharp // Wrong: Expanded needs bounded height new SingleChildScrollView( child: new Column(children: [new Expanded(child: new Text("..."))]) ) // Correct: give explicit height instead new SingleChildScrollView( child: new Column(children: [ new SizedBox(height: 200, child: new Text("...")), new Text("More content") ]) ) ``` -------------------------------- ### Align Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Positions a child at a point within the parent using a normalized (-1,-1) to (1,1) space. Predefined constants like TopLeft, Center, BottomRight are available. ```csharp new Align(alignment: Alignment.BottomRight, child: new Text("BR")) ``` ```csharp new Align(alignment: new Alignment(0, -0.5f), child: new Text("Custom")) ``` -------------------------------- ### Looping Animation with Forward and Reverse Source: https://github.com/ripls56/vslibgui/wiki/Animations Implement a continuous looping animation by calling Forward() to start and then using OnStatusChanged to toggle between Forward() and Reverse() based on the animation's status. ```csharp _ctrl.OnStatusChanged += s => { if (s == AnimationStatus.Completed) _ctrl.Reverse(); else if (s == AnimationStatus.Dismissed) _ctrl.Forward(); }; _ctrl.Forward(); // start ``` -------------------------------- ### Access Widget Element with GlobalKey Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Demonstrates how to use a GlobalKey to get a reference to a widget's Element and RenderObject from anywhere in the widget tree. This is useful for imperative APIs. ```csharp // Allocate once (e.g. in InitState), reuse across rebuilds private GlobalKey _targetKey = new GlobalKey(); // Attach to a widget new MyRow(key: _targetKey, ...) // Access the element and RenderObject Element? el = _targetKey.CurrentElement; RenderObject? ro = _targetKey.CurrentRenderObject; ``` -------------------------------- ### Expanded Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Tells its Row/Column parent to give it all remaining main-axis space. Must be a direct child of Row or Column. Multiple Expanded children share remaining space by their flex ratio. ```csharp new Row(children: [ new Text("Name:"), new Expanded(flex: 1, child: new TextField()), // gets remaining space ]) ``` -------------------------------- ### Continuous Slider Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Creates a continuous slider with a value range from 0.0 to 1.0. The `onChanged` callback updates the internal state with the new value. ```csharp new Slider( value: _volume, onChanged: v => SetState(() => _volume = v) ) ``` -------------------------------- ### Minimal GuiDialogBlockEntityBase Subclass Source: https://github.com/ripls56/vslibgui/wiki/Dialogs Example of a minimal subclass for a chest GUI. It requires block position, inventory, and the client API. The dialog opens a root inventory widget and plays a chest opening sound. ```csharp public class MyChestGui : GuiDialogBlockEntityBase { public MyChestGui(BlockPos pos, InventoryBase inv, ICoreClientAPI capi) : base(pos, inv, capi) { } protected override Widget Build() => new InventoryRoot(capi, () => TryClose()); protected override SoundAttributes? OpenSound => new SoundAttributes { location = new AssetLocation("sounds/block/chestopen") }; } ``` -------------------------------- ### Positioned Widget Example Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Places a child at absolute coordinates within a Stack. Only valid as a direct child of Stack. At least one of left/right and at least one of top/bottom (or explicit width/height) should be set. ```csharp new Positioned( left: 10, // offset from left edge (optional) top: 20, // offset from top edge (optional) right: null, // offset from right edge (optional) bottom: null, // offset from bottom edge (optional) width: 200, // explicit width (optional) height: 100, // explicit height (optional) child: new MyWidget() ) ``` -------------------------------- ### Creating a Basic GUI Screen Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started Subclass GuiBase and override CreateWindowConfig for window behavior and Build for UI structure. The Build method returns an immutable widget tree. ```csharp using Gui; using Gui.Widgets; using OpenTK.Mathematics; using Vintagestory.API.Client; public class MyInventoryGui : GuiBase { public MyInventoryGui(ICoreClientAPI capi) : base(capi) { } protected override WindowConfig CreateWindowConfig() => new() { Size = new Vector2(400, 300), Draggable = true, Resizable = true, }; protected override Widget Build() { return new WindowFrame( title: "My Inventory", onClose: () => TryClose(), child: new Padding( EdgeInsets.All(16), child: new Text("Inventory content here") ) ); } ``` -------------------------------- ### Project References for GUI Development Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started Add these references to your .csproj file to include GUI and related libraries. Ensure Gui.dll is in the correct path and other libraries resolve via the VINTAGE_STORY environment variable. ```xml C:\path\to\Gui\bin\Release\Mods\mod\Gui.dll false $(VINTAGE_STORY)\Lib\OpenTK.Mathematics.dll false $(VINTAGE_STORY)\Lib\SkiaSharp.dll false ``` -------------------------------- ### Configure Window Behavior Source: https://github.com/ripls56/vslibgui/wiki/Windowing Override `CreateWindowConfig` to set properties like size, position, minimum/maximum dimensions, and enable/disable dragging and resizing. ```csharp protected override WindowConfig CreateWindowConfig() => new() { Size = new Vector2(800, 600), // fixed size; null = shrink-wrap to content Position = new Vector2(100, 50), // initial position; null = centered on screen MinSize = new Vector2(200, 100), // resize clamp MaxSize = new Vector2(1600, 900), // resize clamp Draggable = true, // drag by title bar / top handle zone Resizable = true, // resize by edges and corners DragHandleHeight = 24f, // height of the drag zone at the top ResizeHandleSize = 8f, // edge grip width for resize detection }; ``` -------------------------------- ### Widget Lifecycle Reference Source: https://github.com/ripls56/vslibgui/wiki/State-Management Overview of the VSLibGUI widget lifecycle, from creation to disposal. Key methods include CreateState, InitState, Build, UpdateWidget, and Dispose. ```text CreateState() // called once when element is created ↓ InitState() // element is mounted, Owner is set, can create AnimationControllers ↓ Build(ctx) // UI is described; called again after SetState or parent rebuild ↓ (on parent rebuild with new widget) UpdateWidget(oldWidget) // Widget property has been updated; call SetState if rebuild needed ↓ (on unmount) Dispose() // release all resources: controllers, listeners ``` -------------------------------- ### Registering a Hotkey to Open GUI Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started Register a hotkey in StartClientSide and set its handler to open your custom GUI. This allows users to open the GUI with a key press. ```csharp public override void StartClientSide(ICoreClientAPI api) { api.Input.RegisterHotKey("myguiopen", "Open My GUI", GlKeys.M); api.Input.SetHotKeyHandler("myguiopen", _ => { new MyInventoryGui(api).TryOpen(); return true; }); } ``` -------------------------------- ### Using a Stateless Widget Source: https://github.com/ripls56/vslibgui/wiki/Custom-Widgets Instantiate and use the custom `Badge` stateless widget within a layout, similar to built-in widgets. This demonstrates how to integrate custom compositions. ```csharp new Row(children: [ new Badge("NEW", Vector4.UnitX), new Badge("HOT", new Vector4(1, 0.5f, 0, 1)), ]) ``` -------------------------------- ### StatefulWidget and State Lifecycle Source: https://github.com/ripls56/vslibgui/wiki/State-Management Demonstrates the structure of a StatefulWidget and its associated State class, including lifecycle methods like InitState, Build, UpdateWidget, and Dispose. ```csharp class MyWidget : StatefulWidget { public string Title { get; } public MyWidget(string title) { Title = title; } public override State CreateState() => new _MyWidgetState(); } class _MyWidgetState : State { int _count = 0; public override void InitState() { base.InitState(); // Initialize resources here (animations, controllers, listeners) } public override Widget Build(BuildContext ctx) { return new Column(children: [ new Text(Widget.Title), // Widget is the current MyWidget instance new Text($"Count: {_count}"), new GestureDetector( onTap: _ => SetState(() => _count++), child: new Text("Increment") ) ]); } public override void UpdateWidget(MyWidget oldWidget) { // oldWidget.Title != Widget.Title — compare if needed } public override void Dispose() { // Release resources (AnimationControllers, listeners) base.Dispose(); } } ``` -------------------------------- ### DrawBox Example Source: https://github.com/ripls56/vslibgui/wiki/Rendering Draws a filled rounded rectangle with an optional border. This is typically called internally by RenderBox.PaintInternal; direct use is rare. ```csharp context.DrawBox( pos: Vector2.Zero, size: Size, color: new Vector4(0.1f, 0.1f, 0.1f, 0.9f), radii: new Vector4(8), // X=TR, Y=BR, Z=TL, W=BL border: 2f, borderColor: new Vector4(1, 1, 1, 0.4f) ); ``` -------------------------------- ### BoxStyle with BoxShadows Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Demonstrates how to apply outer and inner box shadows to a container using BoxStyle. Multiple shadows can be layered. ```csharp new Container( style: new BoxStyle { Color = new Vector4(0.15f, 0.15f, 0.2f, 1f), CornerRadius = new Vector4(12), BoxShadows = new[] { // Outer drop shadow new BoxShadow( Color: new Vector4(0, 0, 0, 0.6f), Offset: new Vector2(0, 4f), BlurRadius: 16f, SpreadRadius: 2f), // Subtle outer glow new BoxShadow( Color: new Vector4(0.3f, 0.5f, 1f, 0.15f), Offset: Vector2.Zero, BlurRadius: 24f), // Inner shadow (inset) for depth new BoxShadow( Color: new Vector4(0, 0, 0, 0.4f), Offset: new Vector2(0, 2f), BlurRadius: 8f, SpreadRadius: 1f, Inset: true) } }, child: new Text("Shadowed card") ) ``` -------------------------------- ### ListView with External Scroll Controller Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Attach an external ScrollController to programmatically control scrolling, such as jumping to a position or starting a kinetic scroll. ```csharp var ctrl = new ScrollController(); ctrl.Attach(tickerProvider); // Jump to position: ctrl.JumpTo(500f); // Start kinetic scroll: ctrl.StartSimulation(velocity: 800f, min: 0, max: 6000); new ListView(itemCount: 100, itemHeight: 60, controller: ctrl, itemBuilder: (ctx, i) => new Text($"Item {i}")) ``` -------------------------------- ### Opening and Closing a GUI Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started Instantiate your custom GUI class and call TryOpen() to display it. Use TryClose() to close it programmatically. ```csharp // In your ModSystem or event handler: var gui = new MyInventoryGui(capi); gui.TryOpen(); // Close it programmatically: gui.TryClose(); ``` -------------------------------- ### Element Lifecycle Source: https://github.com/ripls56/vslibgui/wiki/Architecture Illustrates the typical lifecycle of an Element, from mounting to updating or unmounting. ```csharp widget.CreateElement() │ ▼ element.Mount(parent) ← attach to tree; InitState() for stateful │ ▼ (on rebuild) element.Update(newWidget) ← parent rebuilt with a compatible new widget │ ▼ (on removal) element.Unmount() ← detach; State.Dispose(); RenderObject.Dispose() ``` -------------------------------- ### Using RepaintBoundary for Caching Source: https://github.com/ripls56/vslibgui/wiki/Rendering Demonstrates how to wrap stable subtrees with RepaintBoundary to cache their rendering as an SKPicture, improving performance by avoiding rebuilds. ```csharp // Wrap stable subtrees adjacent to animated content: new RepaintBoundary( new Sidebar(...) // static — cache hits every animation frame ) ``` -------------------------------- ### Basic Text Rendering Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Renders a simple string with configurable text styles like font, size, color, and alignment. ```csharp new Text("Hello, World!", new TextStyle { FontFamily = "sans-serif", FontSize = 14f, Color = Vector4.One, Weight = FontWeight.Normal, // Normal, Bold, Italic Align = TextAlignment.Left, // Left, Center, Right Overflow = TextOverflow.Clip, // Clip, Ellipsis Boldness = 0.1f, // -0.5 (thin) to 0.5 (bold) OutlineWidth = 0.03f, // fraction of font size OutlineColor = new Vector4(0, 0, 0, 1), GlowWidth = 0.2f, // blur radius fraction of font size GlowColor = new Vector4(0.5f, 0.8f, 1, 0.5f) }) ``` -------------------------------- ### Handle Animation Status Changes Source: https://github.com/ripls56/vslibgui/wiki/Animations Listen to OnStatusChanged events to react to animation completion or dismissal. This example demonstrates a ping-pong loop by reversing the animation on completion and forwarding it on dismissal. ```csharp _ctrl.OnStatusChanged += status => { if (status == AnimationStatus.Completed) _ctrl.Reverse(); if (status == AnimationStatus.Dismissed) _ctrl.Forward(); // ↑ Ping-pong loop }; ``` -------------------------------- ### Container with BoxStyle for Visual Properties Source: https://github.com/ripls56/vslibgui/wiki/Layout Shows how to create a Container with a specified BoxStyle, including dimensions, padding, color, border, and corner radius. ```csharp new Container( style: new BoxStyle { Width = 300, // fixed width (optional) Height = 200, // fixed height (optional) Padding = EdgeInsets.All(16), Color = new Vector4(0.1f, 0.1f, 0.1f, 0.9f), CornerRadius = new Vector4(8), BorderThickness = 1, BorderColor = new Vector4(1, 1, 1, 0.3f), ClipBehavior = ClipBehavior.AntiAlias, }, child: new Text("Content") ) ``` -------------------------------- ### Floaty Positioning for Dialogs Source: https://github.com/ripls56/vslibgui/wiki/Dialogs Override the Anchor property to define floaty positioning for dialogs bound to block entities. This example anchors the dialog above the block when immersive mouse mode is enabled. ```csharp protected override WorldAnchor? Anchor => field ??= new WorldAnchor { WorldPos = pos.ToVec3d().Add(0.5, 0.75, 0.5), Align = 0.75 }; ``` -------------------------------- ### Create a Basic Tooltip Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Wraps a child widget and displays a text tooltip on hover. The tooltip appears above the child. ```csharp new Tooltip( content: new Text("Save the document"), child: myButton ) ``` -------------------------------- ### Custom Scroll Wheel Event Handling Source: https://github.com/ripls56/vslibgui/wiki/Event-Handling This example shows how to attach a custom scroll wheel event handler to a GestureDetector. It demonstrates how to modify a zoom level based on the wheel delta and mark the event as handled to prevent further propagation. ```csharp new GestureDetector( onWheel: e => { SetState(() => _zoom += e.Delta * 0.1f); e.Handled = true; }, child: new ZoomableView(zoom: _zoom) ) ``` -------------------------------- ### Using ValueKey for Reorderable Lists Source: https://github.com/ripls56/vslibgui/wiki/Architecture Demonstrates the use of `ValueKey` to ensure correct element reuse when list items can be reordered, by providing a stable identifier for each item. ```csharp new Row(children: items.Select(item => new Container(key: new ValueKey(item.Id), ...) )) ``` -------------------------------- ### Basic WindowFrame Usage Source: https://github.com/ripls56/vslibgui/wiki/Windowing Demonstrates the basic construction of a WindowFrame with a title, close action, and child content. The child widget is removed from the tree when the window is minimized. ```csharp protected override Widget Build() => new WindowFrame( title: "My Window", onClose: () => TryClose(), child: myContent, ); ``` -------------------------------- ### Create a SlotGrid Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Instantiates a SlotGrid widget for displaying inventory slots. Supports custom slot backgrounds and controllers. ```csharp new SlotGrid( slots: [slot1, slot2, null, slot3], // null = empty slot controller: controller, // optional SlotController columns: 10, slotSize: 48f, spacing: 4f, slotBackground: slotTextureBitmap, // optional nine-slice background slotBackgroundSlice: EdgeInsets.All(16) ) ``` -------------------------------- ### Displaying a Game Asset Image Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Use the Image widget to display game assets. Configure fit and alignment to control how the image scales within its bounds. Optional width and height can be specified. ```csharp new Image( domain: "game", // asset domain path: "textures/gui/icon.png", // path within domain fit: BoxFit.Contain, alignment: Alignment.Center, // optional, defaults to Center width: 64f, // optional explicit width height: 64f // optional explicit height ) ``` -------------------------------- ### Frame Loop Execution Order Source: https://github.com/ripls56/vslibgui/wiki/Architecture Illustrates the sequence of operations within a single frame, from rendering pipeline stages to widget rebuilding, layout, and painting. ```plaintext [PreSkiaPipeline — once per frame] SkiaRenderer.Begin(width, height) // acquire/resize Skia GL surface [GuiBase.OnRenderGUI — per open window] 1. RenderObject.AdvanceFrame() // increment global frame ID 2. _tickerScheduler.Update(elapsed) // fire active animation tickers // → AnimationController.OnValueChanged // → AnimatedBuilder.SetState → dirty elements 3. new/reset PaintingContext(canvas) 4. BuildOwner.BuildDirtyElements() // rebuild dirty widget subtrees 5. if (rootRo.NeedsLayout || ChildNeedsLayout) rootRo.Layout(window constraints) // Tight(windowSize) or Loose(screenSize) 6. canvas.Translate(WindowPos) // position window on screen canvas.ClipRect(windowBounds) // clip to window area 7. rootRo.Paint(context) // recursive paint 8. DebugPainter (if enabled) [PostSkiaPipeline — once per frame] SkiaRenderer.End() // flush Skia, restore GL state prevShader.Use() // restore game shader ``` -------------------------------- ### Initialize and Dispose AnimationController Source: https://github.com/ripls56/vslibgui/wiki/Getting-Started Create an AnimationController in InitState and dispose it in Dispose to manage screen animations. The ticker is tied to the GUI's frame loop. ```csharp class _MyContentState : State<_MyContent> { AnimationController? _fadeCtrl; CurvedAnimation? _fadeAnim; public override void InitState() { base.InitState(); _fadeCtrl = new AnimationController( TimeSpan.FromMilliseconds(400), Element.Owner.GetTickerProvider() // ticker is tied to the GUI's frame loop ); _fadeAnim = new CurvedAnimation(_fadeCtrl, Curves.EaseOut); _fadeCtrl.Forward(); // start playing immediately } public override Widget Build(BuildContext ctx) { return new AnimatedBuilder( animation: _fadeCtrl!, builder: c => new Opacity( (float)_fadeAnim!.Value, child: new Text("Fades in on open") ) ); } public override void Dispose() { _fadeCtrl?.Dispose(); // stops the ticker and frees resources base.Dispose(); } } ``` -------------------------------- ### SizedBox for Fixed Size or Spacing Source: https://github.com/ripls56/vslibgui/wiki/Layout Illustrates creating a fixed-size box or using SizedBox as a spacer in rows or columns. ```csharp // Fixed-size box new SizedBox(width: 200, height: 50, child: new Text("...")) ``` ```csharp // Spacer in a Row/Column new SizedBox(width: 16) // horizontal spacer new SizedBox(height: 8) // vertical spacer ``` -------------------------------- ### Create a WindowFrame Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Instantiates a WindowFrame widget, which combines a WindowTitleBar with content. It automatically handles minimizing the window to show only the title bar. ```csharp new WindowFrame( title: "My Window", onClose: () => TryClose(), child: new Padding( EdgeInsets.All(12), child: new Text("Content") ) ) ``` -------------------------------- ### Basic Progress Bar Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Initializes a progress bar with a specified value. The appearance defaults to the theme's `ProgressBarStyle`. ```csharp new ProgressBar(value: 0.6f) ``` -------------------------------- ### Widget Instance Creation Source: https://github.com/ripls56/vslibgui/wiki/Architecture Widget instances are created fresh in every Build() call and may be discarded after a single frame. This highlights their immutable nature. ```csharp new Text("Hello", new TextStyle { FontSize = 14 }) // ↑ This object may be thrown away after one frame — that is normal. ``` -------------------------------- ### Scrollable.EnsureVisible with GlobalKey Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Shows how to use Scrollable.EnsureVisible to make a specific widget visible within its scrollable ancestor. Requires allocating a GlobalKey and attaching it to the target widget. ```csharp // 1. Allocate a GlobalKey (once, e.g. in InitState) private GlobalKey _targetKey = new GlobalKey(); // 2. Attach the key to the widget you want to scroll to new MyWidget(key: _targetKey, ...) // 3. Call EnsureVisible when needed Scrollable.EnsureVisible(_targetKey.CurrentElement!); ``` ```csharp // With animation: Scrollable.EnsureVisible( _targetKey.CurrentElement!, duration: TimeSpan.FromMilliseconds(150), curve: Curves.EaseOut); ``` -------------------------------- ### ListView with Static Children Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Use this constructor for short, fixed lists where the children are already defined. It's simpler than using a builder. ```csharp new ListView( children: myWidgets, // IEnumerable itemHeight: 40f ) ``` -------------------------------- ### Create a Fixed-Height ListView with Static Children Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A ListView for short lists where all child widgets are provided upfront. Requires a fixed item height for layout. ```csharp new ListView( children: myWidgets, itemHeight: 40f ) ``` -------------------------------- ### RenderObject Paint Pass Steps Source: https://github.com/ripls56/vslibgui/wiki/Rendering The recursive paint entry point for a RenderObject. It outlines the sequence of operations including saving/restoring the canvas, calling subclass painting logic, and handling children. ```text 1. Mark WasPaintedThisFrame (frame-ID comparison, no reset needed) 2. Fire OnAnyPaint if dirty (performance metrics) 3. Save canvas + apply ClipBehavior if != None 4. Call PaintInternal(context) ← subclass draws its visual content here 5. Clear NeedsPaint 6. foreach child: canvas.Save() canvas.Translate(child.X, child.Y) ← position child in local space child.Paint(context) canvas.Restore() 7. Clear ChildNeedsPaint 8. Restore canvas (from clip) ``` -------------------------------- ### ScrollController Usage Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Demonstrates the lifecycle and basic control of a ScrollController, including attaching, reacting to changes, programmatic control, and disposal. Ensure the controller is attached before use. ```csharp var ctrl = new ScrollController(); ctrl.Attach(tickerProvider); // must be attached before use // React to scroll changes: ctrl.OnChanged += () => Console.WriteLine($"Offset: {ctrl.Offset}"); // Programmatic control: ctrl.JumpTo(0f); // instant snap ctrl.StartSimulation(velocity: 600f, min: 0f, max: maxScroll); // fling ctrl.AnimateTo(200f, TimeSpan.FromMilliseconds(300), Curves.EaseOut); // animated // Cleanup: ctrl.Dispose(); ``` -------------------------------- ### Access Open Windows Source: https://github.com/ripls56/vslibgui/wiki/Windowing Retrieve a list of all currently open windows and the active (focused) window from `GuiModSystem`. ```csharp // All open windows: IReadOnlyList windows = modSystem.OpenWindows; // Currently active (focused) window: GuiBase? active = modSystem.ActiveWindow; ``` -------------------------------- ### ListView with Item Builder Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Use this constructor for lists with a large number of items where performance is critical. All items must have the same height. ```csharp new ListView( itemCount: 1000, itemHeight: 60, // all items must have the same height itemBuilder: (ctx, index) => new Container( style: new BoxStyle { Color = new Vector4(0.15f, 0.15f, 0.15f, 0.8f), CornerRadius = new Vector4(4), Padding = EdgeInsets.All(10) }, child: new Text($"Item #{index}") ) ) ``` -------------------------------- ### RepaintBoundary Cache Hit Logic Source: https://github.com/ripls56/vslibgui/wiki/Rendering Illustrates the code executed when a RepaintBoundary cache is hit, replaying recorded commands without needing a widget rebuild. ```csharp canvas.DrawPicture(_cache) // replay recorded commands, no widget rebuild needed ``` -------------------------------- ### Discrete Slider with Label and Callback Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Configures a discrete slider with a defined range (0-100) and a specific number of steps (10). It includes a text label and an `onChangeEnd` callback for saving the final value. ```csharp new Slider( value: _brightness, onChanged: v => SetState(() => _brightness = v), onChangeEnd: v => SaveSetting(v), min: 0, max: 100, divisions: 10, label: "Brightness" ) ``` -------------------------------- ### Create a Variable-Height ListView Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference A virtualized scrollable list where items can have different heights. Enables auto-scrolling to the bottom on new item appends. ```csharp new ListView( itemBuilder: (ctx, i) => new IntrinsicHeight( child: new MyVariableHeightWidget(items[i]) ), itemCount: items.Count, estimatedItemHeight: 44f, variableHeight: true, stickToBottom: true // auto-scroll to bottom on append ) ``` -------------------------------- ### Keyboard Event Handling in a Text Widget Source: https://github.com/ripls56/vslibgui/wiki/Event-Handling This C# code demonstrates how to implement keyboard event handlers (KeyDown, KeyChar) for a text widget. It shows how to manage focus, update text content, and handle the Escape key to clear focus. The widget also visually indicates its focused state. ```csharp class _TextWidgetState : State<_TextWidget>, IFocusable, IKeyDownHandler, IKeyCharHandler { public FocusNode FocusNode { get; } = new(); public void OnKeyDown(KeyboardEvent e) { if (e.KeyCode == (int)GlKeys.Escape) { Element.Owner.FocusManager.RequestFocus(null); // clear focus e.Handled = true; } } public void OnKeyChar(KeyboardEvent e) { SetState(() => _text += e.KeyChar); e.Handled = true; } public override Widget Build(BuildContext ctx) { bool focused = FocusNode.HasFocus; return new GestureDetector( onTap: _ => Element.Owner.FocusManager.RequestFocus(FocusNode), child: new Container( style: new BoxStyle { BorderThickness = focused ? 2 : 1, BorderColor = focused ? new Vector4(0.4f, 0.7f, 1, 1) : new Vector4(1, 1, 1, 0.3f) }, child: new Text(_text) ) ); } } ``` -------------------------------- ### Using TextEditingController for TextFields Source: https://github.com/ripls56/vslibgui/wiki/State-Management Demonstrates how to use TextEditingController to manage and listen to changes in a TextField's text value. The controller should be disposed of when the widget is removed. ```csharp class _FormState : State<_Form> { TextEditingController _nameCtrl = new("default"); public override void InitState() { base.InitState(); _nameCtrl.AddListener(OnTextChanged); } void OnTextChanged() { // Called every time the user types Console.WriteLine(_nameCtrl.Text); } public override Widget Build(BuildContext ctx) => new TextField(controller: _nameCtrl); public override void Dispose() { _nameCtrl.RemoveListener(OnTextChanged); base.Dispose(); } } ``` -------------------------------- ### Manage OverlayEntry in C# Source: https://github.com/ripls56/vslibgui/wiki/Widgets-Reference Demonstrates how to dynamically insert and remove an OverlayEntry from the widget tree. This is typically used for tooltips or dropdowns. ```csharp // Insert an entry dynamically from within a widget: var entry = new OverlayEntry( new Positioned(left: 100, top: 100, child: new Container(style: new BoxStyle { Width = 200, Height = 100 }, child: new Text("Tooltip"))) ); Overlay.Of(context)!.Insert(entry); // Remove it: entry.Remove(); // or entry.Dispose() ``` -------------------------------- ### GridView Static Mode Source: https://github.com/ripls56/vslibgui/wiki/Scrolling Use the static GridView constructor when you have a predefined list of widgets to display in a grid. Configure the grid layout using a SliverGridDelegate. ```csharp new GridView( children: items.Select(item => new ItemCard(item)).ToList(), gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 160, crossAxisSpacing: 6, mainAxisSpacing: 6, fixedItemHeight: 100 ) ) ``` -------------------------------- ### Implement Window Title Bar Source: https://github.com/ripls56/vslibgui/wiki/Windowing Create a title bar widget with title text, minimize, and close buttons by adding `WindowTitleBar` as the first child in a `Column`. ```csharp protected override Widget Build() => new Column(children: [ new WindowTitleBar( title: "My Window", onClose: () => TryClose(), onMinimizeChanged: minimized => { /* show/hide content */ }, ), new Expanded(child: myContent), ]); ``` -------------------------------- ### State Reactivity Flow Source: https://github.com/ripls56/vslibgui/wiki/Architecture Details the process by which state changes propagate through the system, leading to widget rebuilds and subsequent layout and paint passes. ```plaintext State.SetState(fn) → fn() mutates state fields → Element.MarkNeedsBuild() → BuildOwner.ScheduleBuildFor(element) → next frame: BuildDirtyElements() → state.Build(ctx) returns new widget tree → UpdateChild reconciles with existing elements → changed render objects call MarkNeedsLayout() / MarkNeedsPaint() → Layout + Paint pass re-runs on dirty branches only ``` -------------------------------- ### Stateful Widget with Mutable State Source: https://github.com/ripls56/vslibgui/wiki/Custom-Widgets Create a stateful widget that manages mutable state (`_isOn`) and updates the UI when state changes via `SetState`. Use this for interactive elements like toggles. ```csharp public class Toggle : StatefulWidget { public string Label { get; } public Action OnChanged { get; } public Toggle(string label, Action onChanged) { Label = label; OnChanged = onChanged; } public override State CreateState() => new _ToggleState(); } class _ToggleState : State { bool _isOn; public override Widget Build(BuildContext context) => new GestureDetector( onTap: _ => { SetState(() => _isOn = !_isOn); Widget.OnChanged(_isOn); }, child: new Row(children: [ new Container( width: 32, height: 16, color: _isOn ? Vector4.UnitY : new Vector4(0.4f, 0.4f, 0.4f, 1)), new Padding(padding: EdgeInsets.Only(left: 8), child: new Text(Widget.Label)), ])); } ```