34 Commits

Author SHA1 Message Date
Chris Waldon c8801fe233 widget: test update-only editor logic
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-11-10 16:17:56 -05:00
Chris Waldon 3fde0c0061 widget: [API] split text widget Update from Layout
This commit introduces Update(gtx) functions for both Selectable and Editor, allowing their
state to be updated explicitly prior to layout. This completes the transition that allows all
Gio widgets to have their state updated ahead-of-time, ensuring that there is zero frame lag
between an input event and the widget response to that event.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-11-10 14:59:06 -05:00
Chris Waldon 9d89f7c8b1 text: add system font loads to debug log
This commit adds a GIODEBUG=text log message each time a system font is resolved.
This makes it vastly easier for application authors to determine which system fonts
are being used by their application.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-11-10 08:27:43 -05:00
Egon Elbre 48bd5952b1 widget: optimize processGlyph
processGlyph does not modify the value, so there's no reason to
return the struct.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-11-09 15:18:46 -05:00
Egon Elbre df8a8789a3 text: [API] reduce size of Glyph.Runes to uint16
This shrinks text.Glyph from 72B to 58B.

  LabelStatic/1000runes-RTL-arabic-32   63.62µ ± 0%   62.05µ ± 0%  -2.47% (p=0.002 n=6)

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-11-09 15:18:46 -05:00
Egon Elbre 62edabe137 text: use a simpler hash
The hash calculation is a significant bottleneck in caching,
replace it with a simpler "add; multiply by a prime" approach.

  LabelStatic/1000runes-RTL-arabic-32    89.75µ ± 2%   63.58µ ± 1%  -29.16% (p=0.002 n=6)

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-11-09 15:18:46 -05:00
Egon Elbre 49296bd0ca internal/ops: use uint32 for pc, version, macroID
4GB of render data should be sufficient for anyone.

By replacing an int with uint32, it allows for a smaller memory
footprint and faster caching. Example impact on rendering static
labels.

                                     │  before.txt  │             after.txt              │
                                     │    sec/op    │   sec/op     vs base               │
LabelStatic/1000runes-RTL-arabic-32     98.08µ ± 3%   88.17µ ± 1%  -10.10% (p=0.002 n=6)
LabelStatic/1000runes-RTL-complex-32    103.9µ ± 2%   101.9µ ± 1%   -1.84% (p=0.009 n=6)
LabelStatic/1000runes-RTL-emoji-32      113.3µ ± 2%   100.7µ ± 3%  -11.11% (p=0.002 n=6)
LabelStatic/1000runes-RTL-latin-32     100.01µ ± 1%   92.31µ ± 1%   -7.69% (p=0.002 n=6)
LabelStatic/1000runes-LTR-arabic-32     97.90µ ± 2%   87.92µ ± 2%  -10.19% (p=0.002 n=6)
LabelStatic/1000runes-LTR-complex-32   102.63µ ± 2%   99.81µ ± 1%   -2.75% (p=0.002 n=6)
LabelStatic/1000runes-LTR-emoji-32     106.56µ ± 2%   98.47µ ± 1%   -7.59% (p=0.002 n=6)
LabelStatic/1000runes-LTR-latin-32      97.51µ ± 1%   92.60µ ± 3%   -5.03% (p=0.002 n=6)
geomean                                 102.4µ        95.09µ        -7.10%

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-11-09 15:18:46 -05:00
Elias Naur d078bf0ed7 app: unexport NewDisplayLink
It's not supposed to be used outside of package app.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-31 17:56:24 -05:00
Elias Naur ea58aacde2 app: [macOS] don't free nil string in ReadClipboard
Fixes: https://todo.sr.ht/~eliasnaur/gio/539
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-16 08:24:09 -05:00
Larry Clapp ae2b1f42b2 widget: Update Selectable key filter
Selectable was using a key event filter copied directly from editor.go,
but it didn't actually process all those keys. Update the filter to only
ask for the keys that Selectable actually uses.

Signed-off-by: Larry Clapp <larry@theclapp.org>
2023-10-15 10:44:39 -04:00
Elias Naur 63fea3d2bd widget: use local random source to avoid deprecated rand.Seed
This change replace the global rand use with a local source, to avoid
the recently deprecated global rand.Seed function. At the same time, the
time-dependent seeds are replaced with static numbers to ensure
reproducible benchmarks numbers.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-14 15:45:58 -05:00
Elias Naur ce8475a0b9 Revert "app: [Wayland] avoid a race on the send side of the wakeup pipe"
This reverts commit 7fde80e805, because
Wakeup can no longer be called after the window has been destroyed.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-14 10:02:31 -05:00
Elias Naur 37717d0df9 app: [API] replace events channel with an iterator interface
The goroutine started by Window.run runs concurrently with the user
goroutine receiving from Window.Events, leading to races such as #543.
This change replaces the Window.run goroutine and the Window.Events
channel with an iterator API driven by the user goroutine directly.

Fixes: https://todo.sr.ht/~eliasnaur/gio/543
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-14 10:02:28 -05:00
Elias Naur 7550d85447 .builds: remove unused Chrome
Chrome was required when gogio was part of the repository. It is no
longer.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-08 18:46:52 -05:00
Elias Naur c756986d9e gesture: [API] rename gesture state update methods to Update
Change the gesture state update methods to align with the convention.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-08 12:37:12 -05:00
Elias Naur dc170033cd io/router: [API] drop extra frame
This change removes the extra frame scheduled when events was delivered
during a frame. This extra frame was intended to paper over state changes
that happen later than the layout depending on it.

However, it is better for programs to never allow such state change skew,
and recent changes allows them to refresh and query state before layout.

This is an API change because programs may rely on the extra frames.
Those programs should ensure that state is updated before relying on it
in layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:04:32 -05:00
Elias Naur d42dae73f0 widget: [API] separate Float state update; remove min, max, invert parameters
This change allows users of Float to determine its state before Layout
by calling Update.

While here, remove the value transformation represented by the min, max,
invert parameters; they're too many arguments for a computation that
may as well be done by the user.

Remove Float.Pos; it is better to compute its value from the dimensions
returned by Float.Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:04:32 -05:00
Elias Naur 23e44292bb widget: [API] separate state changes from Draggable.Layout to Update
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:04:32 -05:00
Elias Naur fe85136f99 widget: [API] move Enum state update to Changed, rename it to Update
Similar to an earlier change for other widgets, this change separate
Enum state changes for access earlier than Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:04:32 -05:00
Elias Naur b9837def5c widget: [API] move Decorations state update to Actions
Similar to a previous change for Clickable and Bool this change separates
state changes from Decorations.Layout to Actions so that access may
happen before Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:04:13 -05:00
Elias Naur dc97871122 widget: [API] rename Bool.Changed to Update and move state update to it
Similar to a previous change for Clickable, this change separates Bool
state changes to its renamed method Update. This allows access to
the most recent state before calling Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:03:25 -05:00
Elias Naur 4a4fe5a69b widget: [API] move Clickable state update from Layout to Clicks
Before this change, Clickable state updates would happen in Layout.
However, that is too late in cases where clicks affects layout that
contiains the Clickable.

This change removes state changes from Layout and moves them to Clicks,
to allow users pre-layout access. Note that Layout itself processes
events, which means users can no longer access clicks after Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 20:03:22 -05:00
Elias Naur 1686874d07 gesture: [API] rename ClickType to ClickKind
"Kind" is the Go idiomatic name for distinguishing structs outside of
the type system.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 19:11:11 -05:00
Elias Naur 650ccea28d io/pointer: [API] rename PointerEvent.Type to Kind
Kind is the idiomatic field name for distinguishing a struct without
using separate types.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 19:11:08 -05:00
Elias Naur e1b3928819 io/semantic: [API] replace DisabledOp with EnabledOp
The double-negative DisabledOp is harder to understand than a
straightforward EnabledOp. Note that the absence of an EnabledOp
implies still means that the widget is enabled.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 18:08:52 -05:00
Elias Naur b66dcc436c app: [macOS] fix transition from maximized to restored
The NSWindow.zoomed property is not reliable when a window is being
constructed. Only call it when necessary.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-02 18:48:18 -05:00
Elias Naur 526db27c75 gpu: fix opacity layer rendering on OpenGL
Fixes: https://todo.sr.ht/~eliasnaur/gio/536
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-02 13:07:15 -05:00
Chris Waldon 27193ae8e8 op/clip: prevent no-op path segments
This commit prevents the insertion of LineTo and QuadTo path segments that have
no visible effect on the path (because the path's pen is already at their end state).
This eliminates whisker artifacts from some stroked paths. Thanks to Morlay for the
bug report leading to this fix.

Fixes: https://todo.sr.ht/~eliasnaur/gio/535
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-09-18 09:28:11 -05:00
Elias Naur 313c488ec3 app: [Windows] remove padding from maximized custom decorated windows
As described in https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
Windows extends maximized windows outside the visible display. This is
not appropriate for custom decorated windows, so this change implements
a workaround in the handling of WM_NCCALCSIZE.

While here, replace the deltas field from window state to fix issues
when switching between decoration modes.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-09-08 15:22:40 -05:00
Elias Naur f30e936d9a app: [Windows] remove redundant call to SetWindowText
And fix a typo while here.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-09-08 15:12:47 -05:00
Elias Naur ae3bd2a1e1 op/paint: add opacity operation
The new paint.PushOpacity allows for adjusting the opacity of a group
of drawing operations.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-09-08 11:46:17 -05:00
Elias Naur ae43d18ced internal/ops: remove unused TypePushTransform
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-09-07 14:48:11 -05:00
Dominik Honnef b4d93379c4 op: don't allocate for each string reference
When storing a string in an interface value that escapes, Go has to heap
allocate space for the string header, as interface values can only store
pointers. In text-heavy applications, this can lead to hundreds of
allocations per frame due to semantic.LabelOp, the primary user of
string-typed references in ops.

Instead of allocating each string header individually, provide a slice
of strings to store string-typed references in, and store pointers into
this slice as the actual references. This only allocates when resizing
the slice's backing array, and averages out to no allocations, as the
backing array gets reused between calls to Ops.Reset.

We introduce two new functions, Write1String and Write2String, which
make use of this new slice for their last argument. We could've
automated this in the existing Write1 and Write2 methods, but that would
require type assertions on each call, and the vast majority of ops do
not make use of strings.

Signed-off-by: Dominik Honnef <dominik@honnef.co>
2023-09-02 09:02:39 -06:00
Dominik Honnef b9654eb4eb app: support numpad keys in xkb
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2023-09-01 16:34:16 -06:00
61 changed files with 1155 additions and 919 deletions
-5
View File
@@ -67,11 +67,6 @@ tasks:
CGO_ENABLED=1 GOARCH=386 go test ./... CGO_ENABLED=1 GOARCH=386 go test ./...
GOOS=windows go test -exec=wine ./... GOOS=windows go test -exec=wine ./...
GOOS=js GOARCH=wasm go build -o /dev/null ./... GOOS=js GOARCH=wasm go build -o /dev/null ./...
- install_chrome: |
curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
sudo apt-get -qq update
sudo apt-get -qq install -y google-chrome-stable
- install_jdk8: | - install_jdk8: |
curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb" curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
sudo apt-get -qq install -y -f ./jdk.deb sudo apt-get -qq install -y -f ./jdk.deb
+6 -5
View File
@@ -12,16 +12,16 @@ Create a new Window by calling NewWindow. On mobile platforms or when Gio
is embedded in another project, NewWindow merely connects with a previously is embedded in another project, NewWindow merely connects with a previously
created window. created window.
A Window is run by receiving events from its Events channel. The most A Window is run by calling NextEvent in a loop. The most important event is
important event is FrameEvent that prompts an update of the window FrameEvent that prompts an update of the window contents.
contents and state.
For example: For example:
import "gioui.org/unit" import "gioui.org/unit"
w := app.NewWindow() w := app.NewWindow()
for e := range w.Events() { for {
e := w.NextEvent()
if e, ok := e.(system.FrameEvent); ok { if e, ok := e.(system.FrameEvent); ok {
ops.Reset() ops.Reset()
// Add operations to ops. // Add operations to ops.
@@ -50,7 +50,8 @@ For example, to display a blank but otherwise functional window:
func main() { func main() {
go func() { go func() {
w := app.NewWindow() w := app.NewWindow()
for range w.Events() { for {
w.NextEvent()
} }
}() }()
app.Main() app.Main()
+22
View File
@@ -76,6 +76,21 @@ type MinMaxInfo struct {
PtMaxTrackSize Point PtMaxTrackSize Point
} }
type NCCalcSizeParams struct {
Rgrc [3]Rect
LpPos *WindowPos
}
type WindowPos struct {
HWND syscall.Handle
HWNDInsertAfter syscall.Handle
x int32
y int32
cx int32
cy int32
flags uint32
}
type WindowPlacement struct { type WindowPlacement struct {
length uint32 length uint32
flags uint32 flags uint32
@@ -331,6 +346,7 @@ var (
_DispatchMessage = user32.NewProc("DispatchMessageW") _DispatchMessage = user32.NewProc("DispatchMessageW")
_EmptyClipboard = user32.NewProc("EmptyClipboard") _EmptyClipboard = user32.NewProc("EmptyClipboard")
_GetWindowRect = user32.NewProc("GetWindowRect") _GetWindowRect = user32.NewProc("GetWindowRect")
_GetClientRect = user32.NewProc("GetClientRect")
_GetClipboardData = user32.NewProc("GetClipboardData") _GetClipboardData = user32.NewProc("GetClipboardData")
_GetDC = user32.NewProc("GetDC") _GetDC = user32.NewProc("GetDC")
_GetDpiForWindow = user32.NewProc("GetDpiForWindow") _GetDpiForWindow = user32.NewProc("GetDpiForWindow")
@@ -463,6 +479,12 @@ func GetWindowRect(hwnd syscall.Handle) Rect {
return r return r
} }
func GetClientRect(hwnd syscall.Handle) Rect {
var r Rect
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
return r
}
func GetClipboardData(format uint32) (syscall.Handle, error) { func GetClipboardData(format uint32) (syscall.Handle, error) {
r, _, err := _GetClipboardData.Call(uintptr(format)) r, _, err := _GetClipboardData.Call(uintptr(format))
if r == 0 { if r == 0 {
+63 -4
View File
@@ -242,6 +242,9 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
if 'a' <= s && s <= 'z' { if 'a' <= s && s <= 'z' {
return string(rune(s - 'a' + 'A')), true return string(rune(s - 'a' + 'A')), true
} }
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
return string(rune(s - C.XKB_KEY_KP_0 + '0')), true
}
if ' ' < s && s <= '~' { if ' ' < s && s <= '~' {
return string(rune(s)), true return string(rune(s)), true
} }
@@ -255,8 +258,6 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
n = key.NameRightArrow n = key.NameRightArrow
case C.XKB_KEY_Return: case C.XKB_KEY_Return:
n = key.NameReturn n = key.NameReturn
case C.XKB_KEY_KP_Enter:
n = key.NameEnter
case C.XKB_KEY_Up: case C.XKB_KEY_Up:
n = key.NameUpArrow n = key.NameUpArrow
case C.XKB_KEY_Down: case C.XKB_KEY_Down:
@@ -297,9 +298,9 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
n = key.NameF11 n = key.NameF11
case C.XKB_KEY_F12: case C.XKB_KEY_F12:
n = key.NameF12 n = key.NameF12
case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab: case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
n = key.NameTab n = key.NameTab
case 0x20, C.XKB_KEY_KP_Space: case 0x20:
n = key.NameSpace n = key.NameSpace
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R: case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
n = key.NameCtrl n = key.NameCtrl
@@ -309,6 +310,64 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
n = key.NameAlt n = key.NameAlt
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R: case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
n = key.NameSuper n = key.NameSuper
case C.XKB_KEY_KP_Space:
n = key.NameSpace
case C.XKB_KEY_KP_Tab:
n = key.NameTab
case C.XKB_KEY_KP_Enter:
n = key.NameEnter
case C.XKB_KEY_KP_F1:
n = key.NameF1
case C.XKB_KEY_KP_F2:
n = key.NameF2
case C.XKB_KEY_KP_F3:
n = key.NameF3
case C.XKB_KEY_KP_F4:
n = key.NameF4
case C.XKB_KEY_KP_Home:
n = key.NameHome
case C.XKB_KEY_KP_Left:
n = key.NameLeftArrow
case C.XKB_KEY_KP_Up:
n = key.NameUpArrow
case C.XKB_KEY_KP_Right:
n = key.NameRightArrow
case C.XKB_KEY_KP_Down:
n = key.NameDownArrow
case C.XKB_KEY_KP_Prior:
// not supported
return "", false
case C.XKB_KEY_KP_Next:
// not supported
return "", false
case C.XKB_KEY_KP_End:
n = key.NameEnd
case C.XKB_KEY_KP_Begin:
n = key.NameHome
case C.XKB_KEY_KP_Insert:
// not supported
return "", false
case C.XKB_KEY_KP_Delete:
n = key.NameDeleteForward
case C.XKB_KEY_KP_Multiply:
n = "*"
case C.XKB_KEY_KP_Add:
n = "+"
case C.XKB_KEY_KP_Separator:
// not supported
return "", false
case C.XKB_KEY_KP_Subtract:
n = "-"
case C.XKB_KEY_KP_Decimal:
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
// German, the decimal is a comma, not a period.
n = "."
case C.XKB_KEY_KP_Divide:
n = "/"
case C.XKB_KEY_KP_Equal:
n = "="
default: default:
return "", false return "", false
} }
+7 -7
View File
@@ -953,18 +953,18 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
//export Java_org_gioui_GioView_onTouchEvent //export Java_org_gioui_GioView_onTouchEvent
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) { func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
w := cgo.Handle(handle).Value().(*window) w := cgo.Handle(handle).Value().(*window)
var typ pointer.Type var kind pointer.Kind
switch action { switch action {
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
typ = pointer.Press kind = pointer.Press
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
typ = pointer.Release kind = pointer.Release
case C.AMOTION_EVENT_ACTION_CANCEL: case C.AMOTION_EVENT_ACTION_CANCEL:
typ = pointer.Cancel kind = pointer.Cancel
case C.AMOTION_EVENT_ACTION_MOVE: case C.AMOTION_EVENT_ACTION_MOVE:
typ = pointer.Move kind = pointer.Move
case C.AMOTION_EVENT_ACTION_SCROLL: case C.AMOTION_EVENT_ACTION_SCROLL:
typ = pointer.Scroll kind = pointer.Scroll
default: default:
return return
} }
@@ -994,7 +994,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
return return
} }
w.callbacks.Event(pointer.Event{ w.callbacks.Event(pointer.Event{
Type: typ, Kind: kind,
Source: src, Source: src,
Buttons: btns, Buttons: btns,
PointerID: pointer.ID(pointerID), PointerID: pointer.ID(pointerID),
+1 -1
View File
@@ -124,7 +124,7 @@ func stringToNSString(str string) C.CFTypeRef {
return C.newNSString(chars, C.NSUInteger(len(u16))) return C.newNSString(chars, C.NSUInteger(len(u16)))
} }
func NewDisplayLink(callback func()) (*displayLink, error) { func newDisplayLink(callback func()) (*displayLink, error) {
d := &displayLink{ d := &displayLink{
callback: callback, callback: callback,
done: make(chan struct{}), done: make(chan struct{}),
+7 -7
View File
@@ -117,7 +117,7 @@ func onCreate(view, controller C.CFTypeRef) {
w := &window{ w := &window{
view: view, view: view,
} }
dl, err := NewDisplayLink(func() { dl, err := newDisplayLink(func() {
w.draw(false) w.draw(false)
}) })
if err != nil { if err != nil {
@@ -236,16 +236,16 @@ func onText(view, str C.CFTypeRef) {
//export onTouch //export onTouch
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) { func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
var typ pointer.Type var kind pointer.Kind
switch phase { switch phase {
case C.UITouchPhaseBegan: case C.UITouchPhaseBegan:
typ = pointer.Press kind = pointer.Press
case C.UITouchPhaseMoved: case C.UITouchPhaseMoved:
typ = pointer.Move kind = pointer.Move
case C.UITouchPhaseEnded: case C.UITouchPhaseEnded:
typ = pointer.Release kind = pointer.Release
case C.UITouchPhaseCancelled: case C.UITouchPhaseCancelled:
typ = pointer.Cancel kind = pointer.Cancel
default: default:
return return
} }
@@ -253,7 +253,7 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
t := time.Duration(float64(ti) * float64(time.Second)) t := time.Duration(float64(ti) * float64(time.Second))
p := f32.Point{X: float32(x), Y: float32(y)} p := f32.Point{X: float32(x), Y: float32(y)}
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: kind,
Source: pointer.Touch, Source: pointer.Touch,
PointerID: w.lookupTouch(last != 0, touchRef), PointerID: w.lookupTouch(last != 0, touchRef),
Position: p, Position: p,
+5 -5
View File
@@ -275,7 +275,7 @@ func (w *window) addEventListeners() {
} }
w.touches = w.touches[:0] w.touches = w.touches[:0]
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Cancel, Kind: pointer.Cancel,
Source: pointer.Touch, Source: pointer.Touch,
}) })
return nil return nil
@@ -398,7 +398,7 @@ func modifiersFor(e js.Value) key.Modifiers {
return mods return mods
} }
func (w *window) touchEvent(typ pointer.Type, e js.Value) { func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
e.Call("preventDefault") e.Call("preventDefault")
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
changedTouches := e.Get("changedTouches") changedTouches := e.Get("changedTouches")
@@ -426,7 +426,7 @@ func (w *window) touchEvent(typ pointer.Type, e js.Value) {
Y: float32(y) * scale, Y: float32(y) * scale,
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: kind,
Source: pointer.Touch, Source: pointer.Touch,
Position: pos, Position: pos,
PointerID: pid, PointerID: pid,
@@ -448,7 +448,7 @@ func (w *window) touchIDFor(touch js.Value) pointer.ID {
return pid return pid
} }
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) { func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
e.Call("preventDefault") e.Call("preventDefault")
x, y := e.Get("clientX").Float(), e.Get("clientY").Float() x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
rect := w.cnv.Call("getBoundingClientRect") rect := w.cnv.Call("getBoundingClientRect")
@@ -476,7 +476,7 @@ func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
btns |= pointer.ButtonTertiary btns |= pointer.ButtonTertiary
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: kind,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: btns, Buttons: btns,
Position: pos, Position: pos,
+9 -7
View File
@@ -297,7 +297,9 @@ func (w *window) contextView() C.CFTypeRef {
func (w *window) ReadClipboard() { func (w *window) ReadClipboard() {
cstr := C.readClipboard() cstr := C.readClipboard()
defer C.CFRelease(cstr) if cstr != 0 {
defer C.CFRelease(cstr)
}
content := nsstringToString(cstr) content := nsstringToString(cstr)
w.w.Event(clipboard.Event{Text: content}) w.w.Event(clipboard.Event{Text: content})
} }
@@ -365,11 +367,11 @@ func (w *window) Configure(options []Option) {
case Minimized: case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
case Maximized: case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
} }
w.config.Mode = Windowed w.config.Mode = Windowed
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
w.setTitle(prev, cnf) w.setTitle(prev, cnf)
if prev.Size != cnf.Size { if prev.Size != cnf.Size {
w.config.Size = cnf.Size w.config.Size = cnf.Size
@@ -526,7 +528,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
case 2: case 2:
btn = pointer.ButtonTertiary btn = pointer.ButtonTertiary
} }
var typ pointer.Type var typ pointer.Kind
switch cdir { switch cdir {
case C.MOUSE_MOVE: case C.MOUSE_MOVE:
typ = pointer.Move typ = pointer.Move
@@ -550,7 +552,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
panic("invalid direction") panic("invalid direction")
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: typ,
Source: pointer.Mouse, Source: pointer.Mouse,
Time: t, Time: t,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
@@ -884,7 +886,7 @@ func newOSWindow() (*window, error) {
scale: scale, scale: scale,
redraw: make(chan struct{}, 1), redraw: make(chan struct{}, 1),
} }
dl, err := NewDisplayLink(func() { dl, err := newDisplayLink(func() {
select { select {
case w.redraw <- struct{}{}: case w.redraw <- struct{}{}:
default: default:
+12 -22
View File
@@ -94,10 +94,7 @@ type wlDisplay struct {
// Notification pipe fds. // Notification pipe fds.
notify struct { notify struct {
read int read, write int
mu sync.Mutex
write int
} }
repeat repeatState repeat repeatState
@@ -794,7 +791,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
Y: fromFixed(y) * float32(w.scale), Y: fromFixed(y) * float32(w.scale),
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Source: pointer.Touch, Source: pointer.Touch,
Position: w.lastTouch, Position: w.lastTouch,
PointerID: pointer.ID(id), PointerID: pointer.ID(id),
@@ -810,7 +807,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
w := s.touchFoci[id] w := s.touchFoci[id]
delete(s.touchFoci, id) delete(s.touchFoci, id)
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Source: pointer.Touch, Source: pointer.Touch,
Position: w.lastTouch, Position: w.lastTouch,
PointerID: pointer.ID(id), PointerID: pointer.ID(id),
@@ -828,7 +825,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
Y: fromFixed(y) * float32(w.scale), Y: fromFixed(y) * float32(w.scale),
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: w.lastTouch, Position: w.lastTouch,
Source: pointer.Touch, Source: pointer.Touch,
PointerID: pointer.ID(id), PointerID: pointer.ID(id),
@@ -847,7 +844,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
for id, w := range s.touchFoci { for id, w := range s.touchFoci {
delete(s.touchFoci, id) delete(s.touchFoci, id)
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Cancel, Kind: pointer.Cancel,
Source: pointer.Touch, Source: pointer.Touch,
}) })
} }
@@ -872,7 +869,7 @@ func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.ui
s.serial = serial s.serial = serial
if w.inCompositor { if w.inCompositor {
w.inCompositor = false w.inCompositor = false
w.w.Event(pointer.Event{Type: pointer.Cancel}) w.w.Event(pointer.Event{Kind: pointer.Cancel})
} }
} }
@@ -920,21 +917,21 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
} }
} }
} }
var typ pointer.Type var kind pointer.Kind
switch state { switch state {
case 0: case 0:
w.pointerBtns &^= btn w.pointerBtns &^= btn
typ = pointer.Release kind = pointer.Release
// Move or resize gestures no longer applies. // Move or resize gestures no longer applies.
w.inCompositor = false w.inCompositor = false
case 1: case 1:
w.pointerBtns |= btn w.pointerBtns |= btn
typ = pointer.Press kind = pointer.Press
} }
w.flushScroll() w.flushScroll()
w.resetFling() w.resetFling()
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: kind,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
Position: w.lastPos, Position: w.lastPos,
@@ -1445,11 +1442,6 @@ func (w *window) SetAnimating(anim bool) {
// Wakeup wakes up the event loop through the notification pipe. // Wakeup wakes up the event loop through the notification pipe.
func (d *wlDisplay) wakeup() { func (d *wlDisplay) wakeup() {
oneByte := make([]byte, 1) oneByte := make([]byte, 1)
d.notify.mu.Lock()
defer d.notify.mu.Unlock()
if d.notify.write == 0 {
return
}
if _, err := syscall.Write(d.notify.write, oneByte); err != nil && err != syscall.EAGAIN { if _, err := syscall.Write(d.notify.write, oneByte); err != nil && err != syscall.EAGAIN {
panic(fmt.Errorf("failed to write to pipe: %v", err)) panic(fmt.Errorf("failed to write to pipe: %v", err))
} }
@@ -1581,7 +1573,7 @@ func (w *window) flushScroll() {
return return
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
Position: w.lastPos, Position: w.lastPos,
@@ -1604,7 +1596,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
Y: fromFixed(y) * float32(w.scale), Y: fromFixed(y) * float32(w.scale),
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: w.lastPos, Position: w.lastPos,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -1828,12 +1820,10 @@ func newWLDisplay() (*wlDisplay, error) {
} }
func (d *wlDisplay) destroy() { func (d *wlDisplay) destroy() {
d.notify.mu.Lock()
if d.notify.write != 0 { if d.notify.write != 0 {
syscall.Close(d.notify.write) syscall.Close(d.notify.write)
d.notify.write = 0 d.notify.write = 0
} }
d.notify.mu.Unlock()
if d.notify.read != 0 { if d.notify.read != 0 {
syscall.Close(d.notify.read) syscall.Close(d.notify.read)
d.notify.read = 0 d.notify.read = 0
+55 -51
View File
@@ -32,11 +32,6 @@ type ViewEvent struct {
HWND uintptr HWND uintptr
} }
type winDeltas struct {
width int32
height int32
}
type window struct { type window struct {
hwnd syscall.Handle hwnd syscall.Handle
hdc syscall.Handle hdc syscall.Handle
@@ -55,7 +50,6 @@ type window struct {
animating bool animating bool
focused bool focused bool
deltas winDeltas
borderSize image.Point borderSize image.Point
config Config config Config
} }
@@ -192,22 +186,12 @@ func createNativeWindow() (*window, error) {
// It reads the window style and size/position and updates w.config. // It reads the window style and size/position and updates w.config.
// If anything has changed it emits a ConfigEvent to notify the application. // If anything has changed it emits a ConfigEvent to notify the application.
func (w *window) update() { func (w *window) update() {
r := windows.GetWindowRect(w.hwnd) cr := windows.GetClientRect(w.hwnd)
size := image.Point{ w.config.Size = image.Point{
X: int(r.Right - r.Left - w.deltas.width), X: int(cr.Right - cr.Left),
Y: int(r.Bottom - r.Top - w.deltas.height), Y: int(cr.Bottom - cr.Top),
} }
// Check the window mode.
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
if style&windows.WS_OVERLAPPEDWINDOW == 0 {
size = image.Point{
X: int(r.Right - r.Left),
Y: int(r.Bottom - r.Top),
}
}
w.config.Size = size
w.borderSize = image.Pt( w.borderSize = image.Pt(
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
@@ -275,7 +259,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
case windows.WM_CANCELMODE: case windows.WM_CANCELMODE:
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Cancel, Kind: pointer.Cancel,
}) })
case windows.WM_SETFOCUS: case windows.WM_SETFOCUS:
w.focused = true w.focused = true
@@ -304,7 +288,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)} p := f32.Point{X: float32(x), Y: float32(y)}
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: p, Position: p,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
@@ -326,10 +310,27 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
w.hwnd = 0 w.hwnd = 0
windows.PostQuitMessage(0) windows.PostQuitMessage(0)
case windows.WM_NCCALCSIZE: case windows.WM_NCCALCSIZE:
if !w.config.Decorated { if w.config.Decorated {
// No client areas; we draw decorations ourselves. // Let Windows handle decorations.
break
}
// No client areas; we draw decorations ourselves.
if wParam != 1 {
return 0 return 0
} }
// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
place := windows.GetWindowPlacement(w.hwnd)
if !place.IsMaximized() {
// Nothing do adjust.
return 0
}
// Adjust window position to avoid the extra padding in maximized
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
mi := windows.GetMonitorInfo(w.hwnd)
szp.Rgrc[0] = mi.WorkArea
return 0
case windows.WM_PAINT: case windows.WM_PAINT:
w.draw(true) w.draw(true)
case windows.WM_SIZE: case windows.WM_SIZE:
@@ -349,18 +350,26 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
} }
case windows.WM_GETMINMAXINFO: case windows.WM_GETMINMAXINFO:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam))) mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
var bw, bh int32
if w.config.Decorated {
r := windows.GetWindowRect(w.hwnd)
cr := windows.GetClientRect(w.hwnd)
bw = r.Right - r.Left - (cr.Right - cr.Left)
bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
}
if p := w.config.MinSize; p.X > 0 || p.Y > 0 { if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
mm.PtMinTrackSize = windows.Point{ mm.PtMinTrackSize = windows.Point{
X: int32(p.X) + w.deltas.width, X: int32(p.X) + bw,
Y: int32(p.Y) + w.deltas.height, Y: int32(p.Y) + bh,
} }
} }
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
mm.PtMaxTrackSize = windows.Point{ mm.PtMaxTrackSize = windows.Point{
X: int32(p.X) + w.deltas.width, X: int32(p.X) + bw,
Y: int32(p.Y) + w.deltas.height, Y: int32(p.Y) + bh,
} }
} }
return 0
case windows.WM_SETCURSOR: case windows.WM_SETCURSOR:
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
if w.cursorIn { if w.cursorIn {
@@ -492,15 +501,15 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
windows.SetFocus(w.hwnd) windows.SetFocus(w.hwnd)
} }
var typ pointer.Type var kind pointer.Kind
if press { if press {
typ = pointer.Press kind = pointer.Press
if w.pointerBtns == 0 { if w.pointerBtns == 0 {
windows.SetCapture(w.hwnd) windows.SetCapture(w.hwnd)
} }
w.pointerBtns |= btn w.pointerBtns |= btn
} else { } else {
typ = pointer.Release kind = pointer.Release
w.pointerBtns &^= btn w.pointerBtns &^= btn
if w.pointerBtns == 0 { if w.pointerBtns == 0 {
windows.ReleaseCapture() windows.ReleaseCapture()
@@ -509,7 +518,7 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)} p := f32.Point{X: float32(x), Y: float32(y)}
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: typ, Kind: kind,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: p, Position: p,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
@@ -544,7 +553,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
} }
} }
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: p, Position: p,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
@@ -686,31 +695,26 @@ func (w *window) Configure(options []Option) {
showMode = windows.SW_SHOWMAXIMIZED showMode = windows.SW_SHOWMAXIMIZED
case Windowed: case Windowed:
windows.SetWindowText(w.hwnd, w.config.Title)
style |= winStyle style |= winStyle
showMode = windows.SW_SHOWNORMAL showMode = windows.SW_SHOWNORMAL
// Get target for client areaa size. // Get target for client area size.
width = int32(w.config.Size.X) width = int32(w.config.Size.X)
height = int32(w.config.Size.Y) height = int32(w.config.Size.Y)
// Get the current window size and position. // Get the current window size and position.
wr := windows.GetWindowRect(w.hwnd) wr := windows.GetWindowRect(w.hwnd)
// Set desired window size.
wr.Right = wr.Left + width
wr.Bottom = wr.Top + height
// Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations.
r := wr
if w.config.Decorated {
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
}
// Calculate difference between client and full window sizes.
w.deltas.width = r.Right - wr.Right + wr.Left - r.Left
w.deltas.height = r.Bottom - wr.Bottom + wr.Top - r.Top
// Set new window size and position.
x = wr.Left x = wr.Left
y = wr.Top y = wr.Top
width = r.Right - r.Left if w.config.Decorated {
height = r.Bottom - r.Top // Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations.
r := windows.Rect{
Right: width,
Bottom: height,
}
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
width = r.Right - r.Left
height = r.Bottom - r.Top
}
if !w.config.Decorated { if !w.config.Decorated {
// Enable drop shadows when we draw decorations. // Enable drop shadows when we draw decorations.
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1}) windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
@@ -727,7 +731,7 @@ func (w *window) Configure(options []Option) {
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle) windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.ShowWindow(w.hwnd, showMode) windows.ShowWindow(w.hwnd, showMode)
w.w.Event(ConfigEvent{Config: w.config}) w.update()
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(s string) {
+7 -7
View File
@@ -547,7 +547,7 @@ func (h *x11EventHandler) handleEvents() bool {
case C.ButtonPress, C.ButtonRelease: case C.ButtonPress, C.ButtonRelease:
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev)) bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
ev := pointer.Event{ ev := pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: f32.Point{ Position: f32.Point{
X: float32(bevt.x), X: float32(bevt.x),
@@ -557,7 +557,7 @@ func (h *x11EventHandler) handleEvents() bool {
Modifiers: w.xkb.Modifiers(), Modifiers: w.xkb.Modifiers(),
} }
if bevt._type == C.ButtonRelease { if bevt._type == C.ButtonRelease {
ev.Type = pointer.Release ev.Kind = pointer.Release
} }
var btn pointer.Buttons var btn pointer.Buttons
const scrollScale = 10 const scrollScale = 10
@@ -569,7 +569,7 @@ func (h *x11EventHandler) handleEvents() bool {
case C.Button3: case C.Button3:
btn = pointer.ButtonSecondary btn = pointer.ButtonSecondary
case C.Button4: case C.Button4:
ev.Type = pointer.Scroll ev.Kind = pointer.Scroll
// scroll up or left (if shift is pressed). // scroll up or left (if shift is pressed).
if ev.Modifiers == key.ModShift { if ev.Modifiers == key.ModShift {
ev.Scroll.X = -scrollScale ev.Scroll.X = -scrollScale
@@ -578,7 +578,7 @@ func (h *x11EventHandler) handleEvents() bool {
} }
case C.Button5: case C.Button5:
// scroll down or right (if shift is pressed). // scroll down or right (if shift is pressed).
ev.Type = pointer.Scroll ev.Kind = pointer.Scroll
if ev.Modifiers == key.ModShift { if ev.Modifiers == key.ModShift {
ev.Scroll.X = +scrollScale ev.Scroll.X = +scrollScale
} else { } else {
@@ -587,11 +587,11 @@ func (h *x11EventHandler) handleEvents() bool {
case 6: case 6:
// http://xahlee.info/linux/linux_x11_mouse_button_number.html // http://xahlee.info/linux/linux_x11_mouse_button_number.html
// scroll left. // scroll left.
ev.Type = pointer.Scroll ev.Kind = pointer.Scroll
ev.Scroll.X = -scrollScale * 2 ev.Scroll.X = -scrollScale * 2
case 7: case 7:
// scroll right // scroll right
ev.Type = pointer.Scroll ev.Kind = pointer.Scroll
ev.Scroll.X = +scrollScale * 2 ev.Scroll.X = +scrollScale * 2
default: default:
continue continue
@@ -607,7 +607,7 @@ func (h *x11EventHandler) handleEvents() bool {
case C.MotionNotify: case C.MotionNotify:
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev)) mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
w.w.Event(pointer.Event{ w.w.Event(pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: w.pointerBtns, Buttons: w.pointerBtns,
Position: f32.Point{ Position: f32.Point{
+54 -30
View File
@@ -61,6 +61,8 @@ type Window struct {
// actions are the actions waiting to be performed. // actions are the actions waiting to be performed.
actions chan system.Action actions chan system.Action
// out is where the platform backend delivers events bound for the
// user program.
out chan event.Event out chan event.Event
frames chan *op.Ops frames chan *op.Ops
frameAck chan struct{} frameAck chan struct{}
@@ -107,6 +109,16 @@ type Window struct {
} }
imeState editorState imeState editorState
// event stores the state required for processing and delivering events
// from NextEvent. If we had support for range over func, this would
// be the iterator state.
eventState struct {
created bool
initialOpts []Option
wakeup func()
timer *time.Timer
}
} }
type editorState struct { type editorState struct {
@@ -185,7 +197,7 @@ func NewWindow(options ...Option) *Window {
w.imeState.compose = key.Range{Start: -1, End: -1} w.imeState.compose = key.Range{Start: -1, End: -1}
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode) w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
w.callbacks.w = w w.callbacks.w = w
go w.run(options) w.eventState.initialOpts = options
return w return w
} }
@@ -195,11 +207,6 @@ func decoHeightOpt(h unit.Dp) Option {
} }
} }
// Events returns the channel where events are delivered.
func (w *Window) Events() <-chan event.Event {
return w.out
}
// update the window contents, input operations declare input handlers, // update the window contents, input operations declare input handlers,
// and so on. The supplied operations list completely replaces the window state // and so on. The supplied operations list completely replaces the window state
// from previous calls. // from previous calls.
@@ -713,7 +720,7 @@ func (w *Window) waitAck(d driver) {
select { select {
case f := <-w.driverFuncs: case f := <-w.driverFuncs:
f(d) f(d)
case w.out <- event.Event(nil): case w.out <- theFlushEvent:
// A dummy event went through, so we know the application has processed the previous event. // A dummy event went through, so we know the application has processed the previous event.
return return
case <-w.immediateRedraws: case <-w.immediateRedraws:
@@ -747,7 +754,7 @@ func (w *Window) waitFrame(d driver) *op.Ops {
case frame := <-w.frames: case frame := <-w.frames:
// The client called FrameEvent.Frame. // The client called FrameEvent.Frame.
return frame return frame
case w.out <- event.Event(nil): case w.out <- theFlushEvent:
// The client ignored FrameEvent and continued processing // The client ignored FrameEvent and continued processing
// events. // events.
return nil return nil
@@ -881,7 +888,6 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil { if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil {
w.destroyGPU() w.destroyGPU()
w.out <- system.DestroyEvent{Err: err} w.out <- system.DestroyEvent{Err: err}
close(w.out)
close(w.destroy) close(w.destroy)
break break
} }
@@ -890,7 +896,6 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
case system.DestroyEvent: case system.DestroyEvent:
w.destroyGPU() w.destroyGPU()
w.out <- e2 w.out <- e2
close(w.out)
close(w.destroy) close(w.destroy)
case ViewEvent: case ViewEvent:
w.out <- e2 w.out <- e2
@@ -938,43 +943,51 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
return true return true
} }
func (w *Window) run(options []Option) { // NextEvent blocks until an event is received from the window, such as
if err := newWindow(&w.callbacks, options); err != nil { // [io/system.FrameEvent]. It blocks forever if called after [io/system.DestroyEvent]
w.out <- system.DestroyEvent{Err: err} // has been returned.
close(w.out) func (w *Window) NextEvent() event.Event {
close(w.destroy) state := &w.eventState
return if !state.created {
state.created = true
if err := newWindow(&w.callbacks, state.initialOpts); err != nil {
close(w.destroy)
return system.DestroyEvent{Err: err}
}
} }
var wakeup func()
var timer *time.Timer
for { for {
var ( var (
wakeups <-chan struct{} wakeups <-chan struct{}
timeC <-chan time.Time timeC <-chan time.Time
) )
if wakeup != nil { if state.wakeup != nil {
wakeups = w.wakeups wakeups = w.wakeups
if timer != nil { if state.timer != nil {
timeC = timer.C timeC = state.timer.C
} }
} }
select { select {
case t := <-w.scheduledRedraws: case t := <-w.scheduledRedraws:
if timer != nil { if state.timer != nil {
timer.Stop() state.timer.Stop()
} }
timer = time.NewTimer(time.Until(t)) state.timer = time.NewTimer(time.Until(t))
case <-w.destroy: case e := <-w.out:
return // Receiving a flushEvent indicates to the platform backend that
// all previous events have been processed by the user program.
if _, ok := e.(flushEvent); ok {
break
}
return e
case <-timeC: case <-timeC:
select { select {
case w.redraws <- struct{}{}: case w.redraws <- struct{}{}:
wakeup() state.wakeup()
default: default:
} }
case <-wakeups: case <-wakeups:
wakeup() state.wakeup()
case wakeup = <-w.wakeupFuncs: case state.wakeup = <-w.wakeupFuncs:
} }
} }
} }
@@ -1024,7 +1037,7 @@ func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offse
} }
style.Layout(gtx) style.Layout(gtx)
// Update the window based on the actions on the decorations. // Update the window based on the actions on the decorations.
w.Perform(deco.Actions()) w.Perform(deco.Update(gtx))
// Offset to place the frame content below the decorations. // Offset to place the frame content below the decorations.
decoHeight := gtx.Dp(w.decorations.Config.decoHeight) decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
if w.decorations.currentHeight != decoHeight { if w.decorations.currentHeight != decoHeight {
@@ -1165,3 +1178,14 @@ func Decorated(enabled bool) Option {
cnf.Decorated = enabled cnf.Decorated = enabled
} }
} }
// flushEvent is sent to detect when the user program
// has completed processing of all prior events. Its an
// [io/event.Event] but only for internal use.
type flushEvent struct{}
func (t flushEvent) ImplementsEvent() {}
// theFlushEvent avoids allocating garbage when sending
// flushEvents.
var theFlushEvent flushEvent
+34 -35
View File
@@ -39,18 +39,18 @@ type Hover struct {
func (h *Hover) Add(ops *op.Ops) { func (h *Hover) Add(ops *op.Ops) {
pointer.InputOp{ pointer.InputOp{
Tag: h, Tag: h,
Types: pointer.Enter | pointer.Leave, Kinds: pointer.Enter | pointer.Leave,
}.Add(ops) }.Add(ops)
} }
// Hovered returns whether a pointer is inside the area. // Update state and report whether a pointer is inside the area.
func (h *Hover) Hovered(q event.Queue) bool { func (h *Hover) Update(q event.Queue) bool {
for _, ev := range q.Events(h) { for _, ev := range q.Events(h) {
e, ok := ev.(pointer.Event) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Leave, pointer.Cancel: case pointer.Leave, pointer.Cancel:
if h.entered && h.pid == e.PointerID { if h.entered && h.pid == e.PointerID {
h.entered = false h.entered = false
@@ -87,10 +87,10 @@ type Click struct {
} }
// ClickEvent represent a click action, either a // ClickEvent represent a click action, either a
// TypePress for the beginning of a click or a // KindPress for the beginning of a click or a
// TypeClick for a completed click. // KindClick for a completed click.
type ClickEvent struct { type ClickEvent struct {
Type ClickType Kind ClickKind
Position image.Point Position image.Point
Source pointer.Source Source pointer.Source
Modifiers key.Modifiers Modifiers key.Modifiers
@@ -99,7 +99,7 @@ type ClickEvent struct {
NumClicks int NumClicks int
} }
type ClickType uint8 type ClickKind uint8
// Drag detects drag gestures in the form of pointer.Drag events. // Drag detects drag gestures in the form of pointer.Drag events.
type Drag struct { type Drag struct {
@@ -136,15 +136,15 @@ const (
) )
const ( const (
// TypePress is reported for the first pointer // KindPress is reported for the first pointer
// press. // press.
TypePress ClickType = iota KindPress ClickKind = iota
// TypeClick is reported when a click action // KindClick is reported when a click action
// is complete. // is complete.
TypeClick KindClick
// TypeCancel is reported when the gesture is // KindCancel is reported when the gesture is
// cancelled. // cancelled.
TypeCancel KindCancel
) )
const ( const (
@@ -163,7 +163,7 @@ const touchSlop = unit.Dp(3)
func (c *Click) Add(ops *op.Ops) { func (c *Click) Add(ops *op.Ops) {
pointer.InputOp{ pointer.InputOp{
Tag: c, Tag: c,
Types: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave, Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
}.Add(ops) }.Add(ops)
} }
@@ -177,24 +177,24 @@ func (c *Click) Pressed() bool {
return c.pressed return c.pressed
} }
// Events returns the next click events, if any. // Update state and return the click events.
func (c *Click) Events(q event.Queue) []ClickEvent { func (c *Click) Update(q event.Queue) []ClickEvent {
var events []ClickEvent var events []ClickEvent
for _, evt := range q.Events(c) { for _, evt := range q.Events(c) {
e, ok := evt.(pointer.Event) e, ok := evt.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Release: case pointer.Release:
if !c.pressed || c.pid != e.PointerID { if !c.pressed || c.pid != e.PointerID {
break break
} }
c.pressed = false c.pressed = false
if !c.entered || c.hovered { if !c.entered || c.hovered {
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}) events = append(events, ClickEvent{Kind: KindClick, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
} else { } else {
events = append(events, ClickEvent{Type: TypeCancel}) events = append(events, ClickEvent{Kind: KindCancel})
} }
case pointer.Cancel: case pointer.Cancel:
wasPressed := c.pressed wasPressed := c.pressed
@@ -202,7 +202,7 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
c.hovered = false c.hovered = false
c.entered = false c.entered = false
if wasPressed { if wasPressed {
events = append(events, ClickEvent{Type: TypeCancel}) events = append(events, ClickEvent{Kind: KindCancel})
} }
case pointer.Press: case pointer.Press:
if c.pressed { if c.pressed {
@@ -224,7 +224,7 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
c.clicks = 1 c.clicks = 1
} }
c.clickedAt = e.Time c.clickedAt = e.Time
events = append(events, ClickEvent{Type: TypePress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks}) events = append(events, ClickEvent{Kind: KindPress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
case pointer.Leave: case pointer.Leave:
if !c.pressed { if !c.pressed {
c.pid = e.PointerID c.pid = e.PointerID
@@ -254,7 +254,7 @@ func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
oph := pointer.InputOp{ oph := pointer.InputOp{
Tag: s, Tag: s,
Grab: s.grab, Grab: s.grab,
Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll, Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
ScrollBounds: bounds, ScrollBounds: bounds,
} }
oph.Add(ops) oph.Add(ops)
@@ -268,9 +268,8 @@ func (s *Scroll) Stop() {
s.flinger = fling.Animation{} s.flinger = fling.Animation{}
} }
// Scroll detects the scrolling distance from the available events and // Update state and report the scroll distance along axis.
// ongoing fling gestures. func (s *Scroll) Update(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
if s.axis != axis { if s.axis != axis {
s.axis = axis s.axis = axis
return 0 return 0
@@ -281,7 +280,7 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
if s.dragging { if s.dragging {
break break
@@ -368,12 +367,12 @@ func (d *Drag) Add(ops *op.Ops) {
pointer.InputOp{ pointer.InputOp{
Tag: d, Tag: d,
Grab: d.grab, Grab: d.grab,
Types: pointer.Press | pointer.Drag | pointer.Release, Kinds: pointer.Press | pointer.Drag | pointer.Release,
}.Add(ops) }.Add(ops)
} }
// Events returns the next drag events, if any. // Update state and return the drag events.
func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event { func (d *Drag) Update(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
var events []pointer.Event var events []pointer.Event
for _, e := range q.Events(d) { for _, e := range q.Events(d) {
e, ok := e.(pointer.Event) e, ok := e.(pointer.Event)
@@ -381,7 +380,7 @@ func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) { if !(e.Buttons == pointer.ButtonPrimary || e.Source == pointer.Touch) {
continue continue
@@ -444,13 +443,13 @@ func (a Axis) String() string {
} }
} }
func (ct ClickType) String() string { func (ct ClickKind) String() string {
switch ct { switch ct {
case TypePress: case KindPress:
return "TypePress" return "TypePress"
case TypeClick: case KindClick:
return "TypeClick" return "TypeClick"
case TypeCancel: case KindCancel:
return "TypeCancel" return "TypeCancel"
default: default:
panic("invalid ClickType") panic("invalid ClickType")
+8 -8
View File
@@ -26,16 +26,16 @@ func TestHover(t *testing.T) {
r.Frame(ops) r.Frame(ops)
r.Queue( r.Queue(
pointer.Event{Type: pointer.Move, Position: f32.Pt(30, 30)}, pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
) )
if !h.Hovered(r) { if !h.Update(r) {
t.Fatal("expected hovered") t.Fatal("expected hovered")
} }
r.Queue( r.Queue(
pointer.Event{Type: pointer.Move, Position: f32.Pt(50, 50)}, pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
) )
if h.Hovered(r) { if h.Update(r) {
t.Fatal("expected not hovered") t.Fatal("expected not hovered")
} }
} }
@@ -75,7 +75,7 @@ func TestMouseClicks(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue(tc.events...) r.Queue(tc.events...)
events := click.Events(&r) events := click.Update(&r)
clicks := filterMouseClicks(events) clicks := filterMouseClicks(events)
if got, want := len(clicks), len(tc.clicks); got != want { if got, want := len(clicks), len(tc.clicks); got != want {
t.Fatalf("got %d mouse clicks, expected %d", got, want) t.Fatalf("got %d mouse clicks, expected %d", got, want)
@@ -92,7 +92,7 @@ func TestMouseClicks(t *testing.T) {
func mouseClickEvents(times ...time.Duration) []event.Event { func mouseClickEvents(times ...time.Duration) []event.Event {
press := pointer.Event{ press := pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
} }
@@ -101,7 +101,7 @@ func mouseClickEvents(times ...time.Duration) []event.Event {
press := press press := press
press.Time = t press.Time = t
release := press release := press
release.Type = pointer.Release release.Kind = pointer.Release
events = append(events, press, release) events = append(events, press, release)
} }
return events return events
@@ -110,7 +110,7 @@ func mouseClickEvents(times ...time.Duration) []event.Event {
func filterMouseClicks(events []ClickEvent) []ClickEvent { func filterMouseClicks(events []ClickEvent) []ClickEvent {
var clicks []ClickEvent var clicks []ClickEvent
for _, ev := range events { for _, ev := range events {
if ev.Type == TypeClick { if ev.Kind == KindClick {
clicks = append(clicks, ev) clicks = append(clicks, ev)
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@ go 1.19
require ( require (
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
gioui.org/shader v1.0.6 gioui.org/shader v1.0.8
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
+2 -2
View File
@@ -3,8 +3,8 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8v
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
+194 -43
View File
@@ -68,22 +68,40 @@ type renderer struct {
pather *pather pather *pather
packer packer packer packer
intersections packer intersections packer
layers packer
layerFBOs fboSet
} }
type drawOps struct { type drawOps struct {
profile bool profile bool
reader ops.Reader reader ops.Reader
states []f32.Affine2D states []f32.Affine2D
transStack []f32.Affine2D transStack []f32.Affine2D
vertCache []byte layers []opacityLayer
viewport image.Point opacityStack []int
clear bool vertCache []byte
clearColor f32color.RGBA viewport image.Point
imageOps []imageOp clear bool
pathOps []*pathOp clearColor f32color.RGBA
pathOpCache []pathOp imageOps []imageOp
qs quadSplitter pathOps []*pathOp
pathCache *opCache pathOpCache []pathOp
qs quadSplitter
pathCache *opCache
}
type opacityLayer struct {
opacity float32
parent int
// depth of the opacity stack. Layers of equal depth are
// independent and may be packed into one atlas.
depth int
// opStart and opEnd denote the range of drawOps.imageOps
// that belong to the layer.
opStart, opEnd int
// clip of the layer operations.
clip image.Rectangle
place placement
} }
type drawState struct { type drawState struct {
@@ -127,7 +145,12 @@ type imageOp struct {
clip image.Rectangle clip image.Rectangle
material material material material
clipType clipType clipType clipType
place placement // place is either a placement in the path fbos or intersection fbos,
// depending on clipType.
place placement
// layerOps is the number of operations this
// operation replaces.
layerOps int
} }
func decodeStrokeOp(data []byte) float32 { func decodeStrokeOp(data []byte) float32 {
@@ -154,10 +177,12 @@ type material struct {
// For materialTypeColor. // For materialTypeColor.
color f32color.RGBA color f32color.RGBA
// For materialTypeLinearGradient. // For materialTypeLinearGradient.
color1 f32color.RGBA color1 f32color.RGBA
color2 f32color.RGBA color2 f32color.RGBA
opacity float32
// For materialTypeTexture. // For materialTypeTexture.
data imageOpData data imageOpData
tex driver.Texture
uvTrans f32.Affine2D uvTrans f32.Affine2D
} }
@@ -222,8 +247,6 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData {
} }
} }
type clipType uint8
type resource interface { type resource interface {
release() release()
} }
@@ -273,6 +296,9 @@ type blitUniforms struct {
transform [4]float32 transform [4]float32
uvTransformR1 [4]float32 uvTransformR1 [4]float32
uvTransformR2 [4]float32 uvTransformR2 [4]float32
opacity float32
fbo float32
_ [2]float32
} }
type colorUniforms struct { type colorUniforms struct {
@@ -284,7 +310,7 @@ type gradientUniforms struct {
color2 f32color.RGBA color2 f32color.RGBA
} }
type materialType uint8 type clipType uint8
const ( const (
clipTypeNone clipType = iota clipTypeNone clipType = iota
@@ -292,6 +318,8 @@ const (
clipTypeIntersection clipTypeIntersection
) )
type materialType uint8
const ( const (
materialColor materialType = iota materialColor materialType = iota
materialLinearGradient materialLinearGradient
@@ -391,6 +419,8 @@ func (g *gpu) frame(target RenderTarget) error {
g.coverTimer.begin() g.coverTimer.begin()
g.renderer.uploadImages(g.cache, g.drawOps.imageOps) g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps) g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
d := driver.LoadDesc{ d := driver.LoadDesc{
ClearColor: g.drawOps.clearColor, ClearColor: g.drawOps.clearColor,
} }
@@ -400,7 +430,7 @@ func (g *gpu) frame(target RenderTarget) error {
} }
g.ctx.BeginRenderPass(defFBO, d) g.ctx.BeginRenderPass(defFBO, d)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawOps(g.cache, g.drawOps.imageOps) g.renderer.drawOps(g.cache, false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
g.coverTimer.end() g.coverTimer.end()
g.ctx.EndRenderPass() g.ctx.EndRenderPass()
g.cleanupTimer.begin() g.cleanupTimer.begin()
@@ -464,15 +494,18 @@ func newRenderer(ctx driver.Device) *renderer {
if cap := 8192; maxDim > cap { if cap := 8192; maxDim > cap {
maxDim = cap maxDim = cap
} }
d := image.Pt(maxDim, maxDim)
r.packer.maxDims = image.Pt(maxDim, maxDim) r.packer.maxDims = d
r.intersections.maxDims = image.Pt(maxDim, maxDim) r.intersections.maxDims = d
r.layers.maxDims = d
return r return r
} }
func (r *renderer) release() { func (r *renderer) release() {
r.pather.release() r.pather.release()
r.blitter.release() r.blitter.release()
r.layerFBOs.delete(r.ctx, 0)
} }
func newBlitter(ctx driver.Device) *blitter { func newBlitter(ctx driver.Device) *blitter {
@@ -747,8 +780,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
ops = ops[:len(ops)-1] ops = ops[:len(ops)-1]
continue continue
} }
sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()} place, ok := r.packer.add(p.clip.Size())
place, ok := r.packer.add(sz)
if !ok { if !ok {
// The clip area is at most the entire screen. Hopefully no // The clip area is at most the entire screen. Hopefully no
// screen is larger than GL_MAX_TEXTURE_SIZE. // screen is larger than GL_MAX_TEXTURE_SIZE.
@@ -760,6 +792,83 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
*pops = ops *pops = ops
} }
func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
// Make every layer bounds contain nested layers; cull empty layers.
for i := len(layers) - 1; i >= 0; i-- {
l := layers[i]
if l.parent != -1 {
b := layers[l.parent].clip
layers[l.parent].clip = b.Union(l.clip)
}
if l.clip.Empty() {
layers = append(layers[:i], layers[i+1:]...)
}
}
// Pack layers.
r.layers.clear()
depth := 0
for i := range layers {
l := &layers[i]
// Only layers of the same depth may be packed together.
if l.depth != depth {
r.layers.newPage()
}
place, ok := r.layers.add(l.clip.Size())
if !ok {
// The layer area is at most the entire screen. Hopefully no
// screen is larger than GL_MAX_TEXTURE_SIZE.
panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
}
l.place = place
}
return layers
}
func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops []imageOp) {
if len(r.layers.sizes) == 0 {
return
}
fbo := -1
r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
for i := len(layers) - 1; i >= 0; i-- {
l := layers[i]
if fbo != l.place.Idx {
if fbo != -1 {
r.ctx.EndRenderPass()
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
}
fbo = l.place.Idx
f := r.layerFBOs.fbos[fbo]
r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
}
v := image.Rectangle{
Min: l.place.Pos,
Max: l.place.Pos.Add(l.clip.Size()),
}
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
f := r.layerFBOs.fbos[fbo]
r.drawOps(cache, true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
sr := f32.FRect(v)
uvScale, uvOffset := texSpaceTransform(sr, f.size)
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
// Replace layer ops with one textured op.
ops[l.opStart] = imageOp{
clip: l.clip,
material: material{
material: materialTexture,
tex: f.tex,
uvTrans: uvTrans,
opacity: l.opacity,
},
layerOps: l.opEnd - l.opStart - 1,
}
}
if fbo != -1 {
r.ctx.EndRenderPass()
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
}
}
func (d *drawOps) reset(viewport image.Point) { func (d *drawOps) reset(viewport image.Point) {
d.profile = false d.profile = false
d.viewport = viewport d.viewport = viewport
@@ -768,6 +877,8 @@ func (d *drawOps) reset(viewport image.Point) {
d.pathOpCache = d.pathOpCache[:0] d.pathOpCache = d.pathOpCache[:0]
d.vertCache = d.vertCache[:0] d.vertCache = d.vertCache[:0]
d.transStack = d.transStack[:0] d.transStack = d.transStack[:0]
d.layers = d.layers[:0]
d.opacityStack = d.opacityStack[:0]
} }
func (d *drawOps) collect(root *op.Ops, viewport image.Point) { func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
@@ -866,6 +977,27 @@ loop:
state.t = d.transStack[n-1] state.t = d.transStack[n-1]
d.transStack = d.transStack[:n-1] d.transStack = d.transStack[:n-1]
case ops.TypePushOpacity:
opacity := ops.DecodeOpacity(encOp.Data)
parent := -1
depth := len(d.opacityStack)
if depth > 0 {
parent = d.opacityStack[depth-1]
}
lidx := len(d.layers)
d.layers = append(d.layers, opacityLayer{
opacity: opacity,
parent: parent,
depth: depth,
opStart: len(d.imageOps),
})
d.opacityStack = append(d.opacityStack, lidx)
case ops.TypePopOpacity:
n := len(d.opacityStack)
idx := d.opacityStack[n-1]
d.layers[idx].opEnd = len(d.imageOps)
d.opacityStack = d.opacityStack[:n-1]
case ops.TypeStroke: case ops.TypeStroke:
quads.key.strokeWidth = decodeStrokeOp(encOp.Data) quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
@@ -958,7 +1090,7 @@ loop:
mat := state.materialFor(bnd, off, partialTrans, bounds) mat := state.materialFor(bnd, off, partialTrans, bounds)
rect := state.cpath == nil || state.cpath.rect rect := state.cpath == nil || state.cpath.rect
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) { if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
// The image is a uniform opaque color and takes up the whole screen. // The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color. // Scrap images up to and including this image and set clear color.
d.imageOps = d.imageOps[:0] d.imageOps = d.imageOps[:0]
@@ -971,6 +1103,15 @@ loop:
clip: bounds, clip: bounds,
material: mat, material: mat,
} }
if n := len(d.opacityStack); n > 0 {
idx := d.opacityStack[n-1]
lb := d.layers[idx].clip
if lb.Empty() {
d.layers[idx].clip = img.clip
} else {
d.layers[idx].clip = lb.Union(img.clip)
}
}
d.imageOps = append(d.imageOps, img) d.imageOps = append(d.imageOps, img)
if clipData != nil { if clipData != nil {
@@ -1000,7 +1141,9 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
} }
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material { func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
var m material m := material{
opacity: 1.,
}
switch d.matType { switch d.matType {
case materialColor: case materialColor:
m.material = materialColor m.material = materialColor
@@ -1040,10 +1183,11 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
} }
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) { func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
for _, img := range ops { for i := range ops {
img := &ops[i]
m := img.material m := img.material
if m.material == materialTexture { if m.material == materialTexture {
r.texHandle(cache, m.data) img.material.tex = r.texHandle(cache, m.data)
} }
} }
} }
@@ -1053,10 +1197,10 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
m := img.material m := img.material
switch m.material { switch m.material {
case materialTexture: case materialTexture:
r.ctx.PrepareTexture(r.texHandle(cache, m.data)) r.ctx.PrepareTexture(m.tex)
} }
var fbo stencilFBO var fbo FBO
switch img.clipType { switch img.clipType {
case clipTypeNone: case clipTypeNone:
continue continue
@@ -1069,24 +1213,26 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
} }
} }
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) { func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
var coverTex driver.Texture var coverTex driver.Texture
for _, img := range ops { for i := 0; i < len(ops); i++ {
img := ops[i]
i += img.layerOps
m := img.material m := img.material
switch m.material { switch m.material {
case materialTexture: case materialTexture:
r.ctx.BindTexture(0, r.texHandle(cache, m.data)) r.ctx.BindTexture(0, m.tex)
} }
drc := img.clip drc := img.clip.Add(opOff)
scale, off := clipSpaceTransform(drc, r.blitter.viewport) scale, off := clipSpaceTransform(drc, viewport)
var fbo stencilFBO var fbo FBO
switch img.clipType { switch img.clipType {
case clipTypeNone: case clipTypeNone:
p := r.blitter.pipelines[m.material] p := r.blitter.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline) r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans) r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
continue continue
case clipTypePath: case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx) fbo = r.pather.stenciler.cover(img.place.Idx)
@@ -1105,11 +1251,11 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
p := r.pather.coverer.pipelines[m.material] p := r.pather.coverer.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline) r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff) r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
} }
} }
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) { func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
p := b.pipelines[mat] p := b.pipelines[mat]
b.ctx.BindPipeline(p.pipeline) b.ctx.BindPipeline(p.pipeline)
var uniforms *blitUniforms var uniforms *blitUniforms
@@ -1119,18 +1265,23 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
uniforms = &b.colUniforms.blitUniforms uniforms = &b.colUniforms.blitUniforms
case materialTexture: case materialTexture:
t1, t2, t3, t4, t5, t6 := uvTrans.Elems() t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.texUniforms.blitUniforms uniforms = &b.texUniforms.blitUniforms
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
case materialLinearGradient: case materialLinearGradient:
b.linearGradientUniforms.color1 = col1 b.linearGradientUniforms.color1 = col1
b.linearGradientUniforms.color2 = col2 b.linearGradientUniforms.color2 = col2
t1, t2, t3, t4, t5, t6 := uvTrans.Elems() t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.linearGradientUniforms.blitUniforms uniforms = &b.linearGradientUniforms.blitUniforms
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
} }
uniforms.fbo = 0
if fbo {
uniforms.fbo = 1
}
uniforms.opacity = opacity
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
p.UploadUniforms(b.ctx) p.UploadUniforms(b.ctx)
b.ctx.DrawArrays(0, 4) b.ctx.DrawArrays(0, 4)
Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

+16
View File
@@ -413,6 +413,22 @@ func TestGapsInPath(t *testing.T) {
}) })
} }
func TestOpacity(t *testing.T) {
run(t, func(ops *op.Ops) {
opc1 := paint.PushOpacity(ops, .3)
// Fill screen to exercize the glClear optimization.
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
opc2 := paint.PushOpacity(ops, .6)
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
opc2.Pop()
opc1.Pop()
opc3 := paint.PushOpacity(ops, .6)
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
opc3.Pop()
}, func(r result) {
})
}
// lerp calculates linear interpolation with color b and p. // lerp calculates linear interpolation with color b and p.
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA { func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
return f32color.RGBA{ return f32color.RGBA{
+16 -12
View File
@@ -58,7 +58,7 @@ type coverUniforms struct {
uvCoverTransform [4]float32 uvCoverTransform [4]float32
uvTransformR1 [4]float32 uvTransformR1 [4]float32
uvTransformR2 [4]float32 uvTransformR2 [4]float32
_ float32 fbo float32
} }
type stenciler struct { type stenciler struct {
@@ -90,10 +90,10 @@ type intersectUniforms struct {
} }
type fboSet struct { type fboSet struct {
fbos []stencilFBO fbos []FBO
} }
type stencilFBO struct { type FBO struct {
size image.Point size image.Point
tex driver.Texture tex driver.Texture
} }
@@ -247,10 +247,10 @@ func newStenciler(ctx driver.Device) *stenciler {
return st return st
} }
func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) { func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
// Add fbos. // Add fbos.
for i := len(s.fbos); i < len(sizes); i++ { for i := len(s.fbos); i < len(sizes); i++ {
s.fbos = append(s.fbos, stencilFBO{}) s.fbos = append(s.fbos, FBO{})
} }
// Resize fbos. // Resize fbos.
for i, sz := range sizes { for i, sz := range sizes {
@@ -273,7 +273,7 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
if sz.X > max { if sz.X > max {
sz.X = max sz.X = max
} }
tex, err := ctx.NewTexture(driver.TextureFormatFloat, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest, tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
driver.BufferBindingTexture|driver.BufferBindingFramebuffer) driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -340,15 +340,15 @@ func (s *stenciler) beginIntersect(sizes []image.Point) {
// 8 bit coverage is enough, but OpenGL ES only supports single channel // 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available. // no floating point support is available.
s.intersections.resize(s.ctx, sizes) s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
} }
func (s *stenciler) cover(idx int) stencilFBO { func (s *stenciler) cover(idx int) FBO {
return s.fbos.fbos[idx] return s.fbos.fbos[idx]
} }
func (s *stenciler) begin(sizes []image.Point) { func (s *stenciler) begin(sizes []image.Point) {
s.fbos.resize(s.ctx, sizes) s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
} }
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) { func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
@@ -375,11 +375,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima
} }
} }
func (p *pather) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) { func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p.coverer.cover(mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff) p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
} }
func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) { func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
var uniforms *coverUniforms var uniforms *coverUniforms
switch mat { switch mat {
case materialColor: case materialColor:
@@ -399,6 +399,10 @@ func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color
c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &c.texUniforms.coverUniforms uniforms = &c.texUniforms.coverUniforms
} }
uniforms.fbo = 0
if isFBO {
uniforms.fbo = 1
}
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
c.pipelines[mat].UploadUniforms(c.ctx) c.pipelines[mat].UploadUniforms(c.ctx)
+68 -25
View File
@@ -14,14 +14,25 @@ import (
type Ops struct { type Ops struct {
// version is incremented at each Reset. // version is incremented at each Reset.
version int version uint32
// data contains the serialized operations. // data contains the serialized operations.
data []byte data []byte
// refs hold external references for operations. // refs hold external references for operations.
refs []interface{} refs []interface{}
// stringRefs provides space for string references, pointers to which will
// be stored in refs. Storing a string directly in refs would cause a heap
// allocation, to store the string header in an interface value. The backing
// array of stringRefs, on the other hand, gets reused between calls to
// reset, making string references free on average.
//
// Appending to stringRefs might reallocate the backing array, which will
// leave pointers to the old array in refs. This temporarily causes a slight
// increase in memory usage, but this, too, amortizes away as the capacity
// of stringRefs approaches its stable maximum.
stringRefs []string
// nextStateID is the id allocated for the next // nextStateID is the id allocated for the next
// StateOp. // StateOp.
nextStateID int nextStateID uint32
// multipOp indicates a multi-op such as clip.Path is being added. // multipOp indicates a multi-op such as clip.Path is being added.
multipOp bool multipOp bool
@@ -40,9 +51,10 @@ const (
TypeMacro OpType = iota + firstOpIndex TypeMacro OpType = iota + firstOpIndex
TypeCall TypeCall
TypeDefer TypeDefer
TypePushTransform
TypeTransform TypeTransform
TypePopTransform TypePopTransform
TypePushOpacity
TypePopOpacity
TypeInvalidate TypeInvalidate
TypeImage TypeImage
TypePaint TypePaint
@@ -72,30 +84,30 @@ const (
TypeSemanticDesc TypeSemanticDesc
TypeSemanticClass TypeSemanticClass
TypeSemanticSelected TypeSemanticSelected
TypeSemanticDisabled TypeSemanticEnabled
TypeSnippet TypeSnippet
TypeSelection TypeSelection
TypeActionInput TypeActionInput
) )
type StackID struct { type StackID struct {
id int id uint32
prev int prev uint32
} }
// StateOp represents a saved operation snapshot to be restored // StateOp represents a saved operation snapshot to be restored
// later. // later.
type StateOp struct { type StateOp struct {
id int id uint32
macroID int macroID uint32
ops *Ops ops *Ops
} }
// stack tracks the integer identities of stack operations to ensure correct // stack tracks the integer identities of stack operations to ensure correct
// pairing of their push and pop methods. // pairing of their push and pop methods.
type stack struct { type stack struct {
currentID int currentID uint32
nextID int nextID uint32
} }
type StackKind uint8 type StackKind uint8
@@ -111,6 +123,7 @@ const (
ClipStack StackKind = iota ClipStack StackKind = iota
TransStack TransStack
PassStack PassStack
OpacityStack
_StackKind _StackKind
) )
@@ -124,9 +137,10 @@ const (
TypeMacroLen = 1 + 4 + 4 TypeMacroLen = 1 + 4 + 4
TypeCallLen = 1 + 4 + 4 + 4 + 4 TypeCallLen = 1 + 4 + 4 + 4 + 4
TypeDeferLen = 1 TypeDeferLen = 1
TypePushTransformLen = 1 + 4*6
TypeTransformLen = 1 + 1 + 4*6 TypeTransformLen = 1 + 1 + 4*6
TypePopTransformLen = 1 TypePopTransformLen = 1
TypePushOpacityLen = 1 + 4
TypePopOpacityLen = 1
TypeRedrawLen = 1 + 8 TypeRedrawLen = 1 + 8
TypeImageLen = 1 TypeImageLen = 1
TypePaintLen = 1 TypePaintLen = 1
@@ -156,7 +170,7 @@ const (
TypeSemanticDescLen = 1 TypeSemanticDescLen = 1
TypeSemanticClassLen = 2 TypeSemanticClassLen = 2
TypeSemanticSelectedLen = 2 TypeSemanticSelectedLen = 2
TypeSemanticDisabledLen = 2 TypeSemanticEnabledLen = 2
TypeSnippetLen = 1 + 4 + 4 TypeSnippetLen = 1 + 4 + 4
TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4 TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4
TypeActionInputLen = 1 + 1 TypeActionInputLen = 1 + 1
@@ -183,8 +197,12 @@ func Reset(o *Ops) {
for i := range o.refs { for i := range o.refs {
o.refs[i] = nil o.refs[i] = nil
} }
for i := range o.stringRefs {
o.stringRefs[i] = ""
}
o.data = o.data[:0] o.data = o.data[:0]
o.refs = o.refs[:0] o.refs = o.refs[:0]
o.stringRefs = o.stringRefs[:0]
o.nextStateID = 0 o.nextStateID = 0
o.version++ o.version++
} }
@@ -248,11 +266,11 @@ func AddCall(o *Ops, callOps *Ops, pc PC, end PC) {
bo.PutUint32(data[13:], uint32(end.refs)) bo.PutUint32(data[13:], uint32(end.refs))
} }
func PushOp(o *Ops, kind StackKind) (StackID, int) { func PushOp(o *Ops, kind StackKind) (StackID, uint32) {
return o.stacks[kind].push(), o.macroStack.currentID return o.stacks[kind].push(), o.macroStack.currentID
} }
func PopOp(o *Ops, kind StackKind, sid StackID, macroID int) { func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
if o.macroStack.currentID != macroID { if o.macroStack.currentID != macroID {
panic("stack push and pop must not cross macro boundary") panic("stack push and pop must not cross macro boundary")
} }
@@ -265,12 +283,26 @@ func Write1(o *Ops, n int, ref1 interface{}) []byte {
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
} }
func Write1String(o *Ops, n int, ref1 string) []byte {
o.data = append(o.data, make([]byte, n)...)
o.stringRefs = append(o.stringRefs, ref1)
o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
return o.data[len(o.data)-n:]
}
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte { func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1, ref2) o.refs = append(o.refs, ref1, ref2)
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
} }
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
o.data = append(o.data, make([]byte, n)...)
o.stringRefs = append(o.stringRefs, ref2)
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
return o.data[len(o.data)-n:]
}
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte { func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
o.data = append(o.data, make([]byte, n)...) o.data = append(o.data, make([]byte, n)...)
o.refs = append(o.refs, ref1, ref2, ref3) o.refs = append(o.refs, ref1, ref2, ref3)
@@ -278,7 +310,7 @@ func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
} }
func PCFor(o *Ops) PC { func PCFor(o *Ops) PC {
return PC{data: len(o.data), refs: len(o.refs)} return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))}
} }
func (s *stack) push() StackID { func (s *stack) push() StackID {
@@ -354,6 +386,14 @@ func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
return f32.NewAffine2D(a, b, c, d, e, f), push return f32.NewAffine2D(a, b, c, d, e, f), push
} }
func DecodeOpacity(data []byte) float32 {
if OpType(data[0]) != TypePushOpacity {
panic("invalid op")
}
bo := binary.LittleEndian
return math.Float32frombits(bo.Uint32(data[1:]))
}
// DecodeSave decodes the state id of a save op. // DecodeSave decodes the state id of a save op.
func DecodeSave(data []byte) int { func DecodeSave(data []byte) int {
if OpType(data[0]) != TypeSave { if OpType(data[0]) != TypeSave {
@@ -381,9 +421,10 @@ var opProps = [0x100]opProp{
TypeMacro: {Size: TypeMacroLen, NumRefs: 0}, TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
TypeCall: {Size: TypeCallLen, NumRefs: 1}, TypeCall: {Size: TypeCallLen, NumRefs: 1},
TypeDefer: {Size: TypeDeferLen, NumRefs: 0}, TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
TypePushTransform: {Size: TypePushTransformLen, NumRefs: 0},
TypeTransform: {Size: TypeTransformLen, NumRefs: 0}, TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0}, TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0}, TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
TypeImage: {Size: TypeImageLen, NumRefs: 2}, TypeImage: {Size: TypeImageLen, NumRefs: 2},
TypePaint: {Size: TypePaintLen, NumRefs: 0}, TypePaint: {Size: TypePaintLen, NumRefs: 0},
@@ -413,23 +454,23 @@ var opProps = [0x100]opProp{
TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1}, TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1},
TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0}, TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0}, TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
TypeSemanticDisabled: {Size: TypeSemanticDisabledLen, NumRefs: 0}, TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2}, TypeSnippet: {Size: TypeSnippetLen, NumRefs: 2},
TypeSelection: {Size: TypeSelectionLen, NumRefs: 1}, TypeSelection: {Size: TypeSelectionLen, NumRefs: 1},
TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0}, TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
} }
func (t OpType) props() (size, numRefs int) { func (t OpType) props() (size, numRefs uint32) {
v := opProps[t] v := opProps[t]
return int(v.Size), int(v.NumRefs) return uint32(v.Size), uint32(v.NumRefs)
} }
func (t OpType) Size() int { func (t OpType) Size() uint32 {
return int(opProps[t].Size) return uint32(opProps[t].Size)
} }
func (t OpType) NumRefs() int { func (t OpType) NumRefs() uint32 {
return int(opProps[t].NumRefs) return uint32(opProps[t].NumRefs)
} }
func (t OpType) String() string { func (t OpType) String() string {
@@ -440,12 +481,14 @@ func (t OpType) String() string {
return "Call" return "Call"
case TypeDefer: case TypeDefer:
return "Defer" return "Defer"
case TypePushTransform:
return "PushTransform"
case TypeTransform: case TypeTransform:
return "Transform" return "Transform"
case TypePopTransform: case TypePopTransform:
return "PopTransform" return "PopTransform"
case TypePushOpacity:
return "PushOpacity"
case TypePopOpacity:
return "PopOpacity"
case TypeInvalidate: case TypeInvalidate:
return "Invalidate" return "Invalidate"
case TypeImage: case TypeImage:
+13 -13
View File
@@ -26,8 +26,8 @@ type EncodedOp struct {
// Key is a unique key for a given op. // Key is a unique key for a given op.
type Key struct { type Key struct {
ops *Ops ops *Ops
pc int pc uint32
version int version uint32
} }
// Shadow of op.MacroOp. // Shadow of op.MacroOp.
@@ -39,8 +39,8 @@ type macroOp struct {
// PC is an instruction counter for an operation list. // PC is an instruction counter for an operation list.
type PC struct { type PC struct {
data int data uint32
refs int refs uint32
} }
type macro struct { type macro struct {
@@ -128,7 +128,7 @@ func (r *Reader) Decode() (EncodedOp, bool) {
if nrefs != 1 { if nrefs != 1 {
panic("internal error: unexpected number of macro refs") panic("internal error: unexpected number of macro refs")
} }
deferData := Write1(&r.deferOps, n, refs[0]) deferData := Write1(&r.deferOps, int(n), refs[0])
copy(deferData, data) copy(deferData, data)
r.pc.data += n r.pc.data += n
r.pc.refs += nrefs r.pc.refs += nrefs
@@ -154,8 +154,8 @@ func (r *Reader) Decode() (EncodedOp, bool) {
r.pc = op.endpc r.pc = op.endpc
} else { } else {
// Treat an incomplete macro as containing all remaining ops. // Treat an incomplete macro as containing all remaining ops.
r.pc.data = len(r.ops.data) r.pc.data = uint32(len(r.ops.data))
r.pc.refs = len(r.ops.refs) r.pc.refs = uint32(len(r.ops.refs))
} }
continue continue
} }
@@ -171,8 +171,8 @@ func (op *opMacroDef) decode(data []byte) {
} }
bo := binary.LittleEndian bo := binary.LittleEndian
data = data[:TypeMacroLen] data = data[:TypeMacroLen]
op.endpc.data = int(int32(bo.Uint32(data[1:]))) op.endpc.data = bo.Uint32(data[1:])
op.endpc.refs = int(int32(bo.Uint32(data[5:]))) op.endpc.refs = bo.Uint32(data[5:])
} }
func (m *macroOp) decode(data []byte, refs []interface{}) { func (m *macroOp) decode(data []byte, refs []interface{}) {
@@ -183,8 +183,8 @@ func (m *macroOp) decode(data []byte, refs []interface{}) {
data = data[:TypeCallLen] data = data[:TypeCallLen]
m.ops = refs[0].(*Ops) m.ops = refs[0].(*Ops)
m.start.data = int(int32(bo.Uint32(data[1:]))) m.start.data = bo.Uint32(data[1:])
m.start.refs = int(int32(bo.Uint32(data[5:]))) m.start.refs = bo.Uint32(data[5:])
m.end.data = int(int32(bo.Uint32(data[9:]))) m.end.data = bo.Uint32(data[9:])
m.end.refs = int(int32(bo.Uint32(data[13:]))) m.end.refs = bo.Uint32(data[13:])
} }
+1 -1
View File
@@ -30,7 +30,7 @@ func (h ReadOp) Add(o *op.Ops) {
} }
func (h WriteOp) Add(o *op.Ops) { func (h WriteOp) Add(o *op.Ops) {
data := ops.Write1(&o.Internal, ops.TypeClipboardWriteLen, &h.Text) data := ops.Write1String(&o.Internal, ops.TypeClipboardWriteLen, h.Text)
data[0] = byte(ops.TypeClipboardWrite) data[0] = byte(ops.TypeClipboardWrite)
} }
+2 -3
View File
@@ -323,8 +323,7 @@ func (h InputOp) Add(o *op.Ops) {
if h.Tag == nil { if h.Tag == nil {
panic("Tag must be non-nil") panic("Tag must be non-nil")
} }
filter := h.Keys data := ops.Write2String(&o.Internal, ops.TypeKeyInputLen, h.Tag, string(h.Keys))
data := ops.Write2(&o.Internal, ops.TypeKeyInputLen, h.Tag, &filter)
data[0] = byte(ops.TypeKeyInput) data[0] = byte(ops.TypeKeyInput)
data[1] = byte(h.Hint) data[1] = byte(h.Hint)
} }
@@ -343,7 +342,7 @@ func (h FocusOp) Add(o *op.Ops) {
} }
func (s SnippetOp) Add(o *op.Ops) { func (s SnippetOp) Add(o *op.Ops) {
data := ops.Write2(&o.Internal, ops.TypeSnippetLen, s.Tag, &s.Text) data := ops.Write2String(&o.Internal, ops.TypeSnippetLen, s.Tag, s.Text)
data[0] = byte(ops.TypeSnippet) data[0] = byte(ops.TypeSnippet)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[1:], uint32(s.Range.Start)) bo.PutUint32(data[1:], uint32(s.Range.Start))
+12 -12
View File
@@ -18,7 +18,7 @@ import (
// Event is a pointer event. // Event is a pointer event.
type Event struct { type Event struct {
Type Type Kind Kind
Source Source Source Source
// PointerID is the id for the pointer and can be used // PointerID is the id for the pointer and can be used
// to track a particular pointer from Press to // to track a particular pointer from Press to
@@ -51,7 +51,7 @@ type PassOp struct {
type PassStack struct { type PassStack struct {
ops *ops.Ops ops *ops.Ops
id ops.StackID id ops.StackID
macroID int macroID uint32
} }
// InputOp declares an input handler ready for pointer // InputOp declares an input handler ready for pointer
@@ -61,8 +61,8 @@ type InputOp struct {
// Grab, if set, request that the handler get // Grab, if set, request that the handler get
// Grabbed priority. // Grabbed priority.
Grab bool Grab bool
// Types is a bitwise-or of event types to receive. // Kinds is a bitwise-or of event types to receive.
Types Type Kinds Kind
// ScrollBounds describe the maximum scrollable distances in both // ScrollBounds describe the maximum scrollable distances in both
// axes. Specifically, any Event e delivered to Tag will satisfy // axes. Specifically, any Event e delivered to Tag will satisfy
// //
@@ -73,8 +73,8 @@ type InputOp struct {
type ID uint16 type ID uint16
// Type of an Event. // Kind of an Event.
type Type uint type Kind uint
// Priority of an Event. // Priority of an Event.
type Priority uint8 type Priority uint8
@@ -169,7 +169,7 @@ const (
const ( const (
// A Cancel event is generated when the current gesture is // A Cancel event is generated when the current gesture is
// interrupted by other handlers or the system. // interrupted by other handlers or the system.
Cancel Type = (1 << iota) >> 1 Cancel Kind = (1 << iota) >> 1
// Press of a pointer. // Press of a pointer.
Press Press
// Release of a pointer. // Release of a pointer.
@@ -243,7 +243,7 @@ func (op InputOp) Add(o *op.Ops) {
if b := op.ScrollBounds; b.Min.X > 0 || b.Max.X < 0 || b.Min.Y > 0 || b.Max.Y < 0 { if b := op.ScrollBounds; b.Min.X > 0 || b.Max.X < 0 || b.Min.Y > 0 || b.Max.Y < 0 {
panic(fmt.Errorf("invalid scroll range value %v", b)) panic(fmt.Errorf("invalid scroll range value %v", b))
} }
if op.Types>>16 > 0 { if op.Kinds>>16 > 0 {
panic(fmt.Errorf("value in Types overflows uint16")) panic(fmt.Errorf("value in Types overflows uint16"))
} }
data := ops.Write1(&o.Internal, ops.TypePointerInputLen, op.Tag) data := ops.Write1(&o.Internal, ops.TypePointerInputLen, op.Tag)
@@ -252,19 +252,19 @@ func (op InputOp) Add(o *op.Ops) {
data[1] = 1 data[1] = 1
} }
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint16(data[2:], uint16(op.Types)) bo.PutUint16(data[2:], uint16(op.Kinds))
bo.PutUint32(data[4:], uint32(op.ScrollBounds.Min.X)) bo.PutUint32(data[4:], uint32(op.ScrollBounds.Min.X))
bo.PutUint32(data[8:], uint32(op.ScrollBounds.Min.Y)) bo.PutUint32(data[8:], uint32(op.ScrollBounds.Min.Y))
bo.PutUint32(data[12:], uint32(op.ScrollBounds.Max.X)) bo.PutUint32(data[12:], uint32(op.ScrollBounds.Max.X))
bo.PutUint32(data[16:], uint32(op.ScrollBounds.Max.Y)) bo.PutUint32(data[16:], uint32(op.ScrollBounds.Max.Y))
} }
func (t Type) String() string { func (t Kind) String() string {
if t == Cancel { if t == Cancel {
return "Cancel" return "Cancel"
} }
var buf strings.Builder var buf strings.Builder
for tt := Type(1); tt > 0; tt <<= 1 { for tt := Kind(1); tt > 0; tt <<= 1 {
if t&tt > 0 { if t&tt > 0 {
if buf.Len() > 0 { if buf.Len() > 0 {
buf.WriteByte('|') buf.WriteByte('|')
@@ -275,7 +275,7 @@ func (t Type) String() string {
return buf.String() return buf.String()
} }
func (t Type) string() string { func (t Kind) string() string {
switch t { switch t {
case Press: case Press:
return "Press" return "Press"
+1 -1
View File
@@ -8,7 +8,7 @@ import (
func TestTypeString(t *testing.T) { func TestTypeString(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
typ Type typ Kind
res string res string
}{ }{
{Cancel, "Cancel"}, {Cancel, "Cancel"},
+2 -7
View File
@@ -30,11 +30,6 @@ func TestKeyWakeup(t *testing.T) {
if evts := r.Events(handler); len(evts) != 1 { if evts := r.Events(handler); len(evts) != 1 {
t.Errorf("no Focus event for newly registered key.InputOp") t.Errorf("no Focus event for newly registered key.InputOp")
} }
// Verify that r.Events does trigger a redraw.
r.Frame(&ops)
if _, wake := r.WakeupTime(); !wake {
t.Errorf("key.FocusEvent event didn't trigger a redraw")
}
} }
func TestKeyMultiples(t *testing.T) { func TestKeyMultiples(t *testing.T) {
@@ -279,7 +274,7 @@ func TestFocusScroll(t *testing.T) {
key.InputOp{Tag: h}.Add(ops) key.InputOp{Tag: h}.Add(ops)
pointer.InputOp{ pointer.InputOp{
Tag: h, Tag: h,
Types: pointer.Scroll, Kinds: pointer.Scroll,
ScrollBounds: image.Rect(-100, -100, 100, 100), ScrollBounds: image.Rect(-100, -100, 100, 100),
}.Add(ops) }.Add(ops)
// Test that h is scrolled even if behind another handler. // Test that h is scrolled even if behind another handler.
@@ -305,7 +300,7 @@ func TestFocusClick(t *testing.T) {
key.InputOp{Tag: h}.Add(ops) key.InputOp{Tag: h}.Add(ops)
pointer.InputOp{ pointer.InputOp{
Tag: h, Tag: h,
Types: pointer.Press | pointer.Release, Kinds: pointer.Press | pointer.Release,
}.Add(ops) }.Add(ops)
cl.Pop() cl.Pop()
r.Frame(ops) r.Frame(ops)
+21 -21
View File
@@ -66,7 +66,7 @@ type pointerHandler struct {
area int area int
active bool active bool
wantsGrab bool wantsGrab bool
types pointer.Type types pointer.Kind
// min and max horizontal/vertical scroll // min and max horizontal/vertical scroll
scrollRange image.Rectangle scrollRange image.Rectangle
@@ -242,7 +242,7 @@ func (c *pointerCollector) newHandler(tag event.Tag, events *handlerEvents) *poi
c.q.handlers[tag] = h c.q.handlers[tag] = h
// Cancel handlers on (each) first appearance, but don't // Cancel handlers on (each) first appearance, but don't
// trigger redraw. // trigger redraw.
events.AddNoRedraw(tag, pointer.Event{Type: pointer.Cancel}) events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel})
} }
h.active = true h.active = true
h.area = areaID h.area = areaID
@@ -268,16 +268,16 @@ func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) {
areaID := c.currentArea() areaID := c.currentArea()
area := &c.q.areas[areaID] area := &c.q.areas[areaID]
area.semantic.content.tag = op.Tag area.semantic.content.tag = op.Tag
if op.Types&(pointer.Press|pointer.Release) != 0 { if op.Kinds&(pointer.Press|pointer.Release) != 0 {
area.semantic.content.gestures |= ClickGesture area.semantic.content.gestures |= ClickGesture
} }
if op.Types&pointer.Scroll != 0 { if op.Kinds&pointer.Scroll != 0 {
area.semantic.content.gestures |= ScrollGesture area.semantic.content.gestures |= ScrollGesture
} }
area.semantic.valid = area.semantic.content.gestures != 0 area.semantic.valid = area.semantic.content.gestures != 0
h := c.newHandler(op.Tag, events) h := c.newHandler(op.Tag, events)
h.wantsGrab = h.wantsGrab || op.Grab h.wantsGrab = h.wantsGrab || op.Grab
h.types = h.types | op.Types h.types = h.types | op.Kinds
h.scrollRange = op.ScrollBounds h.scrollRange = op.ScrollBounds
} }
@@ -309,11 +309,11 @@ func (c *pointerCollector) semanticSelected(selected bool) {
area.semantic.content.selected = selected area.semantic.content.selected = selected
} }
func (c *pointerCollector) semanticDisabled(disabled bool) { func (c *pointerCollector) semanticEnabled(enabled bool) {
areaID := c.currentArea() areaID := c.currentArea()
area := &c.q.areas[areaID] area := &c.q.areas[areaID]
area.semantic.valid = true area.semantic.valid = true
area.semantic.content.disabled = disabled area.semantic.content.disabled = !enabled
} }
func (c *pointerCollector) cursor(cursor pointer.Cursor) { func (c *pointerCollector) cursor(cursor pointer.Cursor) {
@@ -602,7 +602,7 @@ func (q *pointerQueue) Frame(events *handlerEvents) {
func (q *pointerQueue) dropHandler(events *handlerEvents, tag event.Tag) { func (q *pointerQueue) dropHandler(events *handlerEvents, tag event.Tag) {
if events != nil { if events != nil {
events.Add(tag, pointer.Event{Type: pointer.Cancel}) events.Add(tag, pointer.Event{Kind: pointer.Cancel})
} }
for i := range q.pointers { for i := range q.pointers {
p := &q.pointers[i] p := &q.pointers[i]
@@ -649,11 +649,11 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
continue continue
} }
h := q.handlers[n.tag] h := q.handlers[n.tag]
if e.Type&h.types == 0 { if e.Kind&h.types == 0 {
continue continue
} }
e := e e := e
if e.Type == pointer.Scroll { if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 { if sx == 0 && sy == 0 {
break break
} }
@@ -663,7 +663,7 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event, events *handlerEven
} }
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.area, e.Position)
events.Add(n.tag, e) events.Add(n.tag, e)
if e.Type != pointer.Scroll { if e.Kind != pointer.Scroll {
break break
} }
} }
@@ -683,7 +683,7 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
} }
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) { func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
if e.Type == pointer.Cancel { if e.Kind == pointer.Cancel {
q.pointers = q.pointers[:0] q.pointers = q.pointers[:0]
for k := range q.handlers { for k := range q.handlers {
q.dropHandler(events, k) q.dropHandler(events, k)
@@ -694,14 +694,14 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
p := &q.pointers[pidx] p := &q.pointers[pidx]
p.last = e p.last = e
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
q.deliverEnterLeaveEvents(p, events, e) q.deliverEnterLeaveEvents(p, events, e)
p.pressed = true p.pressed = true
q.deliverEvent(p, events, e) q.deliverEvent(p, events, e)
case pointer.Move: case pointer.Move:
if p.pressed { if p.pressed {
e.Type = pointer.Drag e.Kind = pointer.Drag
} }
q.deliverEnterLeaveEvents(p, events, e) q.deliverEnterLeaveEvents(p, events, e)
q.deliverEvent(p, events, e) q.deliverEvent(p, events, e)
@@ -735,7 +735,7 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
var sx, sy = e.Scroll.X, e.Scroll.Y var sx, sy = e.Scroll.X, e.Scroll.Y
for _, k := range p.handlers { for _, k := range p.handlers {
h := q.handlers[k] h := q.handlers[k]
if e.Type == pointer.Scroll { if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 { if sx == 0 && sy == 0 {
return return
} }
@@ -743,7 +743,7 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
} }
if e.Type&h.types == 0 { if e.Kind&h.types == 0 {
continue continue
} }
e := e e := e
@@ -758,7 +758,7 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) { func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
var hits []event.Tag var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press { if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
// Consider non-mouse pointers leaving when they're released. // Consider non-mouse pointers leaving when they're released.
} else { } else {
hits, q.cursor = q.opHit(e.Position) hits, q.cursor = q.opHit(e.Position)
@@ -790,9 +790,9 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
continue continue
} }
h := q.handlers[k] h := q.handlers[k]
e.Type = pointer.Leave e.Kind = pointer.Leave
if e.Type&h.types != 0 { if e.Kind&h.types != 0 {
e := e e := e
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e) events.Add(k, e)
@@ -804,9 +804,9 @@ func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEv
if _, found := searchTag(p.entered, k); found { if _, found := searchTag(p.entered, k); found {
continue continue
} }
e.Type = pointer.Enter e.Kind = pointer.Enter
if e.Type&h.types != 0 { if e.Kind&h.types != 0 {
e := e e := e
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.area, e.Position)
events.Add(k, e) events.Add(k, e)
+100 -105
View File
@@ -33,11 +33,6 @@ func TestPointerWakeup(t *testing.T) {
} }
// However, adding a handler queues a Cancel event. // However, adding a handler queues a Cancel event.
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel) assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel)
// Verify that r.Events does trigger a redraw.
r.Frame(&ops)
if _, wake := r.WakeupTime(); !wake {
t.Errorf("pointer.Cancel event didn't trigger a redraw")
}
} }
func TestPointerDrag(t *testing.T) { func TestPointerDrag(t *testing.T) {
@@ -50,12 +45,12 @@ func TestPointerDrag(t *testing.T) {
r.Queue( r.Queue(
// Press. // Press.
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
// Move outside the area. // Move outside the area.
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(150, 150), Position: f32.Pt(150, 150),
}, },
) )
@@ -72,12 +67,12 @@ func TestPointerDragNegative(t *testing.T) {
r.Queue( r.Queue(
// Press. // Press.
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(-50, -50), Position: f32.Pt(-50, -50),
}, },
// Move outside the area. // Move outside the area.
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(-150, -150), Position: f32.Pt(-150, -150),
}, },
) )
@@ -92,15 +87,15 @@ func TestPointerGrab(t *testing.T) {
types := pointer.Press | pointer.Release types := pointer.Press | pointer.Release
pointer.InputOp{Tag: handler1, Types: types, Grab: true}.Add(&ops) pointer.InputOp{Tag: handler1, Kinds: types, Grab: true}.Add(&ops)
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops) pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops)
pointer.InputOp{Tag: handler3, Types: types}.Add(&ops) pointer.InputOp{Tag: handler3, Kinds: types}.Add(&ops)
var r Router var r Router
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -110,7 +105,7 @@ func TestPointerGrab(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -126,15 +121,15 @@ func TestPointerGrabSameHandlerTwice(t *testing.T) {
types := pointer.Press | pointer.Release types := pointer.Press | pointer.Release
pointer.InputOp{Tag: handler1, Types: types, Grab: true}.Add(&ops) pointer.InputOp{Tag: handler1, Kinds: types, Grab: true}.Add(&ops)
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops) pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops)
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops) pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops)
var r Router var r Router
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -143,7 +138,7 @@ func TestPointerGrabSameHandlerTwice(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -160,10 +155,10 @@ func TestPointerMove(t *testing.T) {
// Handler 1 area: (0, 0) - (100, 100) // Handler 1 area: (0, 0) - (100, 100)
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops) pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops)
// Handler 2 area: (50, 50) - (100, 100) (areas intersect). // Handler 2 area: (50, 50) - (100, 100) (areas intersect).
r2 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops) r2 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops)
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops) pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops)
r2.Pop() r2.Pop()
r1.Pop() r1.Pop()
@@ -172,21 +167,21 @@ func TestPointerMove(t *testing.T) {
r.Queue( r.Queue(
// Hit both handlers. // Hit both handlers.
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
// Hit handler 1. // Hit handler 1.
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(49, 50), Position: f32.Pt(49, 50),
}, },
// Hit no handlers. // Hit no handlers.
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(100, 50), Position: f32.Pt(100, 50),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Cancel, Kind: pointer.Cancel,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Leave, pointer.Cancel) assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Leave, pointer.Cancel)
@@ -199,7 +194,7 @@ func TestPointerTypes(t *testing.T) {
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
pointer.InputOp{ pointer.InputOp{
Tag: handler, Tag: handler,
Types: pointer.Press | pointer.Release, Kinds: pointer.Press | pointer.Release,
}.Add(&ops) }.Add(&ops)
r1.Pop() r1.Pop()
@@ -207,15 +202,15 @@ func TestPointerTypes(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(150, 150), Position: f32.Pt(150, 150),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(150, 150), Position: f32.Pt(150, 150),
}, },
) )
@@ -268,14 +263,14 @@ func TestPointerPriority(t *testing.T) {
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
pointer.InputOp{ pointer.InputOp{
Tag: handler1, Tag: handler1,
Types: pointer.Scroll, Kinds: pointer.Scroll,
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}}, ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
}.Add(&ops) }.Add(&ops)
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops) r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
pointer.InputOp{ pointer.InputOp{
Tag: handler2, Tag: handler2,
Types: pointer.Scroll, Kinds: pointer.Scroll,
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}}, ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
}.Add(&ops) }.Add(&ops)
r2.Pop() r2.Pop()
@@ -284,7 +279,7 @@ func TestPointerPriority(t *testing.T) {
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops) r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
pointer.InputOp{ pointer.InputOp{
Tag: handler3, Tag: handler3,
Types: pointer.Scroll, Kinds: pointer.Scroll,
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}}, ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
}.Add(&ops) }.Add(&ops)
r3.Pop() r3.Pop()
@@ -294,25 +289,25 @@ func TestPointerPriority(t *testing.T) {
r.Queue( r.Queue(
// Hit handler 1 and 2. // Hit handler 1 and 2.
pointer.Event{ pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Position: f32.Pt(50, 25), Position: f32.Pt(50, 25),
Scroll: f32.Pt(50, 0), Scroll: f32.Pt(50, 0),
}, },
// Hit handler 1. // Hit handler 1.
pointer.Event{ pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Position: f32.Pt(50, 75), Position: f32.Pt(50, 75),
Scroll: f32.Pt(50, 50), Scroll: f32.Pt(50, 50),
}, },
// Hit handler 3. // Hit handler 3.
pointer.Event{ pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Position: f32.Pt(50, 150), Position: f32.Pt(50, 150),
Scroll: f32.Pt(-30, -30), Scroll: f32.Pt(-30, -30),
}, },
// Hit no handlers. // Hit no handlers.
pointer.Event{ pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Position: f32.Pt(50, 225), Position: f32.Pt(50, 225),
}, },
) )
@@ -348,7 +343,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Hit both handlers. // Hit both handlers.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -361,7 +356,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Leave the second area by moving into the first. // Leave the second area by moving into the first.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(45, 45), Position: f32.Pt(45, 45),
}, },
) )
@@ -372,7 +367,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Move, but stay within the same hit area. // Move, but stay within the same hit area.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(40, 40), Position: f32.Pt(40, 40),
}, },
) )
@@ -382,7 +377,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Move outside of both inputs. // Move outside of both inputs.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(300, 300), Position: f32.Pt(300, 300),
}, },
) )
@@ -392,7 +387,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Check that a Press event generates Enter Events. // Check that a Press event generates Enter Events.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(125, 125), Position: f32.Pt(125, 125),
}, },
) )
@@ -403,12 +398,12 @@ func TestPointerEnterLeave(t *testing.T) {
r.Queue( r.Queue(
// Leave // Leave
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(25, 25), Position: f32.Pt(25, 25),
}, },
// Enter // Enter
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -418,7 +413,7 @@ func TestPointerEnterLeave(t *testing.T) {
// Check that a Release event generates Enter/Leave Events. // Check that a Release event generates Enter/Leave Events.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(25, Position: f32.Pt(25,
25), 25),
}, },
@@ -446,15 +441,15 @@ func TestMultipleAreas(t *testing.T) {
// Hit first area, then second area, then both. // Hit first area, then second area, then both.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(25, 25), Position: f32.Pt(25, 25),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(150, 150), Position: f32.Pt(150, 150),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -470,11 +465,11 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Handler 1 area: (0, 0) - (100, 100) // Handler 1 area: (0, 0) - (100, 100)
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops) pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops)
// Handler 2 area: (25, 25) - (75, 75) (nested within first). // Handler 2 area: (25, 25) - (75, 75) (nested within first).
r2 := clip.Rect(image.Rect(25, 25, 75, 75)).Push(&ops) r2 := clip.Rect(image.Rect(25, 25, 75, 75)).Push(&ops)
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops) pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops)
r2.Pop() r2.Pop()
r1.Pop() r1.Pop()
@@ -483,7 +478,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Hit both handlers. // Hit both handlers.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -495,7 +490,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Leave the second area by moving into the first. // Leave the second area by moving into the first.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(20, 20), Position: f32.Pt(20, 20),
}, },
) )
@@ -505,7 +500,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Move, but stay within the same hit area. // Move, but stay within the same hit area.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
}, },
) )
@@ -515,7 +510,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Move outside of both inputs. // Move outside of both inputs.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(200, 200), Position: f32.Pt(200, 200),
}, },
) )
@@ -525,7 +520,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Check that a Press event generates Enter Events. // Check that a Press event generates Enter Events.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
@@ -535,7 +530,7 @@ func TestPointerEnterLeaveNested(t *testing.T) {
// Check that a Release event generates Enter/Leave Events. // Check that a Release event generates Enter/Leave Events.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(20, 20), Position: f32.Pt(20, 20),
}, },
) )
@@ -554,7 +549,7 @@ func TestPointerActiveInputDisappears(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(25, 25), Position: f32.Pt(25, 25),
}, },
) )
@@ -565,7 +560,7 @@ func TestPointerActiveInputDisappears(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(25, 25), Position: f32.Pt(25, 25),
}, },
) )
@@ -587,21 +582,21 @@ func TestMultitouch(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: h1pt, Position: h1pt,
PointerID: p1, PointerID: p1,
}, },
) )
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: h2pt, Position: h2pt,
PointerID: p2, PointerID: p2,
}, },
) )
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: h2pt, Position: h2pt,
PointerID: p2, PointerID: p2,
}, },
@@ -634,7 +629,7 @@ func TestCursor(t *testing.T) {
_at := func(x, y float32) pointer.Event { _at := func(x, y float32) pointer.Event {
return pointer.Event{ return pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Position: f32.Pt(x, y), Position: f32.Pt(x, y),
@@ -734,14 +729,14 @@ func TestPassOp(t *testing.T) {
h1, h2, h3, h4 := new(int), new(int), new(int), new(int) h1, h2, h3, h4 := new(int), new(int), new(int), new(int)
area := clip.Rect(image.Rect(0, 0, 100, 100)) area := clip.Rect(image.Rect(0, 0, 100, 100))
root := area.Push(&ops) root := area.Push(&ops)
pointer.InputOp{Tag: h1, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h1, Kinds: pointer.Press}.Add(&ops)
child1 := area.Push(&ops) child1 := area.Push(&ops)
pointer.InputOp{Tag: h2, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h2, Kinds: pointer.Press}.Add(&ops)
child1.Pop() child1.Pop()
child2 := area.Push(&ops) child2 := area.Push(&ops)
pass := pointer.PassOp{}.Push(&ops) pass := pointer.PassOp{}.Push(&ops)
pointer.InputOp{Tag: h3, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h3, Kinds: pointer.Press}.Add(&ops)
pointer.InputOp{Tag: h4, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h4, Kinds: pointer.Press}.Add(&ops)
pass.Pop() pass.Pop()
child2.Pop() child2.Pop()
root.Pop() root.Pop()
@@ -750,7 +745,7 @@ func TestPassOp(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Press) assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Press)
@@ -763,13 +758,13 @@ func TestAreaPassthrough(t *testing.T) {
var ops op.Ops var ops op.Ops
h := new(int) h := new(int)
pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h, Kinds: pointer.Press}.Add(&ops)
clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop() clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop()
var r Router var r Router
r.Frame(&ops) r.Frame(&ops)
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press) assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
@@ -780,7 +775,7 @@ func TestEllipse(t *testing.T) {
h := new(int) h := new(int)
cl := clip.Ellipse(image.Rect(0, 0, 100, 100)).Push(&ops) cl := clip.Ellipse(image.Rect(0, 0, 100, 100)).Push(&ops)
pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops) pointer.InputOp{Tag: h, Kinds: pointer.Press}.Add(&ops)
cl.Pop() cl.Pop()
var r Router var r Router
r.Frame(&ops) r.Frame(&ops)
@@ -788,15 +783,15 @@ func TestEllipse(t *testing.T) {
// Outside ellipse. // Outside ellipse.
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
}, },
// Inside ellipse. // Inside ellipse.
pointer.Event{ pointer.Event{
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
Type: pointer.Press, Kind: pointer.Press,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press) assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
@@ -825,7 +820,7 @@ func TestTransfer(t *testing.T) {
return src, tgt return src, tgt
} }
// Cancel is received when the pointer is first seen. // Cancel is received when the pointer is first seen.
cancel := pointer.Event{Type: pointer.Cancel} cancel := pointer.Event{Kind: pointer.Cancel}
t.Run("transfer.Offer should panic on nil Data", func(t *testing.T) { t.Run("transfer.Offer should panic on nil Data", func(t *testing.T) {
defer func() { defer func() {
@@ -845,11 +840,11 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventSequence(t, r.Events(src), cancel) assertEventSequence(t, r.Events(src), cancel)
@@ -859,11 +854,11 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(30, 10), Position: f32.Pt(30, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(30, 10), Position: f32.Pt(30, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
@@ -886,11 +881,11 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventSequence(t, r.Events(src), cancel) assertEventSequence(t, r.Events(src), cancel)
@@ -907,11 +902,11 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventSequence(t, r.Events(src), cancel) assertEventSequence(t, r.Events(src), cancel)
@@ -921,7 +916,7 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) assertEventSequence(t, r.Events(src), transfer.CancelEvent{})
@@ -944,11 +939,11 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventSequence(t, r.Events(src), cancel) assertEventSequence(t, r.Events(src), cancel)
@@ -958,7 +953,7 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
assertEventSequence(t, r.Events(src), transfer.RequestEvent{Type: "file"}) assertEventSequence(t, r.Events(src), transfer.RequestEvent{Type: "file"})
@@ -1011,15 +1006,15 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
ofr := &offer{data: "hello"} ofr := &offer{data: "hello"}
@@ -1054,15 +1049,15 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel, pointer.Enter) assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel, pointer.Enter)
@@ -1071,7 +1066,7 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
@@ -1102,15 +1097,15 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
) )
assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel) assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel)
@@ -1119,7 +1114,7 @@ func TestTransfer(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(40, 10), Position: f32.Pt(40, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
@@ -1167,7 +1162,7 @@ func TestPassCursor(t *testing.T) {
r.Frame(&ops) r.Frame(&ops)
r.Queue(pointer.Event{ r.Queue(pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Move, Kind: pointer.Move,
}) })
if got := r.Cursor(); want != got { if got := r.Cursor(); want != got {
t.Errorf("got cursor %v, want %v", got, want) t.Errorf("got cursor %v, want %v", got, want)
@@ -1192,18 +1187,18 @@ func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
defer clip.Rect(area).Push(ops).Pop() defer clip.Rect(area).Push(ops).Pop()
pointer.InputOp{ pointer.InputOp{
Tag: tag, Tag: tag,
Types: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave, Kinds: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave,
}.Add(ops) }.Add(ops)
} }
// pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes // pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes
// that all input events are of underlying type pointer.Event, and thus will // that all input events are of underlying type pointer.Event, and thus will
// panic if some are not. // panic if some are not.
func pointerTypes(events []event.Event) []pointer.Type { func pointerTypes(events []event.Event) []pointer.Kind {
var types []pointer.Type var types []pointer.Kind
for _, e := range events { for _, e := range events {
if e, ok := e.(pointer.Event); ok { if e, ok := e.(pointer.Event); ok {
types = append(types, e.Type) types = append(types, e.Kind)
} }
} }
return types return types
@@ -1211,7 +1206,7 @@ func pointerTypes(events []event.Event) []pointer.Type {
// assertEventPointerTypeSequence checks that the provided events match the expected pointer event types // assertEventPointerTypeSequence checks that the provided events match the expected pointer event types
// in the provided order. // in the provided order.
func assertEventPointerTypeSequence(t *testing.T, events []event.Event, expected ...pointer.Type) { func assertEventPointerTypeSequence(t *testing.T, events []event.Event, expected ...pointer.Kind) {
t.Helper() t.Helper()
got := pointerTypes(events) got := pointerTypes(events)
if !reflect.DeepEqual(got, expected) { if !reflect.DeepEqual(got, expected) {
@@ -1239,7 +1234,7 @@ func eventsToString(evs []event.Event) string {
for _, ev := range evs { for _, ev := range evs {
switch e := ev.(type) { switch e := ev.(type) {
case pointer.Event: case pointer.Event:
s = append(s, fmt.Sprintf("%T{%s}", e, e.Type.String())) s = append(s, fmt.Sprintf("%T{%s}", e, e.Kind.String()))
default: default:
s = append(s, fmt.Sprintf("{%T}", e)) s = append(s, fmt.Sprintf("{%T}", e))
} }
@@ -1308,7 +1303,7 @@ func BenchmarkRouterAdd(b *testing.B) {
Push(&ops) Push(&ops)
pointer.InputOp{ pointer.InputOp{
Tag: handlers[i], Tag: handlers[i],
Types: pointer.Move, Kinds: pointer.Move,
}.Add(&ops) }.Add(&ops)
} }
var r Router var r Router
@@ -1318,7 +1313,7 @@ func BenchmarkRouterAdd(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
+11 -21
View File
@@ -273,7 +273,7 @@ func (q *Router) ScrollFocus(dist image.Point) {
} }
area := q.key.queue.AreaFor(focus) area := q.key.queue.AreaFor(focus)
q.pointer.queue.Deliver(area, pointer.Event{ q.pointer.queue.Deliver(area, pointer.Event{
Type: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Touch, Source: pointer.Touch,
Scroll: f32internal.FPt(dist), Scroll: f32internal.FPt(dist),
}, &q.handlers) }, &q.handlers)
@@ -317,9 +317,9 @@ func (q *Router) ClickFocus() {
Source: pointer.Touch, Source: pointer.Touch,
} }
area := q.key.queue.AreaFor(focus) area := q.key.queue.AreaFor(focus)
e.Type = pointer.Press e.Kind = pointer.Press
q.pointer.queue.Deliver(area, e, &q.handlers) q.pointer.queue.Deliver(area, e, &q.handlers)
e.Type = pointer.Release e.Kind = pointer.Release
q.pointer.queue.Deliver(area, e, &q.handlers) q.pointer.queue.Deliver(area, e, &q.handlers)
} }
@@ -438,7 +438,7 @@ func (q *Router) collect() {
op := pointer.InputOp{ op := pointer.InputOp{
Tag: encOp.Refs[0].(event.Tag), Tag: encOp.Refs[0].(event.Tag),
Grab: encOp.Data[1] != 0, Grab: encOp.Data[1] != 0,
Types: pointer.Type(bo.Uint16(encOp.Data[2:])), Kinds: pointer.Kind(bo.Uint16(encOp.Data[2:])),
ScrollBounds: image.Rectangle{ ScrollBounds: image.Rectangle{
Min: image.Point{ Min: image.Point{
X: int(int32(bo.Uint32(encOp.Data[4:]))), X: int(int32(bo.Uint32(encOp.Data[4:]))),
@@ -490,11 +490,11 @@ func (q *Router) collect() {
} }
kc.softKeyboard(op.Show) kc.softKeyboard(op.Show)
case ops.TypeKeyInput: case ops.TypeKeyInput:
filter := encOp.Refs[1].(*key.Set) filter := key.Set(*encOp.Refs[1].(*string))
op := key.InputOp{ op := key.InputOp{
Tag: encOp.Refs[0].(event.Tag), Tag: encOp.Refs[0].(event.Tag),
Hint: key.InputHint(encOp.Data[1]), Hint: key.InputHint(encOp.Data[1]),
Keys: *filter, Keys: filter,
} }
a := pc.currentArea() a := pc.currentArea()
b := pc.currentAreaBounds() b := pc.currentAreaBounds()
@@ -532,10 +532,10 @@ func (q *Router) collect() {
// Semantic ops. // Semantic ops.
case ops.TypeSemanticLabel: case ops.TypeSemanticLabel:
lbl := encOp.Refs[0].(string) lbl := *encOp.Refs[0].(*string)
pc.semanticLabel(lbl) pc.semanticLabel(lbl)
case ops.TypeSemanticDesc: case ops.TypeSemanticDesc:
desc := encOp.Refs[0].(string) desc := *encOp.Refs[0].(*string)
pc.semanticDesc(desc) pc.semanticDesc(desc)
case ops.TypeSemanticClass: case ops.TypeSemanticClass:
class := semantic.ClassOp(encOp.Data[1]) class := semantic.ClassOp(encOp.Data[1])
@@ -546,11 +546,11 @@ func (q *Router) collect() {
} else { } else {
pc.semanticSelected(false) pc.semanticSelected(false)
} }
case ops.TypeSemanticDisabled: case ops.TypeSemanticEnabled:
if encOp.Data[1] != 0 { if encOp.Data[1] != 0 {
pc.semanticDisabled(true) pc.semanticEnabled(true)
} else { } else {
pc.semanticDisabled(false) pc.semanticEnabled(false)
} }
} }
} }
@@ -593,16 +593,6 @@ func (h *handlerEvents) HadEvents() bool {
func (h *handlerEvents) Events(k event.Tag) []event.Event { func (h *handlerEvents) Events(k event.Tag) []event.Event {
if events, ok := h.handlers[k]; ok { if events, ok := h.handlers[k]; ok {
h.handlers[k] = h.handlers[k][:0] h.handlers[k] = h.handlers[k][:0]
// Schedule another frame if we delivered events to the user
// to flush half-updated state. This is important when an
// event changes UI state that has already been laid out. In
// the worst case, we waste a frame, increasing power usage.
//
// Gio is expected to grow the ability to construct
// frame-to-frame differences and only render to changed
// areas. In that case, the waste of a spurious frame should
// be minimal.
h.hadEvents = h.hadEvents || len(events) > 0
return events return events
} }
return nil return nil
+2 -2
View File
@@ -74,11 +74,11 @@ func TestSemanticTree(t *testing.T) {
func TestSemanticDescription(t *testing.T) { func TestSemanticDescription(t *testing.T) {
var ops op.Ops var ops op.Ops
pointer.InputOp{Tag: new(int), Types: pointer.Press | pointer.Release}.Add(&ops) pointer.InputOp{Tag: new(int), Kinds: pointer.Press | pointer.Release}.Add(&ops)
semantic.DescriptionOp("description").Add(&ops) semantic.DescriptionOp("description").Add(&ops)
semantic.LabelOp("label").Add(&ops) semantic.LabelOp("label").Add(&ops)
semantic.Button.Add(&ops) semantic.Button.Add(&ops)
semantic.DisabledOp(true).Add(&ops) semantic.EnabledOp(false).Add(&ops)
semantic.SelectedOp(true).Add(&ops) semantic.SelectedOp(true).Add(&ops)
var r Router var r Router
r.Frame(&ops) r.Frame(&ops)
+8 -8
View File
@@ -36,16 +36,16 @@ const (
// boolean state. // boolean state.
type SelectedOp bool type SelectedOp bool
// DisabledOp describes the disabled state. // EnabledOp describes the enabled state.
type DisabledOp bool type EnabledOp bool
func (l LabelOp) Add(o *op.Ops) { func (l LabelOp) Add(o *op.Ops) {
data := ops.Write1(&o.Internal, ops.TypeSemanticLabelLen, string(l)) data := ops.Write1String(&o.Internal, ops.TypeSemanticLabelLen, string(l))
data[0] = byte(ops.TypeSemanticLabel) data[0] = byte(ops.TypeSemanticLabel)
} }
func (d DescriptionOp) Add(o *op.Ops) { func (d DescriptionOp) Add(o *op.Ops) {
data := ops.Write1(&o.Internal, ops.TypeSemanticDescLen, string(d)) data := ops.Write1String(&o.Internal, ops.TypeSemanticDescLen, string(d))
data[0] = byte(ops.TypeSemanticDesc) data[0] = byte(ops.TypeSemanticDesc)
} }
@@ -63,10 +63,10 @@ func (s SelectedOp) Add(o *op.Ops) {
} }
} }
func (d DisabledOp) Add(o *op.Ops) { func (e EnabledOp) Add(o *op.Ops) {
data := ops.Write(&o.Internal, ops.TypeSemanticDisabledLen) data := ops.Write(&o.Internal, ops.TypeSemanticEnabledLen)
data[0] = byte(ops.TypeSemanticDisabled) data[0] = byte(ops.TypeSemanticEnabled)
if d { if e {
data[1] = 1 data[1] = 1
} }
} }
+1 -1
View File
@@ -144,7 +144,7 @@ func (l *List) Dragging() bool {
} }
func (l *List) update(gtx Context) { func (l *List) update(gtx Context) {
d := l.scroll.Scroll(gtx.Metric, gtx, gtx.Now, gesture.Axis(l.Axis)) d := l.scroll.Update(gtx.Metric, gtx, gtx.Now, gesture.Axis(l.Axis))
l.scrollDelta = d l.scrollDelta = d
l.Position.Offset += d l.Position.Offset += d
} }
+9 -9
View File
@@ -93,18 +93,18 @@ func TestListPosition(t *testing.T) {
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(0, 0), Position: f32.Pt(0, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Type: pointer.Scroll, Kind: pointer.Scroll,
Scroll: f32.Pt(5, 0), Scroll: f32.Pt(5, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(5, 0), Position: f32.Pt(5, 0),
}, },
)}, )},
@@ -113,18 +113,18 @@ func TestListPosition(t *testing.T) {
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(0, 0), Position: f32.Pt(0, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Type: pointer.Scroll, Kind: pointer.Scroll,
Scroll: f32.Pt(3, 0), Scroll: f32.Pt(3, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(5, 0), Position: f32.Pt(5, 0),
}, },
)}, )},
@@ -133,18 +133,18 @@ func TestListPosition(t *testing.T) {
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(0, 0), Position: f32.Pt(0, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Type: pointer.Scroll, Kind: pointer.Scroll,
Scroll: f32.Pt(10, 0), Scroll: f32.Pt(10, 0),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(15, 0), Position: f32.Pt(15, 0),
}, },
)}, )},
+7 -1
View File
@@ -29,7 +29,7 @@ type Op struct {
type Stack struct { type Stack struct {
ops *ops.Ops ops *ops.Ops
id ops.StackID id ops.StackID
macroID int macroID uint32
} }
var pathSeed maphash.Seed var pathSeed maphash.Seed
@@ -204,6 +204,9 @@ func (p *Path) Line(delta f32.Point) {
// LineTo moves the pen to the absolute point specified, recording a line. // LineTo moves the pen to the absolute point specified, recording a line.
func (p *Path) LineTo(to f32.Point) { func (p *Path) LineTo(to f32.Point) {
if to == p.pen {
return
}
data := ops.WriteMulti(p.ops, scene.CommandSize+4) data := ops.WriteMulti(p.ops, scene.CommandSize+4)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
@@ -250,6 +253,9 @@ func (p *Path) Quad(ctrl, to f32.Point) {
// QuadTo records a quadratic Bézier from the pen to end // QuadTo records a quadratic Bézier from the pen to end
// with the control point ctrl, with absolute coordinates. // with the control point ctrl, with absolute coordinates.
func (p *Path) QuadTo(ctrl, to f32.Point) { func (p *Path) QuadTo(ctrl, to f32.Point) {
if ctrl == p.pen && to == p.pen {
return
}
data := ops.WriteMulti(p.ops, scene.CommandSize+4) data := ops.WriteMulti(p.ops, scene.CommandSize+4)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
+1 -1
View File
@@ -111,7 +111,7 @@ type TransformOp struct {
// TransformStack represents a TransformOp pushed on the transformation stack. // TransformStack represents a TransformOp pushed on the transformation stack.
type TransformStack struct { type TransformStack struct {
id ops.StackID id ops.StackID
macroID int macroID uint32
ops *ops.Ops ops *ops.Ops
} }
+36
View File
@@ -44,6 +44,14 @@ type LinearGradientOp struct {
type PaintOp struct { type PaintOp struct {
} }
// OpacityStack represents an opacity applied to all painting operations
// until Pop is called.
type OpacityStack struct {
id ops.StackID
macroID uint32
ops *ops.Ops
}
// NewImageOp creates an ImageOp backed by src. // NewImageOp creates an ImageOp backed by src.
// //
// NewImageOp assumes the backing image is immutable, and may cache a // NewImageOp assumes the backing image is immutable, and may cache a
@@ -145,3 +153,31 @@ func Fill(ops *op.Ops, c color.NRGBA) {
ColorOp{Color: c}.Add(ops) ColorOp{Color: c}.Add(ops)
PaintOp{}.Add(ops) PaintOp{}.Add(ops)
} }
// PushOpacity creates a drawing layer with an opacity in the range [0;1].
// The layer includes every subsequent drawing operation until [OpacityStack.Pop]
// is called.
//
// The layer is drawn in two steps. First, the layer operations are
// drawn to a separate image. Then, the image is blended on top of
// the frame, with the opacity used as the blending factor.
func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
if opacity > 1 {
opacity = 1
}
if opacity < 0 {
opacity = 0
}
id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
bo := binary.LittleEndian
data[0] = byte(ops.TypePushOpacity)
bo.PutUint32(data[1:], math.Float32bits(opacity))
return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
}
func (t OpacityStack) Pop() {
ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
data := ops.Write(t.ops, ops.TypePopOpacityLen)
data[0] = byte(ops.TypePopOpacity)
}
+1
View File
@@ -284,6 +284,7 @@ func (s *shaperImpl) addFace(f font.Face, md giofont.Font) {
if _, ok := s.faceToIndex[f.Font]; ok { if _, ok := s.faceToIndex[f.Font]; ok {
return return
} }
s.logger.Printf("loaded face %s(style:%s, weight:%d)", md.Typeface, md.Style, md.Weight)
idx := len(s.faces) idx := len(s.faces)
s.faceToIndex[f.Font] = idx s.faceToIndex[f.Font] = idx
s.faces = append(s.faces, f) s.faces = append(s.faces, f)
+19 -20
View File
@@ -3,9 +3,8 @@
package text package text
import ( import (
"encoding/binary"
"hash/maphash"
"image" "image"
"sync/atomic"
giofont "gioui.org/font" giofont "gioui.org/font"
"gioui.org/io/system" "gioui.org/io/system"
@@ -88,32 +87,32 @@ type glyphValue[V any] struct {
} }
type glyphLRU[V any] struct { type glyphLRU[V any] struct {
seed maphash.Seed seed uint64
cache lru[uint64, glyphValue[V]] cache lru[uint64, glyphValue[V]]
} }
var seed uint32
// hashGlyphs computes a hash key based on the ID and X offset of // hashGlyphs computes a hash key based on the ID and X offset of
// every glyph in the slice. // every glyph in the slice.
func (c *glyphLRU[V]) hashGlyphs(gs []Glyph) uint64 { func (c *glyphLRU[V]) hashGlyphs(gs []Glyph) uint64 {
if c.seed == (maphash.Seed{}) { if c.seed == 0 {
c.seed = maphash.MakeSeed() c.seed = uint64(atomic.AddUint32(&seed, 3900798947))
} }
var h maphash.Hash if len(gs) == 0 {
h.SetSeed(c.seed) return 0
var b [8]byte
firstX := fixed.Int26_6(0)
for i, g := range gs {
if i == 0 {
firstX = g.X
}
// Cache glyph X offsets relative to the first glyph.
binary.LittleEndian.PutUint32(b[:4], uint32(g.X-firstX))
h.Write(b[:4])
binary.LittleEndian.PutUint64(b[:], uint64(g.ID))
h.Write(b[:])
} }
sum := h.Sum64()
return sum h := c.seed
firstX := gs[0].X
for _, g := range gs {
h += uint64(g.X - firstX)
h *= 6585573582091643
h += uint64(g.ID)
h *= 3650802748644053
}
return h
} }
func (c *glyphLRU[V]) Get(key uint64, gs []Glyph) (V, bool) { func (c *glyphLRU[V]) Get(key uint64, gs []Glyph) (V, bool) {
+3 -3
View File
@@ -121,7 +121,7 @@ type Glyph struct {
// belongs to. If Flags does not contain FlagClusterBreak, this value will // belongs to. If Flags does not contain FlagClusterBreak, this value will
// always be zero. The final glyph in the cluster contains the runes count // always be zero. The final glyph in the cluster contains the runes count
// for the entire cluster. // for the entire cluster.
Runes int Runes uint16
// Flags encode special properties of this glyph. // Flags encode special properties of this glyph.
Flags Flags Flags Flags
} }
@@ -469,7 +469,7 @@ func (l *Shaper) NextGlyph() (_ Glyph, ok bool) {
Ascent: line.ascent, Ascent: line.ascent,
Descent: line.descent, Descent: line.descent,
Advance: g.xAdvance, Advance: g.xAdvance,
Runes: g.runeCount, Runes: uint16(g.runeCount),
Offset: fixed.Point26_6{ Offset: fixed.Point26_6{
X: g.xOffset, X: g.xOffset,
Y: g.yOffset, Y: g.yOffset,
@@ -505,7 +505,7 @@ func (l *Shaper) NextGlyph() (_ Glyph, ok bool) {
if endOfCluster { if endOfCluster {
glyph.Flags |= FlagClusterBreak glyph.Flags |= FlagClusterBreak
if run.truncator { if run.truncator {
glyph.Runes += l.txt.unreadRuneCount glyph.Runes += uint16(l.txt.unreadRuneCount)
} }
} else { } else {
glyph.Runes = 0 glyph.Runes = 0
+7 -7
View File
@@ -51,9 +51,9 @@ func TestWrappingTruncation(t *testing.T) {
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() { for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
glyphs = append(glyphs, g) glyphs = append(glyphs, g)
if g.Flags&FlagTruncator != 0 && g.Flags&FlagClusterBreak != 0 { if g.Flags&FlagTruncator != 0 && g.Flags&FlagClusterBreak != 0 {
truncatedRunes += g.Runes truncatedRunes += int(g.Runes)
} else { } else {
untruncatedRunes += g.Runes untruncatedRunes += int(g.Runes)
} }
if g.Flags&FlagLineBreak != 0 { if g.Flags&FlagLineBreak != 0 {
lineCount++ lineCount++
@@ -117,9 +117,9 @@ func TestWrappingForcedTruncation(t *testing.T) {
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() { for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
glyphs = append(glyphs, g) glyphs = append(glyphs, g)
if g.Flags&FlagTruncator != 0 && g.Flags&FlagClusterBreak != 0 { if g.Flags&FlagTruncator != 0 && g.Flags&FlagClusterBreak != 0 {
truncatedRunes += g.Runes truncatedRunes += int(g.Runes)
} else { } else {
untruncatedRunes += g.Runes untruncatedRunes += int(g.Runes)
} }
if g.Flags&FlagLineBreak != 0 { if g.Flags&FlagLineBreak != 0 {
lineCount++ lineCount++
@@ -191,9 +191,9 @@ func TestShapingNewlineHandling(t *testing.T) {
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() { for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
glyphs = append(glyphs, g) glyphs = append(glyphs, g)
if g.Flags&FlagTruncator == 0 { if g.Flags&FlagTruncator == 0 {
runes += g.Runes runes += int(g.Runes)
} else { } else {
truncated += g.Runes truncated += int(g.Runes)
} }
} }
if expected := len([]rune(tc.textInput)) - tc.expectedTruncated; expected != runes { if expected := len([]rune(tc.textInput)) - tc.expectedTruncated; expected != runes {
@@ -571,7 +571,7 @@ func TestShapeStringRuneAccounting(t *testing.T) {
} }
totalRunes := 0 totalRunes := 0
for _, g := range glyphs { for _, g := range glyphs {
totalRunes += g.Runes totalRunes += int(g.Runes)
} }
if inputRunes := len([]rune(tc.input)); totalRunes != inputRunes { if inputRunes := len([]rune(tc.input)); totalRunes != inputRunes {
t.Errorf("input contained %d runes, but glyphs contained %d", inputRunes, totalRunes) t.Errorf("input contained %d runes, but glyphs contained %d", inputRunes, totalRunes)
+9 -12
View File
@@ -11,15 +11,15 @@ type Bool struct {
Value bool Value bool
clk Clickable clk Clickable
changed bool
} }
// Changed reports whether Value has changed since the last // Update the widget state and report whether Value was changed.
// call to Changed. func (b *Bool) Update(gtx layout.Context) bool {
func (b *Bool) Changed() bool { changed := false
changed := b.changed for b.clk.Clicked(gtx) {
b.changed = false b.Value = !b.Value
changed = true
}
return changed return changed
} }
@@ -43,13 +43,10 @@ func (b *Bool) History() []Press {
} }
func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
b.Update(gtx)
dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions { dims := b.clk.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
for b.clk.Clicked() {
b.Value = !b.Value
b.changed = true
}
semantic.SelectedOp(b.Value).Add(gtx.Ops) semantic.SelectedOp(b.Value).Add(gtx.Ops)
semantic.DisabledOp(gtx.Queue == nil).Add(gtx.Ops) semantic.EnabledOp(gtx.Queue != nil).Add(gtx.Ops)
return w(gtx) return w(gtx)
}) })
return dims return dims
+57 -63
View File
@@ -17,18 +17,16 @@ import (
// Clickable represents a clickable area. // Clickable represents a clickable area.
type Clickable struct { type Clickable struct {
click gesture.Click click gesture.Click
clicks []Click // clicks is for saved clicks to support Clicked.
// prevClicks is the index into clicks that marks the clicks clicks []Click
// from the most recent Layout call. prevClicks is used to keep history []Press
// clicks bounded.
prevClicks int
history []Press
keyTag struct{} keyTag struct{}
requestFocus bool requestFocus bool
focused bool requestClicks int
pressedKey string focused bool
pressedKey string
} }
// Click represents a click. // Click represents a click.
@@ -50,26 +48,24 @@ type Press struct {
Cancelled bool Cancelled bool
} }
// Click executes a simple programmatic click // Click executes a simple programmatic click.
func (b *Clickable) Click() { func (b *Clickable) Click() {
b.clicks = append(b.clicks, Click{ b.requestClicks++
Modifiers: 0,
NumClicks: 1,
})
} }
// Clicked reports whether there are pending clicks as would be // Clicked reports whether there are pending clicks as would be
// reported by Clicks. If so, Clicked removes the earliest click. // reported by Clicks. If so, Clicked removes the earliest click.
func (b *Clickable) Clicked() bool { func (b *Clickable) Clicked(gtx layout.Context) bool {
if len(b.clicks) == 0 { if len(b.clicks) > 0 {
return false b.clicks = b.clicks[1:]
return true
} }
n := copy(b.clicks, b.clicks[1:]) b.clicks = b.Update(gtx)
b.clicks = b.clicks[:n] if len(b.clicks) > 0 {
if b.prevClicks > 0 { b.clicks = b.clicks[1:]
b.prevClicks-- return true
} }
return true return false
} }
// Hovered reports whether a pointer is over the element. // Hovered reports whether a pointer is over the element.
@@ -92,44 +88,44 @@ func (b *Clickable) Focused() bool {
return b.focused return b.focused
} }
// Clicks returns and clear the clicks since the last call to Clicks.
func (b *Clickable) Clicks() []Click {
clicks := b.clicks
b.clicks = nil
b.prevClicks = 0
return clicks
}
// History is the past pointer presses useful for drawing markers. // History is the past pointer presses useful for drawing markers.
// History is retained for a short duration (about a second). // History is retained for a short duration (about a second).
func (b *Clickable) History() []Press { func (b *Clickable) History() []Press {
return b.history return b.history
} }
// Layout and update the button state // Layout and update the button state.
func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
b.update(gtx) b.Update(gtx)
m := op.Record(gtx.Ops) m := op.Record(gtx.Ops)
dims := w(gtx) dims := w(gtx)
c := m.Stop() c := m.Stop()
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
disabled := gtx.Queue == nil enabled := gtx.Queue != nil
semantic.DisabledOp(disabled).Add(gtx.Ops) semantic.EnabledOp(enabled).Add(gtx.Ops)
b.click.Add(gtx.Ops) b.click.Add(gtx.Ops)
if !disabled { if enabled {
keys := key.Set("⏎|Space") keys := key.Set("⏎|Space")
if !b.focused { if !b.focused {
keys = "" keys = ""
} }
key.InputOp{Tag: &b.keyTag, Keys: keys}.Add(gtx.Ops) key.InputOp{Tag: &b.keyTag, Keys: keys}.Add(gtx.Ops)
if b.requestFocus {
key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops)
b.requestFocus = false
}
} else {
b.focused = false
} }
c.Add(gtx.Ops) c.Add(gtx.Ops)
return dims
}
// Update the button state by processing events, and return the resulting
// clicks, if any.
func (b *Clickable) Update(gtx layout.Context) []Click {
b.clicks = nil
if gtx.Queue == nil {
b.focused = false
}
if b.requestFocus {
key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops)
b.requestFocus = false
}
for len(b.history) > 0 { for len(b.history) > 0 {
c := b.history[0] c := b.history[0]
if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second { if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
@@ -138,34 +134,31 @@ func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimension
n := copy(b.history, b.history[1:]) n := copy(b.history, b.history[1:])
b.history = b.history[:n] b.history = b.history[:n]
} }
return dims var clicks []Click
} if c := b.requestClicks; c > 0 {
b.requestClicks = 0
// update the button state by processing events. clicks = append(clicks, Click{
func (b *Clickable) update(gtx layout.Context) { NumClicks: c,
// Flush clicks from before the last update. })
n := copy(b.clicks, b.clicks[b.prevClicks:]) }
b.clicks = b.clicks[:n] for _, e := range b.click.Update(gtx) {
b.prevClicks = n switch e.Kind {
case gesture.KindClick:
for _, e := range b.click.Events(gtx) {
switch e.Type {
case gesture.TypeClick:
b.clicks = append(b.clicks, Click{
Modifiers: e.Modifiers,
NumClicks: e.NumClicks,
})
if l := len(b.history); l > 0 { if l := len(b.history); l > 0 {
b.history[l-1].End = gtx.Now b.history[l-1].End = gtx.Now
} }
case gesture.TypeCancel: clicks = append(clicks, Click{
Modifiers: e.Modifiers,
NumClicks: e.NumClicks,
})
case gesture.KindCancel:
for i := range b.history { for i := range b.history {
b.history[i].Cancelled = true b.history[i].Cancelled = true
if b.history[i].End.IsZero() { if b.history[i].End.IsZero() {
b.history[i].End = gtx.Now b.history[i].End = gtx.Now
} }
} }
case gesture.TypePress: case gesture.KindPress:
if e.Source == pointer.Mouse { if e.Source == pointer.Mouse {
key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops) key.FocusOp{Tag: &b.keyTag}.Add(gtx.Ops)
} }
@@ -198,11 +191,12 @@ func (b *Clickable) update(gtx layout.Context) {
} }
// only register a key as a click if the key was pressed and released while this button was focused // only register a key as a click if the key was pressed and released while this button was focused
b.pressedKey = "" b.pressedKey = ""
b.clicks = append(b.clicks, Click{ clicks = append(clicks, Click{
Modifiers: e.Modifiers, Modifiers: e.Modifiers,
NumClicks: 1, NumClicks: 1,
}) })
} }
} }
} }
return clicks
} }
+6 -6
View File
@@ -48,6 +48,7 @@ func TestClickable(t *testing.T) {
t.Error("button 2 should not have focus") t.Error("button 2 should not have focus")
} }
// frame: press & release return // frame: press & release return
frame()
r.Queue( r.Queue(
key.Event{ key.Event{
Name: key.NameReturn, Name: key.NameReturn,
@@ -58,11 +59,10 @@ func TestClickable(t *testing.T) {
State: key.Release, State: key.Release,
}, },
) )
frame() if !b1.Clicked(gtx) {
if !b1.Clicked() {
t.Error("button 1 did not get clicked when it got return press & release") t.Error("button 1 did not get clicked when it got return press & release")
} }
if b2.Clicked() { if b2.Clicked(gtx) {
t.Error("button 2 got clicked when it did not have focus") t.Error("button 2 got clicked when it did not have focus")
} }
// frame: press return down // frame: press return down
@@ -73,7 +73,7 @@ func TestClickable(t *testing.T) {
}, },
) )
frame() frame()
if b1.Clicked() { if b1.Clicked(gtx) {
t.Error("button 1 got clicked, even if it only got return press") t.Error("button 1 got clicked, even if it only got return press")
} }
// frame: request focus for button 2 // frame: request focus for button 2
@@ -95,10 +95,10 @@ func TestClickable(t *testing.T) {
}, },
) )
frame() frame()
if b1.Clicked() { if b1.Clicked(gtx) {
t.Error("button 1 got clicked, even if it had lost focus") t.Error("button 1 got clicked, even if it had lost focus")
} }
if b2.Clicked() { if b2.Clicked(gtx) {
t.Error("button 2 should not have been clicked, as it only got return release") t.Error("button 2 should not have been clicked, as it only got return release")
} }
} }
+28 -22
View File
@@ -12,12 +12,11 @@ import (
// Decorations handles the states of window decorations. // Decorations handles the states of window decorations.
type Decorations struct { type Decorations struct {
clicks []Clickable clicks map[int]*Clickable
resize [8]struct { resize [8]struct {
gesture.Hover gesture.Hover
gesture.Drag gesture.Drag
} }
actions system.Action
maximized bool maximized bool
} }
@@ -35,22 +34,13 @@ func (d *Decorations) Clickable(action system.Action) *Clickable {
panic(fmt.Errorf("not a single action")) panic(fmt.Errorf("not a single action"))
} }
idx := bits.TrailingZeros(uint(action)) idx := bits.TrailingZeros(uint(action))
if n := idx - len(d.clicks); n >= 0 { click, found := d.clicks[idx]
d.clicks = append(d.clicks, make([]Clickable, n+1)...) if !found {
} click = new(Clickable)
click := &d.clicks[idx] if d.clicks == nil {
if click.Clicked() { d.clicks = make(map[int]*Clickable)
if action == system.ActionMaximize {
if d.maximized {
d.maximized = false
d.actions |= system.ActionUnmaximize
} else {
d.maximized = true
d.actions |= system.ActionMaximize
}
} else {
d.actions |= action
} }
d.clicks[idx] = click
} }
return click return click
} }
@@ -66,11 +56,27 @@ func (d *Decorations) Perform(actions system.Action) {
} }
} }
// Actions returns the set of actions activated by the user. // Update the state and return the set of actions activated by the user.
func (d *Decorations) Actions() system.Action { func (d *Decorations) Update(gtx layout.Context) system.Action {
a := d.actions var actions system.Action
d.actions = 0 for idx, clk := range d.clicks {
return a if !clk.Clicked(gtx) {
continue
}
action := system.Action(1 << idx)
switch {
case action == system.ActionMaximize && d.maximized:
action = system.ActionUnmaximize
case action == system.ActionUnmaximize && !d.maximized:
action = system.ActionMaximize
}
switch action {
case system.ActionMaximize, system.ActionUnmaximize:
d.maximized = !d.maximized
}
actions |= action
}
return actions
} }
// Maximized returns whether the window is maximized. // Maximized returns whether the window is maximized.
+26 -37
View File
@@ -17,41 +17,16 @@ type Draggable struct {
// Type contains the MIME type and matches transfer.SourceOp. // Type contains the MIME type and matches transfer.SourceOp.
Type string Type string
handle struct{} handle struct{}
drag gesture.Drag drag gesture.Drag
click f32.Point click f32.Point
pos f32.Point pos f32.Point
requested bool
request string
} }
func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions { func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions {
if gtx.Queue == nil { if gtx.Queue == nil {
return w(gtx) return w(gtx)
} }
pos := d.pos
for _, ev := range d.drag.Events(gtx.Metric, gtx.Queue, gesture.Both) {
switch ev.Type {
case pointer.Press:
d.click = ev.Position
pos = f32.Point{}
case pointer.Drag, pointer.Release:
pos = ev.Position.Sub(d.click)
}
}
d.pos = pos
for _, ev := range gtx.Queue.Events(&d.handle) {
switch e := ev.(type) {
case transfer.RequestEvent:
d.requested = true
d.request = e.Type
case transfer.CancelEvent:
d.requested = false
d.request = ""
}
}
dims := w(gtx) dims := w(gtx)
stack := clip.Rect{Max: dims.Size}.Push(gtx.Ops) stack := clip.Rect{Max: dims.Size}.Push(gtx.Ops)
@@ -64,7 +39,7 @@ func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dim
if drag != nil && d.drag.Pressed() { if drag != nil && d.drag.Pressed() {
rec := op.Record(gtx.Ops) rec := op.Record(gtx.Ops)
op.Offset(pos.Round()).Add(gtx.Ops) op.Offset(d.pos.Round()).Add(gtx.Ops)
drag(gtx) drag(gtx)
op.Defer(gtx.Ops, rec.Stop()) op.Defer(gtx.Ops, rec.Stop())
} }
@@ -77,13 +52,27 @@ func (d *Draggable) Dragging() bool {
return d.drag.Dragging() return d.drag.Dragging()
} }
// Requested returns the MIME type, if any, for which the Draggable was requested to offer data. // Update the draggable and returns the MIME type for which the Draggable was
func (d *Draggable) Requested() (mime string, requested bool) { // requested to offer data, if any
mime = d.request func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) {
requested = d.requested pos := d.pos
d.requested = false for _, ev := range d.drag.Update(gtx.Metric, gtx.Queue, gesture.Both) {
d.request = "" switch ev.Kind {
return case pointer.Press:
d.click = ev.Position
pos = f32.Point{}
case pointer.Drag, pointer.Release:
pos = ev.Position.Sub(d.click)
}
}
d.pos = pos
for _, ev := range gtx.Queue.Events(&d.handle) {
if e, ok := ev.(transfer.RequestEvent); ok {
return e.Type, true
}
}
return "", false
} }
// Offer the data ready for a drop. Must be called after being Requested. // Offer the data ready for a drop. Must be called after being Requested.
+3 -3
View File
@@ -39,15 +39,15 @@ func TestDraggable(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Position: f32.Pt(10, 10), Position: f32.Pt(10, 10),
Type: pointer.Press, Kind: pointer.Press,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(20, 10), Position: f32.Pt(20, 10),
Type: pointer.Move, Kind: pointer.Move,
}, },
pointer.Event{ pointer.Event{
Position: f32.Pt(20, 10), Position: f32.Pt(20, 10),
Type: pointer.Release, Kind: pointer.Release,
}, },
) )
ofr := &offer{data: "hello"} ofr := &offer{data: "hello"}
+19 -16
View File
@@ -225,7 +225,7 @@ func (e *Editor) processPointer(gtx layout.Context) {
axis = gesture.Vertical axis = gesture.Vertical
smin, smax = sbounds.Min.Y, sbounds.Max.Y smin, smax = sbounds.Min.Y, sbounds.Max.Y
} }
sdist := e.scroller.Scroll(gtx.Metric, gtx, gtx.Now, axis) sdist := e.scroller.Update(gtx.Metric, gtx, gtx.Now, axis)
var soff int var soff int
if e.SingleLine { if e.SingleLine {
e.text.ScrollRel(sdist, 0) e.text.ScrollRel(sdist, 0)
@@ -238,8 +238,8 @@ func (e *Editor) processPointer(gtx layout.Context) {
switch evt := evt.(type) { switch evt := evt.(type) {
case gesture.ClickEvent: case gesture.ClickEvent:
switch { switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Kind == gesture.KindPress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source != pointer.Mouse: evt.Kind == gesture.KindClick && evt.Source != pointer.Mouse:
prevCaretPos, _ := e.text.Selection() prevCaretPos, _ := e.text.Selection()
e.blinkStart = gtx.Now e.blinkStart = gtx.Now
e.text.MoveCoord(image.Point{ e.text.MoveCoord(image.Point{
@@ -278,10 +278,10 @@ func (e *Editor) processPointer(gtx layout.Context) {
case pointer.Event: case pointer.Event:
release := false release := false
switch { switch {
case evt.Type == pointer.Release && evt.Source == pointer.Mouse: case evt.Kind == pointer.Release && evt.Source == pointer.Mouse:
release = true release = true
fallthrough fallthrough
case evt.Type == pointer.Drag && evt.Source == pointer.Mouse: case evt.Kind == pointer.Drag && evt.Source == pointer.Mouse:
if e.dragging { if e.dragging {
e.blinkStart = gtx.Now e.blinkStart = gtx.Now
e.text.MoveCoord(image.Point{ e.text.MoveCoord(image.Point{
@@ -305,10 +305,10 @@ func (e *Editor) processPointer(gtx layout.Context) {
func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event { func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event {
var combinedEvents []event.Event var combinedEvents []event.Event
for _, evt := range e.clicker.Events(gtx) { for _, evt := range e.clicker.Update(gtx) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
for _, evt := range e.dragger.Events(gtx.Metric, gtx, gesture.Both) { for _, evt := range e.dragger.Update(gtx.Metric, gtx, gesture.Both) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
return combinedEvents return combinedEvents
@@ -517,15 +517,10 @@ func (e *Editor) initBuffer() {
e.text.WrapPolicy = e.WrapPolicy e.text.WrapPolicy = e.WrapPolicy
} }
// Layout lays out the editor using the provided textMaterial as the paint material // Update the state of the editor in response to input events.
// for the text glyphs+caret and the selectMaterial as the paint material for the func (e *Editor) Update(gtx layout.Context) {
// selection rectangle.
func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
e.initBuffer() e.initBuffer()
e.text.Update(gtx, lt, font, size, e.processEvents) e.processEvents(gtx)
dims := e.layout(gtx, textMaterial, selectMaterial)
if e.focused { if e.focused {
// Notify IME of selection if it changed. // Notify IME of selection if it changed.
newSel := e.ime.selection newSel := e.ime.selection
@@ -551,8 +546,16 @@ func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, siz
e.updateSnippet(gtx, e.ime.start, e.ime.end) e.updateSnippet(gtx, e.ime.start, e.ime.end)
} }
}
return dims // Layout lays out the editor using the provided textMaterial as the paint material
// for the text glyphs+caret and the selectMaterial as the paint material for the
// selection rectangle.
func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectMaterial op.CallOp) layout.Dimensions {
e.Update(gtx)
e.text.Layout(gtx, lt, font, size)
return e.layout(gtx, textMaterial, selectMaterial)
} }
// updateSnippet adds a key.SnippetOp if the snippet content or position // updateSnippet adds a key.SnippetOp if the snippet content or position
+11 -54
View File
@@ -139,7 +139,7 @@ func TestEditorReadOnly(t *testing.T) {
// Type some new characters. // Type some new characters.
gtx.Ops.Reset() gtx.Ops.Reset()
gtx.Queue = &testQueue{events: []event.Event{key.EditEvent{Range: key.Range{Start: cStart2, End: cEnd2}, Text: "something else"}}} gtx.Queue = &testQueue{events: []event.Event{key.EditEvent{Range: key.Range{Start: cStart2, End: cEnd2}, Text: "something else"}}}
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
textContent2 := e.Text() textContent2 := e.Text()
if textContent2 != textContent { if textContent2 != textContent {
t.Errorf("readonly editor modified by key.EditEvent") t.Errorf("readonly editor modified by key.EditEvent")
@@ -159,22 +159,22 @@ func TestEditorReadOnly(t *testing.T) {
gtx.Ops.Reset() gtx.Ops.Reset()
gtx.Queue = &testQueue{events: []event.Event{ gtx.Queue = &testQueue{events: []event.Event{
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Position: f32.Pt(float32(dims.Size.X)*.5, 5), Position: f32.Pt(float32(dims.Size.X)*.5, 5),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Drag, Kind: pointer.Drag,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Position: layout.FPt(dims.Size).Mul(.5), Position: layout.FPt(dims.Size).Mul(.5),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Position: layout.FPt(dims.Size).Mul(.5), Position: layout.FPt(dims.Size).Mul(.5),
}, },
}} }}
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
cStart3, cEnd3 := e.Selection() cStart3, cEnd3 := e.Selection()
if cStart3 == cStart2 || cEnd3 == cEnd2 { if cStart3 == cStart2 || cEnd3 == cEnd2 {
t.Errorf("expected mouse interaction to change selection.") t.Errorf("expected mouse interaction to change selection.")
@@ -285,44 +285,10 @@ func TestEditor(t *testing.T) {
e.MoveCaret(-3, -3) e.MoveCaret(-3, -3)
assertCaret(t, e, 1, 1, len("æbc\na")) assertCaret(t, e, 1, 1, len("æbc\na"))
e.text.Mask = '*' e.text.Mask = '*'
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
assertCaret(t, e, 1, 1, len("æbc\na")) assertCaret(t, e, 1, 1, len("æbc\na"))
e.MoveCaret(-3, -3) e.MoveCaret(-3, -3)
assertCaret(t, e, 0, 2, len("æb")) assertCaret(t, e, 0, 2, len("æb"))
/*
NOTE(whereswaldon): it isn't possible to check the raw glyph data
like this anymore. How should we handle this?
e.Mask = '\U0001F92B'
e.Layout(gtx, cache, font, fontSize, op.CallOp{},op.CallOp{})
e.moveEnd(selectionClear)
assertCaret(t, e, 0, 3, len("æbc"))
// When a password mask is applied, it should replace all visible glyphs
spaces := 0
for _, r := range textSample {
if unicode.IsSpace(r) {
spaces++
}
}
nonSpaces := len([]rune(textSample)) - spaces
glyphCounts := make(map[int]int)
// This loop assumes a single-run text, which we know is safe here.
for _, line := range e.lines {
for _, glyph := range line.Runs[0].Glyphs {
glyphCounts[int(glyph.ID)]++
}
}
if len(glyphCounts) > 2 {
t.Errorf("masked text contained glyphs other than mask and whitespace")
}
for gid, count := range glyphCounts {
if count != spaces && count != nonSpaces {
t.Errorf("glyph with id %d occurred %d times, expected either %d or %d", gid, count, spaces, nonSpaces)
}
}
*/
// Test that moveLine applies x offsets from previous moves. // Test that moveLine applies x offsets from previous moves.
e.SetText("long line\nshort") e.SetText("long line\nshort")
e.SetCaret(0, 0) e.SetCaret(0, 0)
@@ -680,11 +646,8 @@ func TestEditorMoveWord(t *testing.T) {
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Locale: english, Locale: english,
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10)
font := font.Font{}
e.SetText(t) e.SetText(t)
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
return e return e
} }
for ii, tt := range tests { for ii, tt := range tests {
@@ -785,11 +748,8 @@ func TestEditorInsert(t *testing.T) {
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Locale: english, Locale: english,
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10)
font := font.Font{}
e.SetText(t) e.SetText(t)
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
return e return e
} }
for ii, tt := range tests { for ii, tt := range tests {
@@ -875,11 +835,8 @@ func TestEditorDeleteWord(t *testing.T) {
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(100, 100)),
Locale: english, Locale: english,
} }
cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
fontSize := unit.Sp(10)
font := font.Font{}
e.SetText(t) e.SetText(t)
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) e.Update(gtx)
return e return e
} }
for ii, tt := range tests { for ii, tt := range tests {
@@ -947,13 +904,13 @@ g 2 4 6 8 g
events: []event.Event{ events: []event.Event{
pointer.Event{ pointer.Event{
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Press, Kind: pointer.Press,
Source: pointer.Mouse, Source: pointer.Mouse,
Time: tim, Time: tim,
Position: f32.Pt(textWidth(e, startPos.lineCol.line, 0, startPos.lineCol.col), textBaseline(e, startPos.lineCol.line)), Position: f32.Pt(textWidth(e, startPos.lineCol.line, 0, startPos.lineCol.col), textBaseline(e, startPos.lineCol.line)),
}, },
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Source: pointer.Mouse, Source: pointer.Mouse,
Time: tim, Time: tim,
Position: f32.Pt(textWidth(e, endPos.lineCol.line, 0, endPos.lineCol.col), textBaseline(e, endPos.lineCol.line)), Position: f32.Pt(textWidth(e, endPos.lineCol.line, 0, endPos.lineCol.col), textBaseline(e, endPos.lineCol.line)),
+53 -54
View File
@@ -20,8 +20,6 @@ type Enum struct {
focus string focus string
focused bool focused bool
changed bool
keys []*enumKey keys []*enumKey
} }
@@ -40,11 +38,55 @@ func (e *Enum) index(k string) *enumKey {
return nil return nil
} }
// Changed reports whether Value has changed by user interaction since the last // Update the state and report whether Value has changed by user interaction.
// call to Changed. func (e *Enum) Update(gtx layout.Context) bool {
func (e *Enum) Changed() bool { if gtx.Queue == nil {
changed := e.changed e.focused = false
e.changed = false }
e.hovering = false
changed := false
for _, state := range e.keys {
for _, ev := range state.click.Update(gtx) {
switch ev.Kind {
case gesture.KindPress:
if ev.Source == pointer.Mouse {
key.FocusOp{Tag: &state.tag}.Add(gtx.Ops)
}
case gesture.KindClick:
if state.key != e.Value {
e.Value = state.key
changed = true
}
}
}
for _, ev := range gtx.Events(&state.tag) {
switch ev := ev.(type) {
case key.FocusEvent:
if ev.Focus {
e.focused = true
e.focus = state.key
} else if state.key == e.focus {
e.focused = false
}
case key.Event:
if !e.focused || ev.State != key.Release {
break
}
if ev.Name != key.NameReturn && ev.Name != key.NameSpace {
break
}
if state.key != e.Value {
e.Value = state.key
changed = true
}
}
}
if state.click.Hovered() {
e.hovered = state.key
e.hovering = true
}
}
return changed return changed
} }
@@ -60,6 +102,7 @@ func (e *Enum) Focused() (string, bool) {
// Layout adds the event handler for the key k. // Layout adds the event handler for the key k.
func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layout.Dimensions { func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layout.Dimensions {
e.Update(gtx)
m := op.Record(gtx.Ops) m := op.Record(gtx.Ops)
dims := content(gtx) dims := content(gtx)
c := m.Stop() c := m.Stop()
@@ -73,57 +116,13 @@ func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layou
e.keys = append(e.keys, state) e.keys = append(e.keys, state)
} }
clk := &state.click clk := &state.click
for _, ev := range clk.Events(gtx) {
switch ev.Type {
case gesture.TypePress:
if ev.Source == pointer.Mouse {
key.FocusOp{Tag: &state.tag}.Add(gtx.Ops)
}
case gesture.TypeClick:
if state.key != e.Value {
e.Value = state.key
e.changed = true
}
}
}
for _, ev := range gtx.Events(&state.tag) {
switch ev := ev.(type) {
case key.FocusEvent:
if ev.Focus {
e.focused = true
e.focus = state.key
} else if state.key == e.focus {
e.focused = false
}
case key.Event:
if !e.focused || ev.State != key.Release {
break
}
if ev.Name != key.NameReturn && ev.Name != key.NameSpace {
break
}
if state.key != e.Value {
e.Value = state.key
e.changed = true
}
}
}
if clk.Hovered() {
e.hovered = k
e.hovering = true
} else if e.hovered == k {
e.hovering = false
}
clk.Add(gtx.Ops) clk.Add(gtx.Ops)
disabled := gtx.Queue == nil enabled := gtx.Queue != nil
if !disabled { if enabled {
key.InputOp{Tag: &state.tag, Keys: "⏎|Space"}.Add(gtx.Ops) key.InputOp{Tag: &state.tag, Keys: "⏎|Space"}.Add(gtx.Ops)
} else if e.focus == k {
e.focused = false
} }
semantic.SelectedOp(k == e.Value).Add(gtx.Ops) semantic.SelectedOp(k == e.Value).Add(gtx.Ops)
semantic.DisabledOp(disabled).Add(gtx.Ops) semantic.EnabledOp(enabled).Add(gtx.Ops)
c.Add(gtx.Ops) c.Add(gtx.Ops)
return dims return dims
+8 -10
View File
@@ -47,23 +47,21 @@ func ExampleClickable_passthrough() {
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Mouse, Source: pointer.Mouse,
Buttons: pointer.ButtonPrimary, Buttons: pointer.ButtonPrimary,
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )
// The second layout ensures that the click event is registered by the buttons.
widget()
if button1.Clicked() { if button1.Clicked(gtx) {
fmt.Println("button1 clicked!") fmt.Println("button1 clicked!")
} }
if button2.Clicked() { if button2.Clicked(gtx) {
fmt.Println("button2 clicked!") fmt.Println("button2 clicked!")
} }
@@ -95,7 +93,7 @@ func ExampleDraggable_Layout() {
drag.Layout(gtx, w, w) drag.Layout(gtx, w, w)
// drag must respond with an Offer event when requested. // drag must respond with an Offer event when requested.
// Use the drag method for this. // Use the drag method for this.
if m, ok := drag.Requested(); ok { if m, ok := drag.Update(gtx); ok {
drag.Offer(gtx.Ops, m, offer{Data: "hello world"}) drag.Offer(gtx.Ops, m, offer{Data: "hello world"})
} }
@@ -125,15 +123,15 @@ func ExampleDraggable_Layout() {
// Send drag and drop gesture events. // Send drag and drop gesture events.
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(5, 5), // in the drag area Position: f32.Pt(5, 5), // in the drag area
}, },
pointer.Event{ pointer.Event{
Type: pointer.Move, Kind: pointer.Move,
Position: f32.Pt(5, 5), // in the drop area Position: f32.Pt(5, 5), // in the drop area
}, },
pointer.Event{ pointer.Event{
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(30, 30), // in the drop area Position: f32.Pt(30, 30), // in the drop area
}, },
) )
+29 -63
View File
@@ -9,59 +9,29 @@ import (
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/unit"
) )
// Float is for selecting a value in a range. // Float is for selecting a value in a range.
type Float struct { type Float struct {
// Value is the value of the Float, in the [0; 1] range.
Value float32 Value float32
drag gesture.Drag drag gesture.Drag
pos float32 // position normalized to [0, 1] axis layout.Axis
length float32 length float32
changed bool
} }
// Dragging returns whether the value is being interacted with. // Dragging returns whether the value is being interacted with.
func (f *Float) Dragging() bool { return f.drag.Dragging() } func (f *Float) Dragging() bool { return f.drag.Dragging() }
// Layout updates the value according to drag events along the f's main axis. func (f *Float) Layout(gtx layout.Context, axis layout.Axis, pointerMargin unit.Dp) layout.Dimensions {
// f.Update(gtx)
// The range of f is set by the minimum constraints main axis value.
func (f *Float) Layout(gtx layout.Context, axis layout.Axis, min, max float32, invert bool, pointerMargin int) layout.Dimensions {
size := gtx.Constraints.Min size := gtx.Constraints.Min
f.length = float32(axis.Convert(size).X) f.length = float32(axis.Convert(size).X)
f.axis = axis
var de *pointer.Event margin := axis.Convert(image.Pt(gtx.Dp(pointerMargin), 0))
for _, e := range f.drag.Events(gtx.Metric, gtx, gesture.Axis(axis)) {
if e.Type == pointer.Press || e.Type == pointer.Drag {
de = &e
}
}
value := f.Value
if de != nil {
xy := de.Position.X
if axis == layout.Vertical {
xy = f.length - de.Position.Y
}
if invert {
xy = f.length - xy
}
f.pos = xy / f.length
value = min + (max-min)*f.pos
} else if min != max {
f.pos = (value - min) / (max - min)
}
// Unconditionally call setValue in case min, max, or value changed.
f.setValue(value, min, max)
if f.pos < 0 {
f.pos = 0
} else if f.pos > 1 {
f.pos = 1
}
margin := axis.Convert(image.Pt(pointerMargin, 0))
rect := image.Rectangle{ rect := image.Rectangle{
Min: margin.Mul(-1), Min: margin.Mul(-1),
Max: size.Add(margin), Max: size.Add(margin),
@@ -72,30 +42,26 @@ func (f *Float) Layout(gtx layout.Context, axis layout.Axis, min, max float32, i
return layout.Dimensions{Size: size} return layout.Dimensions{Size: size}
} }
func (f *Float) setValue(value, min, max float32) { // Update the Value according to drag events along the f's main axis.
if min > max { // The return value reports whether the value was changed.
min, max = max, min //
// The range of f is set by the minimum constraints main axis value.
func (f *Float) Update(gtx layout.Context) bool {
changed := false
for _, e := range f.drag.Update(gtx.Metric, gtx, gesture.Axis(f.axis)) {
if f.length > 0 && (e.Kind == pointer.Press || e.Kind == pointer.Drag) {
pos := e.Position.X
if f.axis == layout.Vertical {
pos = f.length - e.Position.Y
}
f.Value = pos / f.length
if f.Value < 0 {
f.Value = 0
} else if f.Value > 1 {
f.Value = 1
}
changed = true
}
} }
if value < min {
value = min
} else if value > max {
value = max
}
if f.Value != value {
f.Value = value
f.changed = true
}
}
// Pos reports the selected position.
func (f *Float) Pos() float32 {
return f.pos * f.length
}
// Changed reports whether the value has changed since
// the last call to Changed.
func (f *Float) Changed() bool {
changed := f.changed
f.changed = false
return changed return changed
} }
+5 -5
View File
@@ -134,17 +134,17 @@ type textIterator struct {
// processGlyph checks whether the glyph is visible within the iterator's configured // processGlyph checks whether the glyph is visible within the iterator's configured
// viewport and (if so) updates the iterator's text dimensions to include the glyph. // viewport and (if so) updates the iterator's text dimensions to include the glyph.
func (it *textIterator) processGlyph(g text.Glyph, ok bool) (_ text.Glyph, visibleOrBefore bool) { func (it *textIterator) processGlyph(g text.Glyph, ok bool) (visibleOrBefore bool) {
if it.maxLines > 0 { if it.maxLines > 0 {
if g.Flags&text.FlagTruncator != 0 && g.Flags&text.FlagClusterBreak != 0 { if g.Flags&text.FlagTruncator != 0 && g.Flags&text.FlagClusterBreak != 0 {
// A glyph carrying both of these flags provides the count of truncated runes. // A glyph carrying both of these flags provides the count of truncated runes.
it.truncated = g.Runes it.truncated = int(g.Runes)
} }
if g.Flags&text.FlagLineBreak != 0 { if g.Flags&text.FlagLineBreak != 0 {
it.linesSeen++ it.linesSeen++
} }
if it.linesSeen == it.maxLines && g.Flags&text.FlagParagraphBreak != 0 { if it.linesSeen == it.maxLines && g.Flags&text.FlagParagraphBreak != 0 {
return g, false return false
} }
} }
// Compute the maximum extent to which glyphs overhang on the horizontal // Compute the maximum extent to which glyphs overhang on the horizontal
@@ -191,7 +191,7 @@ func (it *textIterator) processGlyph(g text.Glyph, ok bool) (_ text.Glyph, visib
it.bounds.Max.X = max(it.bounds.Max.X, logicalBounds.Max.X) it.bounds.Max.X = max(it.bounds.Max.X, logicalBounds.Max.X)
it.bounds.Max.Y = max(it.bounds.Max.Y, logicalBounds.Max.Y) it.bounds.Max.Y = max(it.bounds.Max.Y, logicalBounds.Max.Y)
} }
return g, ok && !below return ok && !below
} }
func fixedToFloat(i fixed.Int26_6) float32 { func fixedToFloat(i fixed.Int26_6) float32 {
@@ -206,7 +206,7 @@ func fixedToFloat(i fixed.Int26_6) float32 {
// This design is awkward, but prevents the line slice from escaping // This design is awkward, but prevents the line slice from escaping
// to the heap. // to the heap.
func (it *textIterator) paintGlyph(gtx layout.Context, shaper *text.Shaper, glyph text.Glyph, line []text.Glyph) ([]text.Glyph, bool) { func (it *textIterator) paintGlyph(gtx layout.Context, shaper *text.Shaper, glyph text.Glyph, line []text.Glyph) ([]text.Glyph, bool) {
_, visibleOrBefore := it.processGlyph(glyph, true) visibleOrBefore := it.processGlyph(glyph, true)
if it.visible { if it.visible {
if len(line) == 0 { if len(line) == 0 {
it.lineOff = f32.Point{X: fixedToFloat(glyph.X), Y: float32(glyph.Y)}.Sub(layout.FPt(it.viewport.Min)) it.lineOff = f32.Point{X: fixedToFloat(glyph.X), Y: float32(glyph.Y)}.Sub(layout.FPt(it.viewport.Min))
+1 -4
View File
@@ -146,10 +146,7 @@ func TestGlyphIterator(t *testing.T) {
glyphs := getGlyphs(16, 0, maxWidth, text.Start, tc.str) glyphs := getGlyphs(16, 0, maxWidth, text.Start, tc.str)
it := textIterator{viewport: tc.viewport, maxLines: tc.maxLines} it := textIterator{viewport: tc.viewport, maxLines: tc.maxLines}
for i, g := range glyphs { for i, g := range glyphs {
gOut, ok := it.processGlyph(g, true) ok := it.processGlyph(g, true)
if gOut != g {
t.Errorf("textIterator modified glyphs[%d], original:\n%#+v, modified:\n%#+v", i, g, gOut)
}
if !ok && i != tc.stopAtGlyph { if !ok && i != tc.stopAtGlyph {
t.Errorf("expected iterator to stop at glyph %d, stopped at %d", tc.stopAtGlyph, i) t.Errorf("expected iterator to stop at glyph %d, stopped at %d", tc.stopAtGlyph, i)
} }
+5 -5
View File
@@ -61,8 +61,8 @@ func (s *Scrollbar) Layout(gtx layout.Context, axis layout.Axis, viewportStart,
} }
// Jump to a click in the track. // Jump to a click in the track.
for _, event := range s.track.Events(gtx) { for _, event := range s.track.Update(gtx) {
if event.Type != gesture.TypeClick || if event.Kind != gesture.KindClick ||
event.Modifiers != key.Modifiers(0) || event.Modifiers != key.Modifiers(0) ||
event.NumClicks > 1 { event.NumClicks > 1 {
continue continue
@@ -80,8 +80,8 @@ func (s *Scrollbar) Layout(gtx layout.Context, axis layout.Axis, viewportStart,
} }
// Offset to account for any drags. // Offset to account for any drags.
for _, event := range s.drag.Events(gtx.Metric, gtx, gesture.Axis(axis)) { for _, event := range s.drag.Update(gtx.Metric, gtx, gesture.Axis(axis)) {
switch event.Type { switch event.Kind {
case pointer.Drag: case pointer.Drag:
case pointer.Release, pointer.Cancel: case pointer.Release, pointer.Cancel:
s.dragging = false s.dragging = false
@@ -136,7 +136,7 @@ func (s *Scrollbar) Layout(gtx layout.Context, axis layout.Axis, viewportStart,
// Process events from the indicator so that hover is // Process events from the indicator so that hover is
// detected properly. // detected properly.
_ = s.indicator.Events(gtx) _ = s.indicator.Update(gtx)
return layout.Dimensions{} return layout.Dimensions{}
} }
+17 -20
View File
@@ -16,10 +16,8 @@ import (
) )
// Slider is for selecting a value in a range. // Slider is for selecting a value in a range.
func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle { func Slider(th *Theme, float *widget.Float) SliderStyle {
return SliderStyle{ return SliderStyle{
Min: min,
Max: max,
Color: th.Palette.ContrastBg, Color: th.Palette.ContrastBg,
Float: float, Float: float,
FingerSize: th.FingerSize, FingerSize: th.FingerSize,
@@ -27,35 +25,34 @@ func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
} }
type SliderStyle struct { type SliderStyle struct {
Axis layout.Axis Axis layout.Axis
Min, Max float32 Color color.NRGBA
Invert bool Float *widget.Float
Color color.NRGBA
Float *widget.Float
FingerSize unit.Dp FingerSize unit.Dp
} }
func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions { func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
thumbRadius := gtx.Dp(6) const thumbRadius unit.Dp = 6
tr := gtx.Dp(thumbRadius)
trackWidth := gtx.Dp(2) trackWidth := gtx.Dp(2)
axis := s.Axis axis := s.Axis
// Keep a minimum length so that the track is always visible. // Keep a minimum length so that the track is always visible.
minLength := thumbRadius + 3*thumbRadius + thumbRadius minLength := tr + 3*tr + tr
// Try to expand to finger size, but only if the constraints // Try to expand to finger size, but only if the constraints
// allow for it. // allow for it.
touchSizePx := min(gtx.Dp(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y) touchSizePx := min(gtx.Dp(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y)
sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength) sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength)
sizeCross := max(2*thumbRadius, touchSizePx) sizeCross := max(2*tr, touchSizePx)
size := axis.Convert(image.Pt(sizeMain, sizeCross)) size := axis.Convert(image.Pt(sizeMain, sizeCross))
o := axis.Convert(image.Pt(thumbRadius, 0)) o := axis.Convert(image.Pt(tr, 0))
trans := op.Offset(o).Push(gtx.Ops) trans := op.Offset(o).Push(gtx.Ops)
gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*thumbRadius, sizeCross)) gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*tr, sizeCross))
s.Float.Layout(gtx, axis, s.Min, s.Max, s.Invert, thumbRadius) dims := s.Float.Layout(gtx, axis, thumbRadius)
gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross))) gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
thumbPos := thumbRadius + int(s.Float.Pos()) thumbPos := tr + int(s.Float.Value*float32(axis.Convert(dims.Size).X))
trans.Pop() trans.Pop()
color := s.Color color := s.Color
@@ -65,7 +62,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
rect := func(minx, miny, maxx, maxy int) image.Rectangle { rect := func(minx, miny, maxx, maxy int) image.Rectangle {
r := image.Rect(minx, miny, maxx, maxy) r := image.Rect(minx, miny, maxx, maxy)
if s.Invert != (axis == layout.Vertical) { if axis == layout.Vertical {
r.Max.X, r.Min.X = sizeMain-r.Min.X, sizeMain-r.Max.X r.Max.X, r.Min.X = sizeMain-r.Min.X, sizeMain-r.Max.X
} }
r.Min = axis.Convert(r.Min) r.Min = axis.Convert(r.Min)
@@ -75,7 +72,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
// Draw track before thumb. // Draw track before thumb.
track := rect( track := rect(
thumbRadius, sizeCross/2-trackWidth/2, tr, sizeCross/2-trackWidth/2,
thumbPos, sizeCross/2+trackWidth/2, thumbPos, sizeCross/2+trackWidth/2,
) )
paint.FillShape(gtx.Ops, color, clip.Rect(track).Op()) paint.FillShape(gtx.Ops, color, clip.Rect(track).Op())
@@ -83,15 +80,15 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
// Draw track after thumb. // Draw track after thumb.
track = rect( track = rect(
thumbPos, axis.Convert(track.Min).Y, thumbPos, axis.Convert(track.Min).Y,
sizeMain-thumbRadius, axis.Convert(track.Max).Y, sizeMain-tr, axis.Convert(track.Max).Y,
) )
paint.FillShape(gtx.Ops, f32color.MulAlpha(color, 96), clip.Rect(track).Op()) paint.FillShape(gtx.Ops, f32color.MulAlpha(color, 96), clip.Rect(track).Op())
// Draw thumb. // Draw thumb.
pt := image.Pt(thumbPos, sizeCross/2) pt := image.Pt(thumbPos, sizeCross/2)
thumb := rect( thumb := rect(
pt.X-thumbRadius, pt.Y-thumbRadius, pt.X-tr, pt.Y-tr,
pt.X+thumbRadius, pt.Y+thumbRadius, pt.X+tr, pt.Y+tr,
) )
paint.FillShape(gtx.Ops, color, clip.Ellipse(thumb).Op(gtx.Ops)) paint.FillShape(gtx.Ops, color, clip.Ellipse(thumb).Op(gtx.Ops))
+16 -10
View File
@@ -182,25 +182,31 @@ func (l *Selectable) Truncated() bool {
return l.text.Truncated() return l.text.Truncated()
} }
// Update the state of the selectable in response to input events.
func (l *Selectable) Update(gtx layout.Context) {
l.initialize()
l.handleEvents(gtx)
}
// Layout clips to the dimensions of the selectable, updates the shaped text, configures input handling, and paints // Layout clips to the dimensions of the selectable, updates the shaped text, configures input handling, and paints
// the text and selection rectangles. The provided textMaterial and selectionMaterial ops are used to set the // the text and selection rectangles. The provided textMaterial and selectionMaterial ops are used to set the
// paint material for the text and selection rectangles, respectively. // paint material for the text and selection rectangles, respectively.
func (l *Selectable) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectionMaterial op.CallOp) layout.Dimensions { func (l *Selectable) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, textMaterial, selectionMaterial op.CallOp) layout.Dimensions {
l.initialize() l.Update(gtx)
l.text.LineHeight = l.LineHeight l.text.LineHeight = l.LineHeight
l.text.LineHeightScale = l.LineHeightScale l.text.LineHeightScale = l.LineHeightScale
l.text.Alignment = l.Alignment l.text.Alignment = l.Alignment
l.text.MaxLines = l.MaxLines l.text.MaxLines = l.MaxLines
l.text.Truncator = l.Truncator l.text.Truncator = l.Truncator
l.text.WrapPolicy = l.WrapPolicy l.text.WrapPolicy = l.WrapPolicy
l.text.Update(gtx, lt, font, size, l.handleEvents) l.text.Layout(gtx, lt, font, size)
dims := l.text.Dimensions() dims := l.text.Dimensions()
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
pointer.CursorText.Add(gtx.Ops) pointer.CursorText.Add(gtx.Ops)
var keys key.Set var keys key.Set
if l.focused { if l.focused {
const keyFilterAllArrows = "(ShortAlt)-(Shift)-[←,→,↑,↓]|(Shift)-[⏎,⌤]|(ShortAlt)-(Shift)-[⌫,⌦]|(Shift)-[⇞,⇟,⇱,⇲]|Short-[C,V,X,A]|Short-(Shift)-Z" const keyFilter = "(ShortAlt)-(Shift)-[←,→,↑,↓]|(Shift)-[⇞,⇟,⇱,⇲]|Short-[C,X,A]"
keys = keyFilterAllArrows keys = keyFilter
} }
key.InputOp{Tag: l, Keys: keys}.Add(gtx.Ops) key.InputOp{Tag: l, Keys: keys}.Add(gtx.Ops)
if l.requestFocus { if l.requestFocus {
@@ -236,8 +242,8 @@ func (e *Selectable) processPointer(gtx layout.Context) {
switch evt := evt.(type) { switch evt := evt.(type) {
case gesture.ClickEvent: case gesture.ClickEvent:
switch { switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Kind == gesture.KindPress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source != pointer.Mouse: evt.Kind == gesture.KindClick && evt.Source != pointer.Mouse:
prevCaretPos, _ := e.text.Selection() prevCaretPos, _ := e.text.Selection()
e.text.MoveCoord(image.Point{ e.text.MoveCoord(image.Point{
X: int(math.Round(float64(evt.Position.X))), X: int(math.Round(float64(evt.Position.X))),
@@ -271,10 +277,10 @@ func (e *Selectable) processPointer(gtx layout.Context) {
case pointer.Event: case pointer.Event:
release := false release := false
switch { switch {
case evt.Type == pointer.Release && evt.Source == pointer.Mouse: case evt.Kind == pointer.Release && evt.Source == pointer.Mouse:
release = true release = true
fallthrough fallthrough
case evt.Type == pointer.Drag && evt.Source == pointer.Mouse: case evt.Kind == pointer.Drag && evt.Source == pointer.Mouse:
if e.dragging { if e.dragging {
e.text.MoveCoord(image.Point{ e.text.MoveCoord(image.Point{
X: int(math.Round(float64(evt.Position.X))), X: int(math.Round(float64(evt.Position.X))),
@@ -292,10 +298,10 @@ func (e *Selectable) processPointer(gtx layout.Context) {
func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event { func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event {
var combinedEvents []event.Event var combinedEvents []event.Event
for _, evt := range e.clicker.Events(gtx) { for _, evt := range e.clicker.Update(gtx) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
for _, evt := range e.dragger.Events(gtx.Metric, gtx, gesture.Both) { for _, evt := range e.dragger.Update(gtx.Metric, gtx, gesture.Both) {
combinedEvents = append(combinedEvents, evt) combinedEvents = append(combinedEvents, evt)
} }
return combinedEvents return combinedEvents
+10 -11
View File
@@ -228,10 +228,8 @@ func (e *textView) calculateViewSize(gtx layout.Context) image.Point {
return gtx.Constraints.Constrain(base) return gtx.Constraints.Constrain(base)
} }
// Update the text, reshaping it as necessary. If not nil, eventHandling will be invoked after reshaping the text to // Layout the text, reshaping it as necessary.
// allow parent widgets to adapt to any changes in text content or positioning. If eventHandling modifies the contents func (e *textView) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp) {
// of the textView, it is guaranteed to be reshaped (and ready for painting) before Update returns.
func (e *textView) Update(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp, eventHandling func(gtx layout.Context)) {
if e.params.Locale != gtx.Locale { if e.params.Locale != gtx.Locale {
e.params.Locale = gtx.Locale e.params.Locale = gtx.Locale
e.invalidate() e.invalidate()
@@ -289,10 +287,6 @@ func (e *textView) Update(gtx layout.Context, lt *text.Shaper, font font.Font, s
} }
e.makeValid() e.makeValid()
if eventHandling != nil {
eventHandling(gtx)
e.makeValid()
}
if viewSize := e.calculateViewSize(gtx); viewSize != e.viewSize { if viewSize := e.calculateViewSize(gtx); viewSize != e.viewSize {
e.viewSize = viewSize e.viewSize = viewSize
@@ -492,14 +486,19 @@ func (e *textView) layoutText(lt *text.Shaper) {
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}} it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
if lt != nil { if lt != nil {
lt.Layout(e.params, r) lt.Layout(e.params, r)
for glyph, ok := it.processGlyph(lt.NextGlyph()); ok; glyph, ok = it.processGlyph(lt.NextGlyph()) { for {
e.index.Glyph(glyph) g, ok := lt.NextGlyph()
if !it.processGlyph(g, ok) {
break
}
e.index.Glyph(g)
} }
} else { } else {
// Make a fake glyph for every rune in the reader. // Make a fake glyph for every rune in the reader.
b := bufio.NewReader(r) b := bufio.NewReader(r)
for _, _, err := b.ReadRune(); err != io.EOF; _, _, err = b.ReadRune() { for _, _, err := b.ReadRune(); err != io.EOF; _, _, err = b.ReadRune() {
g, _ := it.processGlyph(text.Glyph{Runes: 1, Flags: text.FlagClusterBreak}, true) g := text.Glyph{Runes: 1, Flags: text.FlagClusterBreak}
_ = it.processGlyph(g, true)
e.index.Glyph(g) e.index.Glyph(g)
} }
} }
+6 -9
View File
@@ -7,7 +7,6 @@ import (
"os" "os"
"sort" "sort"
"testing" "testing"
"time"
colEmoji "eliasnaur.com/font/noto/emoji/color" colEmoji "eliasnaur.com/font/noto/emoji/color"
"gioui.org/font" "gioui.org/font"
@@ -48,10 +47,6 @@ var (
}() }()
) )
func init() {
rand.Seed(int64(time.Now().Nanosecond()))
}
func runBenchmarkPermutations(b *testing.B, benchmark func(b *testing.B, runes int, locale system.Locale, document string)) { func runBenchmarkPermutations(b *testing.B, benchmark func(b *testing.B, runes int, locale system.Locale, document string)) {
docKeys := maps.Keys(documents) docKeys := maps.Keys(documents)
sort.Strings(docKeys) sort.Strings(docKeys)
@@ -127,11 +122,12 @@ func BenchmarkLabelDynamic(b *testing.B) {
font := font.Font{} font := font.Font{}
runes := []rune(txt)[:runeCount] runes := []rune(txt)[:runeCount]
l := Label{} l := Label{}
r := rand.New(rand.NewSource(42))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
// simulate a constantly changing string // simulate a constantly changing string
a := rand.Intn(len(runes)) a := r.Intn(len(runes))
b := rand.Intn(len(runes)) b := r.Intn(len(runes))
runes[a], runes[b] = runes[b], runes[a] runes[a], runes[b] = runes[b], runes[a]
l.Layout(gtx, cache, font, fontSize, string(runes), op.CallOp{}) l.Layout(gtx, cache, font, fontSize, string(runes), op.CallOp{})
if render { if render {
@@ -196,11 +192,12 @@ func BenchmarkEditorDynamic(b *testing.B) {
runes := []rune(txt)[:runeCount] runes := []rune(txt)[:runeCount]
e := Editor{} e := Editor{}
e.SetText(string(runes)) e.SetText(string(runes))
r := rand.New(rand.NewSource(42))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
// simulate a constantly changing string // simulate a constantly changing string
a := rand.Intn(e.Len()) a := r.Intn(e.Len())
b := rand.Intn(e.Len()) b := r.Intn(e.Len())
e.SetCaret(a, a+1) e.SetCaret(a, a+1)
takeStr := e.SelectedText() takeStr := e.SelectedText()
e.Insert("") e.Insert("")
+2 -2
View File
@@ -35,12 +35,12 @@ func TestBool(t *testing.T) {
r.Queue( r.Queue(
pointer.Event{ pointer.Event{
Source: pointer.Touch, Source: pointer.Touch,
Type: pointer.Press, Kind: pointer.Press,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
pointer.Event{ pointer.Event{
Source: pointer.Touch, Source: pointer.Touch,
Type: pointer.Release, Kind: pointer.Release,
Position: f32.Pt(50, 50), Position: f32.Pt(50, 50),
}, },
) )