Repository navigation
Releases: belluikit/bellui
Release list
v1.2.0
Mostly about speed. Screens with many widgets, labels that change every frame
and long scrolled lists cost far less CPU and memory than in v1.1.2, idle apps
no longer use CPU, and animations follow the display's refresh rate (the
report in #427 has the
numbers). New in this release: pinch-to-zoom and two-finger scrolling on
phones, tablets and desktop touchscreens, and sign-in tokens per issuer. The
minor bump is for those APIs (event.Pinchable, event.PinchEvent,
auth.SaveTokenFor and its siblings). Three changes may need a look:
Canvas.Capture now returns nil when the window could not be drawn, partial
repaint is on by default on desktop, and an AuthButton with its own
Issuer saves its token under that issuer (see Added). Linux web views
move to Chromium 154. The x/* modules are released at v1.2.0, and the
bellui command alongside as cmd/bellui v1.1.0 (see
cmd/bellui/CHANGELOG.md).
Security
- Linux web views bundle Chromium 154
(#510).bellui package
now lays out CEF 154.0.34 (Chromium 154.0.8037.98) beside a Linux app,
replacing CEF 151, which had fallen four major versions behind Chrome stable.
The subprocess helper is also built so that CEF 154's child processes no
longer abort with "stack smashing detected" when they exit.
Added
- x/dag: rename a node from the properties panel
(#629).PropertiesPanel
shows a Name field for the selected node when itsCanvasRefis set, gated
byCapabilities.EditPropertieslike the panel's other edits. The new
RenameNodeCommandchanges a node's label with undo, for apps that rename
from a menu or dialog of their own. - Two-finger pan scrolls
(#398). Two fingers moving
together scroll what is under them, in both directions, on the web and on
desktop touchscreens. A page of charts or 3D views, which take one-finger drags
for themselves, can now be scrolled past them. Charts pass two-finger
scrolling on to the page; the x/dag canvas pans with it. Two fingers moving
apart or together over a Pinchable are still a pinch. - Pinch-to-zoom (#379).
Newevent.Pinchable(Pinched(*PinchEvent),PinchEnded()): two fingers
on a Pinchable widget report the change in their distance and the point
between them, on phones, tablets and desktop touchscreens. Both fingers must be on the
widget: two fingers on widgets either side of it keep their own taps and
drags (#393). The web follows
once the browser driver handles touch input
(#378). The x/dag canvas
zooms on pinch and pans with the fingers; x/charts (data window, 3D camera
and maps) zooms on pinch. - Sign-in tokens per issuer
(#356). New
auth.SaveTokenFor,LoadTokenForandClearTokenFortake an issuer as
well as a provider, so an app that signs in to two issuers under one provider
name (two Keycloak realms, two Okta tenants) keeps a token for each; with
one token per provider name, the second sign-in replaced the first.
widget.AuthButtonsaves this way with itsIssuer; an emptyIssuer, or
the provider preset's own, is the same as no issuer.
Behaviour change: a button whoseIssueris not the preset's (every
"okta", "keycloak" and "custom" button, and a "microsoft" button set to one
tenant) saves a token that is read back withauth.LoadTokenFor(provider, issuer)and removed withauth.ClearTokenFor(provider, issuer).
auth.LoadToken(provider)no longer finds it andauth.ClearToken(provider)
no longer removes it, so an app that signs out withClearTokenleaves the
refresh token on disk until it switches. A token saved before upgrading
stays in the provider's old file, whereLoadTokenstill reads it; an app
that switches toLoadTokenForcan remove it with oneClearTokencall.
Buttons on a preset issuer (Google, Apple, GitHub, Microsoft's common
endpoint) are unchanged.
Changed
Canvas.Capturereturns nil when the window cannot be drawn
(#631). On macOS, a window
the system reports as occluded (covered, minimised, or just shown) gets
nothing to draw into, andCapturereturned a black or out-of-date image
with no sign that it had failed. It now returns nil, so a caller can tell a
real capture from none and try again. With the WebGPU painter only; GL
draws regardless.- Partial repaint finds what changed without visiting every object
(#599). Each frame used to
walk every visible object to notice what had moved, which in a scroll of
2,000 cards cost more than a full repaint saved. Bellui's objects now tell
their canvas when they are moved, resized or hidden, and the frame visits
only those: on an M1 Mac the region for one refreshed object costs about
0.3 µs instead of 120–390 µs. An app's ownCanvasObjecttypes that do not
embed Bellui's need no change; their parent is checked every frame as
before. - On Linux, an app given only an X display now opens on it
(#534). WithDISPLAY
set andWAYLAND_DISPLAYunset, bellui now picks X11. It used to leave the
choice to GLFW, which tries Wayland first and falls back to the logged-in
desktop's Wayland socket, so an app started overssh -Xon a machine with
a Wayland desktop open appeared on that machine's screen.BELLUI_PLATFORM
still overrides the choice. - Partial repaint is on by default on desktop
(#496). On macOS, Linux and
Windows, each frame now clears and redraws only what changed. On a window
where little moves (a caret, a spinner, a hover) that's about 4.8× less GPU
work, at no CPU cost worth measuring (+1.5% on a scene where every object
moves). It was checked pixel for pixel against a full repaint on all three.
Build with-tags nopartialrepaint, or run withBELLUI_PARTIAL_REPAINT=0,
to turn it off. Mobile and the web keep full repaints until it's validated on
devices there (#593); the
partialrepainttag still turns it on anywhere.
Fixed
- x/dag: tapped-in nodes no longer stack, and adding one can be undone
(#629). Tapping a palette
item used to put every new node on the centre of the view, on top of the last
one. It now uses the centre while it is free and otherwise the nearest spot
clear of other nodes and groups. Dropping a dragged item still places it
where it is released.DAGCanvas.AddComponentNodenow records the add on the
canvas's undo stack, so Undo removes a node added from the palette. - Android APKs work on devices with 16 KB memory pages
(#626).bellui package
now links every Android build for 16 KB pages, not only release builds, and
keeps anyCGO_LDFLAGSyou set instead of replacing them. Inside the APK,
native libraries now start on a 16 KB boundary and other files on a 4-byte
one, aszipalign -P 16checks. Before, the 4-byte padding was miscounted,
so files landed on unaligned offsets. An app built without--release
showed "LOAD segment not aligned" when installed on an Android 17 Pixel. - Elevated buttons cast their shadow
(#609).ButtonStyleElevated
was documented with a shadow but drew none. It now sits at MD3 elevation 1,
rises to 2 under the pointer, and lies flat when disabled. The other button
styles stay flat, as MD3 specifies. - The navigation drawer keeps its destinations clear of the system bars
(#608). On phones and
tablets the drawer's first destination was drawn under the status bar,
because the drawer ignored the canvas's interactive area. The drawer surface
still reaches the screen edges, but its content now starts below the status
bar, scrolls clear of the home indicator, and keeps clear of a left-side
notch in landscape. - Partial repaint no longer darkens or undims a modal drawer's page
(#605). Two faults showed
when a modal navigation drawer was open over an animation. On a frame with
nothing to repaint, an object reaching across the canvas origin, such as the
drawer's shadow or content scrolled past the edge, was painted anyway with
no clip. The semi-transparent shadow built up until the drawer turned black.
Such frames now paint nothing. Separately, a modal pop-up's scrim fades with
its entrance and exit animations but was never refreshed itself, so partial
repaint redrew it only where the pop-up's content was. On an iPad that left
the page beside the drawer undimmed. The scrim is now refreshed with the
pop-up. - Partial repaint no longer cuts text that is placed but not sized
(#602). Painters draw a
canvas.Textat its natural size even when its own box is smaller. Partial
repaint looked only at the box, so when a dirty region crossed just the
overflowing glyphs, they were cleared and not drawn again. In the gallery
this cut the bottom half off a navigation drawer caption whenever a progress
indicator animated behind the drawer. Partial repaint now goes by where an
object actually draws: text grown to its natural size, strokes and italic
overflow, and box-shadow blur. The navigation drawer also sizes its captions
now...
v1.1.2
Fixes a memory leak that every app had: content removed from a window was
never freed. Also makes the slider's value indicator keep up while dragging.
The x/* modules are re-released at v1.1.2 unchanged, and the bellui
command is released alongside as cmd/bellui v1.0.1 (see
cmd/bellui/CHANGELOG.md).
Fixed
- Memory leak: content removed from a window stayed in memory for the
window's lifetime (#380).
The canvas kept a lookup from every object it had ever drawn to its render
node and never removed entries, so a replaced page — with all its data —
could not be collected. In the gallery, every page visit kept about 8 MB;
in the browser this grew past 1 GB in minutes and Android closed the tab.
Entries for objects the latest layout pass no longer reaches are now
dropped. - Slider: the value indicator keeps up while dragging. It was rebuilt
for every drag event, replacing the popup and restarting its fade-in each
time, so it lagged behind the thumb. It is now built once per drag and
updated in place.
Install the command with go install github.com/belluikit/bellui/cmd/bellui@v1.0.1.
v1.1.1
A patch release for x/dag. The toolkit itself is unchanged from v1.1.0; the
other x/* modules are re-released at v1.1.1 to keep one version across the
repository. The bellui command is released alongside as cmd/bellui v1.0.0,
its first tag (see cmd/bellui/CHANGELOG.md).
Fixed
- x/dag: note text no longer overflows its box when zoomed. The box was
sized from the text measured at the base size and scaled by the zoom, but a
fractional text size draws like the next whole pixel size
(#374), so at zoom 0.67 (the
gallery's default view) a one-line note ran 28 px past its box. Notes now
measure at the size they are drawn. - x/dag: palette entries show their whole port summary. The second line
sat in a fixed 14 px box, and its arrow comes from a fallback font whose line
is about 22 px at 11 pt, so it was cut off. Entries now size to their text.
Install the command with go install github.com/belluikit/bellui/cmd/bellui@v1.0.0.
v1.1.0
Android's Storage Access Framework becomes usable, two long-standing cgo
memory bugs in the Android bridge are fixed, and the test helpers stop
shipping in production binaries. The minor bump is for the new
storage.CreateChild API. Apps that use auth should read Changed:
sign-in tokens are now kept per app, so users sign in once more after
upgrading. Everything else is additive or a fix. This is also the first
release made with the repository public.
Added
storage.CreateChild, for stores that name their own children
(#303).storage.Child
does path arithmetic — it names a resource whether or not anything is
there — and that does not work where the store assigns identity. Under
Android's SAF a document ID is opaque andDocumentsContracthands the URI
back only on creation, so a folder the user picked could not be scaffolded
into at all.CreateChild(parent, name, listable)asks the store to make
the resource and returns what it called it, and
repository.ChildCreatingRepositoryis the optional capability a repository
implements to answer it. Repositories that can name a child by path
arithmetic implement nothing: for themCreateChildisChildplus
CreateListableor an emptyWriter, and it verifies the resource exists
before returning rather than handing back a URI pointing at nothing.driver/software.WindowlessCanvas, so the software driver's constructors
return a nameable type without reaching intobellui/test
(#304).
Changed
- Sign-in tokens are stored per app
(#335). Every bellui app
on a machine shared onebellui-auth/<provider>.enc, so one app could read
another's sign-in. Tokens now live underbellui-auth/<app ID>/, and the
app ID is part of the encryption key. Behaviour change: tokens saved by
an earlier version are not read, so users sign in once more after
upgrading; andSaveToken,LoadTokenandClearTokennow need an app ID,
returning the newauth.ErrNoAppIDwithout one (Preferences already need
it).
Fixed
- Ctrl+Tab moves focus out of a widget that keeps Tab
(#340). A multi-line
Entryand the x/dag canvas keep Tab and Shift+Tab for themselves, and every
Tab with a modifier went to the shortcut path, so a keyboard-only user who
tabbed into one could not leave it (WCAG 2.1.2). Ctrl+Tab and Ctrl+Shift+Tab
now move focus to the next and previous widget from any such widget, on the
desktop, embedded and mobile drivers. Behaviour change: while a widget
that keeps Tab is focused, Ctrl+Tab no longer reaches the app's main-menu or
widget shortcuts; on every other widget it still does. The web build is not
covered yet: browsers keep Ctrl+Tab for themselves
(#357). - GitHub sign-in asks for GitHub's scopes
(#335). The OpenID
default (openid email profile) was applied before the GitHub branch, so
GitHub never got its own default ofuser:email read:user. desktop.Appis implemented on every desktop platform
(#338). It gained
SetSystemTrayTooltipandSetSystemTrayOnTapped, but the app did not
forward them on macOS, Windows or Linux/BSD, so the documented
if desk, ok := a.(desktop.App); ok { … }check was always false and an
app following it silently got no system tray.- The test helpers no longer ship in production binaries
(#304).driver/software
importedbellui/testfor one interface and two constructors, and
bellui/testimports testify — so every desktop and web binary linked
testify,gopkg.in/yaml.v3, spew, difflib andtesting. In the js/wasm
build ofcmd/bellui_settingsthat was 988,505 bytes, 2.09% of the
artifact, and 208 yaml symbols. The canvas moved tointernal/memcanvas;
the public API is unchanged, sincetest.WindowlessCanvasand
test.SoftwarePainterare now aliases of the moved types. A CI gate keeps
it that way. - Android: every write to a file was a double free
(#315).writeStream
freed the buffer the Go caller had allocated withC.CBytesand frees
itself, which Scudo catches and turns into an abort — "invalid chunk state
when deallocating". Pre-existing; found by the first complete
hack/safproberun. - Android:
contentURIGetFileNamecorrupted the heap and answered the wrong
name (#315). It returned
a string literal on its error path, which the Go caller frees —free()on
static storage. It also queried the raw URI, but_display_nameis a column
of the document, so a tree URI had no row to find andName()fell back to
the percent-encoded document ID:primary%3ADocumentswhere a user expects
Documents. The same literal-return shape was inlistContentURIand
listURI. - Android: a picked SAF folder can be scaffolded into
(#303).CreateChildfor
content://goes throughDocumentsContract.createDocument, and refuses a
duplicate name rather than letting the provider silently invent
"name (1)".Childresolves an existing child by display name, and
reportsstorage.ErrNotExistsfor one that is not there instead of a URI
that looks real.ExistsandDeletework forcontent://.Parent
remains unsupported, and now says why: SAF exposes no upward link. - Android: listing a subdirectory listed the picked folder instead
(#303).Listand
CanListtook their document ID fromgetTreeDocumentId, which returns the
root's ID for any URI under the tree — so a scaffolded tree looked flat. - Android: folder grants survive a restart
(#303). Nothing called
takePersistableUriPermission, so every pick was single-use and an app
could not store a folder URI and reopen it later. - Android:
listContentURIleaked a JNI local reference per row
(#303) — a directory of
any size overruns the 512-entry table, which aborts the process. - A pending double-tap no longer outlives its widget
(#310). A single tap on a
DoubleTappableleaves a timer waitingTapDoubleDelayfor a second one,
and nothing cancelled it when the canvas went away — only a second tap did.
It then dispatched into widgets whose renderers had been destroyed. Tap a
widget, close the dialog holding it inside the double-tap window, and that
was a nil dereference. URI.Path()now says what it is: the RFC3986 path component, and a
filesystem path only underfile://. On Android acontent://URI gives
/tree/primary%3ADocuments, which looks like a path, is not one, and
resolves under/if handed toos.MkdirAll
(#303).
Note
The SAF behaviour above is verified on a device — 21 assertions against the
external-storage provider on an API 34 emulator, including the duplicate-name
refusal and a grant surviving a force-stop — using the new hack/safprobe
harness. The remaining gap is that the run was driven by hand rather than by
the harness's own script
(#314).
Dependencies
github.com/belluikit/systrayv1.12.1: an app's exit callback could run
twice whenQuitraced with the platform's own close handling; it is now
guarded so it runs once.github.com/belluikit/fsnotifyv1.10.2: synced with upstream fsnotify
(v1.10.1 plus later fixes), keeping the fork's Windows fixes.x/mmediarequiresgithub.com/belluikit/goffmpegv0.4.1, whose prebuilt
purego bridges were rebuilt and now expose the device-modes API
(#330).
Licensing
- The EmojiOne Color font ships with its license file again, and
THIRD_PARTY_NOTICES.mdnow covers the fyne-x code and test assets, the
vendored packages, every bundled font, the Material Design icons and the
sign-in logos (#325).
Attributions the rebrand had rewritten to "bellui-x" name fyne-x again.
CI
- CI runs on GitHub-hosted runners with stock Go instead of self-hosted
machines and the go-plus toolchain, and installs the system packages those
images lack (#325,
#329). apt-get installneeds-y: five Linux legs died the moment apt had an
upgrade to do (#307).- chromium is launched without
snap-confine, which had refused to start it
from the runner service and took every wasm package down with it
(#311). TestRenderasserts the rendered image rather than an exact PNG byte
string, which go1.27'scompress/flatechanged
(#318).- The PowerShell go-plus assertions accept
(plus <version>), which go1.27's
fork build reports (#320).
The x/* modules (charts, dag, editor, mmedia, tables) are released as v1.1.0 alongside.
v1.0.0
The first bellui stable — a cross-platform GUI toolkit for Go covering desktop (Linux, macOS, Windows), mobile (iOS, Android) and web (WebAssembly).
The v1 line, in summary:
- Material Design 3 foundation — HCT tonal palettes mapped to ~30 MD3 color roles, a 15-token type scale, state layers, shape/animation tokens, and 36 MD3 components including Date/Time pickers, SearchField, Paned, Toast and CompletionPopup.
- Text, IME & editors — per-run styled text, per-rune font fallback with CJK support, RTL direction scopes, HTML/markdown parsing with untrusted-content defenses, rope-backed
Entrystorage, IME composition on every platform, mobile touch selection, andRichEntry. - Data model layer — observable
Modelwith sort/filter proxies, identity-keyed selection, write-through mutation and tree projection, wired to the List/Table widgets. - Accessibility — opt-in screen-reader support behind
-tags accessibilitywith five platform backends: macOS NSAccessibility, Windows UIA, Linux AT-SPI2 (pure Go), Android virtual nodes, iOS. - Platform integration — native/portal file dialogs by default, runtime Wayland/X11 selection in one binary, window positioning and native-parented dialogs, single-instance mode with open-with/deep-link events, clipboard flavors, rich notifications.
- Webview — the portable
webview.Hostwith six backends; on Linux it is CEF rendered offscreen and composited, so overlays, capture and clipping work — the app bundles the engine, with the security obligations documented in docs/CEF-MAINTENANCE.md. - DevTools & analytics — the opt-in read-only widget-tree inspector, and a consent-first analytics package that starts disabled and never reads widget values.
The full entry for everything since v1.0.0-rc.5 is in CHANGELOG.md.
The five x/* modules (charts, dag, editor, mmedia, tables) are pinned and tagged x/<module>/v1.0.0 alongside this release.