Guides
Windows with native UI
Window options, title bars drawn by the view, zooming the content, and vibrancy.
A window with Content takes the window options
of any window, such as its size, StateKey, Frameless and Parent, as
well as menus, dialogs and the other desktop APIs. It has no
page: Page() is nil, and it ignores URL and WindowOptions.Page. Its Go
code needs no bindings: the view calls it directly. CapturePage returns a
PNG of what it shows.
mygo.NewWindow(mygo.WindowOptions{
Title: "Notes",
Width: 900,
Height: 600,
StateKey: "main",
Content: ui.View(app.view),
})The view first runs once NewWindow returned, so it can use the window
kept from it, as app.win.Title(): called on the main thread, NewWindow
shows the window on the main loop's next turn (see
showing windows).
Title bars
With TitleBarStyle: mygo.TitleBarHidden, the view draws the title bar
under the window controls, as a page does with the --mygo-titlebar-* CSS
variables: c.TitleBar() returns the room the controls take, zero in full
screen, and DragWindow makes elements drag the window, which
double-clicking them zooms or minimizes as a title bar would:
bar := c.TitleBar()
ui.Row(c).Height(max(bar.Height, 32)).Padding(0, bar.Right+12, 0, bar.Left+12).DragWindow().Children(func() {
ui.Text(c, "Inbox").Bold()
})bar.Bottom is where the controls end, from the top edge: the bottom of
the traffic lights on macOS, which TrafficLightPosition moves, and
bar.Height on Linux and Windows. A sidebar that runs to the top of the
window, with the traffic lights over it, starts its items below them:
ui.Column(c).Width(220).Padding(bar.Bottom+8, 8, 8, 8).DragWindow().Children(func() {
ui.Text(c, "Inbox")
})In a Frameless window, DragWindow makes an element move the window, and
a double click on it maximizes the window.
A handle that means something else when double-clicked, as the empty part
of a sidebar starting something new, takes the double click with
WindowDoubleClicked, and still moves the window:
side := ui.Scroll(c.Key("sidebar")).Grow(1).DragWindow()
if side.WindowDoubleClicked() {
app.newItem()
}To draw the window controls too, where the view wants them, add
HideWindowButtons: true to the hidden title bar: the system's controls go,
c.TitleBar() is zero, and the window keeps its title bar's edges, which
resize it, the top one included. The view's buttons call Minimize,
Maximize, Unmaximize and Close.
Zoom
c.SetZoom(z) zooms the window's content, as a browser zooms a page: the
view is laid out in a window 1/z its size and shown z times larger. It
is not a picture of the window scaled up: frames draw at the screen's
resolution times z, so text, icons, vectors, images and their edges
stay as sharp as at that size natively, and the window's frames keep
their size in pixels. A zoom of 2 on a screen of scale 2 draws the frame
a screen of scale 4 would, glyphs and all; unzoomed, nothing changes. The
zoom stays until set again, between 0.25 and 5; c.Zoom() returns it, 1
for none.
c.SetZoom(app.zoom) // 1.25 for 125%, every frame or once
width, height := c.Size() // the content's: the window's divided by the zoomWhat the view sees is zoomed: c.Size(), c.TitleBar(), Bounds
and every length are in the content's DIPs, z of them to the window's.
A frame that sets another zoom builds again at the new size before it
shows. What leaves the content goes to the window in its DIPs: the
pointer hits what the zoom shows under it, input methods compose at the
caret, assistive technology outlines elements where they show, context
menus and drags start under the pointer, web views take the box the
content gives them, and a MaximizeButton shows Windows' snap layouts
where it shows. A touchpad scrolls the content as far as the fingers
move; a wheel's lines scroll as far as unzoomed. The window controls keep
their size: c.TitleBar() is the room they take, zoomed, so that a title
bar laid out clear of them stays clear.
The window's own sizes are not zoomed: WindowOptions.Width and Height,
Window.SetContentSize and Window.ContentSize are in the window's DIPs,
z times the size the view sees, as a page's are at its zoom
(Page.SetZoomFactor). A flyout of 320 × 400 zoomed by 1.5 lays its view
out in about 213 × 267. To give the view a size in its own DIPs, multiply
it by the zoom:
z := c.Zoom()
flyout.SetContentSize(int(math.Ceil(float64(320*z))), int(math.Ceil(float64(400*z))))A transform's Scale, by contrast, scales what is drawn at the content's
own size: its text is drawn as bitmaps of their untransformed size,
sampled bilinearly (Transforms), right for
motion, blurry for zooming a window.
Vibrancy
A window's Vibrancy shows wherever its native UI draws no background, on
macOS and, in a window created with one, on Windows 11 22H2 and later.
c.Vibrancy() tells whether it shows: elsewhere, what the view leaves
transparent shows no material, so draw a background there instead. The
root draws the theme's by default: make it transparent and give
backgrounds to the parts that need one, as a sidebar beside opaque content
does:
if c.Vibrancy() {
c.Root().Background(ui.Transparent)
}
ui.Row(c).Fill().AlignItems(ui.Stretch).Children(func() {
sidebar := ui.Column(c).Width(240)
if !c.Vibrancy() {
sidebar.Background(c.Theme().Surface)
}
sidebar.Children(func() { app.sidebar(c) }) // over the material
ui.Column(c).Grow(1).Background(c.Theme().Background).Children(func() { app.content(c) })
})Text over an opaque background keeps the system's subpixel antialiasing
(ClearType) under a transparent root; over the material, it is
antialiased in grayscale. examples/vibrancy picks the window's material
in such a sidebar.
Liquid Glass over other apps
VibrancyGlass and VibrancyGlassClear fill the window with macOS 26's
Liquid Glass, which the system draws with what is behind the window:
other apps' windows, the desktop. A frameless window with a
CornerRadius is then a pane of glass floating over them, as a launcher's,
a heads-up display or a floating toolbar:
hud := mygo.NewWindow(mygo.WindowOptions{
Frameless: true,
Transparent: true,
AlwaysOnTop: true,
Vibrancy: mygo.VibrancyGlassClear, // Acrylic on Windows 11
CornerRadius: 18,
Width: 360,
Height: 64,
Content: ui.View(view), // its root transparent where c.Vibrancy()
})
hud.SetActiveAppearance(true) // stays clear over the app in front (macOS)VibrancyTint colors that glass, by its alpha, as AppKit's
NSGlassEffectView does:
hud.SetVibrancyTint("light-dark(rgba(0, 122, 255, 0.2), rgba(255, 159, 10, 0.2))")Other materials and platforms ignore it. VibrancyTint in
WindowOptions sets it at creation.
The glass plugin draws Liquid Glass too, on every
platform, but with what the window itself draws: a toolbar over content
scrolling under it. The window's glass shows what no window of the app
draws, and only where the system has it: before macOS 26 it is the popover
or HUD material, on Windows 11 Acrylic, and on Linux none, which
c.Vibrancy() tells.
The clear glass looks clear only in a window drawn as active, the key
window of the app in front; otherwise the system frosts it, as it does a
panel floating over the app the user is in. SetActiveAppearance keeps
the window drawn as active, with a private AppKit call that a later macOS may
drop, the window then drawn as any other: apps for the Mac App Store
should leave it off.
Flyouts that fit their content
A flyout with FitContent takes the size its native UI measures and
follows it as it changes, within MinWidth, MinHeight, MaxWidth,
MaxHeight and the work area: the root's content at its max-content
width and its height at that width, as a tray panel whose list or error
line comes and goes. A frame that measures another size resizes the
flyout, and the next lays the content out at that size. A hidden flyout
lays out content that changed while it was hidden as it shows, and shows
at the size that content takes. See
Size a flyout to its content.
Active and inactive windows
Native apps draw the window the user's input goes to apart from the
others, so that users tell at a glance which window has it: macOS fades
the material of an inactive window's sidebar to an opaque gray and the
accent of its selections and controls to gray, Windows draws its Mica as
a solid fill, GTK styles it :backdrop. A window is active while it, or
one of its flyouts, has the keyboard (a menu or popover belongs to its
window), and always while SetActiveAppearance keeps it so; panels and
flyouts without a parent float over whatever app is in front and are
always active.
The built-in widgets follow on their own: selected rows, the icons of a
sidebar, checked check boxes, radio buttons and switches, sliders,
progress bars and the line of the selected tab fade from the accent to
the theme's InactiveAccent, a gray, and primary buttons look as the
others (themes keep the accent with
InactiveAccent: Accent). A view draws its own with c.WindowActive():
dot := c.Theme().Accent
if !c.WindowActive() {
dot = c.Theme().TextMuted
}A frame that reads it is drawn again as it changes; one that does not
draws nothing more. Tester.SetWindowActive switches it in tests,
leaving the keyboard focus to SetFocused. The window's material follows
the system (macOS fades it in a window other than a panel or a flyout,
Windows its Mica); examples/vibrancy shows both together.
Hidden windows
c.WindowHidden() reports whether nothing of the window shows on
screen, as the web's document.hidden: a view showing a clock, a price
or a playback position stops its work while it is hidden, and refreshes
as it shows again. The frame that asks builds once more as the window
hides, though no frame draws while it is hidden, and again as it shows:
app.ticker.SetPaused(c.WindowHidden()) // a ticker of your ownFrames for what moves, animations and drawings that move, wait anyway
while it is hidden. macOS tells it as the window is minimized, hidden,
on another space or covered by other windows; browsers as the tab or
the window is out of sight; Linux (on X11) and Windows as it is
minimized. Tester.SetWindowHidden hides and shows it in tests.
Window.IsVisible answers another question: whether Show showed the
window and Hide did not hide it since, so a minimized window is still
visible. Work outside the view, as a goroutine that polls, learns that
the window went out of sight from the view, which builds again as it
does:
paused.Store(c.WindowHidden()) // an atomic.Bool the goroutine readsClosing
A view can close its own window, from a button's click or its callback.
The window goes at once, by Destroy or by a Close no OnClose
listener prevents, and the rest of the view builds as it would, with a
Context that still has the theme and the window's size. The frame ends
there: nothing more is laid out or painted, and no other callback runs.
The closed window keeps nothing of what it showed, the view and what it
reaches included, though the app keeps the *mygo.Window.
Open the window that takes over first, since an app quits
when its last window closes:
if ui.Button(c, "Done").Clicked() {
app.showMain()
win.Close()
}No webview
An app whose windows all show native UI needs no webview: on Linux it needs GTK 3 alone, not WebKitGTK, and on Windows no WebView2 Runtime.