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

latest release: v0.94.0
16 days ago

Added

  • Window.HTML renders a small, safe subset of HTML (#1158). Use it to
    preview HTML that something else shows, such as a promo snippet an admin
    writes for a mobile app. It takes the same MarkdownCfg as Window.Markdown
    and draws through the same renderer, so styles, selection, links and the image
    policy work the same. Supported: p, div, br, hr, h1–h6,
    b/strong, i/em, u, s/del, mark, sup, sub, code, pre,
    blockquote, ul/ol/li, a href and img src alt width height. Other
    tags are dropped and their text kept. script, style and similar elements
    are dropped with their content. Links and image sources pass the same safety
    checks as Markdown, and remote images stay blocked unless AllowRemoteImages
    is set. The text goes straight into the document, so *, _, # and |
    stay literal; converting HTML to Markdown text in the app changed their
    meaning. There is no CSS and no script: this is not a browser. The parser is
    markdown.ParseHTML. See examples/html_preview/ and the HTML page in the
    showcase.

  • Table gets an optional row header column and a select-all corner, like the
    WinForms DataGridView (#1156).
    Set TableCfg.RowHeader to put a narrow
    selector cell in front of every row. A click on it selects that row. Under
    MultiSelect, Ctrl+click (Cmd+click on macOS) toggles the row and Shift+click
    selects a range. On the header row the cell is the corner: a click there
    selects every data row. The row that was clicked or moved to last shows a ▶
    marker. The cell is not part of Data, so column widths, alignments, and your
    sort and copy code do not see it. Selection still goes through Selected and
    OnSelect, and it keys on row index, so it stays stable while virtualization
    scrolls rows out of view. RowHeaderWidth sets the width (default 24). A
    focusable table with a row header also takes Shift+Space (select only the
    current row) and Escape (clear the selection; with nothing selected, Escape
    travels on). Any focusable MultiSelect table now takes Ctrl+A (Cmd+A on
    macOS) to select every data row. The showcase table demo has a "Row header"
    toggle.

  • A right press on an Input opens a Cut, Copy, Paste and Select All menu
    (#1151).
    Before, a right press in a text field did nothing. Copy and Cut act
    on the current selection, which the press keeps. Cut and Paste are off in a
    ReadOnly field, Copy and Cut are off in a password field, and Paste is off
    when the clipboard is empty. A paste from the menu goes through the field's
    mask and PreTextChange, the same as Ctrl+V. The field keeps its focus look
    while the menu is open, and it does not fire OnBlur or a blur
    OnTextCommit. Escape closes the menu and gives focus back to the field. The
    menu also works in an Input inside a dialog. To use your own menu, set
    InputCfg.ContextMenuDisabled and wrap the field in a ContextMenu. A
    FocusDisabled field gets no menu.

  • examples/solar_system gets pause, speed and mute controls, a close button,
    nav-dot names and a phone-width layout.
    Space pauses the orbits, Up and Down
    double and halve their speed (0.25x to 16x), and M mutes the ambience track.
    Buttons for mute and pause, and a speed readout, sit beside the zoom buttons.
    The camera keeps running while the orbits are paused. The fact sheet has a ×
    button, because a touch screen has no Escape key. Hovering a nav dot names its
    body with the canvas tooltip. Below 792 px wide, the fact sheet shrinks to the
    window and puts its six cards in two rows of three. Before, it ran off the
    edge of a narrow window. The embedded track is now FLAC (3.05 MB, was a 4.08
    MB WAV), lossless so the loop stays seamless, and low-passed at 3.5 kHz to cut
    the 8-bit source's hiss by 13 dB.

  • KrokiMermaidFetcher(baseURL) renders Mermaid diagrams through a
    self-hosted Kroki server (#1147).
    Set
    MarkdownCfg.MermaidFetcher: gui.KrokiMermaidFetcher("http://localhost:8000").
    Before, the Kroki address was fixed to the public https://kroki.io. When
    that service was slow or down, diagrams did not render. To use another server,
    an app had to write its own fetcher and copy the size limits and error
    handling. The new fetcher keeps all of them and uses the shared diagram HTTP
    client. A self-hosted server also keeps diagram source on your own network.
    The default is unchanged.

  • cmd/shaderport ports a Shadertoy shader to a *gui.Shader (#573). Run
    shaderport -name myShader -o my_shader.go my.frag on a shader with
    mainImage(out vec4, in vec2). The tool writes a Go file whose *gui.Shader
    has both bodies: GLSL for OpenGL, WebGL2 and Android, and MSL for macOS and
    iOS. Before, a copied shader was GLSL only and drew nothing on Metal. The tool
    is a token rewriter, so the output keeps your comments and macros and reads as
    a diff to review. It handles helper functions, structs, const globals,
    macros, out parameters, and the GLSL names that MSL spells differently. It
    translates mod with GLSL's floor rule, because MSL fmod truncates. It sets
    Animate when the shader reads iTime, iTimeDelta or iFrame. A construct
    it cannot translate (iMouse, iChannel*, texture(), a mutable global)
    stops it with the line and column, and it writes no file.
    examples/custom_shader adds a ported tile, and docs/shader-porting.md
    lists what the tool supports and rejects.

  • Shader.GLSLDecls and Shader.MetalDecls hold program-scope declarations
    (#573).
    A body runs inside the fragment function, so it cannot define a
    helper function or a struct. The two new fields go before that function, on
    every backend. A GLSL helper can read the built-in inputs, which are globals
    there. In MSL they are locals of the fragment function, so a helper takes them
    as parameters. gui.BuildGLSLFragmentDecls and gui.BuildGLSLESFragmentDecls
    wrap a body together with its decls. ShaderHash now covers all four strings.

  • gui.ShaderHashCache memoizes ShaderHash per *Shader (#573). Each
    backend calls ShaderHash on every custom-shader draw of every frame, and it
    reads every byte of the source. For a ported shader of 32 KB that took about
    30 µs per draw. Every GPU backend now keeps one cache per window: a hit takes
    about 4 ns and allocates nothing. The cache compares each string's data
    pointer and length, so a field set to new text gets a new hash.

Changed

  • BREAKING: an Input inside a ContextMenu must now set
    ContextMenuDisabled to keep the app's menu (#1151).
    A right press on an
    Input now opens the built-in Cut, Copy, Paste and Select All menu and
    consumes the press, so a ContextMenu wrapped around the field no longer gets
    it. To keep your own menu, set InputCfg.ContextMenuDisabled: true on the
    field.

  • A menu item now runs when the mouse button comes up, not when it goes
    down.
    This applies to every menu: ContextMenu, OverflowPanel, Menubar
    dropdowns, submenus and the Input menu. The item runs only when the press and
    the release are both on it, so a press dragged from another item, or the
    release of the right press that opened the menu, runs nothing. A press inside
    an open menu no longer closes it: a press on a disabled item, a separator or
    the menu padding leaves the menu open, as in a native menu. An item with a
    submenu still opens the submenu on the press. A right click on an item no
    longer runs it. A test that sends only a mouse down to a menu item must now
    send the release too; Window.TestClick already sends both.

  • Touch: a long press that no handler consumed lets the finger go when it
    drags, so a finger that rests before it drags scrolls (#1117).
    A touch held
    still for 500 ms became a long press, and from then on its moves were ignored:
    the page did not scroll until the finger lifted. A reader who placed a finger
    carefully, for example aiming at the thin scrollbar and missing it by a few
    pixels, saw the page freeze. Now a move past the pan distance cancels the long
    press (GesturePhaseCancelled, not Ended), releases the press it sent where
    the finger now is, and scrolls. A long press still keeps the finger when its
    press took the mouse lock (a reorderable row, a text selection, a scrollbar
    thumb, which the finger then drags) or when an OnGesture handler consumed
    its Began. An app whose OnGesture acts on GestureLongPress without
    ctx.Consume() changes behaviour: a finger that drags after the long press
    now gets a Cancelled phase and pans. Call ctx.Consume() in that handler to
    keep the finger, as the showcase gesture pad now does.

Fixed

  • The open list of a Select or Combobox is at least as wide as the field
    (#1155).
    Before, a Select list was only as wide as its longest option, so a
    FillFit field in a wide column opened a narrow list. A Combobox list took
    the field's width, but its rows kept their own width, so the hover color
    stopped short of the edge, and an option wider than the field was cut off. Now
    both lists are as wide as the field, or wider when an option needs more space.
    The rows fill the list. MaxWidth is still the upper limit.

  • A macOS app built with CGO_ENABLED=0 fails to link instead of panicking at
    launch (#1159).
    The macOS backend needs cgo. Before, a darwin build with cgo
    off linked, and the app panicked at start with "no native backend available".
    Now the link fails and the error names the cause:
    relocation target .../gui/backend.macOS_requires_cgo__set_CGO_ENABLED_1 not defined.
    Set CGO_ENABLED=1 to build for macOS. go vet and lint with cgo off still
    pass, and builds for other platforms do not change.

  • Touch: a vertical pan over a horizontally scrolling child scrolls the page
    (#1118).
    Before, a page barely moved while the finger was over a child that
    scrolls only sideways, such as a wide Markdown table: the table took each
    move's sideways drift, and the vertical part never reached the page. A pan now
    locks to one axis when it starts more than twice as far along it as across, as
    on iOS and Android, and scrolls only on that axis until the lift. A diagonal
    start scrolls both axes. Each axis goes to the innermost scrollable that moves
    on it, so a diagonal pan over the table scrolls the table sideways and the
    page vertically, and the lift flings each container on its own axis. The lock
    applies to scrolling only: an OnGesture handler still reads the finger's raw
    deltas, less any axis a scrollable inside it took. Note for apps: an
    ancestor's OnGesture now runs on a pan move that an inner scrollable took
    only one axis of, with that axis zeroed. Before, the inner scroll stopped the
    walk. A handler that must stay quiet while an inner scrollable moves (a
    swipe-to-dismiss) checks for a zero delta on its axis.

  • An open menu no longer passes hover to the widgets under it. Before, the
    pointer over a disabled item, a separator or the edge of a ContextMenu,
    OverflowPanel, submenu or Input menu also hovered what was below the menu,
    so a button under it showed its hover color. The menu panel now takes the
    hover. Enter, Space or a click on a menu item now keeps a focus that either
    action set, the item's MenuItemCfg.Action or the menu's MenubarCfg.Action.
    Before, Enter and Space always cleared focus after the action, and a click
    kept only a focus set by MenubarCfg.Action. While a modal dialog is open,
    the layers above it (the Input menu, the inspector) now take hover too;
    before, only the dialog's own shapes did. Found while testing #1151.

  • A widget shows its pressed color only for a press that began on it.
    Button, Input, InputNumeric, Radio, Switch, Toggle, ExpandPanel
    and Breadcrumb read the held left button to draw their pressed color, so a
    press that began on another shape and dragged over them made them look
    pressed. A menu item picked over a button left that button pressed until the
    release. They now use the shape the press went down on, the same rule
    Window.IsPressed uses. GoldenCfg.MousePressed now records its press on the
    shape under HoverX, HoverY. Found while testing #1151.

  • An open Combobox dropdown now closes when focus leaves the field
    (#1150).
    Before, a press on an Input or another focusable widget moved
    focus there but left the dropdown open over the form. The dropdown now also
    closes on a press in empty space, which clears focus. A press on the dropdown
    scrollbar keeps it open, and a press on a row still picks that row.

  • audio.LoadMusicBytes: embedded MP3, OGG and FLAC tracks play. Play and
    FadeIn seek the track to its start, but in-memory MP3, OGG and FLAC data
    reached the decoder through a reader that could not seek. FLAC and OGG
    returned seek music: ... not enabled and stayed silent. MP3 panicked with a
    divide by zero inside go-mp3. Only WAV worked. All four formats now get a
    seekable reader. LoadMusic from a file path was not affected.

  • examples/solar_system: motion stays smooth on long runs, keeps pace when
    frames drop, and -screenshot plays no audio.
    The simulation clock was
    float32. After about a day, one float32 step was larger than a tick: Earth
    froze on some ticks and jumped on others. The clock is now float64, and
    every orbit, spin and twinkle angle is reduced to a fraction of a turn before
    float32 math. Each tick also advanced a fixed 16 ms, so a slow frame slowed
    the orrery. A tick now measures the real time since the last one, capped at
    100 ms so a laptop wake does not jump the planets. And -screenshot started
    the ambience track, because soft.RenderToPNG runs OnInit. The track now
    starts in main, on the interactive path only.

  • SVG: geometry from CSS (r, cx, cy, ...) renders, so the showcase
    var() + calc() sample is no longer blank (#1145).
    A circle whose radius
    came only from a stylesheet (.v { r: calc(var(--r) - 4); }) parsed to zero
    paths: the cascade dropped the unknown property and the shape parsers read
    only the r attribute. The SVG2 geometry properties now ride the normal
    cascade (presentation attribute < author rule < inline style, with
    var()/calc() resolved), and the circle/rect/ellipse/line parsers use the
    cascade winner. A bogus rule value is ignored instead of clobbering the
    attribute, a negative r still disables rendering, and geometry never
    inherits from the parent.

  • Touch: a slow drag no longer jumps the content where it starts (#1117). A
    pan is recognized after 10 px of travel, and its first move caught up those 10
    px in one step, so the content sat under the finger. A fast swipe hid it; a
    slow drag, such as one that rested first, showed a 10 px jump. The pan now
    starts where the finger crossed the threshold and drops the distance, as
    Android does and DragScroll containers already did: the content stays 10 px
    behind the finger. The pan's GestureDX / GestureDY drop it too, so an
    OnGesture handler that sums them lags the finger by the same 10 px, and a
    short, slow flick reads a little slower in VelocityX / VelocityY and is
    less often a GestureSwipe. The fling after a lift reads the finger's real
    positions and is unchanged.

  • Android: temp files land in the app's own directory, so icons and other
    temp-file assets load (#1140).
    Go's os.TempDir() fell back to
    /data/local/tmp, which an app cannot write: a Go shared library reads its
    environment once when it loads and never sees the TMPDIR Android sets later
    through libc (from Android 13), and before Android 13 Android sets none. The
    icon font failed to load, so every icon showed as a box, and anything else
    written with os.CreateTemp failed too: app fonts added as bytes, the diagram
    cache, PDF printing, the showcase's images. TMPDIR is now the app's cache
    directory on every Android version: the backend takes it from libc where
    Android set it, and below Android 13 uses the directory the host passes from
    Context.getCacheDir() to SetCacheDir. Android trims that directory when
    storage runs low, so the remote image cache persists across launches, and all
    processes of one app share it. A host calls
    android.SetCacheDir(cacheDir.absolutePath) before Start, as
    examples/android_demo now does; without it, temp files still fail below
    Android 13. Android fonts loaded from bytes use stable cache files rather than
    leaking new temporary files on every launch. The remote image cache now
    resolves its directory on first use instead of at package init, so it follows
    the same TMPDIR.

  • Web: a NaN, infinite or out-of-range event field reads as 0, and a fake
    touch list cannot hang the page (#1131).
    A script can dispatch an event
    whose offsetX or deltaY is NaN, ±Inf or too large for a float32. The web
    backend passed the value on to Event.MouseX or ScrollY, and from there
    into hit tests and scroll offsets. It now reads such a value as 0, as it
    already did for a missing field. Integer fields (keyCode, deltaMode, a
    touch identifier) do the same. A touch list that claims a huge length made
    the backend scan it without end; it now looks at no more than 64 entries.

  • Touch: a long press, then a drag, reorders a list on a scrolling page
    (#1134).
    Since #1116, a touch on a reorderable ListBox or Tree row on a
    page that scrolls vertically waited to see a tap or a scroll, and a vertical
    drag counted as a scroll: the row could not be reordered by touch. A long
    press armed the drag, but its moves were dropped. Now the moves after a long
    press go to the drag, so holding a row for half a second and then dragging
    reorders it, as on iOS and Android. A quick swipe still scrolls the page. A
    touch drag that the platform cancels, or that a second finger takes over, is
    now cancelled through the lock's Cancel hook instead of released, so it no
    longer drops a reordered row, a docked panel or any other drag where the
    finger happened to be. The moves after a long press drive any drag this
    touch's press started, so a row held and then dragged on a list that does not
    scroll reorders too, and a held text selection grows as the finger moves. A
    drag already held when the finger landed is another drag's: it gets none of
    the moves, and an abort of this touch does not cancel it.

  • Android: an app returning from the background is drawn again, not left black
    (#1136).
    GLSurfaceView destroys its GL context while the app is in the
    background, and the next frame ran in a new context with the old programs,
    buffers and textures, so only the background color appeared. The new
    android.SurfaceCreated tells the backend about the new context. It then
    rebuilds its GL resources without deleting the dead ones: images and custom
    shaders are uploaded again on first use, and the glyph atlas is sent again
    from its CPU copy before the first frame draws, so text does not flash blank.
    The window, its view and app state, and the loaded fonts stay as they were. If
    the rebuild fails, the backend logs it once and tries again each frame; the
    app no longer has to crash. A host must call SurfaceCreated from
    GLSurfaceView.Renderer.onSurfaceCreated, as examples/android_demo now
    does. A host built from that demo should also set
    preserveEGLContextOnPause = true on its GLSurfaceView: Android then
    usually keeps the context, and a resume rebuilds nothing.

  • Android: rotating the device lays the window out at the new size (#1138).
    A host calls Start from every onSurfaceChanged, and on a running backend
    Start only resized the GL viewport. The window was never told, so after a
    rotation, a split-screen change or a fold it kept laying out at the size it
    started with: in landscape the content stayed portrait-wide with the rest of
    the screen empty. Start now sends the window the new size, as Resize
    already did. A host needs no change.

What's Changed

Other changes

Full Changelog: v0.92.0...v0.93.0

Don't miss a new go-gui release

NewReleases is sending notifications on new releases.