forked from joejulian/gio
all: rename the gioui.org/ui module to gioui.org
The "ui" is redundant and stutters. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"gioui.org/ui"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/key"
|
||||
)
|
||||
|
||||
type TextInputState uint8
|
||||
|
||||
type keyQueue struct {
|
||||
focus ui.Key
|
||||
handlers map[ui.Key]*keyHandler
|
||||
reader ops.Reader
|
||||
state TextInputState
|
||||
}
|
||||
|
||||
type keyHandler struct {
|
||||
active bool
|
||||
}
|
||||
|
||||
type listenerPriority uint8
|
||||
|
||||
const (
|
||||
priNone listenerPriority = iota
|
||||
priDefault
|
||||
priCurrentFocus
|
||||
priNewFocus
|
||||
)
|
||||
|
||||
const (
|
||||
TextInputKeep TextInputState = iota
|
||||
TextInputClose
|
||||
TextInputOpen
|
||||
)
|
||||
|
||||
// InputState returns the last text input state as
|
||||
// determined in Frame.
|
||||
func (q *keyQueue) InputState() TextInputState {
|
||||
return q.state
|
||||
}
|
||||
|
||||
func (q *keyQueue) Frame(root *ui.Ops, events *handlerEvents) {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[ui.Key]*keyHandler)
|
||||
}
|
||||
for _, h := range q.handlers {
|
||||
h.active = false
|
||||
}
|
||||
q.reader.Reset(root)
|
||||
focus, pri, hide := q.resolveFocus(events)
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
delete(q.handlers, k)
|
||||
if q.focus == k {
|
||||
q.focus = nil
|
||||
hide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if focus != q.focus {
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: false})
|
||||
}
|
||||
q.focus = focus
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: true})
|
||||
} else {
|
||||
hide = true
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case pri == priNewFocus:
|
||||
q.state = TextInputOpen
|
||||
case hide:
|
||||
q.state = TextInputClose
|
||||
default:
|
||||
q.state = TextInputKeep
|
||||
}
|
||||
}
|
||||
|
||||
func (q *keyQueue) Push(e ui.Event, events *handlerEvents) {
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, e)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *keyQueue) resolveFocus(events *handlerEvents) (ui.Key, listenerPriority, bool) {
|
||||
var k ui.Key
|
||||
var pri listenerPriority
|
||||
var hide bool
|
||||
loop:
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeKeyInput:
|
||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||
var newPri listenerPriority
|
||||
switch {
|
||||
case op.Focus:
|
||||
newPri = priNewFocus
|
||||
case op.Key == q.focus:
|
||||
newPri = priCurrentFocus
|
||||
default:
|
||||
newPri = priDefault
|
||||
}
|
||||
// Switch focus if higher priority or if focus requested.
|
||||
if newPri.replaces(pri) {
|
||||
k, pri = op.Key, newPri
|
||||
}
|
||||
h, ok := q.handlers[op.Key]
|
||||
if !ok {
|
||||
h = new(keyHandler)
|
||||
q.handlers[op.Key] = h
|
||||
// Reset the handler on (each) first appearance.
|
||||
events.Set(op.Key, []ui.Event{key.FocusEvent{Focus: false}})
|
||||
}
|
||||
h.active = true
|
||||
case opconst.TypeHideInput:
|
||||
hide = true
|
||||
case opconst.TypePush:
|
||||
newK, newPri, h := q.resolveFocus(events)
|
||||
hide = hide || h
|
||||
if newPri.replaces(pri) {
|
||||
k, pri = newK, newPri
|
||||
}
|
||||
case opconst.TypePop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return k, pri, hide
|
||||
}
|
||||
|
||||
func (p listenerPriority) replaces(p2 listenerPriority) bool {
|
||||
// Favor earliest default focus or latest requested focus.
|
||||
return p > p2 || p == p2 && p == priNewFocus
|
||||
}
|
||||
|
||||
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypeKeyInput {
|
||||
panic("invalid op")
|
||||
}
|
||||
return key.InputOp{
|
||||
Focus: d[1] != 0,
|
||||
Key: refs[0].(ui.Key),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
|
||||
"gioui.org/ui"
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/pointer"
|
||||
)
|
||||
|
||||
type pointerQueue struct {
|
||||
hitTree []hitNode
|
||||
areas []areaNode
|
||||
handlers map[ui.Key]*pointerHandler
|
||||
pointers []pointerInfo
|
||||
reader ops.Reader
|
||||
scratch []ui.Key
|
||||
}
|
||||
|
||||
type hitNode struct {
|
||||
next int
|
||||
area int
|
||||
// Pass tracks the most recent PassOp mode.
|
||||
pass bool
|
||||
|
||||
// For handler nodes.
|
||||
key ui.Key
|
||||
}
|
||||
|
||||
type pointerInfo struct {
|
||||
id pointer.ID
|
||||
pressed bool
|
||||
handlers []ui.Key
|
||||
}
|
||||
|
||||
type pointerHandler struct {
|
||||
area int
|
||||
active bool
|
||||
transform ui.TransformOp
|
||||
wantsGrab bool
|
||||
}
|
||||
|
||||
type areaOp struct {
|
||||
kind areaKind
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
type areaNode struct {
|
||||
trans ui.TransformOp
|
||||
next int
|
||||
area areaOp
|
||||
}
|
||||
|
||||
type areaKind uint8
|
||||
|
||||
const (
|
||||
areaRect areaKind = iota
|
||||
areaEllipse
|
||||
)
|
||||
|
||||
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t ui.TransformOp, area, node int, pass bool) {
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypePush:
|
||||
q.collectHandlers(r, events, t, area, node, pass)
|
||||
case opconst.TypePop:
|
||||
return
|
||||
case opconst.TypePass:
|
||||
op := decodePassOp(encOp.Data)
|
||||
pass = op.Pass
|
||||
case opconst.TypeArea:
|
||||
var op areaOp
|
||||
op.Decode(encOp.Data)
|
||||
q.areas = append(q.areas, areaNode{trans: t, next: area, area: op})
|
||||
area = len(q.areas) - 1
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
case opconst.TypeTransform:
|
||||
op := ops.DecodeTransformOp(encOp.Data)
|
||||
t = t.Multiply(ui.TransformOp(op))
|
||||
case opconst.TypePointerInput:
|
||||
op := decodePointerInputOp(encOp.Data, encOp.Refs)
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
key: op.Key,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
h, ok := q.handlers[op.Key]
|
||||
if !ok {
|
||||
h = new(pointerHandler)
|
||||
q.handlers[op.Key] = h
|
||||
events.Set(op.Key, []ui.Event{pointer.Event{Type: pointer.Cancel}})
|
||||
}
|
||||
h.active = true
|
||||
h.area = area
|
||||
h.transform = t
|
||||
h.wantsGrab = h.wantsGrab || op.Grab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) opHit(handlers *[]ui.Key, pos f32.Point) {
|
||||
// Track whether we're passing through hits.
|
||||
pass := true
|
||||
idx := len(q.hitTree) - 1
|
||||
for idx >= 0 {
|
||||
n := &q.hitTree[idx]
|
||||
if !q.hit(n.area, pos) {
|
||||
idx--
|
||||
continue
|
||||
}
|
||||
pass = pass && n.pass
|
||||
if pass {
|
||||
idx--
|
||||
} else {
|
||||
idx = n.next
|
||||
}
|
||||
if n.key != nil {
|
||||
if _, exists := q.handlers[n.key]; exists {
|
||||
*handlers = append(*handlers, n.key)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
|
||||
for areaIdx != -1 {
|
||||
a := &q.areas[areaIdx]
|
||||
if !a.hit(p) {
|
||||
return false
|
||||
}
|
||||
areaIdx = a.next
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *areaNode) hit(p f32.Point) bool {
|
||||
p = a.trans.Invert().Transform(p)
|
||||
return a.area.Hit(p)
|
||||
}
|
||||
|
||||
func (q *pointerQueue) init() {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[ui.Key]*pointerHandler)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Frame(root *ui.Ops, events *handlerEvents) {
|
||||
q.init()
|
||||
for _, h := range q.handlers {
|
||||
// Reset handler.
|
||||
h.active = false
|
||||
}
|
||||
q.hitTree = q.hitTree[:0]
|
||||
q.areas = q.areas[:0]
|
||||
q.reader.Reset(root)
|
||||
q.collectHandlers(&q.reader, events, ui.TransformOp{}, -1, -1, false)
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
q.dropHandler(k)
|
||||
delete(q.handlers, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) dropHandler(k ui.Key) {
|
||||
for i := range q.pointers {
|
||||
p := &q.pointers[i]
|
||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
||||
if p.handlers[i] == k {
|
||||
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
|
||||
q.init()
|
||||
if e.Type == pointer.Cancel {
|
||||
q.pointers = q.pointers[:0]
|
||||
for k := range q.handlers {
|
||||
q.dropHandler(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
pidx := -1
|
||||
for i, p := range q.pointers {
|
||||
if p.id == e.PointerID {
|
||||
pidx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pidx == -1 {
|
||||
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
||||
pidx = len(q.pointers) - 1
|
||||
}
|
||||
p := &q.pointers[pidx]
|
||||
if !p.pressed && (e.Type == pointer.Move || e.Type == pointer.Press) {
|
||||
p.handlers, q.scratch = q.scratch[:0], p.handlers
|
||||
q.opHit(&p.handlers, e.Position)
|
||||
if e.Type == pointer.Press {
|
||||
p.pressed = true
|
||||
}
|
||||
}
|
||||
if p.pressed {
|
||||
// Resolve grabs.
|
||||
q.scratch = q.scratch[:0]
|
||||
for i, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
if h.wantsGrab {
|
||||
q.scratch = append(q.scratch, p.handlers[:i]...)
|
||||
q.scratch = append(q.scratch, p.handlers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Drop handlers that lost their grab.
|
||||
for _, k := range q.scratch {
|
||||
q.dropHandler(k)
|
||||
}
|
||||
}
|
||||
if e.Type == pointer.Release {
|
||||
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
||||
}
|
||||
for i, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
e := e
|
||||
switch {
|
||||
case p.pressed && len(p.handlers) == 1:
|
||||
e.Priority = pointer.Grabbed
|
||||
case i == 0:
|
||||
e.Priority = pointer.Foremost
|
||||
}
|
||||
e.Hit = q.hit(h.area, e.Position)
|
||||
e.Position = h.transform.Invert().Transform(e.Position)
|
||||
events.Add(k, e)
|
||||
if e.Type == pointer.Release {
|
||||
// Release grab when the number of grabs reaches zero.
|
||||
grabs := 0
|
||||
for _, p := range q.pointers {
|
||||
if p.pressed && len(p.handlers) == 1 && p.handlers[0] == k {
|
||||
grabs++
|
||||
}
|
||||
}
|
||||
if grabs == 0 {
|
||||
h.wantsGrab = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (op *areaOp) Decode(d []byte) {
|
||||
if opconst.OpType(d[0]) != opconst.TypeArea {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
rect := image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: int(int32(bo.Uint32(d[2:]))),
|
||||
Y: int(int32(bo.Uint32(d[6:]))),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: int(int32(bo.Uint32(d[10:]))),
|
||||
Y: int(int32(bo.Uint32(d[14:]))),
|
||||
},
|
||||
}
|
||||
*op = areaOp{
|
||||
kind: areaKind(d[1]),
|
||||
rect: rect,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *areaOp) Hit(pos f32.Point) bool {
|
||||
min := f32.Point{
|
||||
X: float32(op.rect.Min.X),
|
||||
Y: float32(op.rect.Min.Y),
|
||||
}
|
||||
pos = pos.Sub(min)
|
||||
size := op.rect.Size()
|
||||
switch op.kind {
|
||||
case areaRect:
|
||||
if 0 <= pos.X && pos.X < float32(size.X) &&
|
||||
0 <= pos.Y && pos.Y < float32(size.Y) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case areaEllipse:
|
||||
rx := float32(size.X) / 2
|
||||
ry := float32(size.Y) / 2
|
||||
rx2 := rx * rx
|
||||
ry2 := ry * ry
|
||||
xh := pos.X - rx
|
||||
yk := pos.Y - ry
|
||||
if xh*xh*ry2+yk*yk*rx2 <= rx2*ry2 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
panic("invalid area kind")
|
||||
}
|
||||
}
|
||||
|
||||
func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePointerInput {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.InputOp{
|
||||
Grab: d[1] != 0,
|
||||
Key: refs[0].(ui.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func decodePassOp(d []byte) pointer.PassOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePass {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.PassOp{
|
||||
Pass: d[1] != 0,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package input
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"gioui.org/ui"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/key"
|
||||
"gioui.org/pointer"
|
||||
"gioui.org/system"
|
||||
)
|
||||
|
||||
// Router is a Queue implementation that routes events from
|
||||
// all available input sources to registered handlers.
|
||||
type Router struct {
|
||||
pqueue pointerQueue
|
||||
kqueue keyQueue
|
||||
|
||||
handlers handlerEvents
|
||||
|
||||
reader ops.Reader
|
||||
|
||||
// deliveredEvents tracks whether events has been returned to the
|
||||
// user from Events. If so, another frame is scheduled to flush
|
||||
// half-updated state. This is important when a 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.
|
||||
deliveredEvents bool
|
||||
// InvalidateOp summary.
|
||||
wakeup bool
|
||||
wakeupTime time.Time
|
||||
|
||||
// ProfileOp summary.
|
||||
profHandlers []ui.Key
|
||||
}
|
||||
|
||||
type handlerEvents struct {
|
||||
handlers map[ui.Key][]ui.Event
|
||||
hadEvents bool
|
||||
}
|
||||
|
||||
func (q *Router) Events(k ui.Key) []ui.Event {
|
||||
events := q.handlers.Events(k)
|
||||
q.deliveredEvents = q.deliveredEvents || len(events) > 0
|
||||
return events
|
||||
}
|
||||
|
||||
func (q *Router) Frame(ops *ui.Ops) {
|
||||
q.handlers.Clear()
|
||||
q.wakeup = false
|
||||
q.profHandlers = q.profHandlers[:0]
|
||||
q.reader.Reset(ops)
|
||||
q.collect()
|
||||
|
||||
q.pqueue.Frame(ops, &q.handlers)
|
||||
q.kqueue.Frame(ops, &q.handlers)
|
||||
if q.deliveredEvents || q.handlers.HadEvents() {
|
||||
q.deliveredEvents = false
|
||||
q.wakeup = true
|
||||
q.wakeupTime = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) Add(e ui.Event) bool {
|
||||
switch e := e.(type) {
|
||||
case pointer.Event:
|
||||
q.pqueue.Push(e, &q.handlers)
|
||||
case key.EditEvent, key.Event, key.FocusEvent:
|
||||
q.kqueue.Push(e, &q.handlers)
|
||||
}
|
||||
return q.handlers.HadEvents()
|
||||
}
|
||||
|
||||
func (q *Router) TextInputState() TextInputState {
|
||||
return q.kqueue.InputState()
|
||||
}
|
||||
|
||||
func (q *Router) collect() {
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeInvalidate:
|
||||
op := decodeInvalidateOp(encOp.Data)
|
||||
if !q.wakeup || op.At.Before(q.wakeupTime) {
|
||||
q.wakeup = true
|
||||
q.wakeupTime = op.At
|
||||
}
|
||||
case opconst.TypeProfile:
|
||||
op := decodeProfileOp(encOp.Data, encOp.Refs)
|
||||
q.profHandlers = append(q.profHandlers, op.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) AddProfile(e system.ProfileEvent) {
|
||||
for _, h := range q.profHandlers {
|
||||
q.handlers.Add(h, e)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Router) Profiling() bool {
|
||||
return len(q.profHandlers) > 0
|
||||
}
|
||||
|
||||
func (q *Router) WakeupTime() (time.Time, bool) {
|
||||
return q.wakeupTime, q.wakeup
|
||||
}
|
||||
|
||||
func (h *handlerEvents) init() {
|
||||
if h.handlers == nil {
|
||||
h.handlers = make(map[ui.Key][]ui.Event)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Set(k ui.Key, evts []ui.Event) {
|
||||
h.init()
|
||||
h.handlers[k] = evts
|
||||
h.hadEvents = true
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Add(k ui.Key, e ui.Event) {
|
||||
h.init()
|
||||
h.handlers[k] = append(h.handlers[k], e)
|
||||
h.hadEvents = true
|
||||
}
|
||||
|
||||
func (h *handlerEvents) HadEvents() bool {
|
||||
u := h.hadEvents
|
||||
h.hadEvents = false
|
||||
return u
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Events(k ui.Key) []ui.Event {
|
||||
if events, ok := h.handlers[k]; ok {
|
||||
h.handlers[k] = h.handlers[k][:0]
|
||||
return events
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handlerEvents) Clear() {
|
||||
for k := range h.handlers {
|
||||
delete(h.handlers, k)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeProfileOp(d []byte, refs []interface{}) system.ProfileOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypeProfile {
|
||||
panic("invalid op")
|
||||
}
|
||||
return system.ProfileOp{
|
||||
Key: refs[0].(ui.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeInvalidateOp(d []byte) ui.InvalidateOp {
|
||||
bo := binary.LittleEndian
|
||||
if opconst.OpType(d[0]) != opconst.TypeInvalidate {
|
||||
panic("invalid op")
|
||||
}
|
||||
var o ui.InvalidateOp
|
||||
if nanos := bo.Uint64(d[1:]); nanos > 0 {
|
||||
o.At = time.Unix(0, int64(nanos))
|
||||
}
|
||||
return o
|
||||
}
|
||||
Reference in New Issue
Block a user