github go-gui-org/go-gui v0.94.0

11 days ago

Added

  • TitlebarDark switches the window frame on Wayland under GNOME (#1192).
    On a compositor that leaves the frame to the app (GNOME, weston), go-gui draws
    it through libdecor, whose GTK plugin takes its light or dark variant from a
    GTK setting that the plugin sets at most once, at start-up.
    Window.TitlebarDark now sets that variant and repaints the frame at once. It
    was a no-op on every backend before. FollowSystemAppearance already calls
    it, so a following window's frame now switches with the desktop setting. An
    app that pins its theme with SetTheme calls
    TitlebarDark(theme.TitlebarDark) to match. Before, a dark app on libdecor
    0.2.2 (Debian 13, Ubuntu 24.04) sat under a light frame, and on later versions
    the frame kept its start-up variant after the desktop switched. The variant is
    one per process, so the last call sets it for every window. The cairo plugin's
    colours are fixed, and X11, Windows and macOS frames do not change yet.
    examples/system_appearance/ follows the
    desktop and pins a light or dark theme with the frame to match.

  • Key codes for every printable ASCII character, and ModAltGr (#1188). The
    keys that had no KeyCode constant now have one, valued at their character
    code like the existing ones: KeyExclamation, KeyQuotation,
    KeyNumberSign, KeyDollar, KeyPercent, KeyAmpersand, KeyLeftParen,
    KeyRightParen, KeyAsterisk, KeyPlus, KeyColon, KeyLess,
    KeyGreater, KeyQuestion, KeyAt, KeyCircumflex, KeyUnderscore,
    KeyLeftBrace, KeyBar, KeyRightBrace and KeyTilde. KeyScrollLock,
    KeyPrintScreen, KeyPause and KeyF21–KeyF24 are exported. ModAltGr
    names ISO level 3 (AltGr), which is not Ctrl+Alt; Shortcut.String shows it
    as AltGr+. Linux sends them (see Fixed); other platforms do not yet.

  • Shortcuts match the key a press typed: Event.TypedKey and
    Event.TypedModifiers (#1188).
    A key down now also carries the key the
    press produced with Shift, AltGr and NumLock applied, and the modifiers that
    translation did not use up. On a German layout Shift+7 types /: KeyCode
    stays Key7 with Shift, TypedKey is KeySlash with no modifiers. Command
    dispatch tries an exact KeyCode/Modifiers match first and, only when no
    command matches, TypedKey/TypedModifiers. So Shortcut{Key: KeySlash}
    fires on German Shift+7, Ctrl+/ on Ctrl+Shift+7, KeyLeftBracket on AltGr+8
    and Ctrl++ on US Ctrl+Shift+=, while a command bound to the exact keys still
    wins. Ctrl+Shift+A stays Ctrl+Shift+A: a press that types the same key keeps
    all its modifiers. On the default US layout every existing shortcut behaves as
    before. The Linux backends (Wayland and X11) set the fields; on Windows, macOS
    and web they are zero and matching is exact, as before.
    examples/key_inspector/ shows, for every key
    press, its KeyCode, TypedKey, modifiers and character, and the shortcut to
    bind it with; its terminal output is what a keyboard bug report needs.

  • Custom shaders can read textures: Shader.Surface binds a PixelSurface's
    planes as iChannel0-iChannel3 (#1168).
    A surface can now hold up to four
    planes in R8, RG8 or RGBA8, each with its own size
    (PixelSurfaceCfg.Planes), and UpdatePlanes publishes all of them as one
    frame, so a shader never sees one plane's new pixels with another's old ones.
    A shader with a Surface reads plane i as iChanneli, Shadertoy style,
    with iChannelResolution; sampling is linear and clamps to the edge. This is
    what video needs: NV12 frames go to the GPU as an R8 and an RG8 plane and the
    shader converts them, with no YUV code in go-gui. A new frame redraws without
    rebuilding layout or Shader.Animate. Shaders without a Surface compile
    exactly as before. Window.RendersShaders reports whether the backend runs
    shaders (not the soft renderer or PDF), so a library can fall back to an RGBA8
    Image. On Metal an upload into a surface texture waits for an earlier frame
    still sampling it, as replaceRegion does not. See
    examples/shader_channels/.

  • PixelSurface: an image the app can update every frame (#1168). Create
    one with NewPixelSurface, pass it as ImageCfg.Surface, and call Update
    with RGBA8 pixels from any goroutine. Update copies the pixels, so the
    producer can reuse its buffer, and keeps only the newest frame. Every window
    that draws the surface redraws without rebuilding its layout, and each
    renderer uploads only when there is a new frame, reusing its texture at a
    fixed size. This is what a video player, camera preview or emulator screen
    needs; until now an image could only change by registering a new one. All
    backends draw it (OpenGL, Metal, WebGL/Canvas2D, Android, iOS, software) and
    PDF export embeds the current frame. Resize changes the size and Close
    stops it and releases the textures. Images without a Surface are unchanged.
    examples/pixel_surface/ animates one at 60 Hz.

  • InputCfg.OnPasteRejected reports a paste the field refused (#1187). It
    is called with the whole paste, before any cap, when Ctrl+V, the context
    menu's Paste or the macOS Ctrl+Y yank leaves the field unchanged: the text
    would exceed the field's 65,536-rune budget, the Mask has no room for it, or
    PreTextChange refused it. Until now only the reject cue marked a refusal,
    and that is silent unless the app installs a sound player. The app can now say
    why ("Paste too large: 92,359 characters"), keep the text, or open it in a
    view that can hold it. ctx.Layout is the input's own layout and ctx.Event
    is nil, as in OnTextChanged.

Fixed

  • A Wayland window with a libdecor frame no longer quits on its own (#1194).
    On a compositor that leaves the frame to the client (GNOME, weston), the event
    loop sometimes ended after logging gl: wayland: libdecor_dispatch: -4, with
    no user action, and the app quit. libdecor's dispatch ends in a poll(),
    which a signal fails with EINTR, and the Go runtime sends SIGURG to
    preempt goroutines, so a busy app gets one now and then. The backend took the
    interrupted poll for a dead connection. It now counts as a dispatch that found
    nothing, and the loop's next pass picks up the events. Other libdecor errors
    still end the loop. The GODEBUG=asyncpreemptoff=1 workaround is no longer
    needed.

  • A ContextMenu opened near the bottom or right edge of the window opens
    next to the cursor (#1193).
    Right-clicking near the bottom of a tall menu
    area, such as a table filling the window, opened the menu at the top of the
    window. The flip that keeps a popup in the window treated the click position
    as a gap from the area's top edge and mirrored it to the area's bottom edge,
    which for a tall area is far above the cursor. The popup now hangs from the
    click point and flips around it: with no room below it opens above the cursor,
    with no room to the right it opens to the left. The same fix applies to the
    link menu on a right-clicked link in RTF and Markdown, which had the same
    fault in long documents. Where the menu fits below and to the right of the
    cursor, it opens exactly where it did before. The tooltip on an RTF or
    Markdown run had the same fault: hovered near the top of the window, it
    flipped to the bottom of the text block, far from the word. It now opens 3px
    below the word when there is no room above it.

  • Keys on non-US keyboard layouts get a KeyCode on Linux (#1188). A key's
    KeyCode came from its first-level symbol through a US-only table, so on
    German, French, Russian and most other layouts many keys reported
    KeyInvalid: #, +, <, ^, the numpad, NumLock, Menu, the AZERTY number
    row, and every letter on a Cyrillic layout. Shortcuts on those keys never
    fired, numpad arrows did nothing in a text field, and Ctrl+C/V/X/Z did nothing
    on a Russian layout. On Wayland and X11 the code now follows the rules of
    docs/specs/keyboard-layouts.md: named and keypad keys by their symbol (the
    numpad navigates with NumLock off), ASCII letters and digits, the digit Shift
    types on AZERTY, on non-Latin layouts the letter of the first configured Latin
    layout (else the US one at that position), and every printable ASCII
    character. AltGr is reported as ModAltGr, so AltGr+8 typing [ no longer
    fires a Key8 shortcut, and an AltGr chord no longer acts as the plain key
    for focus traversal (Tab) or keyboard scrolling. A key's release reports the
    code of its press. On the default US layout nothing that worked changes; a
    test pins this.

  • On X11, AltGr, NumLock, Caps Lock and non-Latin layouts type the right text
    (#1188).
    The X11 backend chose a key's symbol from Shift alone, so on a
    German layout AltGr+Q typed q instead of @ (and [, ], {, }, \,
    |, ~, € could not be typed at all), numpad digits typed nothing with
    NumLock on, Caps Lock typed lowercase letters, and Cyrillic, Greek and other
    non-Latin letters typed nothing. It now applies the AltGr level, the NumLock
    rule for keypad keys, Caps Lock as XKB does (Shift+Caps Lock types the
    lowercase letter), and libxkbcommon's character table when libxkbcommon is
    installed, as the Wayland backend does. Without libxkbcommon, € and the
    numpad's characters type too; only non-Latin text needs it. A layout changed
    while the window is open (setxkbmap, the desktop settings) applies to the next
    key press; the keyboard was read only at start. AltGr is recognized on
    whichever modifier the keyboard mapping puts it, and a key an IBus input
    method forwards is reported as the same key typed directly. go-gui now enables
    the XKB extension on X11, as GTK and Qt do: without it Xorg reported a second
    layout as a held AltGr. Key symbols come from the server's XKB map, so a third
    and fourth layout configured together type their own characters too. Where
    only XKB's key types say how a key behaves (a layout's own level order, as on
    some keypads and on cm(azerty)'s digit row, ISO level 5, a layout's own Caps
    Lock type, as Turkish declares for i/İ), the X11 result can differ from
    Wayland; the spec lists these.

  • A paste too large for an Input is refused whole instead of cut short
    (#1187).
    A paste that would take the field past 65,536 runes inserted only
    the first 65,536, with no sign anything was dropped, so pasted JSON, CSV or
    logs ended mid-document. Two smaller pastes that together crossed the budget
    were kept until the next keystroke cut the text silently. The field now stays
    unchanged, sounds the reject cue and calls OnPasteRejected. The budget
    counts the text after the paste, so replacing a selection frees room. Typed
    text and IME commits are unchanged. Two cases that used to keep part of a
    paste now refuse it: a Mask field handed more than 65,536 runes, which took
    its first matching characters, and a field whose app-set text is already over
    the budget, which now takes a paste only over a selection.

  • Typing or pasting the same digit over itself in a masked Input no longer
    counts as refused (#1187).
    In a full field such as 111, typing 1 at the
    start sounded the reject cue and left the caret where it was, so typing over a
    filled mask stalled on every digit that matched the one already there. Pasting
    123 over a selected 123 did the same and now would also have called
    OnPasteRejected. The mask accepted the input; only the text came out the
    same. The caret now moves on and the selection clears, as for any accepted
    edit, with no undo step, since the text did not change.

  • A press anywhere in an Input now places the caret and opens its context
    menu (#1186).
    In a Scrollable multiline Input, only the lines holding
    text took a press: a left click below the last line placed no caret, and a
    right click there opened no Cut/Copy/Paste menu. A field scrolled sideways
    past a long word took no press at all, not even on the visible text. The
    handler sat on the inner row that holds the text, which is only as tall as the
    text and moves with the scroll. It now sits on the field, which is where the
    I-beam cursor already shows. So a press in the padding of any Input places
    the caret too, at the nearest character, as in a native text field; before, it
    only focused the field. Scrolling is unchanged, and a press on a scrollbar
    still goes to the scrollbar, a right press included. A screen reader's
    activate action (VoiceOver press, AT-SPI press, TalkBack double tap) now
    focuses the field and leaves the caret where it was; before, it did nothing.

  • An open Select dropdown now closes when focus leaves the field (#1181).
    Before, a press on an Input or another focusable widget moved focus there
    but left the list open over the form, and a SetFocus from app code did the
    same. This is the check Combobox got in #1150; both widgets now share it. A
    press on the dropdown scrollbar keeps the list open, a pick in a multi-select
    still leaves it open, and a FocusDisabled select skips the check. Opening
    the list now also focuses the field, as Combobox does, so a screen reader's
    press opens a list that stays open.

  • A FocusDisabled Select or Combobox now opens on a click (#1183).
    Before, the click did nothing: the field takes no focus, so the press counted
    as unhandled and the window closed the dropdown again in the same press. A
    Select click also took focus away from the widget that held it. The field
    now consumes the press, so the dropdown opens, an option can be picked with
    the pointer, and focus stays where it was, as FocusDisabled promises. A
    FocusDisabled Combobox no longer moves focus to the first tab stop when it
    opens. Picking a Combobox row with the pointer now keeps focus where it was,
    as Select already did: on the field, or for FocusDisabled on the widget
    that held it. Before, the pick cleared focus.

What's Changed

Other changes

  • feat(gui): PixelSurface, an image the app updates every frame (#1168) by @ollex in #1178
  • feat(gui): shader texture inputs from multi-plane PixelSurfaces (#1168) by @ollex in #1180
  • fix(select): close dropdown when focus leaves the field (#1181) by @ollex in #1182
  • fix(select,combobox): open a FocusDisabled dropdown on a click (#1183) by @ollex in #1184
  • fix(input): take presses on the whole field, not the inner row (#1186) by @ollex in #1189
  • fix(input): refuse an oversize paste whole, add OnPasteRejected (#1187) by @ollex in #1191
  • fix(menu): open a context menu next to the cursor near the window edge (#1193) by @ollex in #1195
  • fix(keyboard): layout-correct keys, text and shortcuts on Linux (#1188) by @ollex in #1196
  • fix(wayland): treat EINTR from libdecor_dispatch as no events (#1194) by @ollex in #1197
  • feat(wayland): TitlebarDark switches the libdecor GTK frame (#1192) by @ollex in #1199
  • changelog: add v0.94.0 by @ollex in #1200

Full Changelog: v0.93.0...v0.94.0

Don't miss a new go-gui release

NewReleases is sending notifications on new releases.