Skip to content

Releases: belluikit/bellui

v1.2.0

Choose a tag to compare

@trendvidia trendvidia released this 22 Oct 04:31
4c40618

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 its CanvasRef is set, gated
    by Capabilities.EditProperties like the panel's other edits. The new
    RenameNodeCommand changes 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).
    New event.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, LoadTokenFor and ClearTokenFor take 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.AuthButton saves this way with its Issuer; an empty Issuer, or
    the provider preset's own, is the same as no issuer.
    Behaviour change: a button whose Issuer is 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 with auth.LoadTokenFor(provider, issuer) and removed with auth.ClearTokenFor(provider, issuer).
    auth.LoadToken(provider) no longer finds it and auth.ClearToken(provider)
    no longer removes it, so an app that signs out with ClearToken leaves the
    refresh token on disk until it switches. A token saved before upgrading
    stays in the provider's old file, where LoadToken still reads it; an app
    that switches to LoadTokenFor can remove it with one ClearToken call.
    Buttons on a preset issuer (Google, Apple, GitHub, Microsoft's common
    endpoint) are unchanged.

Changed

  • Canvas.Capture returns 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, and Capture returned 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 own CanvasObject types 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). With DISPLAY
    set and WAYLAND_DISPLAY unset, 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 over ssh -X on 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 with BELLUI_PARTIAL_REPAINT=0,
    to turn it off. Mobile and the web keep full repaints until it's validated on
    devices there (#593); the
    partialrepaint tag 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.AddComponentNode now 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 any CGO_LDFLAGS you 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, as zipalign -P 16 checks. 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.Text at 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...
Read more

v1.1.2

Choose a tag to compare

@bfrancojr bfrancojr released this 16 Oct 21:54

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

Choose a tag to compare

@bfrancojr bfrancojr released this 16 Oct 17:36

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

Choose a tag to compare

@trendvidia trendvidia released this 16 Oct 13:40

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 and DocumentsContract hands 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.ChildCreatingRepository is the optional capability a repository
    implements to answer it. Repositories that can name a child by path
    arithmetic implement nothing: for them CreateChild is Child plus
    CreateListable or an empty Writer, 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 into bellui/test
    (#304).

Changed

  • Sign-in tokens are stored per app
    (#335). Every bellui app
    on a machine shared one bellui-auth/<provider>.enc, so one app could read
    another's sign-in. Tokens now live under bellui-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; and SaveToken, LoadToken and ClearToken now need an app ID,
    returning the new auth.ErrNoAppID without one (Preferences already need
    it).

Fixed

  • Ctrl+Tab moves focus out of a widget that keeps Tab
    (#340). A multi-line
    Entry and 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 of user:email read:user.
  • desktop.App is implemented on every desktop platform
    (#338). It gained
    SetSystemTrayTooltip and SetSystemTrayOnTapped, 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
    imported bellui/test for one interface and two constructors, and
    bellui/test imports testify — so every desktop and web binary linked
    testify, gopkg.in/yaml.v3, spew, difflib and testing. In the js/wasm
    build of cmd/bellui_settings that was 988,505 bytes, 2.09% of the
    artifact, and 208 yaml symbols. The canvas moved to internal/memcanvas;
    the public API is unchanged, since test.WindowlessCanvas and
    test.SoftwarePainter are 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 with C.CBytes and frees
    itself, which Scudo catches and turns into an abort — "invalid chunk state
    when deallocating"
    . Pre-existing; found by the first complete
    hack/safprobe run.
  • Android: contentURIGetFileName corrupted 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_name is a column
    of the document, so a tree URI had no row to find and Name() fell back to
    the percent-encoded document ID: primary%3ADocuments where a user expects
    Documents. The same literal-return shape was in listContentURI and
    listURI.
  • Android: a picked SAF folder can be scaffolded into
    (#303). CreateChild for
    content:// goes through DocumentsContract.createDocument, and refuses a
    duplicate name rather than letting the provider silently invent
    "name (1)". Child resolves an existing child by display name, and
    reports storage.ErrNotExists for one that is not there instead of a URI
    that looks real. Exists and Delete work for content://. Parent
    remains unsupported, and now says why: SAF exposes no upward link.
  • Android: listing a subdirectory listed the picked folder instead
    (#303). List and
    CanList took their document ID from getTreeDocumentId, 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: listContentURI leaked 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
    DoubleTappable leaves a timer waiting TapDoubleDelay for 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 under file://. On Android a content:// URI gives
    /tree/primary%3ADocuments, which looks like a path, is not one, and
    resolves under / if handed to os.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/systray v1.12.1: an app's exit callback could run
    twice when Quit raced with the platform's own close handling; it is now
    guarded so it runs once.
  • github.com/belluikit/fsnotify v1.10.2: synced with upstream fsnotify
    (v1.10.1 plus later fixes), keeping the fork's Windows fixes.
  • x/mmedia requires github.com/belluikit/goffmpeg v0.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.md now 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 install needs -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).
  • TestRender asserts the rendered image rather than an exact PNG byte
    string, which go1.27's compress/flate changed
    (#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

Choose a tag to compare

@trendvidia trendvidia released this 06 Aug 01:29

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 Entry storage, IME composition on every platform, mobile touch selection, and RichEntry.
  • Data model layer — observable Model with 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 accessibility with 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.Host with 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.