mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
15 Commits
b1cadbdd76
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a5fa17a39 | |||
| 5191409708 | |||
| 3eab806940 | |||
| 30dc7ff294 | |||
| 9e18cb93fb | |||
| e4932e163e | |||
| a8fe27488f | |||
| 06307313cd | |||
| 15335a2b37 | |||
| acf5635575 | |||
| caccb608a5 | |||
| d52632b475 | |||
| dec57aea1c | |||
| e2e2c1a046 | |||
| e8c1e1ba11 |
@@ -207,7 +207,9 @@ const (
|
|||||||
CFS_POINT = 0x0002
|
CFS_POINT = 0x0002
|
||||||
CFS_CANDIDATEPOS = 0x0040
|
CFS_CANDIDATEPOS = 0x0040
|
||||||
|
|
||||||
HWND_TOPMOST = ^(uint32(1) - 1) // -1
|
HWND_TOP = syscall.Handle(0)
|
||||||
|
HWND_TOPMOST = ^(syscall.Handle(1) - 1) // -1
|
||||||
|
HWND_NOTOPMOST = ^(syscall.Handle(2) - 1) // -2
|
||||||
|
|
||||||
HTCAPTION = 2
|
HTCAPTION = 2
|
||||||
HTCLIENT = 1
|
HTCLIENT = 1
|
||||||
@@ -782,7 +784,7 @@ func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
|
|||||||
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int32, style uintptr) {
|
func SetWindowPos(hwnd, hwndInsertAfter syscall.Handle, x, y, dx, dy int32, style uintptr) {
|
||||||
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
||||||
uintptr(x), uintptr(y),
|
uintptr(x), uintptr(y),
|
||||||
uintptr(dx), uintptr(dy),
|
uintptr(dx), uintptr(dy),
|
||||||
|
|||||||
+54
-41
@@ -369,8 +369,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
w.update()
|
w.update()
|
||||||
case windows.WM_WINDOWPOSCHANGED:
|
case windows.WM_WINDOWPOSCHANGED:
|
||||||
w.update()
|
w.update()
|
||||||
case windows.WM_SIZE:
|
return 0
|
||||||
w.update()
|
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||||
|
|
||||||
@@ -413,6 +412,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
|
icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
|
||||||
windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
|
windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
|
||||||
windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
|
windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
|
||||||
|
return windows.TRUE
|
||||||
case windows.WM_IME_COMPOSITION:
|
case windows.WM_IME_COMPOSITION:
|
||||||
imc := windows.ImmGetContext(w.hwnd)
|
imc := windows.ImmGetContext(w.hwnd)
|
||||||
if imc == 0 {
|
if imc == 0 {
|
||||||
@@ -495,34 +495,32 @@ func getModifiers() key.Modifiers {
|
|||||||
// hitTest returns the non-client area hit by the point, needed to
|
// hitTest returns the non-client area hit by the point, needed to
|
||||||
// process WM_NCHITTEST.
|
// process WM_NCHITTEST.
|
||||||
func (w *window) hitTest(x, y int) uintptr {
|
func (w *window) hitTest(x, y int) uintptr {
|
||||||
if w.config.Mode != Windowed {
|
if w.config.Mode == Windowed {
|
||||||
// Only windowed mode should allow resizing.
|
// Check for resize handle before system actions; otherwise it can be impossible to
|
||||||
return windows.HTCLIENT
|
// resize a custom-decorations window when the system move area is flush with the
|
||||||
}
|
// edge of the window.
|
||||||
// Check for resize handle before system actions; otherwise it can be impossible to
|
top := y <= w.borderSize.Y
|
||||||
// resize a custom-decorations window when the system move area is flush with the
|
bottom := y >= w.config.Size.Y-w.borderSize.Y
|
||||||
// edge of the window.
|
left := x <= w.borderSize.X
|
||||||
top := y <= w.borderSize.Y
|
right := x >= w.config.Size.X-w.borderSize.X
|
||||||
bottom := y >= w.config.Size.Y-w.borderSize.Y
|
switch {
|
||||||
left := x <= w.borderSize.X
|
case top && left:
|
||||||
right := x >= w.config.Size.X-w.borderSize.X
|
return windows.HTTOPLEFT
|
||||||
switch {
|
case top && right:
|
||||||
case top && left:
|
return windows.HTTOPRIGHT
|
||||||
return windows.HTTOPLEFT
|
case bottom && left:
|
||||||
case top && right:
|
return windows.HTBOTTOMLEFT
|
||||||
return windows.HTTOPRIGHT
|
case bottom && right:
|
||||||
case bottom && left:
|
return windows.HTBOTTOMRIGHT
|
||||||
return windows.HTBOTTOMLEFT
|
case top:
|
||||||
case bottom && right:
|
return windows.HTTOP
|
||||||
return windows.HTBOTTOMRIGHT
|
case bottom:
|
||||||
case top:
|
return windows.HTBOTTOM
|
||||||
return windows.HTTOP
|
case left:
|
||||||
case bottom:
|
return windows.HTLEFT
|
||||||
return windows.HTBOTTOM
|
case right:
|
||||||
case left:
|
return windows.HTRIGHT
|
||||||
return windows.HTLEFT
|
}
|
||||||
case right:
|
|
||||||
return windows.HTRIGHT
|
|
||||||
}
|
}
|
||||||
p := f32.Pt(float32(x), float32(y))
|
p := f32.Pt(float32(x), float32(y))
|
||||||
if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
|
if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
|
||||||
@@ -701,7 +699,13 @@ func (w *window) ReadClipboard() {
|
|||||||
w.readClipboard()
|
w.readClipboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) readClipboard() error {
|
func (w *window) readClipboard() (cerr error) {
|
||||||
|
defer func() {
|
||||||
|
if cerr != nil {
|
||||||
|
w.processDataEvent("")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -716,13 +720,17 @@ func (w *window) readClipboard() error {
|
|||||||
}
|
}
|
||||||
defer windows.GlobalUnlock(mem)
|
defer windows.GlobalUnlock(mem)
|
||||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||||
|
w.processDataEvent(content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processDataEvent(content string) {
|
||||||
w.ProcessEvent(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
@@ -739,7 +747,16 @@ func (w *window) Configure(options []Option) {
|
|||||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||||
var showMode int32
|
var showMode int32
|
||||||
var x, y, width, height int32
|
var x, y, width, height int32
|
||||||
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
|
swpStyle := uintptr(windows.SWP_FRAMECHANGED)
|
||||||
|
if cnf.TopMost == w.config.TopMost {
|
||||||
|
// Don't change the z-order if TopMost didn't change.
|
||||||
|
swpStyle |= windows.SWP_NOZORDER
|
||||||
|
}
|
||||||
|
hwndAfter := windows.HWND_NOTOPMOST
|
||||||
|
if cnf.TopMost {
|
||||||
|
hwndAfter = windows.HWND_TOPMOST
|
||||||
|
}
|
||||||
|
w.config.TopMost = cnf.TopMost
|
||||||
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
||||||
style &^= winStyle
|
style &^= winStyle
|
||||||
switch cnf.Mode {
|
switch cnf.Mode {
|
||||||
@@ -791,7 +808,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
|
|
||||||
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
|
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
|
||||||
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
|
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
|
||||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
windows.SetWindowPos(w.hwnd, hwndAfter, x, y, width, height, swpStyle)
|
||||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
||||||
windows.ShowWindow(w.hwnd, showMode)
|
windows.ShowWindow(w.hwnd, showMode)
|
||||||
}
|
}
|
||||||
@@ -912,19 +929,15 @@ func (w *window) Perform(acts system.Action) {
|
|||||||
y := (mi.Bottom - mi.Top - dy) / 2
|
y := (mi.Bottom - mi.Top - dy) / 2
|
||||||
windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
|
windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
|
||||||
case system.ActionRaise:
|
case system.ActionRaise:
|
||||||
w.raise()
|
windows.SetForegroundWindow(w.hwnd)
|
||||||
|
windows.SetWindowPos(w.hwnd, windows.HWND_TOP, 0, 0, 0, 0,
|
||||||
|
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
||||||
case system.ActionClose:
|
case system.ActionClose:
|
||||||
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) raise() {
|
|
||||||
windows.SetForegroundWindow(w.hwnd)
|
|
||||||
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
|
|
||||||
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertKeyCode(code uintptr) (key.Name, bool) {
|
func convertKeyCode(code uintptr) (key.Name, bool) {
|
||||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||||
return key.Name(rune(code)), true
|
return key.Name(rune(code)), true
|
||||||
|
|||||||
+2
-1
@@ -439,6 +439,7 @@ func (c *callbacks) EditorState() editorState {
|
|||||||
|
|
||||||
func (c *callbacks) SetComposingRegion(r key.Range) {
|
func (c *callbacks) SetComposingRegion(r key.Range) {
|
||||||
c.w.imeState.compose = r
|
c.w.imeState.compose = r
|
||||||
|
c.w.driver.ProcessEvent(key.CompositionEvent(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) EditorInsert(text string) {
|
func (c *callbacks) EditorInsert(text string) {
|
||||||
@@ -971,7 +972,7 @@ func Decorated(enabled bool) Option {
|
|||||||
|
|
||||||
// TopMost windows will be rendered above all other non-top-most windows.
|
// TopMost windows will be rendered above all other non-top-most windows.
|
||||||
//
|
//
|
||||||
// TopMost windows are only supported on MacOS currently.
|
// TopMost windows are supported on macOS, Windows.
|
||||||
func TopMost(enabled bool) Option {
|
func TopMost(enabled bool) Option {
|
||||||
return func(_ unit.Metric, cnf *Config) {
|
return func(_ unit.Metric, cnf *Config) {
|
||||||
cnf.TopMost = enabled
|
cnf.TopMost = enabled
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
|
|||||||
// Face many be invoked any number of times and is safe so long as each return value is
|
// Face many be invoked any number of times and is safe so long as each return value is
|
||||||
// only used by one goroutine.
|
// only used by one goroutine.
|
||||||
func (f Face) Face() *fontapi.Face {
|
func (f Face) Face() *fontapi.Face {
|
||||||
return &fontapi.Face{Font: f.face}
|
return fontapi.NewFace(f.face)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FontFace returns a text.Font with populated font metadata for the
|
// FontFace returns a text.Font with populated font metadata for the
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
require (
|
require (
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||||
gioui.org/shader v1.0.8
|
gioui.org/shader v1.0.8
|
||||||
github.com/go-text/typesetting v0.3.0
|
github.com/go-text/typesetting v0.3.4
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/image v0.26.0
|
golang.org/x/image v0.26.0
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8v
|
|||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
||||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||||
|
|||||||
@@ -41,14 +41,10 @@ var (
|
|||||||
_eglWaitClient *syscall.Proc
|
_eglWaitClient *syscall.Proc
|
||||||
)
|
)
|
||||||
|
|
||||||
var loadOnce sync.Once
|
var loadOnce = sync.OnceValue(loadDLLs)
|
||||||
|
|
||||||
func loadEGL() error {
|
func loadEGL() error {
|
||||||
var err error
|
return loadOnce()
|
||||||
loadOnce.Do(func() {
|
|
||||||
err = loadDLLs()
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDLLs() error {
|
func loadDLLs() error {
|
||||||
|
|||||||
+3
-1
@@ -760,6 +760,8 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState,
|
|||||||
if p.pressed {
|
if p.pressed {
|
||||||
p, evts = q.deliverDragEvent(handlers, p, evts)
|
p, evts = q.deliverDragEvent(handlers, p, evts)
|
||||||
}
|
}
|
||||||
|
case pointer.Leave:
|
||||||
|
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
|
||||||
case pointer.Release:
|
case pointer.Release:
|
||||||
evts = q.deliverEvent(handlers, p, evts, e)
|
evts = q.deliverEvent(handlers, p, evts, e)
|
||||||
p.pressed = false
|
p.pressed = false
|
||||||
@@ -823,7 +825,7 @@ func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerIn
|
|||||||
func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
|
func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
|
||||||
changed := false
|
changed := false
|
||||||
var hits []event.Tag
|
var hits []event.Tag
|
||||||
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
|
if e.Kind == pointer.Leave || 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 {
|
||||||
var transSrc *pointerFilter
|
var transSrc *pointerFilter
|
||||||
|
|||||||
@@ -255,6 +255,45 @@ func TestPointerMove(t *testing.T) {
|
|||||||
assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel)
|
assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPointerLeave(t *testing.T) {
|
||||||
|
handler := new(int)
|
||||||
|
var ops op.Ops
|
||||||
|
|
||||||
|
filter := pointer.Filter{
|
||||||
|
Target: handler,
|
||||||
|
Kinds: pointer.Move | pointer.Enter | pointer.Leave | pointer.Cancel,
|
||||||
|
}
|
||||||
|
defer clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop()
|
||||||
|
event.Op(&ops, handler)
|
||||||
|
|
||||||
|
var r Router
|
||||||
|
events(&r, -1, filter)
|
||||||
|
r.Frame(&ops)
|
||||||
|
r.Queue(
|
||||||
|
pointer.Event{
|
||||||
|
Kind: pointer.Move,
|
||||||
|
Source: pointer.Mouse,
|
||||||
|
PointerID: 1,
|
||||||
|
Position: f32.Pt(50, 50),
|
||||||
|
},
|
||||||
|
pointer.Event{
|
||||||
|
Kind: pointer.Leave,
|
||||||
|
Source: pointer.Mouse,
|
||||||
|
PointerID: 1,
|
||||||
|
Position: f32.Pt(50, 50),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assertEventPointerTypeSequence(t, events(&r, -1, filter), pointer.Enter, pointer.Move, pointer.Leave)
|
||||||
|
|
||||||
|
r.Queue(pointer.Event{
|
||||||
|
Kind: pointer.Move,
|
||||||
|
Source: pointer.Mouse,
|
||||||
|
PointerID: 1,
|
||||||
|
Position: f32.Pt(50, 50),
|
||||||
|
})
|
||||||
|
assertEventPointerTypeSequence(t, events(&r, -1, filter), pointer.Enter, pointer.Move)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPointerTypes(t *testing.T) {
|
func TestPointerTypes(t *testing.T) {
|
||||||
handler := new(int)
|
handler := new(int)
|
||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
|
|||||||
+8
-1
@@ -419,7 +419,7 @@ func (f *filter) Merge(f2 filter) {
|
|||||||
|
|
||||||
func (f *filter) Matches(e event.Event) bool {
|
func (f *filter) Matches(e event.Event) bool {
|
||||||
switch e.(type) {
|
switch e.(type) {
|
||||||
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
|
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent, key.CompositionEvent:
|
||||||
return f.focusable
|
return f.focusable
|
||||||
default:
|
default:
|
||||||
return f.pointer.Matches(e)
|
return f.pointer.Matches(e)
|
||||||
@@ -463,6 +463,13 @@ func (q *Router) processEvent(e event.Event, system bool) {
|
|||||||
evts = append(evts, taggedEvent{tag: f, event: e})
|
evts = append(evts, taggedEvent{tag: f, event: e})
|
||||||
}
|
}
|
||||||
q.changeState(e, state, evts)
|
q.changeState(e, state, evts)
|
||||||
|
case key.CompositionEvent:
|
||||||
|
e = key.CompositionEvent(rangeNorm(key.Range(e)))
|
||||||
|
var evts []taggedEvent
|
||||||
|
if f := state.focus; f != nil {
|
||||||
|
evts = append(evts, taggedEvent{tag: f, event: e})
|
||||||
|
}
|
||||||
|
q.changeState(e, state, evts)
|
||||||
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
|
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
|
||||||
var evts []taggedEvent
|
var evts []taggedEvent
|
||||||
if f := state.focus; f != nil {
|
if f := state.focus; f != nil {
|
||||||
|
|||||||
+9
-5
@@ -77,6 +77,9 @@ type Caret struct {
|
|||||||
// SelectionEvent is generated when an input method changes the selection.
|
// SelectionEvent is generated when an input method changes the selection.
|
||||||
type SelectionEvent Range
|
type SelectionEvent Range
|
||||||
|
|
||||||
|
// CompositionEvent is generated when an input method changes the composing range.
|
||||||
|
type CompositionEvent Range
|
||||||
|
|
||||||
// SnippetEvent is generated when the snippet range is updated by an
|
// SnippetEvent is generated when the snippet range is updated by an
|
||||||
// input method.
|
// input method.
|
||||||
type SnippetEvent Range
|
type SnippetEvent Range
|
||||||
@@ -243,11 +246,12 @@ func (h InputHintOp) Add(o *op.Ops) {
|
|||||||
data[1] = byte(h.Hint)
|
data[1] = byte(h.Hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (EditEvent) ImplementsEvent() {}
|
func (EditEvent) ImplementsEvent() {}
|
||||||
func (Event) ImplementsEvent() {}
|
func (Event) ImplementsEvent() {}
|
||||||
func (FocusEvent) ImplementsEvent() {}
|
func (FocusEvent) ImplementsEvent() {}
|
||||||
func (SnippetEvent) ImplementsEvent() {}
|
func (CompositionEvent) ImplementsEvent() {}
|
||||||
func (SelectionEvent) ImplementsEvent() {}
|
func (SnippetEvent) ImplementsEvent() {}
|
||||||
|
func (SelectionEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
func (FocusCmd) ImplementsCommand() {}
|
func (FocusCmd) ImplementsCommand() {}
|
||||||
func (SoftKeyboardCmd) ImplementsCommand() {}
|
func (SoftKeyboardCmd) ImplementsCommand() {}
|
||||||
|
|||||||
+12
-14
@@ -103,8 +103,7 @@ func (l *line) insertTrailingSyntheticNewline(newLineClusterIdx int) {
|
|||||||
clusterIndex: newLineClusterIdx,
|
clusterIndex: newLineClusterIdx,
|
||||||
glyphCount: 0,
|
glyphCount: 0,
|
||||||
runeCount: 1,
|
runeCount: 1,
|
||||||
xAdvance: 0,
|
advance: 0,
|
||||||
yAdvance: 0,
|
|
||||||
xOffset: 0,
|
xOffset: 0,
|
||||||
yOffset: 0,
|
yOffset: 0,
|
||||||
}
|
}
|
||||||
@@ -160,9 +159,9 @@ type glyph struct {
|
|||||||
// runeCount is the quantity of runes in the source text that this glyph
|
// runeCount is the quantity of runes in the source text that this glyph
|
||||||
// corresponds to.
|
// corresponds to.
|
||||||
runeCount int
|
runeCount int
|
||||||
// xAdvance and yAdvance describe the distance the dot moves when
|
// advance is the distance the dot moves when laying out the glyph along
|
||||||
// laying out the glyph on the X or Y axis.
|
// the run's primary axis.
|
||||||
xAdvance, yAdvance fixed.Int26_6
|
advance fixed.Int26_6
|
||||||
// xOffset and yOffset describe offsets from the dot that should be
|
// xOffset and yOffset describe offsets from the dot that should be
|
||||||
// applied when rendering the glyph.
|
// applied when rendering the glyph.
|
||||||
xOffset, yOffset fixed.Int26_6
|
xOffset, yOffset fixed.Int26_6
|
||||||
@@ -270,8 +269,9 @@ func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
|
|||||||
// in the order in which they are loaded, with the first face being the default.
|
// in the order in which they are loaded, with the first face being the default.
|
||||||
func (s *shaperImpl) Load(f FontFace) {
|
func (s *shaperImpl) Load(f FontFace) {
|
||||||
desc := opentype.FontToDescription(f.Font)
|
desc := opentype.FontToDescription(f.Font)
|
||||||
s.fontMap.AddFace(f.Face.Face(), fontscan.Location{File: fmt.Sprint(desc)}, desc)
|
face := f.Face.Face()
|
||||||
s.addFace(f.Face.Face(), f.Font)
|
s.fontMap.AddFace(face, fontscan.Location{File: fmt.Sprint(desc)}, desc)
|
||||||
|
s.addFace(face, f.Font)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shaperImpl) addFace(f *font.Face, md giofont.Font) {
|
func (s *shaperImpl) addFace(f *font.Face, md giofont.Font) {
|
||||||
@@ -437,8 +437,7 @@ func (s *shaperImpl) shapeText(ppem fixed.Int26_6, lc system.Locale, txt []rune)
|
|||||||
Height: input.Size,
|
Height: input.Size,
|
||||||
XBearing: 0,
|
XBearing: 0,
|
||||||
YBearing: 0,
|
YBearing: 0,
|
||||||
XAdvance: input.Size,
|
Advance: input.Size,
|
||||||
YAdvance: input.Size,
|
|
||||||
XOffset: 0,
|
XOffset: 0,
|
||||||
YOffset: 0,
|
YOffset: 0,
|
||||||
ClusterIndex: input.RunStart,
|
ClusterIndex: input.RunStart,
|
||||||
@@ -854,11 +853,10 @@ func toGioGlyphs(in []shaping.Glyph, ppem fixed.Int26_6, faceIdx int) []glyph {
|
|||||||
bounds.Max = bounds.Min.Add(fixed.Point26_6{X: g.Width, Y: -g.Height})
|
bounds.Max = bounds.Min.Add(fixed.Point26_6{X: g.Width, Y: -g.Height})
|
||||||
out = append(out, glyph{
|
out = append(out, glyph{
|
||||||
id: newGlyphID(ppem, faceIdx, g.GlyphID),
|
id: newGlyphID(ppem, faceIdx, g.GlyphID),
|
||||||
clusterIndex: g.ClusterIndex,
|
clusterIndex: g.TextIndex(),
|
||||||
runeCount: g.RuneCount,
|
runeCount: g.RunesCount(),
|
||||||
glyphCount: g.GlyphCount,
|
glyphCount: g.GlyphsCount(),
|
||||||
xAdvance: g.XAdvance,
|
advance: g.Advance,
|
||||||
yAdvance: g.YAdvance,
|
|
||||||
xOffset: g.XOffset,
|
xOffset: g.XOffset,
|
||||||
yOffset: g.YOffset,
|
yOffset: g.YOffset,
|
||||||
bounds: bounds,
|
bounds: bounds,
|
||||||
|
|||||||
+3
-3
@@ -464,7 +464,7 @@ func (l *Shaper) NextGlyph() (_ Glyph, ok bool) {
|
|||||||
if rtl {
|
if rtl {
|
||||||
// Modify the advance prior to computing runOffset to ensure that the
|
// Modify the advance prior to computing runOffset to ensure that the
|
||||||
// current glyph's width is subtracted in RTL.
|
// current glyph's width is subtracted in RTL.
|
||||||
l.advance += g.xAdvance
|
l.advance += g.advance
|
||||||
}
|
}
|
||||||
// runOffset computes how far into the run the dot should be positioned.
|
// runOffset computes how far into the run the dot should be positioned.
|
||||||
runOffset := l.advance
|
runOffset := l.advance
|
||||||
@@ -477,7 +477,7 @@ func (l *Shaper) NextGlyph() (_ Glyph, ok bool) {
|
|||||||
Y: int32(line.yOffset),
|
Y: int32(line.yOffset),
|
||||||
Ascent: line.ascent,
|
Ascent: line.ascent,
|
||||||
Descent: line.descent,
|
Descent: line.descent,
|
||||||
Advance: g.xAdvance,
|
Advance: g.advance,
|
||||||
Runes: uint16(g.runeCount),
|
Runes: uint16(g.runeCount),
|
||||||
Offset: fixed.Point26_6{
|
Offset: fixed.Point26_6{
|
||||||
X: g.xOffset,
|
X: g.xOffset,
|
||||||
@@ -490,7 +490,7 @@ func (l *Shaper) NextGlyph() (_ Glyph, ok bool) {
|
|||||||
}
|
}
|
||||||
l.glyph++
|
l.glyph++
|
||||||
if !rtl {
|
if !rtl {
|
||||||
l.advance += g.xAdvance
|
l.advance += g.advance
|
||||||
}
|
}
|
||||||
|
|
||||||
endOfRun := l.glyph == len(run.Glyphs)
|
endOfRun := l.glyph == len(run.Glyphs)
|
||||||
|
|||||||
+2
-2
@@ -450,8 +450,8 @@ func printLinePositioning(t *testing.T, lines []line, glyphs []Glyph) {
|
|||||||
for g := start; ; g += inc {
|
for g := start; ; g += inc {
|
||||||
glyph := run.Glyphs[g]
|
glyph := run.Glyphs[g]
|
||||||
if glyphCursor < len(glyphs) {
|
if glyphCursor < len(glyphs) {
|
||||||
t.Logf("glyph %2d, adv %3d, runes %2d, glyphs %d - glyphs[%2d] flags %s", g, glyph.xAdvance, glyph.runeCount, glyph.glyphCount, glyphCursor, glyphs[glyphCursor].Flags)
|
t.Logf("glyph %2d, adv %3d, runes %2d, glyphs %d - glyphs[%2d] flags %s", g, glyph.advance, glyph.runeCount, glyph.glyphCount, glyphCursor, glyphs[glyphCursor].Flags)
|
||||||
t.Logf("glyph %2d, adv %3d, runes %2d, glyphs %d - n/a", g, glyph.xAdvance, glyph.runeCount, glyph.glyphCount)
|
t.Logf("glyph %2d, adv %3d, runes %2d, glyphs %d - n/a", g, glyph.advance, glyph.runeCount, glyph.glyphCount)
|
||||||
}
|
}
|
||||||
glyphCursor++
|
glyphCursor++
|
||||||
if g == end {
|
if g == end {
|
||||||
|
|||||||
+30
-2
@@ -25,6 +25,7 @@ import (
|
|||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
|
"gioui.org/op/paint"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
@@ -107,8 +108,9 @@ type imeState struct {
|
|||||||
rng key.Range
|
rng key.Range
|
||||||
caret key.Caret
|
caret key.Caret
|
||||||
}
|
}
|
||||||
snippet key.Snippet
|
snippet key.Snippet
|
||||||
start, end int
|
composition key.Range
|
||||||
|
start, end int
|
||||||
}
|
}
|
||||||
|
|
||||||
type maskReader struct {
|
type maskReader struct {
|
||||||
@@ -398,9 +400,12 @@ func (e *Editor) processKey(gtx layout.Context) (EditorEvent, bool) {
|
|||||||
case key.FocusEvent:
|
case key.FocusEvent:
|
||||||
// Reset IME state.
|
// Reset IME state.
|
||||||
e.ime.imeState = imeState{}
|
e.ime.imeState = imeState{}
|
||||||
|
e.ime.composition = key.Range{Start: -1, End: -1}
|
||||||
if ke.Focus && !e.ReadOnly {
|
if ke.Focus && !e.ReadOnly {
|
||||||
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||||
}
|
}
|
||||||
|
case key.CompositionEvent:
|
||||||
|
e.ime.composition = key.Range(ke)
|
||||||
case key.Event:
|
case key.Event:
|
||||||
if !gtx.Focused(e) || ke.State != key.Press {
|
if !gtx.Focused(e) || ke.State != key.Press {
|
||||||
break
|
break
|
||||||
@@ -735,6 +740,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call
|
|||||||
if e.Len() > 0 {
|
if e.Len() > 0 {
|
||||||
e.paintSelection(gtx, selectMaterial)
|
e.paintSelection(gtx, selectMaterial)
|
||||||
e.paintText(gtx, textMaterial)
|
e.paintText(gtx, textMaterial)
|
||||||
|
e.paintComposition(gtx, textMaterial)
|
||||||
}
|
}
|
||||||
if gtx.Enabled() {
|
if gtx.Enabled() {
|
||||||
e.paintCaret(gtx, textMaterial)
|
e.paintCaret(gtx, textMaterial)
|
||||||
@@ -759,6 +765,28 @@ func (e *Editor) paintText(gtx layout.Context, material op.CallOp) {
|
|||||||
e.text.PaintText(gtx, material)
|
e.text.PaintText(gtx, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Editor) paintComposition(gtx layout.Context, material op.CallOp) {
|
||||||
|
e.initBuffer()
|
||||||
|
r := e.ime.composition
|
||||||
|
if r.Start == -1 || r.Start == r.End {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.text.regions = e.text.Regions(r.Start, r.End, e.text.regions)
|
||||||
|
thickness := max(gtx.Dp(unit.Dp(1)), 1)
|
||||||
|
for _, region := range e.text.regions {
|
||||||
|
y := region.Bounds.Max.Y - max(region.Baseline/3, thickness)
|
||||||
|
underline := image.Rect(region.Bounds.Min.X, y, region.Bounds.Max.X, y+thickness)
|
||||||
|
underline = underline.Intersect(image.Rectangle{Max: e.text.viewSize})
|
||||||
|
if underline.Empty() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack := clip.Rect(underline).Push(gtx.Ops)
|
||||||
|
material.Add(gtx.Ops)
|
||||||
|
paint.PaintOp{}.Add(gtx.Ops)
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// paintCaret paints the text glyphs using the provided material to set the fill material
|
// paintCaret paints the text glyphs using the provided material to set the fill material
|
||||||
// of the caret rectangle.
|
// of the caret rectangle.
|
||||||
func (e *Editor) paintCaret(gtx layout.Context, material op.CallOp) {
|
func (e *Editor) paintCaret(gtx layout.Context, material op.CallOp) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/io/semantic"
|
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@@ -86,21 +85,10 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cl := d.Decorations.Clickable(a)
|
cl := d.Decorations.Clickable(a)
|
||||||
dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
dims := Clickable(gtx, cl, func(gtx layout.Context) layout.Dimensions {
|
||||||
semantic.Button.Add(gtx.Ops)
|
system.ActionInputOp(a).Add(gtx.Ops)
|
||||||
return layout.Background{}.Layout(gtx,
|
paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
return inset.Layout(gtx, w)
|
||||||
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
|
||||||
for _, c := range cl.History() {
|
|
||||||
drawInk(gtx, c)
|
|
||||||
}
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
|
|
||||||
return inset.Layout(gtx, w)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
size.X += dims.Size.X
|
size.X += dims.Size.X
|
||||||
if size.Y < dims.Size.Y {
|
if size.Y < dims.Size.Y {
|
||||||
|
|||||||
Reference in New Issue
Block a user