My GUI Toolkit and Desktop Environment, Built from Scratch Specifically for FreeBSD

First of all, congratulations on the work — building both a GUI toolkit and a desktop environment is an impressive achievement.

I want to ask you : how would you describe the learning curve for someone who wants to:
  • use your toolkit in their own projects
  • contribute to the codebase
And what is the current state of the user and developer documentation?
 
First of all, congratulations on the work — building both a GUI toolkit and a desktop environment is an impressive achievement.
Thank you so much for the complement it means alot.

I want to ask you : how would you describe the learning curve for someone who wants to:
  • use your toolkit in their own projects

If you’re familiar with Qt and QML, development will feel very similar. The toolkit isn’t fully polished yet, but the following example shows what you can expect when creating an application.
hello.hml
Code:
Window {
    id: "main"
    title: "FreeBSD Toolkit Demo"
    width: 400
    height: 200

    signal buttonPressed()

    Layout {
        direction: "column"
        spacing: 16
        padding: 20

        Text {
            text: "Hello from HML"
            fontSize: 18
            align: "center"
        }

        Button {
            id: "btn"
            text: "Click me"
            onClicked: buttonPressed()
        }
    }
}


main.cc
C++:
#include <iostream>

#include <gui/application.h>
#include <gui/hml_loader.h>
#include <gui/window.h>

int main() {
    gui::Application app;
    if (!app.isValid()) {
        return 1;
    }

    // Load declarative UI
    auto doc = gui::HmlLoader::loadFromFile("hello.hml");
    if (!doc || !doc->isValid()) {
        return 1;
    }

    auto window = doc->rootAs<gui::Window>();
    if (!window || !window->isValid()) {
        return 1;
    }

    // Bind HML signal to C++
    doc->onSignal("buttonPressed", []() {
        std::cout << "Button was clicked!" << std::endl;
    });

    window->requestFrame();
    app.run();
    return 0;
}

  • contribute to the codebase


The SDK is organized into a set of modules: archive, audio, compression, cloud, core, cryptography, data, ecs, graphics2d, graphics3d, gui, network, physics2d, physics3d, scripting, system, math, widgets, and web.

The graphics2d module is a wrapper around Skia (see https://skia.org/docs/), the same 2D graphics engine used by Android, ChromeOS, Google Chrome, and Flutter. Skia is developed by Google and released under a BSD license. This module is just over 3,000 lines of code, and most semi-experienced C++ developers should be comfortable contributing to it. Below is an overview of what the module contains.

graphics2d/include/ this is the public api
Code:
alpha_type.h        mat3.h              rect.h
color.h             paint.h             shape.h
color_type.h        painter.h           surface.h
font.h              path.h              text_style.h
gpu_context.h       pattern.h           vulkan_context.h
image.h             pixmap.h            vulkan_image_info.h
image_info.h        point.h

graphics2d/private/ this is the private api (pimpl),
Code:
font_impl.h         image_info_skia.cpp path_impl.h
image_impl.h        jpeg_shim.cpp       pixmap_impl.h
image_info_impl.h   painter_impl.h      skia_support.cpp

graphics2d/src/ this is the implementation
Code:
font.cpp        image_svg.cpp   pixmap.cpp
gpu_context.cpp painter.cpp     shape.cpp
image.cpp       path.cpp        surface.cpp
image_info.cpp  pattern.cpp

Then we have the gui module, which includes the window system, widgets, layout engine, rendering engine, focus manager, and more, essentially the heart and soul of the GUI toolkit. It uses Wayland client, Vulkan (see https://www.vulkan.org/), and the Yoga layout engine (see https://www.yogalayout.dev/) library's. Yoga is developed by Facebook and released under the MIT license.

This is the most important part of the toolkit and is just over 10,000 lines of code, with much of it generated from Wayland protocol files. The layout class itself is a thin wrapper around Yoga. Anyone with working knowledge of Wayland and Vulkan should be comfortable contributing in this area. The layout wrapper requires just semi-experienced c++ developer.

gui/include/ this is the public api
Code:
application.h   layout.h        style.h
focus_manager.h layout_types.h  widget.h
input.h         paint_list.h    window.h

gui/private/ this is the private api (pimpl),
Code:
vulkan_swapchain.cpp
vulkan_swapchain.h
wayland_context.h
wlr-layer-shell-unstable-v1-protocol.c
wlr-layer-shell-unstable-v1-protocol.h
xdg-shell-client-protocol.c
xdg-shell-client-protocol.h

gui/src/ this is the implementation
Code:
application.cpp   paint_list.cpp    window.cpp
focus_manager.cpp style.cpp
layout.cpp        widget.cpp

Then we have widgets,

widgets/include this is the public api
Code:
button.h       progress_bar.h slider.h
checkbox.h     radio_button.h status_bar.h
container.h    scroll_area.h  text_edit.h
label.h        scroll_bar.h   title_bar.h

widgets/src/ this is the implementation
Code:
button.cpp       progress_bar.cpp slider.cpp
checkbox.cpp     radio_button.cpp status_bar.cpp
container.cpp    scroll_area.cpp  text_edit.cpp
label.cpp        scroll_bar.cpp   title_bar.cpp

Here is the implementation for a button widget if you can understand this code then you should be find developing widgets.

C++:
w#include "gui/widgets/button.h"

#include "graphics2d/text_style.h"

#include <algorithm>

namespace gui {

Button::Button(std::string text) : text_(std::move(text)) {}

void Button::setText(std::string text) {
    text_ = std::move(text);
    invalidate();
}

const std::string& Button::text() const {
    return text_;
}

void Button::setTextColor(graphics2d::Color color) {
    textColor_ = color;
    colorOverride_ = true;
    invalidate();
}

void Button::setBackgroundColor(graphics2d::Color color) {
    background_ = color;
    colorOverride_ = true;
    backgroundEnabled_ = true;
    invalidate();
}

void Button::setHoverColor(graphics2d::Color color) {
    hover_ = color;
    colorOverride_ = true;
    backgroundEnabled_ = true;
    invalidate();
}

void Button::setPressedColor(graphics2d::Color color) {
    pressed_ = color;
    colorOverride_ = true;
    backgroundEnabled_ = true;
    invalidate();
}

void Button::setCornerRadius(float radius) {
    cornerRadius_ = radius;
    invalidate();
}

void Button::setFont(std::shared_ptr<graphics2d::Font> font) {
    font_ = std::move(font);
    if (font_ && fontSize_ > 0.0f) {
        auto sized = font_->withSize(fontSize_);
        if (sized && sized->isValid()) {
            font_ = std::shared_ptr<graphics2d::Font>(std::move(sized));
        }
    }
    invalidate();
}

void Button::setFontSize(float size) {
    fontSize_ = size;
    if (font_ && fontSize_ > 0.0f) {
        auto sized = font_->withSize(fontSize_);
        if (sized && sized->isValid()) {
            font_ = std::shared_ptr<graphics2d::Font>(std::move(sized));
        }
    }
    invalidate();
}

void Button::setTextAlign(TextAlign align) {
    textAlign_ = align;
    invalidate();
}

void Button::setCircle(bool enabled) {
    circle_ = enabled;
    invalidate();
}

void Button::setBackgroundEnabled(bool enabled) {
    backgroundEnabled_ = enabled;
    invalidate();
}

void Button::setTextOffset(float dx, float dy) {
    textOffsetX_ = dx;
    textOffsetY_ = dy;
    invalidate();
}

void Button::setOnClick(ClickCallback callback) {
    onClick_ = std::move(callback);
}

void Button::onPaint(PaintList& list) {
    graphics2d::Rect rect = bounds();
    if (backgroundEnabled_) {
        graphics2d::Paint bgPaint;
        if (pressedState_) {
            bgPaint.setColor(pressed_);
        } else if (hovered_) {
            bgPaint.setColor(hover_);
        } else {
            bgPaint.setColor(background_);
        }

        if (circle_) {
            float radius = 0.5f * std::min(rect.w, rect.h);
            graphics2d::Point center{rect.x + rect.w * 0.5f, rect.y + rect.h * 0.5f};
            list.drawCircle(center, radius, bgPaint);
        } else if (cornerRadius_ > 0.0f) {
            list.drawRoundedRect(rect, cornerRadius_, cornerRadius_, bgPaint);
        } else {
            list.drawRect(rect, bgPaint);
        }
    }

    if (!text_.empty()) {
        graphics2d::Paint textPaint;
        textPaint.setColor(textColor_);
        float size = fontSize_;
        if (font_ && font_->isValid()) {
            size = font_->size();
        }
        if (textAlign_ == TextAlign::Center) {
            graphics2d::TextStyle style;
            style.setSize(size);
            style.setColor(textColor_);
            style.setAlign(graphics2d::TextStyle::Align::Center);
            style.setVerticalAlign(graphics2d::TextStyle::VerticalAlign::Middle);
            style.setWordWrap(false);
            if (font_ && font_->isValid()) {
                style.setFont(font_.get());
            }
            graphics2d::Rect box = rect;
            box.x += textOffsetX_;
            box.y += textOffsetY_;
            list.drawTextBox(text_, box, style);
        } else {
            auto countCodepoints = [](const std::string& text) -> std::size_t {
                std::size_t count = 0;
                for (unsigned char c : text) {
                    if ((c & 0xC0) != 0x80) {
                        ++count;
                    }
                }
                return count;
            };
            std::size_t glyphs = countCodepoints(text_);
            float approxWidth = size * 0.6f * static_cast<float>(glyphs == 0 ? 1 : glyphs);
            float textX = rect.x + 8.0f;
            float textY = rect.y + size + (rect.h - size) * 0.5f;
            if (font_ && font_->isValid()) {
                list.drawText(text_, {textX, textY}, *font_, textPaint);
            } else {
                list.drawText(text_, {textX, textY}, size, textPaint);
            }
        }
    }
}

bool Button::onPointerDown(const PointerEvent& event) {
    (void)event;
    pressedState_ = true;
    invalidate();
    return true;
}

bool Button::onPointerUp(const PointerEvent& event) {
    (void)event;
    bool wasPressed = pressedState_;
    pressedState_ = false;
    invalidate();
    if (wasPressed && onClick_) {
        onClick_();
    }
    return true;
}

bool Button::onPointerMove(const PointerEvent& event) {
    (void)event;
    return true;
}

void Button::onHoverEnter(const PointerEvent& event) {
    (void)event;
    hovered_ = true;button.h       progress_bar.h slider.h
checkbox.h     radio_button.h status_bar.h
container.h    scroll_area.h  text_edit.h
label.h        scroll_bar.h   title_bar.h

    invalidate();
}

void Button::onHoverLeave(const PointerEvent& event) {
    (void)event;
    hovered_ = false;
    invalidate();
}

void Button::onStyleChanged(const Style& style) {
    if (style.metrics.buttonFontSize > 0.0f) {
        fontSize_ = style.metrics.buttonFontSize;
    }
    if (style.metrics.buttonMinHeight > 0.0f) {
        layout().setMinHeight(style.metrics.buttonMinHeight);
    }
    if (style.cornerRadius > 0.0f) {
        cornerRadius_ = style.cornerRadius;
    }
    if (!colorOverride_) {
        background_ = style.accent.a == 0 ? style.background : style.accent;
        hover_ = applyAlpha(background_, 0.85f);
        pressed_ = applyAlpha(background_, 0.7f);
        textColor_ = style.accentForeground.a == 0 ? style.foreground : style.accentForeground;
        backgroundEnabled_ = true;
    }
    invalidate();
}

}  // namespace gui

And what is the current state of the user and developer documentation?

There is currently no documentation as the api isn't stable yet.
Let me know if you have anymore questions.
 
C++, HML, and inline JavaScript are alive! Take that Qt!! Going to go see if lexbor https://github.com/lexbor/lexbor is in the ports repo so I can add css styling support.

Holy cat crap!! Its actually in there! And it s recent version!!!
sh:
root@freebsd:~/Projects/sdk_freebsd # pkg search lexbor
lexbor-2.6.0                   Modular web engine (HTML/CSS parser, renderer, ...)
root@freebsd:~/Projects/sdk_freebsd #


C++:
#include <iostream>

#include <gui/application.h>
#include <gui/hml_loader.h>
#include <gui/hml_document.h>
#include <gui/window.h>
#include <gui/widget.h>

int main(int argc, char** argv) {

    gui::Application app;
    if (!app.isValid()) {
        std::cerr << "failed to initialize application" << std::endl;
        return 1;
    }

    const char* hmlPath = (argc > 1 && argv[1]) ? argv[1] : "hello.hml";
    auto doc = gui::HmlLoader::loadFromFile(hmlPath);
    if (!doc || !doc->isValid()) {
        std::cerr << "failed to load hml file: " << doc->errorMessage() << std::endl;
        return 1;
    }

    gui::Widget* rootWidget = doc->root();
    if (!rootWidget) {
        std::cerr << "no root widget" << std::endl;
        return 1;
    }

    gui::Window window(app, 400, 200, "FreeBSD Toolkit Demo");
    if (!window.isValid()) {
        std::cerr << "failed to create window" << std::endl;
        return 1;
    }

    doc->onSignal("buttonPressed", []() {
        std::cout << "you pressed a button" << std::endl;
    });


    auto rootShared = std::shared_ptr<gui::Widget>(rootWidget);
    window.setRootWidget(rootShared);

    window.requestFrame();

    app.run();

    return 0;
}

Code:
Window {
    id: "main"
    title: "FreeBSD Toolkit Demo"
    width: 400
    height: 200

    signal buttonPressed()

    Layout {
        direction: "column"
        spacing: 0
        padding: 0

        TitleBar {
            id: "titlebar"
            title: "FreeBSD Toolkit Demo"
            subtitle: "HML + JS"
            background: "#2b3139"
            height: 40

            Left {
                Button {
                    text: "Back"
                }
            }
        }

        Layout {
            direction: "column"
            spacing: 16
            padding: 20

            Text {
                text: "Hello from HML"
                fontSize: 18
                align: "center"
            }

            Button {
                id: "btn"
                text: "Click me"
                onClicked: buttonPressed()
            }
        }

        StatusBar {
            background: "#23272e"
            height: 28
            Left {
                Text {
                    text: "Ready"
                }
            }
            Right {
                Text {
                    text: "v0.1"
                }
            }
        }
    }
}

1770646412356.png
 
Note for future explorers: client-side window drop shadows are not the way!!! Seriously not the wayyyyyy. Drop shadows belong in the compositor. Doing them client-side leads to inconsistent geometry, broken hit-testing, and all kinds of subtle layout bugs.

What you’re seeing:
  1. Server-side decorations with server-side drop shadows
    It ensures applications like SDL3, Godot 4, SFML 3, Raylib, etc. look native and visually consistent with the rest of the system. The terminal shown here is foot.
  2. Client-side decorations with server-side drop shadows
    This application is very special, and I’ll go into more detail about it in the next post.
  3. Client-side decorations (GTK) with server-side drop shadows
    Even with GTK’s client-side decorations, shadows are now handled correctly and consistently by the compositor.
The compositor is getting very close to feature complete. Remaining work includes sub-windows, clipboard support, drag-and-drop, tiling mode, and a ton of edge cases, but the core architecture is now solid.


1770682281914.png
 
Once I figure out how to monetize this whether through donations or sponsorships, I’ll start releasing the code. The reality is that continuing this project will require full-time development. Feel free to share your thoughts in the comments.
Well first kudos, because writing something like that is a big project. And for sure a valuable lesson.

Monetising such a thing though sounds really hard to me, because there are so many GUI toolkits already out there. Beside GTK and Qt there's Tk, Athena Widgets, Fltk and many more.

So the question is: what are your goals with that project and what are you making different? What makes you unique?

You've got have some answers ready in order if you want people to support you with money.
 
Thank you for the compliment. It’s not really a learning curve so much as a huge amount of work and figuring out how to work around the limitations of GPU support and tooling that come with developing on FreeBSD. Fortunately, I do have some help.

Since this thread already goes into a lot of technical detail, I’ll summarize the core differences briefly. Tk, Athena Widgets, and FLTK aren’t comparable and I’d even go so far as to say GTK isn’t either. My stack is extremely modern: Wayland-only, GPU-accelerated via Vulkan or OpenGL with CPU fallback, and currently the fastest open-source 2D renderer available. It uses modern layout algorithms for responsive design, and all of the libraries I rely on have corporate backing from Google, Meta, Intel, AMD, and others so they’re virtually guaranteed not to be abandoned anytime soon, since those companies actively use them in their own products.

My SDK isn’t limited to 2D graphics, GUI, or widgets, it also includes audio and video, archiving and compression, HTML5, CSS3, XML, JSON, TOML, cryptography, cloud services, AI, networking, 3D graphics, and more.

It supports Lottie animations, SVG, scene graphs, and render lists in a retained-mode architecture. The stack is built specifically for FreeBSD rather than adapted from a cross-platform abstraction layer. The SDK and applications created with it are intended to remain FreeBSD-focused, giving developers targeting the platform a stronger opportunity to build and sustain a FreeBSD-centric user base.

It's also completely free of viral copyleft licenses so if someone want's to build closed source software they are welcome to do so and I will support them in doing that. Good luck finding that on Linux.

My goal is to build a FreeBSD development community that creates extremely high-quality commercial software that people can sell, prosper from, and ultimately build better lives with. That’s what I want for myself as well.
 
My stack is extremely modern: Wayland-only, GPU-accelerated via Vulkan or OpenGL with CPU fallback, and currently the fastest open-source 2D renderer available. It uses modern layout algorithms for responsive design, and all of the libraries I rely on have corporate backing from Google, Meta, Intel, AMD, and others so they’re virtually guaranteed not to be abandoned anytime soon, since those companies actively use them in their own products.
I'm interested in the fastest performance for fullscreen games and lowest-latency everywhere. I found GNOME 48+ slower with default GSK_RENDERER=vulkan (DE + GTK apps like Text Edit all GPU-rendering) vs cairo not GPU accelerating. I get better latency/perf Xfce disabling compositor. On Windows Steam and Bnet client have better perf in client windows with browser acceleration disabled. I have many cases where CPU-accelerating GUI windows was faster than trying to force it GPU.

Does this stack offer better performance and lower-latency than current solutions? I'm skeptical of mouse input (I use evdev on X but if I understand right libinput on Wayland does translation to evdev). GNOME on Wayland put cursors on timers or something that can make cursor movement inconsistent with high CPU/GPU load (I'm thinking it's a Wayland technical thing/how it has to be)
 
I'm interested in the fastest performance for fullscreen games and lowest-latency everywhere. I found GNOME 48+ slower with default GSK_RENDERER=vulkan (DE + GTK apps like Text Edit all GPU-rendering) vs cairo not GPU accelerating. I get better latency/perf Xfce disabling compositor. On Windows Steam and Bnet client have better perf in client windows with browser acceleration disabled. I have many cases where CPU-accelerating GUI windows was faster than trying to force it GPU.

Does this stack offer better performance and lower-latency than current solutions? I'm skeptical of mouse input (I use evdev on X but if I understand right libinput on Wayland does translation to evdev). GNOME on Wayland put cursors on timers or something that can make cursor movement inconsistent with high CPU/GPU load (I'm thinking it's a Wayland technical thing/how it has to be)
My renderer is hybrid: Vulkan, OpenGL, or CPU can be selected per configuration depending on workload and hardware.

The render path is retained mode with explicit control over frame submission, damage regions, and synchronization. For fullscreen workloads the compositor can run in a minimal pass-through mode, or be bypassed entirely if the game supports direct KMS/DRM.

I’ve also been exploring a console-style configuration: a stripped-down compositor, a dashboard similar to Xbox/PS5/Switch, and a system where the game is effectively the only active client so input, scheduling, and rendering are tuned purely for latency.

On input, we don’t use evdev or libinput we read devices directly via hidraw, keeping us close to the device driver.

I can’t give a definitive performance answer yet because I haven’t done any profiling. What I can say is that Qt’s published benchmarks show Skia performing significantly faster than their own renderer and other open-source options in several workloads.
 
For those of you who play games or are into vfx, I’d love to collaborate and help get your projects running as smoothly as possible. If you’re willing to support my efforts, I’m fully committed to doing everything I can to make that happen.

I also dabble a bit in the world of VFX 😉
You can check out my work here:

 
Ohhhhhh boys!!!!! yes, I am excited! Why? Because I just ripped out Flutter’s newest rendering engine, Impeller(BSD-3). It’s written in C++, and I also pulled their latest Render List, converted the entire build system to CMake, and wired it up to Vulkan then passed it some signed-distance-field GLSL examples from Inigo Quilez… https://iquilezles.org/articles/distfunctions2d/ and wow this is very very promising. SDF shapes are some of the nicest you can get they blend like metaballs. This could replace Skia and open up a ton of possibilities, think macOS-style liquid effects if you’re into that look. It also slashes an insane number of dependencies… easily 35+ gone.

Notice that insanely smooth anti-aliasing.

1770784109089.png


View: https://youtu.be/vd5NqS01rlA?si=BX0m04rDyJXumt-_
 
The graphics2d module now includes the initial implementation of a C API, making it possible to support additional programming languages. The CPU renderer also no longer relies on external dependencies beyond zlib.

1771004326239.png



C:
#include "graphics2d/c_api.h"

#include <stdio.h>

#define CHECK_STEP(expr, code, label)                                              \
  do {                                                                              \
    status = (expr);                                                                \
    if (status != G2D_STATUS_OK) {                                                  \
      fprintf(stderr, "%s failed: %s\n", (label), g2d_status_to_string(status));   \
      g2d_surface_destroy(surface);                                                 \
      g2d_context_destroy(context);                                                 \
      return (code);                                                                \
    }                                                                               \
  } while (0)

int main(void) {
  g2d_context_t* context = NULL;
  g2d_surface_t* surface = NULL;
  g2d_display_list_builder_t* dl_builder = NULL;
  g2d_display_list_t* dl = NULL;

  const g2d_context_desc_t context_desc = {
      .backend = G2D_BACKEND_SOFTWARE,
      .enable_validation = false,
  };
  const g2d_surface_desc_t surface_desc = {
      .width = 3840,
      .height = 2160,
  };

  g2d_status_t status = g2d_context_create(&context_desc, &context);
  if (status != G2D_STATUS_OK) {
    fprintf(stderr, "g2d_context_create failed: %s\n", g2d_status_to_string(status));
    return 1;
  }

  status = g2d_surface_create_raster(context, &surface_desc, &surface);
  if (status != G2D_STATUS_OK) {
    fprintf(stderr, "g2d_surface_create_raster failed: %s\n", g2d_status_to_string(status));
    g2d_context_destroy(context);
    return 2;
  }

  CHECK_STEP(g2d_surface_clear_rgba8(surface, 10, 12, 18, 255), 3,
             "g2d_surface_clear_rgba8");

  // Vertical gradient bands (simulated gradient with many filled strips).
  for (int y = 0; y < 2160; y += 8) {
    const int t = (y * 255) / 2159;
    const uint8_t r = (uint8_t)(12 + (t * 18) / 255);
    const uint8_t g = (uint8_t)(18 + (t * 44) / 255);
    const uint8_t b = (uint8_t)(30 + (t * 88) / 255);
    CHECK_STEP(g2d_surface_fill_rect_rgba8(surface, 0, y, 3840, 8, r, g, b, 255), 4,
               "g2d_surface_fill_rect_rgba8(gradient)");
  }

  // Soft center glow.
  for (int radius = 1100; radius >= 120; radius -= 28) {
    const int d = 1100 - radius;
    const uint8_t a = (uint8_t)(10 + (d * 55) / 980);
    CHECK_STEP(g2d_surface_fill_circle_rgba8(surface, 1920, 920, (uint32_t)radius, 70, 130,
                                             220, a),
               5, "g2d_surface_fill_circle_rgba8(glow)");
  }

  // Grid lines for depth.
  for (int x = 0; x < 3840; x += 160) {
    CHECK_STEP(g2d_surface_draw_line_rgba8(surface, x, 0, x, 2159, 1, 255, 255, 255, 30), 6,
               "g2d_surface_draw_line_rgba8(grid-v)");
  }
  for (int y = 0; y < 2160; y += 120) {
    CHECK_STEP(g2d_surface_draw_line_rgba8(surface, 0, y, 3839, y, 1, 255, 255, 255, 24), 7,
               "g2d_surface_draw_line_rgba8(grid-h)");
  }

  // Main cards/panels.
  CHECK_STEP(g2d_surface_fill_rect_rgba8(surface, 180, 120, 1180, 660, 240, 190, 80, 220), 8,
             "g2d_surface_fill_rect_rgba8(panel-a)");
  CHECK_STEP(g2d_surface_draw_rect_rgba8(surface, 160, 100, 1220, 700, 14, 255, 255, 255, 255),
             9, "g2d_surface_draw_rect_rgba8(panel-a)");
  CHECK_STEP(g2d_surface_fill_rect_rgba8(surface, 2460, 260, 1160, 760, 120, 210, 255, 195), 10,
             "g2d_surface_fill_rect_rgba8(panel-b)");
  CHECK_STEP(g2d_surface_draw_rect_rgba8(surface, 2430, 230, 1220, 820, 14, 255, 255, 255, 240),
             11, "g2d_surface_draw_rect_rgba8(panel-b)");
  CHECK_STEP(g2d_surface_fill_round_rect_rgba8(surface, 420, 340, 760, 300, 56, 56, 40, 70, 120,
                                               205),
             12, "g2d_surface_fill_round_rect_rgba8(card)");
  CHECK_STEP(g2d_surface_draw_round_rect_rgba8(surface, 390, 312, 820, 356, 72, 72, 8, 255, 255,
                                               255, 220),
             13, "g2d_surface_draw_round_rect_rgba8(card)");

  // Circles and rings.
  CHECK_STEP(g2d_surface_fill_circle_rgba8(surface, 2860, 620, 250, 120, 210, 255, 230), 14,
             "g2d_surface_fill_circle_rgba8(main)");
  CHECK_STEP(g2d_surface_draw_circle_rgba8(surface, 2860, 620, 340, 16, 255, 255, 255, 245), 15,
             "g2d_surface_draw_circle_rgba8(main)");
  for (int r = 420; r <= 760; r += 85) {
    CHECK_STEP(g2d_surface_draw_circle_rgba8(surface, 2860, 620, (uint32_t)r, 4, 180, 220, 255,
                                             115),
               16, "g2d_surface_draw_circle_rgba8(rings)");
  }

  // Burst lines.
  for (int i = 0; i <= 12; i++) {
    const int x0 = 1920 + (i - 6) * 280;
    const int y0 = 260;
    const int x1 = 1900 + (i - 6) * 340;
    const int y1 = 1960;
    CHECK_STEP(g2d_surface_draw_line_rgba8(surface, x0, y0, x1, y1, 3, 255, 255, 255, 90), 17,
               "g2d_surface_draw_line_rgba8(burst)");
  }

  // Curves and path strokes.
  CHECK_STEP(g2d_surface_draw_quadratic_bezier_rgba8(surface, 220, 980, 1180, 620, 2100, 1120, 12,
                                                     255, 170, 120, 230),
             18, "g2d_surface_draw_quadratic_bezier_rgba8(curve-a)");
  CHECK_STEP(g2d_surface_draw_quadratic_bezier_rgba8(surface, 200, 1160, 1180, 760, 2120, 1280,
                                                     5, 255, 240, 220, 180),
             19, "g2d_surface_draw_quadratic_bezier_rgba8(curve-b)");
  CHECK_STEP(g2d_surface_draw_cubic_bezier_rgba8(surface, 1420, 320, 1780, 20, 3340, 1220, 3620,
                                                 880, 9, 120, 240, 255, 235),
             20, "g2d_surface_draw_cubic_bezier_rgba8(cubic-a)");
  CHECK_STEP(g2d_surface_draw_cubic_bezier_rgba8(surface, 1380, 500, 1860, 220, 3360, 1400, 3600,
                                                 1120, 4, 235, 255, 255, 170),
             21, "g2d_surface_draw_cubic_bezier_rgba8(cubic-b)");

  const g2d_point_i32_t zig_path[] = {
      {360, 1240}, {540, 1110}, {720, 1275}, {900, 1140}, {1080, 1310},
      {1260, 1180}, {1440, 1350}, {1620, 1210}, {1800, 1395},
  };
  CHECK_STEP(g2d_surface_draw_path_rgba8(surface, zig_path,
                                         (uint32_t)(sizeof(zig_path) / sizeof(zig_path[0])),
                                         false, 8, 150, 255, 190, 230),
             22, "g2d_surface_draw_path_rgba8(zig)");

  const g2d_point_i32_t poly_loop[] = {
      {2350, 1260}, {2530, 1120}, {2800, 1135}, {2970, 1305},
      {2900, 1540}, {2620, 1600}, {2400, 1470},
  };
  CHECK_STEP(g2d_surface_draw_path_rgba8(surface, poly_loop,
                                         (uint32_t)(sizeof(poly_loop) / sizeof(poly_loop[0])),
                                         true, 6, 255, 220, 120, 210),
             23, "g2d_surface_draw_path_rgba8(loop)");

  // Small decorative nodes.
  for (int i = 0; i < 18; i++) {
    const int x = 220 + i * 200;
    const int y = 1840 + (i % 3) * 70;
    const int rr = 10 + (i % 4) * 5;
    CHECK_STEP(g2d_surface_fill_circle_rgba8(surface, x, y, (uint32_t)rr, 255, 210, 120, 230),
               24, "g2d_surface_fill_circle_rgba8(nodes)");
  }

  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8(surface, 180, 1420, 7, "GRAPHICS2D 4K SCENE",
                                              255, 255, 255, 255),
             25, "g2d_surface_draw_text_utf8_rgba8(title)");
  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8(surface, 184, 1540, 4,
                                              "more shapes  roundrects  curves  paths",
                                              220, 235, 255, 255),
             26, "g2d_surface_draw_text_utf8_rgba8(subtitle)");
  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8_font_file(
                 surface, 180, 1668, 4, "Inter Variable - headline",
                 "assets/fonts/Inter-4.1/InterVariable.ttf", 255, 245, 210, 255),
             27, "g2d_surface_draw_text_utf8_rgba8_font_file(inter)");
  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8_font_file(
                 surface, 180, 1752, 3, "IBM Plex Sans - system label",
                 "assets/fonts/IBM_Plex_Sans/IBMPlexSans-VariableFont_wdth,wght.ttf", 215,
                 238, 255, 245),
             28, "g2d_surface_draw_text_utf8_rgba8_font_file(plex)");
  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8_font_file(
                 surface, 180, 1826, 3, "JetBrains Mono - diagnostics",
                 "assets/fonts/JetBrainsMono-2.304/fonts/ttf/JetBrainsMono-Bold.ttf", 190,
                 255, 220, 240),
             29, "g2d_surface_draw_text_utf8_rgba8_font_file(jetbrains)");
  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8_aligned(surface, 2860, 620, 3, "rings + glow",
                                                      G2D_TEXT_ALIGN_CENTER,
                                                      G2D_TEXT_BASELINE_MIDDLE, 10, 22, 40, 245),
             30, "g2d_surface_draw_text_utf8_rgba8_aligned(node-label)");

  const char* app_icons[] = {
      "assets/icons/src/scalable/apps/blender.svg",
      "assets/icons/src/scalable/apps/chromium-browser.svg",
      "assets/icons/src/scalable/apps/cmake.svg",
      "assets/icons/src/scalable/apps/audacity.svg",
      "assets/icons/src/scalable/apps/arduino.svg",
      "assets/icons/src/scalable/apps/calibre.svg",
      "assets/icons/src/scalable/apps/atom.svg",
      "assets/icons/src/scalable/apps/clementine.svg",
      "assets/icons/src/scalable/apps/cheese.svg",
      "assets/icons/src/scalable/apps/anki.svg",
      "assets/icons/src/scalable/apps/bitcoin.svg",
      "assets/icons/src/scalable/apps/brave.svg",
  };

  for (int i = 0; i < 12; i++) {
    const int col = i % 6;
    const int row = i / 6;
    const int px = 1460 + col * 380;
    const int py = 1200 + row * 430;
    CHECK_STEP(g2d_surface_draw_svg_file(surface, app_icons[i], px, py, 260, 260, 235), 31,
               "g2d_surface_draw_svg_file(icon)");
    CHECK_STEP(g2d_surface_draw_rect_rgba8(surface, px - 16, py - 16, 292, 292, 4, 255, 255,
                                           255, 90),
               32, "g2d_surface_draw_rect_rgba8(icon-frame)");
  }

  status = g2d_display_list_builder_create(&dl_builder);
  if (status != G2D_STATUS_OK) {
    fprintf(stderr, "g2d_display_list_builder_create failed: %s\n", g2d_status_to_string(status));
    g2d_surface_destroy(surface);
    g2d_context_destroy(context);
    return 33;
  }
  CHECK_STEP(g2d_display_list_builder_fill_rect_rgba8(dl_builder, 0, 0, 760, 220, 16, 24, 40, 170),
             34, "g2d_display_list_builder_fill_rect_rgba8");
  CHECK_STEP(g2d_display_list_builder_draw_round_rect_rgba8(dl_builder, 0, 0, 760, 220, 34, 34, 6,
                                                            255, 255, 255, 220),
             35, "g2d_display_list_builder_draw_round_rect_rgba8");
  const g2d_point_i32_t dl_wave[] = {
      {30, 140}, {120, 70}, {220, 154}, {330, 62}, {440, 165}, {560, 76}, {690, 145},
  };
  CHECK_STEP(g2d_display_list_builder_draw_path_rgba8(
                 dl_builder, dl_wave, (uint32_t)(sizeof(dl_wave) / sizeof(dl_wave[0])), false,
                 7, 140, 255, 200, 235),
             36, "g2d_display_list_builder_draw_path_rgba8");
  status = g2d_display_list_builder_build(dl_builder, &dl);
  if (status != G2D_STATUS_OK) {
    fprintf(stderr, "g2d_display_list_builder_build failed: %s\n", g2d_status_to_string(status));
    g2d_display_list_builder_destroy(dl_builder);
    g2d_surface_destroy(surface);
    g2d_context_destroy(context);
    return 37;
  }
  CHECK_STEP(g2d_surface_draw_display_list(surface, dl, 220, 1890), 38,
             "g2d_surface_draw_display_list(a)");
  CHECK_STEP(g2d_surface_draw_display_list(surface, dl, 2880, 1860), 39,
             "g2d_surface_draw_display_list(b)");

  CHECK_STEP(g2d_surface_draw_text_utf8_rgba8_aligned(
                 surface, 1920, 2040, 4, "assets/smoke_scene_4k.ppm", G2D_TEXT_ALIGN_CENTER,
                 G2D_TEXT_BASELINE_BOTTOM, 255, 220, 120, 255),
             40, "g2d_surface_draw_text_utf8_rgba8_aligned(footer)");

  status = g2d_surface_save_ppm(surface, "assets/smoke_scene_4k.ppm");
  if (status != G2D_STATUS_OK) {
    fprintf(stderr, "g2d_surface_save_ppm failed: %s\n", g2d_status_to_string(status));
    g2d_surface_destroy(surface);
    g2d_context_destroy(context);
    g2d_display_list_destroy(dl);
    g2d_display_list_builder_destroy(dl_builder);
    return 41;
  }

  status = g2d_surface_save_png(surface, "assets/smoke_scene_4k.png");
  if (status != G2D_STATUS_OK && status != G2D_STATUS_NOT_IMPLEMENTED) {
    fprintf(stderr, "g2d_surface_save_png failed: %s\n", g2d_status_to_string(status));
    g2d_surface_destroy(surface);
    g2d_context_destroy(context);
    g2d_display_list_destroy(dl);
    g2d_display_list_builder_destroy(dl_builder);
    return 42;
  }

  printf("Wrote assets/smoke_scene_4k.ppm (%ux%u).\n",
         g2d_surface_get_width(surface),
         g2d_surface_get_height(surface));
  if (status == G2D_STATUS_OK) {
    printf("Wrote assets/smoke_scene_4k.png.\n");
  } else {
    printf("PNG writer unavailable in this build.\n");
  }

  g2d_display_list_destroy(dl);
  g2d_display_list_builder_destroy(dl_builder);
  g2d_surface_destroy(surface);
  g2d_context_destroy(context);
  return 0;
}
 
Website Development & Domain

I chose the domain name osforge.org to evoke a place where developers can shape their own vision of FreeBSD. The goal is to allow users to hand-pick their desktop shell, compositor, panels, docks, file manager, and more assembling something unique instead of another look-alike environment. The platform would provide software distribution, tools, SDKs, documentation, and guidance so developers and users can build their own FreeBSD experience.

My long-term hope is that, if the project proves useful, it could eventually operate under community governance with an elected board. At that point I would prefer to step back into a support and development role rather than direct the project.

The site is currently independent and not affiliated with or endorsed by the FreeBSD Project or Foundation.

The website itself was designed using Bootstrap Studio. https://bootstrapstudio.io/

1771125303714.png
 
The compositor and the desktop supports multiple monitors now. And I just realized that monitor color settings are not the same. Lol ;)

1771220654361.jpeg
 
Now we have the web browser widget working in hml/js and c++ its not perfect but its a start, also this is being shown on fedora because this library is especially
hard to get working on FreeBSD.

 
Today I am working on refactoring the GUI module, and I also did a LOC count.
Code:
  - modules/audio:          45 files, 10,450 lines
  - modules/compression:    5 files, 282 lines
  - modules/core:                18 files, 860 lines
  - modules/cryptography:     5 files, 349 lines
  - modules/data:                 38 files, 8,777 lines   (hml, toml, json, xml, css, html, ..)
  - modules/graphics2d:      65 files, 4,551 lines
  - modules/graphics3d:      56 files, 4,637 lines
  - modules/gui:                   63 files, 15,794 lines
  - modules/math:                  2 files, 119 lines
  - modules/physics3d:          2 files, 276 lines
  - modules/scripting:          29 files, 4,537 lines
  - modules/system:              2 files, 206 lines
  - modules/tools:                11 files, 2,358 lines
  - modules/video:               23 files, 1,842 lines
  - modules/widgets:            57 files, 11,362 lines

  Total: 421 files, 66,400 lines.

Some really exciting news: we will have a BSD-licensed replacement for FFmpeg and JUCE (https://juce.com/). The only issue I’m having with audio is the Steinberg VST3 SDK. It’s going to take me at least six months, if not longer, just to add VST3 support or I can skip it and develop a new audio plugin architecture.

In addition, my employer will be allowing me and several of my coworkers to spend two hours a day working on this project, and has donated our company's’ version control system developed in-house now licensed BSD3. It's the same design as Git but hyper optimized and with a few modern architectural differences. The neatest part about it is that it runs in Cloudflare!

The audio module uses rtaudio, rtmidi, stk, dr_libs, ebur128, flac, kissfft, libmysofa, ogg, opus, q, rnnoise, signalsmith_dsp, signalsmith_stretch, speexdsp, vst3sdk(not implemented yet) and Spatial_Audio_Framework

Vocal Comping + Clip Gain + Cleanup Chain Example.

C++:
#include "audio/editor_api.hpp"

audio::EditorSession editor;
editor.openProject("vocals_tracking.audio");

editor.comp().beginLaneComp("Lead Vox");
editor.comp().promoteTake("Lead Vox", /*lane=*/2, /*start=*/8 * 48000, /*end=*/12 * 48000);
editor.comp().promoteTake("Lead Vox", /*lane=*/1, /*start=*/12 * 48000, /*end=*/16 * 48000);
editor.comp().flattenToMainLane("Lead Vox");

editor.timeline().selectRange("Lead Vox", 8 * 48000, 16 * 48000);
editor.timeline().createClipGainEnvelope();
editor.timeline().setClipGainPoint(8 * 48000, -3.0f);
editor.timeline().setClipGainPoint(12 * 48000, 0.0f);
editor.timeline().setClipGainPoint(16 * 48000, -1.5f);

auto& strip = editor.mixer().track("Lead Vox");
strip.insertFx("HighPass");
strip.setParam("HighPass", "frequency_hz", 80.0f);
strip.insertFx("DeEsser");
strip.setParam("DeEsser", "frequency_hz", 6500.0f);
strip.setParam("DeEsser", "amount", 0.35f);
strip.insertFx("Compressor");
strip.setParam("Compressor", "ratio", 3.0f);
strip.setParam("Compressor", "attack_ms", 12.0f);
strip.setParam("Compressor", "release_ms", 70.0f);

editor.saveProject("vocals_comped.audio");
 
This is a DAW (Digital Audio Workstation) template essentially a starter application. The mixer panel and the arrangement view are built as components, and the entire application itself functions as a template. I recently had to begin rewriting parts of the graphics stack because I didn’t properly define the separation between the backend and the UI. That lack of separation caused significant architectural problems within the template.

As I mentioned earlier, we’re using a layered system made up of primitives > widgets > components > templates. The backend handles all processing and core functionality, and it feeds into a GUI built with C++, HTML, and CSS.


 
For the past 14 years, I’ve been teaching myself how to build a GUI toolkit in C++. My dream has always been to be the first to bring a bsd licensed gui toolkit and desktop environment to freebsd built from scratch.

I was even featured on phoronix https://www.phoronix.com/news/MTE4NTk in 2012 when I was still a Qt dev this was my breaking point when I decided I was going to do my own toolkit.

Day one - January 24, 2012:
https://stackoverflow.com/questions/6639640/want-to-learn-graphics-using-skia-on-ubuntu
View attachment 25238

See 14 years of me working it out https://gist.github.com/zester
I was stitching together random code snippets, experimenting through pure trial and error, just trying to make anything work.

This video marks my eureka moment when things finally started to click. I had spent years fighting performance issues because I was massively overthinking the problem and over-engineering my rendering engine. I was convinced I needed a scene graph with a renderer manager, state managers, focus managers, everything talking back and forth to decide what needed to be re-rendered, when, and how. In reality, that approach was a dead end and led to an overly complex, fragile design. The real solution turned out to be much simpler a flat render list. With something as complex as a GUI toolkit, performance problems don’t hit you all at once. They quietly build up over time until you’re suddenly wondering why something as basic as resizing a window is lagging.

The video below shows the GUI toolkit, compositor, and desktop shell.

This is the desktop environment itself. It’s built on wlroots, Skia, Yoga, and libvterm for the term, and it’s inspired by the Meta Quest UI.
Building a terminal emulator sucks!!!!! Easily more complex than anything else you’ll see here.
View attachment 25237

Here’s an example of animating vector graphics:

And this is an early test of integrating Google Filament into the GUI toolkit. It works well overall, but I’m still fighting some object-picking issues. At the moment, this only works on Linux.

I also ended up building a new pkg tool and port system for this project because freebsd port system and how packages are built is not cool!!!

Once I figure out how to monetize this whether through donations or sponsorships, I’ll start releasing the code. The reality is that continuing this project will require full-time development. Feel free to share your thoughts in the comments.
This guy does FreeBSD, I tell ya
 
This is great—this is exactly what I was expecting and want to achieve. I hope this project can find a way to get sponsorship and be released under a permissive license, with dependencies also under permissive licenses, without any GPL or LGPL-like components. I have an immature idea: perhaps it could serve as a backend for xtd(https://github.com/gammasoft71/xtd), enabling native cross-platform FreeBSD desktop support for xtd.
 
This is great—this is exactly what I was expecting and want to achieve. I hope this project can find a way to get sponsorship and be released under a permissive license, with dependencies also under permissive licenses, without any GPL or LGPL-like components. I have an immature idea: perhaps it could serve as a backend for xtd(https://github.com/gammasoft71/xtd), enabling native cross-platform FreeBSD desktop support for xtd.
I’m not entirely sure how useful my project would be to xtd in its current form.

We’re both using fluent, chainable styles. xtd relies on multiple backends (Qt5, GTK3, GTK4, FLTK, etc.), while my framework doesn't need to use a thirdparty backend and already supports Linux, FreeBSD, Windows, Android, iOS, macOS, and WebAssembly, its just a matter of building it for those platforms and fixing whtever bugs appear.

If you were to integrate my project into xtd, it would essentially mean placing xtd abstraction layer on top of my abstraction layer. It would likely make more sense to combine efforts directly and build a larger, unified project rather than layering one system on top of another.
 
Back
Top