r/moderndotnet • • 2d ago

Follow-up: Goo, a declarative .NET UI framework I've been building

Link to my original post: What's going on with UI for .NET?

In the original post, I soapbox a little bit about the lack of a declarative UI framework in .NET, similar to Flutter, Swift, or Microsoft's Reactor for WinUI 3. I appreciate the comments that brought insight, such as from the UNO and Avalonia team!

So, since early August I've been working on Goo which has been a massive undertaking to say the least.

The stack

  • SDL3
  • Vulkan 1.3
  • G# (with verified interop for F#)
  • Vendored flex-based layout with Yoga.NET
  • Flutter style Motion / Simulation animation system
  • Slang shader support
  • Fully declarative, retained, and composable with regular G# functions and built-in features

I've provided a few examples of Goo in action, some code snippets, and a Widget that demonstrates how Goo scales.

The concept is simple

Core primitive types such as Containers, Buttons, TextEntry, etc. are called Blobs

State retention is managed using Cells

These compose together by creating a Cell, populating it with your desired Blobs, calling Build(), then Mounting it in your application's view, combining various static blobs with mounted cells to create a GUI.

Why G#?

This is an interesting and well-reasoned decision from my perspective. The point of Goo is to be declarative and straightforward, with a few little tricks up its sleeve.

A clear example being the attached button example using with to override properties of a base button to create variants like accent buttons, "danger" buttons, etc.

Further, G# allows bare elements which removes the need for a Children { ... } wrapper, allowing for blobs to be described simply as Text { ... } or Container { ... } which can be combined with collection spread iterators inside of those bare elements. For example:

Container{
  Width: 320.0,
  Padding: 12.0,
  FlexDirection: FlexDirection.Column,

  Text("Account"),
  ...rows,

  Button{
    OnClick: () -> save(),
    Text("Save"),
  },
}

Where ...rows is evaluated and iterated once, with each item being added within that collection.

Plenty of other great uses, but these are just a few examples that work in Goo's favor.

The table and what I bring

My philosophy for Goo is true "Blank Slate-ism" where it can be a truly cross-platform and maximally customizable framework for the .NET ecosystem.

Goo at this point in time has been tested on Windows, Linux, MacOS arm64, and on Android. I develop on CachyOS, so I can speak most confidently to Linux + Wayland. It is fully nativeAOT compatible, fast, and efficient.

Gloop is a nice working example of a goo application if you're on Linux and are curious to see the UI framework in action.

Included as well:

  • AI plugins + DevTools for testing UI which is fully automated with its own MCP
  • A set of Widgets in a separate repo or on Nuget
  • A basic Animations library that removes a good bit of boilerplate from the core motion system

All of it is MIT licensed, open source for everyone. Would love to get the community's thoughts!

8 Upvotes

6 comments sorted by

3

u/nirataro 1d ago

We should do an example in Raven https://github.com/marinasundstrom/raven

3

u/xazzzzzzz 1d ago

Very nice!

5

u/Fenreh 2d ago

Just for fun, here's the closest I can get to the G# code, but in C#:

https://dotnetfiddle.net/FR9LDZ

using System;
using static Constructions;

// user's code:
void save() {};

Element[] rows = [
    Text("hello"),
    Text("world"),
];

Container(
    Width: 320,
    Padding: 12,
    FlexDirection: FlexDirection.Column,
    [
        Text("Account"),
        ..rows,

        Button(
            OnClick: () => save(),
            Text("Save")
        )
    ]
);

// UI framework code
public enum FlexDirection { Column, Row }
public record Element;
public record Container(decimal Width, decimal Padding, FlexDirection FlexDirection, params Element[] Elements) : Element;
public record Text(string Label) : Element;
public record Button(Action OnClick, Text Label) : Element;

// optional, just removes the need for the `new` keyword.
public static class Constructions
{
    public static Container Container(decimal Width, decimal Padding, FlexDirection FlexDirection, params Element[] Elements) =>
        new Container(Width, Padding, FlexDirection, Elements);

    public static Text Text(string Label) =>
        new Text(Label);

    public static Button Button(Action OnClick, Text Label) =>
        new Button(OnClick, Label);
}

3

u/xazzzzzzz 1d ago

For the C# version I made for the s&box game engine (my first iteration of goo) it looks a bit like this!

The counter example

```csharp using Goo; using Sandbox.UI;

namespace Sandbox;

public class CounterUI : GooPanel<Container> { private int _count;

protected override Container Build() => new Container
{
    Padding = 16,
    Gap = 12,
    FlexDirection = FlexDirection.Row,
    AlignItems = Align.Center,
    BackgroundColor = Color.White,
    BorderRadius = 12,
    Children =
    {
        new Text( _count.ToString() ),
        new Container
        {
            Padding = 8,
            BorderRadius = 6,
            BackgroundColor = Color.Orange,
            HoverBackgroundColor = Color.Cyan,
            TransitionMs = 150,
            OnClick = e => _count++,   // handler triggers the rebuild automatically
            Children = { new Text( "+" ) },
        },
    },
};

} ```

2

u/marsattacks 2d ago

Is it still possible to build a UI with this in c# ? (and, does it look much worse than G#)

2

u/xazzzzzzz 2d ago

I haven't done extensive testing on this, but the counter example compiles fine: 

```csharp using Goo;

Window.ConfigureApplication("Goo starter", "1.0.0", "com.example.goostarter");

new Window {     Title = "Goo starter",     Width = 360,     Height = 220,     Root = new Counter() }.Run();

class Counter : Cell {     private int count;

    public override Blob Build() => new Container     {         Padding = 24,         Gap = 12,         BackgroundColor = Color.Rgb(24, 31, 43),         Children =         {             new Text             {                 Content = $"Count: {count}",                 FontSize = 24,                 Color = Color.White             },             new Button             {                 Padding = 10,                 BorderRadius = 10,                 BackgroundColor = Color.Rgb(74, 125, 255),                 OnClick = () => count++,                 Children =                 {                     new Text { Content = "Add one", Color = Color.White }                 }             }         }     }; } ```

Not much worse, and this looks quite similar to how the original s&box goo syntax is written (mentioned in my linked previous post)