mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 00:16:15 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 718be79d9e | |||
| 0073e1a167 | |||
| 32ecec5538 | |||
| 6eb33b8a56 | |||
| 4617526e12 | |||
| dbc7a900bd | |||
| fb3ae95b28 | |||
| c458eb30f0 | |||
| d96c954769 | |||
| f39245df99 | |||
| 8097df9930 | |||
| fc6e51deba | |||
| 5fa94ff67b | |||
| 23b6f06e3e | |||
| c8801fe233 | |||
| 3fde0c0061 | |||
| 9d89f7c8b1 | |||
| 48bd5952b1 | |||
| df8a8789a3 | |||
| 62edabe137 | |||
| 49296bd0ca | |||
| d078bf0ed7 | |||
| ea58aacde2 | |||
| ae2b1f42b2 | |||
| 63fea3d2bd | |||
| ce8475a0b9 | |||
| 37717d0df9 | |||
| 7550d85447 | |||
| c756986d9e | |||
| dc170033cd | |||
| d42dae73f0 | |||
| 23e44292bb | |||
| fe85136f99 | |||
| b9837def5c | |||
| dc97871122 | |||
| 4a4fe5a69b | |||
| 1686874d07 | |||
| 650ccea28d | |||
| e1b3928819 |
+16
-6
@@ -8,16 +8,21 @@ packages:
|
|||||||
- libxml2-dev
|
- libxml2-dev
|
||||||
- libssl-dev
|
- libssl-dev
|
||||||
- libz-dev
|
- libz-dev
|
||||||
- llvm-dev # for cctools
|
- llvm-dev # cctools
|
||||||
- uuid-dev ## for cctools
|
- uuid-dev # cctools
|
||||||
|
- ninja-build # cctools
|
||||||
|
- systemtap-sdt-dev # cctools
|
||||||
|
- libbsd-dev # cctools
|
||||||
|
- linux-libc-dev # cctools
|
||||||
- libplist-utils # for gogio
|
- libplist-utils # for gogio
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~eliasnaur/applesdks
|
- https://git.sr.ht/~eliasnaur/applesdks
|
||||||
- https://git.sr.ht/~eliasnaur/gio
|
- https://git.sr.ht/~eliasnaur/gio
|
||||||
- https://git.sr.ht/~eliasnaur/giouiorg
|
- https://git.sr.ht/~eliasnaur/giouiorg
|
||||||
- https://github.com/tpoechtrager/cctools-port.git
|
- https://github.com/tpoechtrager/cctools-port
|
||||||
- https://github.com/tpoechtrager/apple-libtapi.git
|
- https://github.com/tpoechtrager/apple-libtapi
|
||||||
- https://github.com/mackyle/xar.git
|
- https://github.com/tpoechtrager/apple-libdispatch
|
||||||
|
- https://github.com/mackyle/xar
|
||||||
environment:
|
environment:
|
||||||
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
||||||
PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
|
PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
|
||||||
@@ -42,6 +47,11 @@ tasks:
|
|||||||
- install_appletoolchain: |
|
- install_appletoolchain: |
|
||||||
cd giouiorg
|
cd giouiorg
|
||||||
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
||||||
|
- build_libdispatch: |
|
||||||
|
cd apple-libdispatch
|
||||||
|
cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$APPLE_TOOLCHAIN_ROOT/libdispatch .
|
||||||
|
ninja
|
||||||
|
ninja install
|
||||||
- build_xar: |
|
- build_xar: |
|
||||||
cd xar/xar
|
cd xar/xar
|
||||||
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
||||||
@@ -53,7 +63,7 @@ tasks:
|
|||||||
./install.sh
|
./install.sh
|
||||||
- build_cctools: |
|
- build_cctools: |
|
||||||
cd cctools-port/cctools
|
cd cctools-port/cctools
|
||||||
./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --target=x86_64-apple-darwin19
|
./configure --target=x86_64-apple-darwin19 --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --with-libblocksruntime=$APPLE_TOOLCHAIN_ROOT/libdispatch
|
||||||
make install
|
make install
|
||||||
- test_macos: |
|
- test_macos: |
|
||||||
cd gio
|
cd gio
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *d3d11Context) Present() error {
|
func (c *d3d11Context) Present() error {
|
||||||
err := c.swchain.Present(1, 0)
|
return wrapErr(c.swchain.Present(1, 0))
|
||||||
if err == nil {
|
}
|
||||||
return nil
|
|
||||||
}
|
func wrapErr(err error) error {
|
||||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||||
@@ -84,7 +84,7 @@ func (c *d3d11Context) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.releaseFBO()
|
c.releaseFBO()
|
||||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||||
return err
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
c.width = width
|
c.width = width
|
||||||
c.height = height
|
c.height = height
|
||||||
|
|||||||
+6
-5
@@ -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()
|
||||||
|
|||||||
+7
-7
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
+10
-4
@@ -192,6 +192,10 @@ static CFTypeRef windowForView(CFTypeRef viewRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void raiseWindow(CFTypeRef windowRef) {
|
static void raiseWindow(CFTypeRef windowRef) {
|
||||||
|
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||||
|
if (![currentApp isActive]) {
|
||||||
|
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||||
|
}
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
[window makeKeyAndOrderFront:nil];
|
[window makeKeyAndOrderFront:nil];
|
||||||
}
|
}
|
||||||
@@ -297,7 +301,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})
|
||||||
}
|
}
|
||||||
@@ -526,7 +532,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 +556,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 +890,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
@@ -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
|
||||||
|
|||||||
+9
-8
@@ -259,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
|
||||||
@@ -288,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,
|
||||||
@@ -501,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()
|
||||||
@@ -518,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,
|
||||||
@@ -553,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,
|
||||||
@@ -721,11 +721,12 @@ func (w *window) Configure(options []Option) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Fullscreen:
|
case Fullscreen:
|
||||||
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
x, y = mi.Monitor.Left, mi.Monitor.Top
|
x, y = mi.Monitor.Left, mi.Monitor.Top
|
||||||
width = mi.Monitor.Right - mi.Monitor.Left
|
width = mi.Monitor.Right - mi.Monitor.Left
|
||||||
height = mi.Monitor.Bottom - mi.Monitor.Top
|
height = mi.Monitor.Bottom - mi.Monitor.Top
|
||||||
showMode = windows.SW_SHOW
|
showMode = windows.SW_SHOWMAXIMIZED
|
||||||
}
|
}
|
||||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
||||||
|
|||||||
+7
-7
@@ -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{
|
||||||
|
|||||||
+56
-31
@@ -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
|
||||||
@@ -899,6 +904,7 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
w.decorations.Config = e2.Config
|
w.decorations.Config = e2.Config
|
||||||
e2.Config = w.effectiveConfig()
|
e2.Config = w.effectiveConfig()
|
||||||
w.out <- e2
|
w.out <- e2
|
||||||
|
case wakeupEvent:
|
||||||
case event.Event:
|
case event.Event:
|
||||||
handled := w.queue.q.Queue(e2)
|
handled := w.queue.q.Queue(e2)
|
||||||
if e, ok := e.(key.Event); ok && !handled {
|
if e, ok := e.(key.Event); ok && !handled {
|
||||||
@@ -938,43 +944,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:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1022,9 +1036,9 @@ func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offse
|
|||||||
Metric: e.Metric,
|
Metric: e.Metric,
|
||||||
Constraints: layout.Exact(e.Size),
|
Constraints: layout.Exact(e.Size),
|
||||||
}
|
}
|
||||||
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))
|
||||||
|
style.Layout(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 +1179,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
|
||||||
|
|||||||
+38
-39
@@ -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,16 +443,16 @@ 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 "KindPress"
|
||||||
case TypeClick:
|
case KindClick:
|
||||||
return "TypeClick"
|
return "KindClick"
|
||||||
case TypeCancel:
|
case KindCancel:
|
||||||
return "TypeCancel"
|
return "KindCancel"
|
||||||
default:
|
default:
|
||||||
panic("invalid ClickType")
|
panic("invalid ClickKind")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-3
@@ -186,10 +186,16 @@ type material struct {
|
|||||||
uvTrans f32.Affine2D
|
uvTrans f32.Affine2D
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
filterLinear = 0
|
||||||
|
filterNearest = 1
|
||||||
|
)
|
||||||
|
|
||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
type imageOpData struct {
|
type imageOpData struct {
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
handle interface{}
|
handle interface{}
|
||||||
|
filter byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type linearGradientOpData struct {
|
type linearGradientOpData struct {
|
||||||
@@ -207,6 +213,7 @@ func decodeImageOp(data []byte, refs []interface{}) imageOpData {
|
|||||||
return imageOpData{
|
return imageOpData{
|
||||||
src: refs[0].(*image.RGBA),
|
src: refs[0].(*image.RGBA),
|
||||||
handle: handle,
|
handle: handle,
|
||||||
|
filter: data[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,19 +461,41 @@ func (g *gpu) Profile() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
func (r *renderer) texHandle(cache *resourceCache, data imageOpData) driver.Texture {
|
||||||
|
type cachekey struct {
|
||||||
|
filter byte
|
||||||
|
handle any
|
||||||
|
}
|
||||||
|
key := cachekey{
|
||||||
|
filter: data.filter,
|
||||||
|
handle: data.handle,
|
||||||
|
}
|
||||||
|
|
||||||
var tex *texture
|
var tex *texture
|
||||||
t, exists := cache.get(data.handle)
|
t, exists := cache.get(key)
|
||||||
if !exists {
|
if !exists {
|
||||||
t = &texture{
|
t = &texture{
|
||||||
src: data.src,
|
src: data.src,
|
||||||
}
|
}
|
||||||
cache.put(data.handle, t)
|
cache.put(key, t)
|
||||||
}
|
}
|
||||||
tex = t.(*texture)
|
tex = t.(*texture)
|
||||||
if tex.tex != nil {
|
if tex.tex != nil {
|
||||||
return tex.tex
|
return tex.tex
|
||||||
}
|
}
|
||||||
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, data.src.Bounds().Dx(), data.src.Bounds().Dy(), driver.FilterLinearMipmapLinear, driver.FilterLinear, driver.BufferBindingTexture)
|
|
||||||
|
var minFilter, magFilter driver.TextureFilter
|
||||||
|
switch data.filter {
|
||||||
|
case filterLinear:
|
||||||
|
minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
|
||||||
|
case filterNearest:
|
||||||
|
minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
|
||||||
|
}
|
||||||
|
|
||||||
|
handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
|
||||||
|
data.src.Bounds().Dx(), data.src.Bounds().Dy(),
|
||||||
|
minFilter, magFilter,
|
||||||
|
driver.BufferBindingTexture,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 379 B |
@@ -359,6 +359,73 @@ func TestImageRGBA(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageRGBA_ScaleLinear(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
w := newWindow(t, 128, 128)
|
||||||
|
defer clip.Rect{Max: image.Pt(128, 128)}.Push(o).Pop()
|
||||||
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
|
im.Set(0, 0, colornames.Red)
|
||||||
|
im.Set(1, 0, colornames.Green)
|
||||||
|
im.Set(0, 1, colornames.White)
|
||||||
|
im.Set(1, 1, colornames.Black)
|
||||||
|
|
||||||
|
op := paint.NewImageOp(im)
|
||||||
|
op.Filter = paint.FilterLinear
|
||||||
|
op.Add(o)
|
||||||
|
|
||||||
|
paint.PaintOp{}.Add(o)
|
||||||
|
|
||||||
|
if err := w.Frame(o); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.Red)
|
||||||
|
r.expect(8, 8, colornames.Red)
|
||||||
|
|
||||||
|
// TODO: this currently seems to do srgb scaling
|
||||||
|
// instead of linear rgb scaling,
|
||||||
|
r.expect(64-4, 0, color.RGBA{R: 197, G: 87, B: 0, A: 255})
|
||||||
|
r.expect(64+4, 0, color.RGBA{R: 175, G: 98, B: 0, A: 255})
|
||||||
|
|
||||||
|
r.expect(127, 0, colornames.Green)
|
||||||
|
r.expect(127-8, 8, colornames.Green)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageRGBA_ScaleNearest(t *testing.T) {
|
||||||
|
run(t, func(o *op.Ops) {
|
||||||
|
w := newWindow(t, 128, 128)
|
||||||
|
op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(64, 64))).Add(o)
|
||||||
|
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||||
|
im.Set(0, 0, colornames.Red)
|
||||||
|
im.Set(1, 0, colornames.Green)
|
||||||
|
im.Set(0, 1, colornames.White)
|
||||||
|
im.Set(1, 1, colornames.Black)
|
||||||
|
|
||||||
|
op := paint.NewImageOp(im)
|
||||||
|
op.Filter = paint.FilterNearest
|
||||||
|
op.Add(o)
|
||||||
|
|
||||||
|
paint.PaintOp{}.Add(o)
|
||||||
|
|
||||||
|
if err := w.Frame(o); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, func(r result) {
|
||||||
|
r.expect(0, 0, colornames.Red)
|
||||||
|
r.expect(8, 8, colornames.Red)
|
||||||
|
|
||||||
|
r.expect(64-4, 0, colornames.Red)
|
||||||
|
r.expect(64+4, 0, colornames.Green)
|
||||||
|
|
||||||
|
r.expect(127, 0, colornames.Green)
|
||||||
|
r.expect(127-8, 8, colornames.Green)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGapsInPath(t *testing.T) {
|
func TestGapsInPath(t *testing.T) {
|
||||||
ops := new(op.Ops)
|
ops := new(op.Ops)
|
||||||
var p clip.Path
|
var p clip.Path
|
||||||
|
|||||||
+21
-21
@@ -14,7 +14,7 @@ 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.
|
||||||
@@ -32,7 +32,7 @@ type Ops struct {
|
|||||||
stringRefs []string
|
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
|
||||||
|
|
||||||
@@ -84,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
|
||||||
@@ -142,7 +142,7 @@ const (
|
|||||||
TypePushOpacityLen = 1 + 4
|
TypePushOpacityLen = 1 + 4
|
||||||
TypePopOpacityLen = 1
|
TypePopOpacityLen = 1
|
||||||
TypeRedrawLen = 1 + 8
|
TypeRedrawLen = 1 + 8
|
||||||
TypeImageLen = 1
|
TypeImageLen = 1 + 1
|
||||||
TypePaintLen = 1
|
TypePaintLen = 1
|
||||||
TypeColorLen = 1 + 4
|
TypeColorLen = 1 + 4
|
||||||
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
TypeLinearGradientLen = 1 + 8*2 + 4*2
|
||||||
@@ -170,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
|
||||||
@@ -266,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")
|
||||||
}
|
}
|
||||||
@@ -310,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 {
|
||||||
@@ -454,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 {
|
||||||
|
|||||||
+13
-13
@@ -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:])
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ object such as a finger.
|
|||||||
The InputOp operation is used to declare a handler ready for pointer
|
The InputOp operation is used to declare a handler ready for pointer
|
||||||
events. Use an event.Queue to receive events.
|
events. Use an event.Queue to receive events.
|
||||||
|
|
||||||
# Types
|
# Kinds
|
||||||
|
|
||||||
Only events that match a specified list of types are delivered to a handler.
|
Only events that match a specified list of types are delivered to a handler.
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ Leave, or Scroll):
|
|||||||
|
|
||||||
pointer.InputOp{
|
pointer.InputOp{
|
||||||
Tag: h,
|
Tag: h,
|
||||||
Types: pointer.Press | pointer.Drag | pointer.Release,
|
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
||||||
}.Add(ops)
|
}.Add(ops)
|
||||||
|
|
||||||
Cancel events are always delivered.
|
Cancel events are always delivered.
|
||||||
|
|||||||
+16
-14
@@ -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
|
||||||
@@ -32,8 +32,10 @@ type Event struct {
|
|||||||
Time time.Duration
|
Time time.Duration
|
||||||
// Buttons are the set of pressed mouse buttons for this event.
|
// Buttons are the set of pressed mouse buttons for this event.
|
||||||
Buttons Buttons
|
Buttons Buttons
|
||||||
// Position is the position of the event, relative to
|
// Position is the coordinates of the event in the local coordinate
|
||||||
// the current transformation, as set by op.TransformOp.
|
// system of the receiving tag. The transformation from global window
|
||||||
|
// coordinates to local coordinates is performed by the inverse of
|
||||||
|
// the effective transformation of the tag.
|
||||||
Position f32.Point
|
Position f32.Point
|
||||||
// Scroll is the scroll amount, if any.
|
// Scroll is the scroll amount, if any.
|
||||||
Scroll f32.Point
|
Scroll f32.Point
|
||||||
@@ -51,7 +53,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 +63,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 +75,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 +171,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 +245,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 +254,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 +277,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"
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-17
@@ -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:]))),
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ 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.Write1String(&o.Internal, ops.TypeSemanticLabelLen, string(l))
|
data := ops.Write1String(&o.Internal, ops.TypeSemanticLabelLen, string(l))
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,30 @@ func ExampleStack() {
|
|||||||
// Expand: {(50,50) (100,100)}
|
// Expand: {(50,50) (100,100)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleBackground() {
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: new(op.Ops),
|
||||||
|
Constraints: layout.Constraints{
|
||||||
|
Max: image.Point{X: 100, Y: 100},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.Background{}.Layout(gtx,
|
||||||
|
// Force widget to the same size as the second.
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
fmt.Printf("Expand: %v\n", gtx.Constraints)
|
||||||
|
return layoutWidget(gtx, 10, 10)
|
||||||
|
},
|
||||||
|
// Rigid 50x50 widget.
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layoutWidget(gtx, 50, 50)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Expand: {(50,50) (100,100)}
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleList() {
|
func ExampleList() {
|
||||||
gtx := layout.Context{
|
gtx := layout.Context{
|
||||||
Ops: new(op.Ops),
|
Ops: new(op.Ops),
|
||||||
|
|||||||
+1
-1
@@ -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
@@ -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),
|
||||||
},
|
},
|
||||||
)},
|
)},
|
||||||
|
|||||||
@@ -118,3 +118,36 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
|||||||
Baseline: baseline,
|
Baseline: baseline,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Background lays out single child widget on top of a background,
|
||||||
|
// centering, if necessary.
|
||||||
|
type Background struct{}
|
||||||
|
|
||||||
|
// Layout a widget and then add a background to it.
|
||||||
|
func (Background) Layout(gtx Context, background, widget Widget) Dimensions {
|
||||||
|
macro := op.Record(gtx.Ops)
|
||||||
|
wdims := widget(gtx)
|
||||||
|
baseline := wdims.Baseline
|
||||||
|
call := macro.Stop()
|
||||||
|
|
||||||
|
cgtx := gtx
|
||||||
|
cgtx.Constraints.Min = gtx.Constraints.Constrain(wdims.Size)
|
||||||
|
bdims := background(cgtx)
|
||||||
|
|
||||||
|
if bdims.Size != wdims.Size {
|
||||||
|
p := image.Point{
|
||||||
|
X: (bdims.Size.X - wdims.Size.X) / 2,
|
||||||
|
Y: (bdims.Size.Y - wdims.Size.Y) / 2,
|
||||||
|
}
|
||||||
|
baseline += (bdims.Size.Y - wdims.Size.Y) / 2
|
||||||
|
trans := op.Offset(p).Push(gtx.Ops)
|
||||||
|
defer trans.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
call.Add(gtx.Ops)
|
||||||
|
|
||||||
|
return Dimensions{
|
||||||
|
Size: bdims.Size,
|
||||||
|
Baseline: baseline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package layout
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gioui.org/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkStack(b *testing.B) {
|
||||||
|
gtx := Context{
|
||||||
|
Ops: new(op.Ops),
|
||||||
|
Constraints: Constraints{
|
||||||
|
Max: image.Point{X: 100, Y: 100},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
gtx.Ops.Reset()
|
||||||
|
|
||||||
|
Stack{}.Layout(gtx,
|
||||||
|
Expanded(emptyWidget{
|
||||||
|
Size: image.Point{X: 60, Y: 60},
|
||||||
|
}.Layout),
|
||||||
|
Stacked(emptyWidget{
|
||||||
|
Size: image.Point{X: 30, Y: 30},
|
||||||
|
}.Layout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBackground(b *testing.B) {
|
||||||
|
gtx := Context{
|
||||||
|
Ops: new(op.Ops),
|
||||||
|
Constraints: Constraints{
|
||||||
|
Max: image.Point{X: 100, Y: 100},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
gtx.Ops.Reset()
|
||||||
|
|
||||||
|
Background{}.Layout(gtx,
|
||||||
|
emptyWidget{
|
||||||
|
Size: image.Point{X: 60, Y: 60},
|
||||||
|
}.Layout,
|
||||||
|
emptyWidget{
|
||||||
|
Size: image.Point{X: 30, Y: 30},
|
||||||
|
}.Layout,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyWidget struct {
|
||||||
|
Size image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w emptyWidget) Layout(gtx Context) Dimensions {
|
||||||
|
return Dimensions{Size: w.Size}
|
||||||
|
}
|
||||||
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -15,8 +15,20 @@ import (
|
|||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ImageFilter is the scaling filter for images.
|
||||||
|
type ImageFilter byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FilterLinear uses linear interpolation for scaling.
|
||||||
|
FilterLinear ImageFilter = iota
|
||||||
|
// FilterNearest uses nearest neighbor interpolation for scaling.
|
||||||
|
FilterNearest
|
||||||
|
)
|
||||||
|
|
||||||
// ImageOp sets the brush to an image.
|
// ImageOp sets the brush to an image.
|
||||||
type ImageOp struct {
|
type ImageOp struct {
|
||||||
|
Filter ImageFilter
|
||||||
|
|
||||||
uniform bool
|
uniform bool
|
||||||
color color.NRGBA
|
color color.NRGBA
|
||||||
src *image.RGBA
|
src *image.RGBA
|
||||||
@@ -48,7 +60,7 @@ type PaintOp struct {
|
|||||||
// until Pop is called.
|
// until Pop is called.
|
||||||
type OpacityStack struct {
|
type OpacityStack struct {
|
||||||
id ops.StackID
|
id ops.StackID
|
||||||
macroID int
|
macroID uint32
|
||||||
ops *ops.Ops
|
ops *ops.Ops
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +115,7 @@ func (i ImageOp) Add(o *op.Ops) {
|
|||||||
}
|
}
|
||||||
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
data := ops.Write2(&o.Internal, ops.TypeImageLen, i.src, i.handle)
|
||||||
data[0] = byte(ops.TypeImage)
|
data[0] = byte(ops.TypeImage)
|
||||||
|
data[1] = byte(i.Filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ColorOp) Add(o *op.Ops) {
|
func (c ColorOp) Add(o *op.Ops) {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+59
-65
@@ -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. If so, Clicked
|
||||||
// reported by Clicks. If so, Clicked removes the earliest click.
|
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||||
|
|||||||
@@ -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
@@ -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{}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-18
@@ -93,9 +93,8 @@ func IconButton(th *Theme, button *widget.Clickable, icon *widget.Icon, descript
|
|||||||
func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
|
func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
|
||||||
return button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.Button.Add(gtx.Ops)
|
semantic.Button.Add(gtx.Ops)
|
||||||
constraints := gtx.Constraints
|
return layout.Background{}.Layout(gtx,
|
||||||
return layout.Stack{}.Layout(gtx,
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
||||||
if button.Hovered() || button.Focused() {
|
if button.Hovered() || button.Focused() {
|
||||||
paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{}))
|
paint.Fill(gtx.Ops, f32color.Hovered(color.NRGBA{}))
|
||||||
@@ -104,11 +103,8 @@ func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) la
|
|||||||
drawInk(gtx, c)
|
drawInk(gtx, c)
|
||||||
}
|
}
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}),
|
},
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
w,
|
||||||
gtx.Constraints = constraints
|
|
||||||
return w(gtx)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -131,8 +127,8 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
|
|||||||
min := gtx.Constraints.Min
|
min := gtx.Constraints.Min
|
||||||
return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.Button.Add(gtx.Ops)
|
semantic.Button.Add(gtx.Ops)
|
||||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
return layout.Background{}.Layout(gtx,
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
rr := gtx.Dp(b.CornerRadius)
|
rr := gtx.Dp(b.CornerRadius)
|
||||||
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
||||||
background := b.Background
|
background := b.Background
|
||||||
@@ -147,11 +143,11 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
|
|||||||
drawInk(gtx, c)
|
drawInk(gtx, c)
|
||||||
}
|
}
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}),
|
},
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
gtx.Constraints.Min = min
|
gtx.Constraints.Min = min
|
||||||
return layout.Center.Layout(gtx, w)
|
return layout.Center.Layout(gtx, w)
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -163,8 +159,8 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
if d := b.Description; d != "" {
|
if d := b.Description; d != "" {
|
||||||
semantic.DescriptionOp(b.Description).Add(gtx.Ops)
|
semantic.DescriptionOp(b.Description).Add(gtx.Ops)
|
||||||
}
|
}
|
||||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
return layout.Background{}.Layout(gtx,
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
rr := (gtx.Constraints.Min.X + gtx.Constraints.Min.Y) / 4
|
rr := (gtx.Constraints.Min.X + gtx.Constraints.Min.Y) / 4
|
||||||
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
||||||
background := b.Background
|
background := b.Background
|
||||||
@@ -179,8 +175,8 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
drawInk(gtx, c)
|
drawInk(gtx, c)
|
||||||
}
|
}
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}),
|
},
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
size := gtx.Dp(b.Size)
|
size := gtx.Dp(b.Size)
|
||||||
if b.Icon != nil {
|
if b.Icon != nil {
|
||||||
@@ -191,7 +187,7 @@ func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
Size: image.Point{X: size, Y: size},
|
Size: image.Point{X: size, Y: size},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
c := m.Stop()
|
c := m.Stop()
|
||||||
|
|||||||
@@ -88,18 +88,18 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension
|
|||||||
cl := d.Decorations.Clickable(a)
|
cl := d.Decorations.Clickable(a)
|
||||||
dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.Button.Add(gtx.Ops)
|
semantic.Button.Add(gtx.Ops)
|
||||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
return layout.Background{}.Layout(gtx,
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
||||||
for _, c := range cl.History() {
|
for _, c := range cl.History() {
|
||||||
drawInk(gtx, c)
|
drawInk(gtx, c)
|
||||||
}
|
}
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}),
|
},
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
|
paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
|
||||||
return inset.Layout(gtx, w)
|
return inset.Layout(gtx, w)
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
size.X += dims.Size.X
|
size.X += dims.Size.X
|
||||||
|
|||||||
+6
-10
@@ -165,12 +165,9 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
|
|||||||
if axis == layout.Horizontal {
|
if axis == layout.Horizontal {
|
||||||
inset.Top, inset.Bottom, inset.Left, inset.Right = inset.Left, inset.Right, inset.Top, inset.Bottom
|
inset.Top, inset.Bottom, inset.Left, inset.Right = inset.Left, inset.Right, inset.Top, inset.Bottom
|
||||||
}
|
}
|
||||||
// Capture the outer constraints because layout.Stack will reset
|
|
||||||
// the minimum to zero.
|
|
||||||
outerConstraints := gtx.Constraints
|
|
||||||
|
|
||||||
return layout.Stack{}.Layout(gtx,
|
return layout.Background{}.Layout(gtx,
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
// Lay out the draggable track underneath the scroll indicator.
|
// Lay out the draggable track underneath the scroll indicator.
|
||||||
area := image.Rectangle{
|
area := image.Rectangle{
|
||||||
Max: gtx.Constraints.Min,
|
Max: gtx.Constraints.Min,
|
||||||
@@ -186,10 +183,9 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
|
|||||||
s.Scrollbar.AddTrack(gtx.Ops)
|
s.Scrollbar.AddTrack(gtx.Ops)
|
||||||
|
|
||||||
paint.FillShape(gtx.Ops, s.Track.Color, clip.Rect(area).Op())
|
paint.FillShape(gtx.Ops, s.Track.Color, clip.Rect(area).Op())
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
}),
|
},
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
gtx.Constraints = outerConstraints
|
|
||||||
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
// Use axis-independent constraints.
|
// Use axis-independent constraints.
|
||||||
gtx.Constraints.Min = axis.Convert(gtx.Constraints.Min)
|
gtx.Constraints.Min = axis.Convert(gtx.Constraints.Min)
|
||||||
@@ -231,7 +227,7 @@ func (s ScrollbarStyle) layout(gtx layout.Context, axis layout.Axis, viewportSta
|
|||||||
|
|
||||||
return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)}
|
return layout.Dimensions{Size: axis.Convert(gtx.Constraints.Min)}
|
||||||
})
|
})
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonSt
|
|||||||
|
|
||||||
// Layout updates enum and displays the radio button.
|
// Layout updates enum and displays the radio button.
|
||||||
func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
|
r.Group.Update(gtx)
|
||||||
hovered, hovering := r.Group.Hovered()
|
hovered, hovering := r.Group.Hovered()
|
||||||
focus, focused := r.Group.Focused()
|
focus, focused := r.Group.Focused()
|
||||||
return r.Group.Layout(gtx, r.Key, func(gtx layout.Context) layout.Dimensions {
|
return r.Group.Layout(gtx, r.Key, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
|||||||
+17
-20
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func Switch(th *Theme, swtch *widget.Bool, description string) SwitchStyle {
|
|||||||
|
|
||||||
// Layout updates the switch and displays it.
|
// Layout updates the switch and displays it.
|
||||||
func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
|
s.Switch.Update(gtx)
|
||||||
trackWidth := gtx.Dp(36)
|
trackWidth := gtx.Dp(36)
|
||||||
trackHeight := gtx.Dp(16)
|
trackHeight := gtx.Dp(16)
|
||||||
thumbSize := gtx.Dp(20)
|
thumbSize := gtx.Dp(20)
|
||||||
|
|||||||
+16
-10
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("")
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user