Releases: gitgusilva/gbui
Release list
gbui 0.3.2
Releases carry binaries now, and the site has a page to get them from.
Added
-
A shared library, the headers and the CMake package files, attached to
every release — Linux x86-64, Windows x86-64, and both kinds of Mac. Unpack
one, pointCMAKE_PREFIX_PATHat it, andfind_package(gbui)finds it;
there is nothing else to install and nothing to build. ASHA256SUMScovers
the set. Until now a release was a source tarball GitHub made for free, which
is to say: build it yourself.They are built without the SDL2 backend, deliberately. A prebuilt library
that links SDL2 needs the same SDL2 on the machine that loads it, and the
version a distribution ships is never the version the runner had — so the one
build that can be handed to a stranger is the one with nothing underneath it.
Window::createreturns nothing in these and every other part of the library
is the same code. The alternative was a download that fails at load time on
most machines, with a message about a shared object rather than about why.Three platforms, not four:
macos-13is retired, and a matrix entry naming a
runner that no longer exists does not fail — it queues forever, and with
needs: packagethe release never publishes. Intel Macs come back when there
is a reason to addmacos-15-intel.Each archive is consumed before it is allowed out: configured against,
built against and run, on the machine that produced it, by
tools/consumer. That is the check whose absence 0.3.1 was, and it now
stands between a broken package and a published one rather than after it. -
A download page, in the nav and in the guide's first section. It reads
the newest release from the GitHub API at run time rather than baking a list
into a page that gets deployed once and then goes quietly stale, puts the
visitor's own platform first, and falls back to the releases page when the
API rate-limits — sixty requests an hour per address, and a visitor left on a
spinner has been told nothing.
Prebuilt libraries
A shared library, the headers and the CMake package files, for
each platform. Unpack one, point CMAKE_PREFIX_PATH at it and
find_package(gbui) finds it — the archive's README has the
three lines. Built without the SDL2 backend, because a
prebuilt library that links SDL2 needs the same SDL2 on your
machine; build from source if you want a window.
Verify with sha256sum -c SHA256SUMS.
gbui 0.3.1
A portability fix, and the two jobs that caught it. The library's own sources
are the same ones 0.3 shipped; what would not build is the test suite on a Mac
and the sample that links against an installed prefix.
Fixed
Modifiershad a reader for the platform's shortcut key and no writer.
command()answers "Ctrl, or Cmd on a Mac", so everything that had to make
such an event wrote the#ifdefagain instead — two test harnesses so far,
and the second spelt the fieldmeta, which is not a field. Only one branch
of a copy like that ever compiles, so the machine you are on cannot tell you:
it built here and on both Linux jobs and failed on macOS.withCommand()
holds down whichever key the reader reads, and both harnesses now call it.components.hppstopped exportingtextandbuttonand nothing noticed.
Splitting "controls" into elements and components moved them to
elements.hpp, which is what the guide has told callers to include ever
since — but the consumer sample was still on the old umbrella. No in-tree
build catches that, because everything in the tree includes the specific
header it wants; the consumer is the only thing that asks the installed
prefix for an umbrella and finds out what is in it. It now includes all four,
which is the whole point of it.
gbui 0.3
The release that made the toolkit reachable. Everything below the components is
the same three-stage pipeline it was; what changed is that every control now
says what it is, that saying so is a rule rather than a milestone, and that
seven components arrived — including the three the component inventory had named
as the gaps that bite first.
Added
-
splitPane— two panes and a divider the reader can drag, which is the
shape every IDE-shaped application is built from.The share is a percentage basis rather than a
growratio, and finding
out why is the useful part: this layout engine computes its free space from
the hypothetical sizes, which are already clamped to each item's minimum, so
two panes with a 120-pixel floor take their 240 first and split only what is
left — asking for a quarter of 600 got 208 instead of 148, and with large
minimums the fraction stopped meaning anything. A basis ofp%plusshrink
is exact: the overflow the divider causes comes back off each pane in
proportion to its basis, which lands the leading one atp × (width − divider).The minimums are the layout's rather than the drag's, so they hold when the
window shrinks under a split nobody touched. The divider is ARIA's window
splitter — aSeparatorthat takes the keyboard and carries a value — because
a split only draggable with a pointer is a layout most people cannot change. -
treeView— the expandable hierarchy the component inventory calls the
single biggest gap for a git client. Expansion, keyboard walking and
virtualisation, which are each easy and never all three.The data is a flat vector in pre-order with a depth on each row. Flat is
what makes virtualisation possible at all — a slice of a tree is only a slice
if the tree is already a sequence — and it is what a caller usually has from a
git ls-treewalk or a directory listing. Which rows are visible is worked
out here in one pass with a watermark, so a caller that collapses a node
passes exactly the same vector as before.Right opens a closed node and steps into an open one; Left closes an open one
and steps out of a closed one. That pair is the whole of why a tree feels like
a tree. The twisty opens without choosing and the row chooses, because "show
me what is in here" and "I want this one" are two gestures.Each row reports its
leveland its position among its siblings — new
Accessibility::level, ARIA'saria-level— since "item 2 of 5" in a
hierarchy means whose five and "row 340 of 900" is the size of the repository
rather than of the directory. Computed in two linear passes with a counter per
depth, because the obvious version is quadratic on a directory with a thousand
files in it, which is a directory people have.VirtualListOptions::itemRoleis new with it:Role::Nonehands the slot's
semantics to the row callback, so a tree's rows can be counted among their
siblings rather than among the nine hundred the list holds. -
selectfilters, which is thecomboboxthe inventory called the gap that
bites first — a branch picker past about thirty branches is unusable without
type-to-filter. An option rather than a component of its own, for the reason
textInputabsorbed two fields: everything that makes a select a select is
unchanged by typing into it, and the two would be one control described twice.SelectResultgrew afocus, and it is the caller's half of the deal: a
filter box has to hold the keyboard to be typed into, so the control cannot
keep it on the closed box — and a component here never moves focus behind the
caller's back. Same contractlabelandfieldalready have. Not wiring it
leaves a filter that works only once clicked.The highlight stays an index into the caller's list rather than into the
filtered view of it, which is the invariant this is easiest to get wrong. The
match is a case-insensitive substring rather than a fuzzy score, because fuzzy
matching reorders the list under the reader and matches things they cannot see
the reason for. Escape clears the filter before it closes the list; Space
types a space instead of committing, since a combobox that cannot have a space
in its query cannot findfeat/nord tuning; the arrows walk what is on screen
rather than stepping into rows that are not.The filter box carries
controlsandactiveDescendantbecause that is where
the keyboard is; the match count is aStatuslive region; and each row
reports its place in what is shown, since "3 of 40" in a list narrowed to
four is three lies in five words.MenuItemOptionsgrewpositionInSetand
setSizeto carry it. -
carousel— a strip of slides, one screenful at a time, with indicators,
navigators, looping, a fractionalslidesPerPageand autoplay. It moves by
slides rather than by pages even when several are showing, which is the
convention that keeps a four-across gallery usable: "next" is the thing after
the one you are looking at.An autoplaying carousel always draws a pause button, and there is no option
to remove it. WCAG's "pause, stop, hide" is a rule rather than a judgement,
and an option to remove the button would be a switch labelled "make this
inaccessible". Hovering the slides pauses it and so does the keyboard being
inside them — but not reaching for a control, because the first attempt
paused on focus anywhere in the carousel and pressing Play then left focus on
Play and refused to move.Off-screen slides are
hiddenfrom the accessibility tree rather than left in
it: eight slides all present at once turns a control into a list a reader has
to find their way out of. The dots are aTabListwithactiveDescendant,
one of the two patterns ARIA blesses for a carousel. -
gallery— one picture at a time out of a set, with arrows, a caption and
a thumbnail strip that keeps the current one in view. Every picture has a
name: itsalt, its caption, or "Image 3 of 9", because an unnamed picture in
a set of nine is "image, image, image".Zoom, rotate, flip, download and fullscreen are absent, and each for a
reason written into the header rather than left to be discovered: the first
two need a transform on a node that the painter has not got, download needs a
native file dialog and nothing here touches the filesystem, and fullscreen is
a second window. A rotate button that does not rotate is worse than no button. -
compare— two things in the same rectangle with a handle saying how much
of each, which is the shape PrimeVue calls Compare and every before-and-after
on the web is. Both sides are drawn at the full size of the box and one is
revealed over the other, because a comparison laid out side by side is asking
the reader to remember rather than to see.The seam is a percentage, not a measured width, and that is the whole
design: a clip sized from last frame's geometry is a frame late and jumps on
every resize, while a percentage resolves during layout and is right on the
first frame. The content inside the clip is100 / positionpercent of it,
which comes back out to the full width — a layout identity rather than an
arithmetic one. The handle is placed by two flexible spacers for the same
reason, and that also keeps it wholly inside the box at either end.It is a slider and genuinely one — value, range, arrow keys at 2%, Page at
10%, Home and End — rather than PrimeVue's hidden range input beside a div.
The value is announced as"60% Retouched", because "60 percent" alone says
neither how much of what nor revealing what, and both sides stay named in the
tree whatever the handle is doing. -
toast— short-lived messages, stacked in a corner and gone on their own.
The last of the three the component inventory called blocking, and the one
it described as "a queue, a timer and a live region".The queue is
ToastState, owned by the application. That matters more here
than usual: toasts are raised from anywhere — a network reply, a file watcher,
a shortcut three screens away — and a component that owned them would be a
component with a global.The id is the whole of the grouping. Two entries with the same id are one
toast with a count on it, and an empty id is derived from the kind, the title
and the message — so a retry loop reports "still offline ×40" instead of forty
copies of one sentence, which is the failure every application's first toast
queue has.groupis a second and different axis: it routes an entry to an
outlet, so a dialog can report into itself while the application's messages
go to the corner.Placement is six corners, or anywhere at all.
ToastPlacement::Anchored
puts the stack against a tagged node with the same engine a popover uses, and
boundssays which rectangle the corners are measured from, so a stack can
live inside a panel. Which way it grows is never a decision the caller
makes. A bottom stack does not measure itself to find its own bottom either —
the container is the whole column andjustifyputs the toasts at the end of
it, which is right on the first frame where arithmetic on last frame's height
is not.The timer stops while it is being read, which is Toastify's behaviour and
also what WCAG's "enough time" rule asks for: the stack pauses while the
pointer is over a toast or the keyboard is inside it.duration = 0never
expires. Only what is on screen ages, so an entry waiting behindmaxVisible
has not started its clock. The progress bar is drawn only where there is a
time to show, and dims while paused.Each toast is its own live region —
Statusfor info and success,Alertfor
warning and error, because the next thing the reader was about to do will not
work. The stack never takes the keyboard, an...
gbui 0.2.1
A packaging fix, released from 0.2.x rather than from main: the branch is
0.2 and this, and nothing else.
Fixed
- A shared build could not run its own executables on Windows. Windows has
no rpath — a program finds a DLL beside itself or onPATH— while CMake's
multi-config generator puts the library inbuild/Release/and every
executable inbuild/<dir>/Release/. The build and the link both succeed and
the loader fails at startup, which on a machine with no desktop is a dialog
nobody can dismiss: the process never returns and never says why. The CI job
that builds shared and runs the tests sat for six hours before it was
cancelled. The DLLs are copied beside each executable now, on Windows and only
when the build is shared. - CI grew the ceilings whose absence turned that bug into six hours:
ctest --timeout 180, so a hung test fails with its own name, and
timeout-minuteson the job that found it.
gbui 0.2
The first version with a published site, a CI pipeline, an installable library
and a set of application screens to look at. It is the version the documentation
described from the start; it had never been tagged, which is what this tag fixes
— an archived version with no tag is a dropdown entry that 404s.
The toolkit
- Build → layout → paint, three stages in one direction: layout is
arithmetic that can be asserted without a window, and painting is a display
list that can be inspected without a GPU. - CSS flexbox with wrapping, percentages, min-content sizing and out-of-flow
positioning, in logical pixels — a 200% display is a property of the output
rather than something a component knows about. - Theming as data: 24 semantic tokens read from the
gitbox-themes
registry's JSON, with Material 3, Cupertino and Fluent palettes built in, and
aDesignbeside them for shape, sizing and motion. - A software rasteriser with antialiasing, gradients and clipping, and an
SVG writer for review and golden images. - The interaction layer in full: hover, press, click, focus,
:focus-visible, focus-within, Tab traversal, wheel routing and per-node
cursors. - An animation clock on CSS's
transitionmodel — a component says where a
value should be, not how to get there. - Around fifty components, from a button to a table, a rich-text editor and
eight kinds of chart, all stateless functions themed by token. - One allocation per frame: nodes live in an arena addressed by index, so
building a tree is apush_backand releasing one is a reset.
The component set as data
gbui::metadescribes every component — its group, its documentation, its
signature, and each option with type, default and doc — generated from the
headers bytools/generate_meta.py, so a table nobody maintains cannot fall
behind the code it describes. CI regenerates it and fails on a difference.
Demos and documentation
- Six application screens built from the public headers alone: a revenue
dashboard, a weather desk, a plant supervisory HMI, a production line monitor,
a grid control desk and a logistics control tower. They linkgbui::gbuiand
nothing else, so a change that makes them awkward has made the library
awkward. - The demos run in the browser through WebAssembly, rasterised on the CPU
into a<canvas>— the same source, with no DOM inside the rectangle. - A documentation site on VitePress, published per version: the current
release at the root and every archived one at its own address, built from its
own tag. - The source is shown first and the screen runs when the reader asks, so nothing
downloads until they press Run.
Build and packaging
- Installable with CMake, as a static or shared library, and consumed from
outside the tree in CI to prove the install actually works. - SDL2 is optional: without it everything still builds and every test still
passes, and onlyWindow::createchanges. - CI builds, tests, sanitises and lints on Linux, macOS and Windows, with
warnings as errors.
Fixed
- Every
std::optionalin an options struct has a default member initialiser,
so a designated-initialiser call site cannot leave one indeterminate. - Four things that only writing six screens against the library could find — see
acf2897. - The runner works on a fresh machine and on MSVC; the gallery example the
gitignore was hiding is committed.
Known at the time
- No accessibility tree, and no bridge for a screen reader.
- No text shaping, so Arabic, Devanagari and emoji are wrong.
- No GPU painter — the
Painterinterface is six methods precisely so one can
be written. - No image decoding.