Packages that provide support for external events such as pointer, key and
system are only the beginning. Future packages are expected for clipboard
access, drag and drop, gps positions and so on.
To keep the number of top-level packages under control, move such I/O packages
to the new `io` directory.
The `system` package name was the previous solution to keeping the number of
top-level packages under control: I named it `system` instead of the narrower
`profile` because I expected to put all the less common events into it, turning
`system` into a "package util" smell.
With `io`, package system can be renamed to `profile`.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
A program might choose to process events that affect UI state already laid out.
For example, a button click might switch to a completely different UI page, but
the click might be processed during the drawing of the current page.
Avoiding that require either processing events very early during layout or
adding InvalidateOps whenever events are handled late.
Early event processing is awkward and InvalidateOps are easy to forget and
their absence masked by any other cause of redraw.
This change adds an implicit InvalidateOp for each frame where events have been
delivered to the program, allowing late event handling without use of
InvalidateOps.
In the worst case we waste a frame, increasing power use. I hope that future
optimizations will detect and discard the duplicate frame before it reaches
the GPU.
A similar situation applies to the delayed delivery of Editor events, but
since Editor.Layout flushes remaining events, extra InvalidateOps are not
required. Add a comment.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The Queue interface was changed from
type Queue interface {
Events(k Key) []Event
}
to the more complex single-step protocol
type Queue interface {
Next(k Key) (Event, bool)
}
to cater for a particular use case: Editor's SubmitEvent. When a
SubmitEvent is passed to a caller of Editor.Next, the Editor state,
in particular the current text, must not have changed by edits
later in the command stream. For example, pressing the keys <E>,
<Enter>, <E> should result in a SubmitEvent where the Editor has
a single 'e' in Text(), not two.
However, there is no reason to push the more complex Queue to every user.
Rather, store remaining input events inside Editor and process them as
Editor.Event (or Layout) is called.
Finally, revert the Queue interface to the simpler Events method.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Almost every layout and widget need the ui.Config for its environment,
an ui.Ops to store operations. Stateful widgets need an input.Queue
for events.
Add all these common objects to Context, greatly simplifying the
function signatures for Gio programs.
Fixes gio#33
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Context keeps the current Constraints and Dimensions so the layout
function scopes don't have to.
With
ctx := new(layout.Context)
a label with margins and alignment goes from
return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
in := layout.Inset{...}
return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
return text.Label{...}.Layout(ops, cs)
})
})
to
al.Layout(ops, ctx, func() {
in := layout.Inset{...}
in.Layout(c, ops, ctx, func() {
text.Label{...}.Layout(ops, ctx)
})
})
It was a difficult trade-off between the verbose functional approach
and the shorter but more complex Context.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit adds the first fully end-to-end test. It builds a very
simple Gio app, loads it on Chrome, and checks that it works.
To control Chrome, we use chromedp, a library in pure Go that takes care
of starting the browser and talking to it via the devtools protocol.
We add the test directly in the cmd module, since it mainly interacts
with the gogio tool, and also because the code might turn into some sort
of 'gogio test' command in the future. This does add chromedp and ui as
test dependencies to go.mod, but GOPROXY should allow a 'go get' of
gogio to not download their entire source code archives.
We don't replace ui with ../../ui in the go.mod, to ensure that testing
the cmd module works from anywhere without unintended differences.
The test app being used is inside a testdata directory, to ensure it's
not go-gettable, and that it doesn't otherwise affect the cmd module.
Finally, the test itself is pretty simple. The app just paints a red
background, and the test verifies that, once loaded, the background of
the browser viewport is indeed red.
The test does currently require Chrome or Chromium to be installed,
which is fine for now. It may also require a GPU, though I don't have a
headless machine to check for sure. The test uses Chrome in headless
mode though, so it doesn't open up any visible browser window.
All in all, the test succeeds in just over a second on my laptop with
Chromium 77.0.3865.75.
Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
The `gio` name clashes with a widely deployed GNOME tool.
Rename our tool to `gogio`, "the go tool for gio programs".
Fixes gio#20
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Before this change, layout objects followed a pattern where a
begin method would set up the layout and return a tweaked set
of constraints, and a end method would take the widget dimensions
and return the tweaked dimensions.
As has been pointed out, this process is error prone, because the
scope of the layout objects are not clear and because it is easy
to swap two begins or two ends.
It turns out that it is possible to implement layout with function
scopes in garbage free way. A typical layout changes from
al := layout.Align{Alignment: layout.NE}
cs = al.Begin(ops, cs)
in := layout.Inset{Top: ui.Dp(16)}
cs = in.Begin(c, ops, cs)
txt := fmt.Sprintf("m: %d %s", mallocs, u.profile.Timings)
dims := text.Label{Material: theme.text, Face: u.face(fonts.mono, 10), Text: txt}.Layout(ops, cs)
dims = in.End(dims)
return al.End(dims)
to
al := layout.Align{Alignment: layout.NE}
return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
in := layout.Inset{Top: ui.Dp(16)}
return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
txt := fmt.Sprintf("m: %d %s", mallocs, u.profile.Timings)
return text.Label{Material: theme.text, Face: u.face(fonts.mono, 10), Text: txt}.Layout(ops, cs)
})
})
Signed-off-by: Elias Naur <mail@eliasnaur.com>
I was seeing an extra newline when some errors were printed, like:
$ gio foo
please specify target
$
The source of the little bug was a trailing newline in the error
messages. Printing the messages already adds a newline.
Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Unlike macOS, Wayland leaves it up to the client to animate the
implied fling gesture when scrolling on a touchpad.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Use the Go import path to create an appID based on the domain name
plus the last directory location in the import path.
Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
Finding the maximum or adding Values are particularly for adjusting
margins for the safe area insets returned in app.UpdateEvent.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Invalidate is intended to be called as a result of external events,
which might very well be from a different goroutine than the one
driving the window.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Before this change MacroOp.Record simply reserved enough space for Stop to fill
out. If a user Record but never Stop'ed a MacroOp, the resulting Ops buffer
would end up with a zero, invalid opcode and panic at decode.
Fill out an empty MacroOp at Record, ensuring the Ops remains valid.
Signed-off-by: Elias Naur <mail@eliasnaur.com>