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

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
APb85.png


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.
Untitled.jpg


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.
 
Last edited:
The system takes care of a graphical screen without any X-server running but it does run Xorg programs on it?
Well wlroots https://gitlab.freedesktop.org/wlroots/wlroots/ which is the c library we use to build the compositor, it does in fact support xwayland which is the protocol for running xorg programs on wayland compositors, now I haven't tested that feature and the compositor as it is now doesn't currently have support for xwayland, but implementing that feature is certainly a possibility if others need that capability. Out of curiosity do you have a specific xorg program you have in mind you would like to see work?
 
Well wlroots https://gitlab.freedesktop.org/wlroots/wlroots/ which is the c library we use to build the compositor, it does in fact support xwayland which is the protocol for running xorg programs on wayland compositors, now I haven't tested that feature and the compositor as it is now doesn't currently have support for xwayland, but implementing that feature is certainly a possibility if others need that capability. Out of curiosity do you have a specific xorg program you have in mind you would like to see work?
Not in particullar but I'm interested in having a controllable 2d display show fast pixel graphics on all working resolutions while not depending on anything else for graphics.
Problem is that GPU manufacturers are doing everything to prevent this because it would provide a platform that utilizes all of the hardware except 3d accelleration without the need of anything commercial. They fear open graphics because it can become serious competition that's non-profit. While not being an insider at all, I don't trust the X,org support and think they take money as donations to keep real control away from the end-user.
 
so it's from scratch but it uses wlroots? lol
As this is a wayland based compositor, that should be as "from scratch" as anyone should go. Even basing on wlroots the amount of work necessary to bring up a partially working "compositor" is insane. And that is without the extra stuff wlroots still doesn't support but that users expect.

So kudos to you zester for enduring this long road and seeing it to a functional point.
If it works for you that's all you need, opening the code to others is a bonus :)
 
You R really cool!!!
I'm new 2 FreeBSD and I'm a boy from China who is 14.
I'm happy to install FreeBSD,It's interesting!
Hmm,I wanna talk to somebody just for fun and improving my English
Could you plz B my Friends???:):):)
 
I like the technical approach of this project. I dislike the visuals, heavily.
Don't take it as a criticism, of course aesthetics is subjective. But keep in mind that a lot of people are running away from the so called flat interfaces and rounded rectangles.

Building a terminal emulator sucks!!!!! Easily more complex than anything else you’ll see here
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!!!

The hardest thing about OS Desktop is not the window manager and compositor, it is the utilities.

Just to realize the computer management 'applet' on FreeBSD at the quality of 25 year old Microsoft one would require whole project and a lot of time.

Personally, a FreeBSD-first desktop would have these kind of traits. UI management of ZFS, network, jails.
A FreeBSD-native desktop that brings its display server, wm, and toolkit is great, but it should be the beggining of the road.
Because left as is, it doesn't have any functional benefits over standard portable *nix X11/WM/toolkit combo.

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.

I wouldn't tie donations or sponsorships to fate of the project because similar projects that were more ahead in development, used generic libraries, and did not ask for sponsorhips, went stale, never were attractive enough to get commiters/maintainers.

You're more likely to get help through the community efforts.

Btw. great work, I wish I had direction and focus to keep one project alive and going for so long.
 
I like the technical approach of this project. I dislike the visuals, heavily.
Don't take it as a criticism, of course aesthetics is subjective. But keep in mind that a lot of people are running away from the so called flat interfaces and rounded rectangles.



The hardest thing about OS Desktop is not the window manager and compositor, it is the utilities.

Just to realize the computer management 'applet' on FreeBSD at the quality of 25 year old Microsoft one would require whole project and a lot of time.

Personally, a FreeBSD-first desktop would have these kind of traits. UI management of ZFS, network, jails.
A FreeBSD-native desktop that brings its display server, wm, and toolkit is great, but it should be the beggining of the road.
Because left as is, it doesn't have any functional benefits over standard portable *nix X11/WM/toolkit combo.



I wouldn't tie donations or sponsorships to fate of the project because similar projects that were more ahead in development, used generic libraries, and did not ask for sponsorhips, went stale, never were attractive enough to get commiters/maintainers.

You're more likely to get help through the community efforts.

Btw. great work, I wish I had direction and focus to keep one project alive and going for so long.
The default visual style reflects what "I" can comfortably design while still finding it visually pleasing, but the UI is fully theme-able.

The toolkit is designed around a core engine that provides primitive building blocks, images, text, layout, shapes (ovals, rounded rectangles), and containers for clipping. Using these primitives, widgets are constructed; collections of widgets then form higher-level components.

The canvas and timeline in the video are components, the vector art and animation capabilitys are built in so its just a matter of wiring components to other ui elements, this is for rapid app development. You could have basic c++ programming capabilitys and reproduce the example in the video.


My point about donations or sponsorships is that this project exists because it’s a passion of mine. That said, I have a family to support, and I also believe the work I’m doing has real value and deserves to be compensated. Too often, open-source developers are expected to invest enormous amounts of time and effort. That time comes at a real cost. My wife and kids would understandably prefer that I spend more of it with them.

To be clear, I’m not even ready to release anything yet. Development is still ongoing, and I’m currently working through some Wayland related issues on FreeBSD, particularly with NVIDIA GPUs.

Thank you for the complament and responding.
 
You R really cool!!!
I'm new 2 FreeBSD and I'm a boy from China who is 14.
I'm happy to install FreeBSD,It's interesting!
Hmm,I wanna talk to somebody just for fun and improving my English
Could you plz B my Friends???:):):)

Ahh thank you so much, I wish my teen children thought I was really cool still!! I hear china has really good food. Yes freebsd is very interesting, I really like how its one unified core system instead of various parts put together to build a os like you see on linux.
 
As this is a wayland based compositor, that should be as "from scratch" as anyone should go. Even basing on wlroots the amount of work necessary to bring up a partially working "compositor" is insane. And that is without the extra stuff wlroots still doesn't support but that users expect.

So kudos to you zester for enduring this long road and seeing it to a functional point.
If it works for you that's all you need, opening the code to others is a bonus :)

Thank you for the kind words, would you like to be a beta tester?
 
Here is a code example and couple of screenshots of the graphics2d module.

C++:
#include <graphics2d/color.h>
#include <graphics2d/font.h>
#include <graphics2d/image.h>
#include <graphics2d/paint.h>
#include <graphics2d/painter.h>
#include <graphics2d/pattern.h>
#include <graphics2d/path.h>
#include <graphics2d/point.h>
#include <graphics2d/rect.h>
#include <graphics2d/shape.h>
#include <graphics2d/surface.h>

#include <cstdlib>
#include <iostream>
#include <string>

namespace {
std::string utf8FromCodepoint(std::uint32_t codepoint) {
    std::string out;
    if (codepoint <= 0x7F) {
        out.push_back(static_cast<char>(codepoint));
    } else if (codepoint <= 0x7FF) {
        out.push_back(static_cast<char>(0xC0 | (codepoint >> 6)));
        out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
    } else if (codepoint <= 0xFFFF) {
        out.push_back(static_cast<char>(0xE0 | (codepoint >> 12)));
        out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
        out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
    } else {
        out.push_back(static_cast<char>(0xF0 | (codepoint >> 18)));
        out.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
        out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
        out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
    }
    return out;
}
}  // namespace

int main() {
    auto surface = graphics2d::Surface::makeRaster(800, 600);
    if (!surface) {
        std::cerr << "Failed to create surface." << std::endl;
        return EXIT_FAILURE;
    }

    surface->clear(graphics2d::Color::fromRGBA(20, 24, 28));

    graphics2d::Painter painter = surface->painter();

    graphics2d::Paint fill;
    fill.setColor(graphics2d::Color::fromRGBA(76, 144, 235));
    painter.drawCircle({200.0f, 200.0f}, 120.0f, fill);

    graphics2d::Paint stroke;
    stroke.setStyle(graphics2d::Paint::Style::Stroke);
    stroke.setStrokeWidth(8.0f);
    stroke.setColor(graphics2d::Color::fromRGBA(239, 178, 53));
    graphics2d::RectShape rectShape({320.0f, 120.0f, 360.0f, 240.0f});
    rectShape.draw(painter, stroke);

    graphics2d::Paint dashed;
    dashed.setStyle(graphics2d::Paint::Style::Stroke);
    dashed.setStrokeWidth(6.0f);
    dashed.setStrokePreset(graphics2d::Paint::StrokePreset::Smooth);
    dashed.setColor(graphics2d::Color::fromRGBA(76, 144, 235));
    dashed.setDash(18.0f, 10.0f);
    graphics2d::RectShape dashRect({320.0f, 380.0f, 220.0f, 120.0f});
    dashRect.draw(painter, dashed);

    graphics2d::RoundedRectShape roundRect({100.0f, 260.0f, 180.0f, 100.0f}, 18.0f, 18.0f);
    roundRect.draw(painter, fill);

    graphics2d::OvalShape oval({100.0f, 380.0f, 180.0f, 90.0f});
    oval.draw(painter, stroke);

    graphics2d::Path path;
    path.moveTo({520.0f, 320.0f});
    path.cubicTo({620.0f, 260.0f}, {700.0f, 360.0f}, {600.0f, 420.0f});
    path.lineTo({520.0f, 360.0f});
    path.close();
    graphics2d::PathShape pathShape(std::move(path));
    pathShape.draw(painter, fill);

    graphics2d::Path circlePath;
    circlePath.moveTo({200.0f, 220.0f});
    circlePath.cubicTo({240.0f, 140.0f}, {340.0f, 140.0f}, {340.0f, 220.0f});
    circlePath.cubicTo({340.0f, 300.0f}, {240.0f, 300.0f}, {200.0f, 220.0f});
    circlePath.close();
    graphics2d::Path rectPath;
    rectPath.moveTo({240.0f, 180.0f});
    rectPath.lineTo({360.0f, 180.0f});
    rectPath.lineTo({360.0f, 300.0f});
    rectPath.lineTo({240.0f, 300.0f});
    rectPath.close();
    graphics2d::Path diffPath = graphics2d::Path::combine(
        circlePath,
        rectPath,
        graphics2d::Path::Op::Difference
    );
    graphics2d::Paint diffFill;
    diffFill.setColor(graphics2d::Color::fromRGBA(216, 76, 76));
    painter.drawPath(diffPath, diffFill);

    graphics2d::PolygonShape polygon({
        {180.0f, 120.0f},
        {240.0f, 140.0f},
        {220.0f, 200.0f},
        {140.0f, 190.0f},
    });
    graphics2d::Paint bevelStroke = stroke;
    bevelStroke.setStrokePreset(graphics2d::Paint::StrokePreset::Sharp);
    bevelStroke.setStrokeJoin(graphics2d::Paint::StrokeJoin::Bevel);
    bevelStroke.setStrokeCap(graphics2d::Paint::StrokeCap::Square);
    bevelStroke.setStrokeMiterLimit(2.0f);
    polygon.draw(painter, bevelStroke);

    graphics2d::Paint text;
    text.setColor(graphics2d::Color::fromRGBA(255, 255, 255));
    painter.drawText("Hultrix Graphics2D", {140.0f, 520.0f}, 48.0f, text);

    graphics2d::Paint linear;
    linear.setLinearGradient(
        {140.0f, 500.0f},
        {420.0f, 520.0f},
        {
            graphics2d::Color::fromRGBA(76, 144, 235),
            graphics2d::Color::fromRGBA(239, 178, 53),
            graphics2d::Color::fromRGBA(216, 76, 76),
        },
        {0.0f, 0.5f, 1.0f}
    );
    painter.drawText("Gradient Text", {140.0f, 560.0f}, 32.0f, linear);

    graphics2d::Paint radial;
    radial.setRadialGradient(
        {220.0f, 160.0f},
        60.0f,
        {
            graphics2d::Color::fromRGBA(239, 178, 53),
            graphics2d::Color::fromRGBA(20, 24, 28),
        },
        {0.0f, 1.0f}
    );
    graphics2d::CircleShape glow({220.0f, 160.0f}, 60.0f);
    glow.draw(painter, radial);

    graphics2d::Paint blendA;
    blendA.setColor(graphics2d::Color::fromRGBA(76, 144, 235, 200));
    graphics2d::Paint blendB;
    blendB.setColor(graphics2d::Color::fromRGBA(239, 178, 53, 200));
    blendB.setBlendMode(graphics2d::Paint::BlendMode::Xor);
    graphics2d::CircleShape blendCircle({420.0f, 180.0f}, 60.0f);
    graphics2d::RoundedRectShape blendRect({380.0f, 140.0f, 120.0f, 120.0f}, 20.0f, 20.0f);
    blendCircle.draw(painter, blendA);
    blendRect.draw(painter, blendB);

    graphics2d::Paint sweep;
    sweep.setStrokePreset(graphics2d::Paint::StrokePreset::Smooth);
    sweep.setSweepGradient(
        {640.0f, 120.0f},
        0.0f,
        300.0f,
        {
            graphics2d::Color::fromRGBA(216, 76, 76),
            graphics2d::Color::fromRGBA(239, 178, 53),
            graphics2d::Color::fromRGBA(76, 144, 235),
            graphics2d::Color::fromRGBA(216, 76, 76),
        },
        {0.0f, 0.4f, 0.8f, 1.0f}
    );
    graphics2d::CircleShape sweepDisk({640.0f, 120.0f}, 60.0f);
    sweepDisk.draw(painter, sweep);

    graphics2d::Paint conic;
    conic.setConicalGradient(
        {640.0f, 240.0f},
        10.0f,
        {700.0f, 260.0f},
        80.0f,
        {
            graphics2d::Color::fromRGBA(35, 38, 45),
            graphics2d::Color::fromRGBA(76, 144, 235),
            graphics2d::Color::fromRGBA(239, 178, 53),
        },
        {0.0f, 0.5f, 1.0f}
    );
    graphics2d::OvalShape conicOval({600.0f, 200.0f, 160.0f, 120.0f});
    conicOval.draw(painter, conic);

    graphics2d::Paint resetPaint = sweep;
    resetPaint.reset();
    resetPaint.setStyle(graphics2d::Paint::Style::Stroke);
    resetPaint.setStrokeWidth(4.0f);
    resetPaint.setColor(graphics2d::Color::fromRGBA(180, 180, 180));
    graphics2d::RectShape resetRect({560.0f, 320.0f, 160.0f, 80.0f});
    resetRect.draw(painter, resetPaint);

    auto inter = graphics2d::Font::fromFile(
        "assets/fonts/Inter-4.1/InterVariable.ttf",
        36.0f
    );
    if (inter && inter->isValid()) {
        painter.drawText("Inter Variable", {140.0f, 470.0f}, *inter, text);
    }

    auto icons = graphics2d::Font::fromFile(
        "assets/fonts/material-design-icons-4.0.0/MaterialIconsRound-Regular.otf",
        96.0f
    );
    if (icons && icons->isValid()) {
        const std::string icon = utf8FromCodepoint(0xE84D);
        painter.drawText(icon, {140.0f, 380.0f}, *icons, text);
    }

    auto logo = graphics2d::Image::fromFile("assets/images/logo.png");
    if (logo) {
        painter.drawImage(*logo, {520.0f, 40.0f});
    }

    auto icon = graphics2d::Image::fromSvgFile(
        "assets/images/svg/action/3d_rotation/materialiconsround/24px.svg",
        128,
        128
    );
    if (icon) {
        painter.drawImage(*icon, {520.0f, 200.0f});
    }

    auto stripes = graphics2d::Pattern::stripes(
        200,
        120,
        graphics2d::Color::fromRGBA(35, 38, 45),
        graphics2d::Color::fromRGBA(70, 76, 90),
        12,
        true
    );
    if (stripes) {below is some example of graphics2d
        painter.drawImage(*stripes, {80.0f, 40.0f});
    }

    auto checker = graphics2d::Pattern::checker(
        160,
        160,
        graphics2d::Color::fromRGBA(32, 32, 32),
        graphics2d::Color::fromRGBA(96, 96, 96),
        20
    );
    if (checker) {
        painter.drawImage(*checker, {300.0f, 320.0f});
    }

    auto dots = graphics2d::Pattern::dots(
        180,
        120,
        graphics2d::Color::fromRGBA(16, 18, 22),
        graphics2d::Color::fromRGBA(239, 178, 53),
        24,
        6
    );
    if (dots) {
        painter.drawImage(*dots, {520.0f, 360.0f});
    }

    auto dither = graphics2d::Pattern::dither(
        160,
        120,
        graphics2d::Color::fromRGBA(30, 120, 200),
        graphics2d::Color::fromRGBA(8, 10, 14),
        4
    );
    if (dither) {
        painter.drawImage(*dither, {300.0f, 40.0f});
    }

    if (!surface->savePng("out.png")) {
        std::cerr << "Failed to write PNG." << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Wrote out.png" << std::endl;
    return EXIT_SUCCESS;
}
 

Attachments

  • out.png
    out.png
    166.4 KB · Views: 33
  • out_blend_ops.png
    out_blend_ops.png
    25.1 KB · Views: 33
  • out_text.png
    out_text.png
    65.3 KB · Views: 31
I was able to knock off a bunch of low hanging fruit,

1. Desktop Icons and Rubber Band Selection
2. Windows style dock with quick launch, task list, date/time, status icons
3. Hotplug Daemon for Removable Devices..

screenshot-20260207-102844.png



This is the dock.toml it used to configure the dock.

Code:
[dock]
mode = "full"
width = 620
height = 35
margin = 0
padding = 16
corner_radius = 0

[[group]]
name = "left"
position = 1

  [[group.item]]
  type = "launcher"
  name = "Home"
  exec = "thunar ~"
  icon = "0xE88A"
  position = 1

  [[group.item]]
  type = "launcher"
  name = "Search"
  exec = ""
  icon = "0xE8B6"
  position = 2

  [[group.item]]
  type = "launcher"
  name = "Firefox"
  exec = "firefox"
  icon = "0xE894"
  position = 3

  [[group.item]]
  type = "launcher"
  name = "Files"
  exec = "thunar"
  icon = "assets/icons/png/64/default-folder.png"
  position = 4

  [[group.item]]
  type = "launcher"
  name = "Music"
  exec = "audacious"
  icon = "assets/icons/png/64/default-folder-music.png"
  position = 5

  [[group.item]]
  type = "launcher"
  name = "Settings"
  exec = "xfce4-settings-manager"
  icon = "0xE8B8"
  position = 6

[[group]]
name = "right"
position = 2

  [[group.item]]
  type = "removable"
  icon = "assets/icons/png/64/drive-removable-media.png"
  position = 1

  [[group.item]]
  type = "clock"
  format = "%a %m/%d  %H:%M"
  position = 2

  [[group.item]]
  type = "status"
  icons = ["0xE1D6", "0xE63E", "0xE8B5"]
  position = 3

[[group]]
name = "center"
position = 3

  [[group.item]]
  type = "tasklist"
  position = 1

[[menu]]
icon = "0xE5D2"
position = 0
 
The is the layout visual debugging, we use flexbox for most layouts which give the capability of being responsive, nether Gtk or Qt has this feature. Gnome fakes it in there desktop shell.

If you have ever done any development with qt you would be familiar with qml, we have our own qml parser, applications can be built in the same way, except we don't have inline scripting, we design using hml(its what we call our version of qml) and then load it in c++ and code all interactions etc in c++. All while using Wayland with Hardware Accelerated Vector Graphics in Vulkan.

The foundation of the sdk is bound very tightly to FreeBSD core system, currently the code base is around 12,000 loc.

screenshot-20260207-105009.png
 
Once I figure out how to monetize this whether through donations or sponsorships, I’ll start releasing the code.
I find what you show really interesting to say the least, but I fear saying anything because I don't want to be misinterpreted.

Anyway, I'll say it. Have you considered first open sourcing the code ("releasing it" under the open-source license that you consider best) and then looking for the monetization or sponsorship? Isn't this the path many others have followed?
 
we use flexbox for most layouts which give the capability of being responsive, nether Gtk or Qt has this feature. Gnome fakes it in there desktop shell.
This is interesting. Isn't Gtk's VBox and HBox essentially just flexbox? Same with QT's QHBoxLayout/QVBoxLayout.

I originally implemented FLTK's FL_Flex to mimic flexbox and used the Gtk behaviour as inspiration.

I'll say it. Have you considered first open sourcing the code ("releasing it" under the open-source license that you consider best) and then looking for the monetization or sponsorship? Isn't this the path many others have followed?
Looking at the mess that other open-source desktops have become, it might be worth keeping a sole stewardship in the early days. Yes closed-source is a turn-off for many in the *nix specific community but at the same time, it can add percieved value to new contenders.

I would build on your suggestion and instead propose a pledge kind of system. "If we can reach $XXXX then it will be open-sourced!". The wider community might engage and also feel part of the process.
 
This is interesting. Isn't Gtk's VBox and HBox essentially just flexbox?

I originally implemented FLTK's FL_Flex to mimic flexbox and used the Gtk behaviour as inspiration.

Yeah, similar idea (linear layout), but not the same thing. GtkBox is more “pack children in one dimension.” Yoga what I am using is literally the flexbox algorithm (grow/shrink/basis, alignment, etc.), so it behaves like web flex layouts.
 
This is interesting. Isn't Gtk's VBox and HBox essentially just flexbox? Same with QT's QHBoxLayout/QVBoxLayout.

I originally implemented FLTK's FL_Flex to mimic flexbox and used the Gtk behaviour as inspiration.


Looking at the mess that other open-source desktops have become, it might be worth keeping a sole stewardship in the early days. Yes closed-source is a turn-off for many in the *nix specific community but at the same time, it can add percieved value to new contenders.

I would build on your suggestion and instead propose a pledge kind of system. "If we can reach $XXXX then it will be open-sourced!". The wider community might engage and also feel part of the process.

That’s pretty much the model I’ve been thinking about.

A recoup-then-open approach. Each piece of software has a clear development cost. While that cost is being recouped (via name-your-price sales), the code stays closed. Once it’s met, that version is released as BSD-2 open source.

No one is ever paying for code that’s already open payments only fund future work. That keeps things fair while still moving everything toward open source over time.

On top of that, a percentage of early sales would go back into upstream FreeBSD development, especially driver and hardware support. So early adopters aren’t just funding an app, they’re also helping improve the platform it’s built on.

To me, that feels healthier than donations and more honest than pretending development time is free.
 
Here’s a look at the GUI API in the C++ SDK. The code shown is the demo application used in the example above. Useing hml(aka my version of qml) you would radically reduce the amount of code your writing. I provided a example of hml below.

C++:
int main() {
    gui::Application app;
    if (!app.isValid()) {
        std::cerr << "Wayland not available." << std::endl;
        return EXIT_FAILURE;
    }

    gui::Window window(app, 960, 640, "Hultrix Wayland Window");
    if (!window.isValid()) {
        std::cerr << "Failed to create window." << std::endl;
        return EXIT_FAILURE;
    }
    window.setMinSize(800, 600);
    window.setMaxSize(1600, 1000);

    auto findFontPath = []() -> std::vector<std::filesystem::path> {
        const std::vector<std::filesystem::path> kFonts = {
            "assets/fonts/variable/InterVariable.ttf",
            "assets/fonts/variable/JetBrainsMono[wght].ttf",
            "assets/fonts/variable/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf"
        };
        std::filesystem::path dir = std::filesystem::current_path();
        for (int i = 0; i < 6; ++i) {
            std::vector<std::filesystem::path> found;
            for (const auto& font : kFonts) {
                std::filesystem::path candidate = dir / font;
                if (std::filesystem::exists(candidate)) {
                    found.push_back(candidate);
                }
            }
            if (!found.empty()) {
                return found;
            }
            if (!dir.has_parent_path()) {
                break;
            }
            dir = dir.parent_path();
        }
        return {};
    };

    std::vector<std::filesystem::path> fontPaths = findFontPath();
    auto loadFont = [&](float size) -> std::shared_ptr<graphics2d::Font> {
        for (const auto& path : fontPaths) {
            auto font = graphics2d::Font::fromFile(path.string(), size);
            if (font && font->isValid()) {
                std::cout << "Using font: " << path.string() << std::endl;
                return std::shared_ptr<graphics2d::Font>(std::move(font));
            }
        }
        std::cout << "Font load failed. Ensure assets/fonts/variable exists." << std::endl;
        return {};
    };
    auto interTitle = loadFont(24.0f);
    auto interBody = loadFont(16.0f);
    auto interButton = loadFont(14.0f);

    auto root = std::make_shared<gui::Container>();
    root->setBackgroundColor(graphics2d::Color::fromRGBA(0, 0, 0, 0));
    root->layout().setFlexDirection(gui::FlexDirection::Column);
    root->layout().setWidthPercent(100.0f);
    root->layout().setHeightPercent(100.0f);

    auto backdrop = std::make_shared<GradientPanel>();
    backdrop->setBaseGradient(
        graphics2d::Color::fromRGBA(8, 10, 16, 255),
        graphics2d::Color::fromRGBA(26, 10, 18, 255),
        {0.0f, 0.0f},
        {1.0f, 1.0f}
    );
    backdrop->setGlow(
        graphics2d::Color::fromRGBA(196, 0, 36, 90),
        graphics2d::Color::fromRGBA(0, 0, 0, 0),
        {0.78f, 0.18f},
        0.7f
    );
    backdrop->layout().setPositionType(gui::PositionType::Absolute);
    backdrop->layout().setPosition(gui::Edge::Top, 0.0f);
    backdrop->layout().setPosition(gui::Edge::Left, 0.0f);
    backdrop->layout().setPosition(gui::Edge::Right, 0.0f);
    backdrop->layout().setPosition(gui::Edge::Bottom, 0.0f);
    backdrop->layout().setWidthPercent(100.0f);
    backdrop->layout().setHeightPercent(100.0f);
    backdrop->setHitTestVisible(false);
    backdrop->setZIndex(-10);
    root->addChild(backdrop);

    const float titleBarHeight = 44.0f;
    const float statusBarHeight = 36.0f;
    auto titleBar = std::make_shared<gui::TitleBar>("Title");
    titleBar->layout().setHeight(titleBarHeight);
    titleBar->setBackgroundColor(graphics2d::Color::fromRGBA(0, 0, 0, 0));
    titleBar->setBorderWidth(0.0f);
    titleBar->setSubtitle("Subtitle");
    if (interTitle) {
        titleBar->setTitleFont(interTitle);
    }
    if (interBody) {
        titleBar->setSubtitleFont(interBody);
    }
    auto titleLeft = std::make_shared<gui::Label>("Leftside Container");
    titleLeft->setHorizontalAlign(gui::Label::HorizontalAlign::Left);
    titleLeft->setVerticalAlign(gui::Label::VerticalAlign::Middle);
    titleLeft->layout().setFlexGrow(1.0f);
    titleLeft->layout().setFlexShrink(1.0f);
    titleLeft->layout().setMinWidth(0.0f);
    titleLeft->layout().setAlignSelf(gui::Align::Stretch);
    if (interBody) {
        titleLeft->setFont(interBody);
    }
    titleLeft->setFontSize(12.0f);
    graphics2d::Paint titleLeftPaint;
    titleLeftPaint.setColor(graphics2d::Color::fromRGBA(200, 200, 200, 255));
    titleLeft->setPaint(titleLeftPaint);
    titleBar->setMenuWidget(titleLeft);
    titleBar->setOnClose([&] {
        app.stop();
    });
    window.setDragRegion({0.0f, 0.0f, 0.0f, titleBarHeight});

    auto titleBarBackdrop = std::make_shared<GradientPanel>();
    titleBarBackdrop->setBaseGradient(
        graphics2d::Color::fromRGBA(22, 22, 30, 220),
        graphics2d::Color::fromRGBA(12, 12, 18, 220),
        {0.0f, 0.0f},
        {1.0f, 1.0f}
    );
    titleBarBackdrop->setGlow(
        graphics2d::Color::fromRGBA(196, 0, 36, 60),
        graphics2d::Color::fromRGBA(0, 0, 0, 0),
        {0.1f, 0.1f},
        0.6f
    );
    titleBarBackdrop->setBorder(1.0f, graphics2d::Color::fromRGBA(90, 16, 22, 80));
    titleBarBackdrop->layout().setPositionType(gui::PositionType::Absolute);
    titleBarBackdrop->layout().setPosition(gui::Edge::Top, 0.0f);
    titleBarBackdrop->layout().setPosition(gui::Edge::Left, 0.0f);
    titleBarBackdrop->layout().setPosition(gui::Edge::Right, 0.0f);
    titleBarBackdrop->layout().setPosition(gui::Edge::Bottom, 0.0f);
    titleBarBackdrop->layout().setWidthPercent(100.0f);
    titleBarBackdrop->layout().setHeightPercent(100.0f);
    titleBarBackdrop->setHitTestVisible(false);
    titleBarBackdrop->setZIndex(-10);
    titleBar->addChild(titleBarBackdrop);

    root->layout().setGap(gui::Gutter::Row, 12.0f);

    auto contentArea = std::make_shared<gui::Container>();
    contentArea->setBackgroundColor(graphics2d::Color::fromRGBA(0, 0, 0, 0));
    contentArea->layout().setFlexGrow(1.0f);
    contentArea->layout().setFlexShrink(1.0f);
    contentArea->layout().setMinHeight(0.0f);
    contentArea->layout().setFlexDirection(gui::FlexDirection::Column);
    contentArea->layout().setPadding(gui::Edge::Left, 24.0f);
    contentArea->layout().setPadding(gui::Edge::Right, 24.0f);
    contentArea->layout().setPadding(gui::Edge::Top, 12.0f);
    contentArea->layout().setPadding(gui::Edge::Bottom, 16.0f);

    auto contentShell = std::make_shared<gui::Container>();
    contentShell->setBackgroundColor(graphics2d::Color::fromRGBA(0, 0, 0, 0));
    contentShell->layout().setFlexDirection(gui::FlexDirection::Column);
    contentShell->layout().setPadding(gui::Edge::All, 22.0f);
    contentShell->layout().setPadding(gui::Edge::Left, 32.0f);
    contentShell->layout().setPadding(gui::Edge::Right, 32.0f);
    contentShell->layout().setPadding(gui::Edge::Bottom, 28.0f);
    contentShell->layout().setWidthPercent(100.0f);

    auto contentBackdrop = std::make_shared<GradientPanel>();
    contentBackdrop->setCornerRadius(18.0f);
    contentBackdrop->setBaseGradient(
        graphics2d::Color::fromRGBA(18, 20, 28, 235),
        graphics2d::Color::fromRGBA(12, 14, 20, 235),
        {0.0f, 0.0f},
        {1.0f, 1.0f}
    );
    contentBackdrop->setGlow(
        graphics2d::Color::fromRGBA(196, 0, 36, 45),
        graphics2d::Color::fromRGBA(0, 0, 0, 0),
        {0.9f, 0.15f},
        0.8f
    );
    contentBackdrop->setBorder(1.0f, graphics2d::Color::fromRGBA(80, 18, 28, 90));
    contentBackdrop->layout().setPositionType(gui::PositionType::Absolute);
    contentBackdrop->layout().setPosition(gui::Edge::Top, 0.0f);
    contentBackdrop->layout().setPosition(gui::Edge::Left, 0.0f);
    contentBackdrop->layout().setPosition(gui::Edge::Right, 0.0f);
    contentBackdrop->layout().setPosition(gui::Edge::Bottom, 0.0f);
    contentBackdrop->layout().setWidthPercent(100.0f);
    contentBackdrop->layout().setHeightPercent(100.0f);
    contentBackdrop->setHitTestVisible(false);
    contentBackdrop->setZIndex(-10);
    contentShell->addChild(contentBackdrop);

    auto content = std::make_shared<gui::Container>();
    content->setBackgroundColor(graphics2d::Color::fromRGBA(0, 0, 0, 0));
    content->layout().setFlexDirection(gui::FlexDirection::Column);
    content->layout().setGap(gui::Gutter::Row, 12.0f);
    content->layout().setWidthPercent(100.0f);

    auto subtitle = std::make_shared<gui::Label>("Starter widgets + input");
    if (interBody) {
        subtitle->setFont(interBody);Demo
    }
    subtitle->setFontSize(16.0f);
    graphics2d::Paint subtitlePaint;
    subtitlePaint.setColor(graphics2d::Color::fromRGBA(190, 196, 210, 255));
    subtitle->setPaint(subtitlePaint);
    subtitle->layout().setHeight(22.0f);

    root->addChild(titleBar);
    contentShell->addChild(content);
    contentArea->addChild(contentShell);
    content->addChild(subtitle);

    auto button = std::make_shared<GradientButton>("PushButton");
    button->layout().setWidth(200.0f);
    button->layout().setHeight(40.0f);
    if (interButton) {
        button->setFont(interButton);
    }
    button->setFontSize(14.0f);
    button->setCornerRadius(14.0f);
    button->setBaseGradient(
        graphics2d::Color::fromRGBA(132, 8, 26, 255),
        graphics2d::Color::fromRGBA(222, 20, 42, 255)
    );
    button->setGlow(
        graphics2d::Color::fromRGBA(255, 110, 110, 255),
        graphics2d::Color::fromRGBA(0, 0, 0, 0)
    );
    button->setBorder(graphics2d::Color::fromRGBA(255, 255, 255, 40), 1.0f);
    button->setOnClick([&] {
        std::cout << "Button clicked." << std::endl;
    });

    auto checkbox = std::make_shared<gui::CheckBox>("Enable feature");
    if (interBody) {
        checkbox->setFont(interBody);
    }
    checkbox->layout().setHeight(28.0f);

    gui::RadioGroup radioGroup;
    auto radio1 = std::make_shared<gui::RadioButton>("Option A");
    auto radio2 = std::make_shared<gui::RadioButton>("Option B");
    radio1->setGroup(&radioGroup);
    radio2->setGroup(&radioGroup);
    radio1->setChecked(true);
    if (interBody) {
        radio1->setFont(interBody);
        radio2->setFont(interBody);
    }
    radio1->layout().setHeight(28.0f);
    radio2->layout().setHeight(28.0f);

    auto slider = std::make_shared<gui::Slider>();
    slider->layout().setHeight(32.0f);
    slider->setRange(0.0f, 100.0f);
    slider->setValue(35.0f);

    auto progress = std::make_shared<gui::ProgressBar>();
    progress->layout().setHeight(16.0f);
    progress->setRange(0.0f, 100.0f);
    progress->setValue(35.0f);
    slider->setOnValueChanged([progress](float value) {
        progress->setValue(value);
    });

    auto textEdit = std::make_shared<gui::TextEdit>();
    textEdit->layout().setHeight(36.0f);
    textEdit->setPlaceholder("Type here...");
    if (interBody) {
        textEdit->setFont(interBody);
    }

    content->addChild(button);
    content->addChild(checkbox);
    content->addChild(radio1);
    content->addChild(radio2);
    content->addChild(slider);
    content->addChild(progress);
    content->addChild(textEdit);

    auto scrollArea = std::make_shared<GradientScrollArea>();
    scrollArea->layout().setHeight(140.0f);
    scrollArea->setScrollbarThickness(8.0f);
    scrollArea->setShowHorizontal(false);
    scrollArea->setCornerRadius(14.0f);
    scrollArea->setBaseGradient(
        graphics2d::Color::fromRGBA(14, 16, 22, 235),
        graphics2d::Color::fromRGBA(10, 12, 18, 235)
    );
    scrollArea->setBorder(graphics2d::Color::fromRGBA(255, 255, 255, 20), 1.0f);

    auto scrollContent = std::make_shared<gui::Container>();
    scrollContent->layout().setFlexDirection(gui::FlexDirection::Column);
    scrollContent->layout().setGap(gui::Gutter::Row, 8.0f);
    scrollContent->layout().setPadding(gui::Edge::All, 8.0f);
    for (int i = 0; i < 12; ++i) {
        auto item = std::make_shared<gui::Label>("Scrollable item");
        if (interBody) {
            item->setFont(interBody);
        }
        item->layout().setHeight(24.0f);
        scrollContent->addChild(item);
    }
    scrollArea->setContent(scrollContent);

    content->addChild(scrollArea);

    auto statusBar = std::make_shared<gui::StatusBar>();
    statusBar->layout().setWidthPercent(100.0f);
    statusBar->layout().setHeight(statusBarHeight);
    statusBar->layout().setMinHeight(statusBarHeight);
    statusBar->setZIndex(10);
    statusBar->setBackgroundColor(graphics2d::Color::fromRGBA(12, 14, 18, 255));
    statusBar->setBorderWidth(0.0f);
    auto leftStatus = std::make_shared<gui::Label>("Leftside Container");
    leftStatus->setHorizontalAlign(gui::Label::HorizontalAlign::Left);
    leftStatus->layout().setFlexGrow(1.0f);
    leftStatus->layout().setFlexShrink(1.0f);
    leftStatus->layout().setMinWidth(0.0f);
    auto rightStatus = std::make_shared<gui::Label>("Rightside Container");
    rightStatus->setHorizontalAlign(gui::Label::HorizontalAlign::Right);
    rightStatus->layout().setFlexGrow(1.0f);
    rightStatus->layout().setFlexShrink(1.0f);
    rightStatus->layout().setMinWidth(0.0f);
    if (interBody) {
        leftStatus->setFont(interBody);
        rightStatus->setFont(interBody);
    }
    leftStatus->setFontSize(12.0f);
    rightStatus->setFontSize(12.0f);
    graphics2d::Paint statusPaint;
    statusPaint.setColor(graphics2d::Color::fromRGBA(200, 200, 200, 255));
    leftStatus->setPaint(statusPaint);
    rightStatus->setPaint(statusPaint);
    statusBar->addLeftWidget(leftStatus);
    statusBar->addRightWidget(rightStatus);
    if (interBody) {
        statusBar->setFont(interBody);
        statusBar->setFontSize(12.0f);
    }

    auto statusBackdrop = std::make_shared<GradientPanel>();
    statusBackdrop->setBaseGradient(
        graphics2d::Color::fromRGBA(16, 18, 24, 230),
        graphics2d::Color::fromRGBA(10, 12, 18, 230),
        {0.0f, 0.0f},
        {1.0f, 0.0f}
    );
    statusBackdrop->setGlow(
        graphics2d::Color::fromRGBA(196, 0, 36, 45),
        graphics2d::Color::fromRGBA(0, 0, 0, 0),
        {0.95f, 0.5f},
        0.9f
    );
    statusBackdrop->setBorder(1.0f, graphics2d::Color::fromRGBA(80, 18, 28, 80));
    statusBackdrop->layout().setPositionType(gui::PositionType::Absolute);
    statusBackdrop->layout().setPosition(gui::Edge::Top, 0.0f);
    statusBackdrop->layout().setPosition(gui::Edge::Left, 0.0f);
    statusBackdrop->layout().setPosition(gui::Edge::Right, 0.0f);
    statusBackdrop->layout().setPosition(gui::Edge::Bottom, 0.0f);
    statusBackdrop->layout().setWidthPercent(100.0f);
    statusBackdrop->layout().setHeightPercent(100.0f);
    statusBackdrop->setHitTestVisible(false);
    statusBackdrop->setZIndex(-10);
    statusBar->addChild(statusBackdrop);

    root->addChild(contentArea);
    root->addChild(statusBar);

    gui::Style style = gui::Style::designerDark();
    style.windowBackground = graphics2d::ColoDemor::fromRGBA(14, 16, 22, 255);
    style.windowBackdrop = graphics2d::Color::fromRGBA(12, 14, 20, 255);
    style.background = style.windowBackground;
    style.headerbarBackground = graphics2d::Color::fromRGBA(20, 22, 30, 255);
    style.headerbarBorder = graphics2d::Color::fromRGBA(90, 16, 22, 64);
    style.headerbarForeground = graphics2d::Color::fromRGBA(232, 234, 239, 255);
    style.foreground = style.headerbarForeground;
    style.sidebarPrimary = graphics2d::Color::fromRGBA(18, 20, 28, 255);
    style.sidebarSecondary = graphics2d::Color::fromRGBA(14, 16, 23, 255);
    style.sidebarBackground = style.sidebarPrimary;
    style.sidebarBackdrop = style.sidebarSecondary;
    style.accent = graphics2d::Color::fromRGBA(196, 0, 36, 255);
    style.accentForeground = graphics2d::Color::fromRGBA(255, 255, 255, 255);
    style.borderColor = graphics2d::Color::fromRGBA(120, 16, 28, 92);
    root->applyStyleRecursive(style);

    window.setRootWidget(root);

    window.requestFrame();
    app.run();
    return EXIT_SUCCESS;
}


signal-demo.hml
Code:
// Advanced Signals and Events Demo

var title: string = "Signals & Events Demo"
var clickCount: int = 0

Window {
    title: ${title}
    width: 600
    height: 500
   
    // Global signals
    signal appStarted()
    signal userAction(action: string, data: any)
   
    Layout {
        direction: "column"
        spacing: 20
        padding: 20
       
        Text {
            text: ${title}
            fontSize: 24
            weight: "bold"
            align: "center"
        }
       
        // Signal demonstration
        Layout {
            direction: "row"
            spacing: 20
           
            // Emitter component
            Layout {
                direction: "column"
                spacing: 10
               
                Text {
                    text: "Signal Emitter"
                    fontSize: 16
                    weight: "bold"
                }
               
                CustomButton {
                    signal buttonClicked(count: int)
                    signal buttonHovered()
                   
                    text: "Click Me!"
                    clickCount: 0
                   
                    MouseArea {
                        width: 150
                        height: 50
                       
                        Shape {
                            background: "#007acc"
                            borderRadius: 8
                        }
                       
                        Text {
                            text: "Clicked: ${parent.clickCount}"
                            color: "white"
                            align: "center"
                        }
                       
                        onClick: `
                            parent.clickCount++;
                            parent.buttonClicked(parent.clickCount);
                        `
                       
                        onEnter: `parent.buttonHovered();`
                    }
                   
                    onButtonClicked: `
                        console.log("Button clicked", count, "times");
                        window.userAction("click", {count: count});
                    `
                   
                    onButtonHovered: `
                        console.log("Button hovered");
                        window.userAction("hover", {});
                    `
                }
            }
           
            // Receiver component
            Layout {
                direction: "column"
                spacing: 10
               
                Text {
                    text: "Event Receiver"
                    fontSize: 16
                    weight: "bold"
                }
               
                EventLogger {
                    signal logAdded(message: string)
                   
                    var logs: any = []
                   
                    Layout {
                        direction: "column"
                        spacing: 5
                       
                        Text {
                            text: "Event Log:"
                            fontSize: 14
                            weight: "bold"
                        }
                       
                        LogDisplay {
                            width: 200
                            height: 150
                           
                            Shape {
                                background: "#f5f5f5"
                                borderWidth: 1
                                borderColor: "#ddd"
                            }
                           
                            Text {
                                text: "Events will appear here..."
                                fontSize: 12
                                color: "#666"
                            }
                        }
                    }
                   
                    onLogAdded: `
                        console.log("Log added:", message);
                        this.logs.push(message);
                    `
                }
            }
        }
       
        // Global event handlers
        onAppStarted: `
            console.log("Application started!");
            this.logEvent("App started");
        `
       
        onUserAction: `
            console.log("User action:", action, data);
            this.logEvent("User " + action + ": " + JSON.stringify(data));
        `
       
        // Status display
        Layout {
            direction: "column"
            spacing: 10
           
            Text {
                text: "Status Information"
                fontSize: 16
                weight: "bold"
            }
           
            StatusPanel {
                signal statusUpdated(status: string)
               
                width: 400
                height: 100
               
                Shape {
                    background: "#e8f5e8"
                    borderWidth: 1
                    borderColor: "#4caf50"
                    borderRadius: 5
                }
               
                Layout {
                    direction: "column"
                    spacing: 5
                    padding: 10
                   
                    Text {
                        text: "System Status: Ready"
                        fontSize: 14
                        color: "#2e7d32"
                    }
                   
                    Text {
                        text: "Events processed: ${clickCount}"
                        fontSize: 12
                        color: "#388e3c"
                    }
                   
                    Text {
                        text: "Last action: None"
                        fontSize: 12
                        color: "#4caf50"
                    }
                }
               
                onStatusUpdated: `
                    console.log("Status updated:", status);
                `
            }
        }
       
        // Footer instructions
        Text {
            text: "Click the button and watch the signals flow! Check the console for detailed logs."
            fontSize: 12
            color: "#666"
            align: "center"
        }
    }
}
 
Back
Top