Containers
Web view
A web page in the window, under the native UI painted after it, as menus and dialogs.
ui.WebView shows a web page in a window of native UI: a document the web
engine renders beside native controls, a page per tab, a preview of HTML
the app makes. The page is the system's web view (WKWebView, WebView2,
WebKitGTK), made with Window.NewWebView, and works as a window's page
does: it loads the app's frontend, calls bound Go methods with the typed
client and hears events. What the view paints after the web view shows
over the page: a menu opening from a button above it, a select's options,
a dialog and its dimmed backdrop, tooltips, toasts, a badge over a corner.
type browser struct {
win *mygo.Window
docs *mygo.WebView
menu bool
}
func (app *browser) view(c *ui.Context) {
ui.Column(c).Fill().AlignItems(ui.Stretch).Children(func() {
ui.Row(c).Padding(8).Gap(8).Children(func() {
menu := ui.Button(c, "Menu ▾")
if menu.Clicked() {
app.menu = !app.menu
}
// The popover shows over the page.
ui.Popover(c, menu, &app.menu, func() { ui.Text(c, "Over the page") })
})
ui.WebView(c, app.docs).Grow(1).Radius(8)
})
}
func main() {
app := &browser{}
mygo.App.WhenReady(func() {
app.win = mygo.NewWindow(mygo.WindowOptions{Title: "Docs", Content: ui.View(app.view)})
app.docs, _ = app.win.NewWebView(mygo.WebViewOptions{URL: "/docs"})
app.win.Invalidate()
})
if err := mygo.App.Run(); err != nil {
log.Fatal(err)
}
}Making web views
Window.NewWebView makes a web view of a window showing Content, which
it closes with. Its options are the page's (Page, as a window's
PageOptions), the URL to load, a BackgroundColor to fill it with
until the page paints, and Transparent, which shows what is behind the
web view through the transparent parts of its page: the window's
background or material, as native UI paints nothing under a web view.
WebView.Page returns its page, whose methods are a window's page's:
LoadURL, LoadHTML, Eval, GoBack, OnDidNavigate, OnWillNavigate
and the others. Focus gives it the keyboard, SetBackgroundColor
changes the color it fills with, as when the app's own theme changes
(one given as light-dark(<light>, <dark>) follows the appearance by
itself), CapturePage takes a screenshot of its page, and Destroy
closes it.
Make a web view once, as a window, not in the view: the view only shows it. One the view does not build hides and keeps its page, so a view of tabs builds the web view of the tab shown, and switching back finds the page where it was.
if app.tab == "docs" {
ui.WebView(c, app.docs).Grow(1)
} else {
ui.WebView(c, app.changelog).Grow(1)
}Its box
A web view has no size of its own. The element stretches across its
container (AlignSelf(Stretch)) and takes the room along it with Grow,
or a size. The page shows in the element's content box: inside its border
and padding, which paint around it with the element's background, rounded
as its Radius makes the border's inner edge. Scroll containers and clips
around it cut it as they cut any element, rounded corners included, and a
web view scrolled out of view hides. Opacity fades the page with the
element, as a page of a Router fading in does: the page blends over what
is painted under the element.
ui.WebView(c, app.preview).Height(320).Radius(12).Border(1, t.Border)What shows over it
The web view is under the window's native UI: the element cuts a hole through what the window paints, where the page shows. Everything painted after the element paints over the page, as it would over any element: its children, elements absolutely positioned over it, and the overlay's popovers, menus, selects, comboboxes, dialogs, tooltips and toasts. Shadows and translucent backgrounds blend over the page.
ui.WebView(c, app.page).Grow(1).Children(func() {
// A badge over the page's corner.
ui.Text(c, "Draft").Absolute().Top(8).Right(8).Padding(2, 8).Radius(8).Background(t.Accent)
})The pointer goes to the page where it shows and nothing painted over it
takes the pointer: a dialog's backdrop or a select's keeps the pointer
from the page while open, a popover keeps it only over its panel, and an
element with PassThrough lets it through. Files dragged there drop on
the page. A press on the page closes the popovers it is outside of
(PressedOutside), and takes the keyboard from the native UI, which gets
it back as the user clicks it. The page sets the cursor over it.
Tab stops at the web view among the native UI's controls: its page takes
the keyboard at its first element, or its last with Shift+Tab, Tab moves
through its elements, and past its last one goes on to the control after
the web view. Focus gives the page the keyboard where it had it.
Assistive technology finds the page inside the web view's element, where it is among the native UI: VoiceOver and Orca read and move through it as through the controls around it.
Its page
The page of a web view works as a window's does (see
Bindings). mygo.CallerPage(ctx) returns the page that
called a bound method, and Page.WebView its web view, while
mygo.CallerWindow(ctx) returns the window the web view is in, which the
page's window controls (mygo.window.minimize(), drag regions) act on.
Event.Emit to the window reaches the pages of its web views,
Event.EmitPage one page, and Broadcast every page.
func (s *Notes) Save(ctx context.Context, text string) error {
page := mygo.CallerPage(ctx) // the web view's
...
}The page's window.close() leaves the window open.
The typed client
The page calls Go with the client mygo generate writes, as a window's
page does: the client knows nothing of web views. A project of native UI
has no frontend, and so no client: give it one, say a Vite app in a
frontend directory, with the fields of the
frontend in its
mygo.json:
{
"name": "Notes",
"devUrl": "http://localhost:5173",
"devCommand": "bun run --cwd frontend dev",
"buildCommand": "bun run --cwd frontend build",
"frontendDist": "frontend/dist"
}The client is then frontend/src/mygo.ts (bindings changes where),
which mygo dev rewrites as the Go code changes and mygo build before
it builds the frontend; the frontend depends on mygo-runtime, which the
client imports. A web view loading / shows the frontend, from the dev
server during mygo dev and embedded in builds (see how pages
load), and its page calls the services
bound with mygo.Bind and hears the events declared with mygo.NewEvent:
// Notes is bound for the pages.
type Notes struct{ likes atomic.Int64 }
// Liked tells pages how many likes there are.
var Liked = mygo.NewEvent[int]("liked")
// Like counts a like and tells every page.
func (n *Notes) Like() int {
likes := int(n.likes.Add(1))
Liked.Broadcast(likes)
return likes
}
var page *mygo.WebView
func view(c *ui.Context) {
if page != nil {
ui.WebView(c, page).Fill()
}
}
func main() {
mygo.Bind(&Notes{})
mygo.App.WhenReady(func() {
win := mygo.NewWindow(mygo.WindowOptions{Title: "Notes", Content: ui.View(view)})
page, _ = win.NewWebView(mygo.WebViewOptions{URL: "/"})
win.Invalidate()
})
// ...
}import { Notes, events } from "./mygo";
likeButton.onclick = async () => {
count.textContent = String(await Notes.like());
};
events.liked.on((likes) => (count.textContent = String(likes)));Calls with a context, errors, channels and
events work as in a window. A page without a build, such as HTML the app
loads with LoadHTML, calls by name with the runtime the bridge puts in
every page, mygo.call("Notes.Like") and mygo.on("liked", …) (see
without the generated
client). Pages that are not
the app's own, as a web view showing https://example.com, cannot call Go
at all: see who may call.
Limits
The page is the system's web view, which the window composites with the
native UI; MyGo does not draw it. As with SwiftUI's WebView:
- Effects reading what is behind them, as the glass plugin's blur, see none of the page: it is not part of the window's frames.
Window.CapturePageof the window shows a hole where the page is;WebView.CapturePagecaptures the page.
On Windows, WebView2 keeps the elements of a page it shows this way under a window of its own: Narrator finds the web view in place, and the page's elements in that window, not inside it.
Testing
In a Tester, WebViewAt returns the web view that takes the pointer at a
point of the window, as the last frame showed it: the one under it, unless
what is painted over it takes the pointer there. ClickAt and Press over
a web view press the page, which the view hears of as the platform tells
it.