io/pointer,io/router: replace AreaOp with clip.Op

Pointer hit areas and paint clip areas are separate concepts, but
similar enough to warrant merging. This change replaces pointer hit
areas with clip areas, so Gio is left with just one area concept (in
package op/clip).

The reason for separating the concepts in the original Gio release was
because of my being unsure general path/stroke hit areas would ever be
implemented, let alone efficient.

This change represents a change of mind, in the sense that it's better
to have an incomplete API than two separate area concepts.

Leave the deprecated pointer.Rect, pointer.Ellipse for temporary
backwards compatibility.

This is an API change. Most existing programs should continue to build
with this change, but may have to adjust to having all clip.Ops participate
in InputOp hit areas.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-10-25 11:31:04 +02:00
parent 28fc08a508
commit 29cea1db49
12 changed files with 127 additions and 140 deletions
+16 -12
View File
@@ -25,19 +25,23 @@ Leave, or Scroll):
Cancel events are always delivered.
Areas
Hit areas
The area operations are used for specifying the area where
subsequent InputOp are active.
Clip operations from package op/clip are used for specifying
hit areas where subsequent InputOps are active.
For example, to set up a rectangular hit area:
For example, to set up a handler with a rectangular hit area:
r := image.Rectangle{...}
pointer.Rect(r).Add(ops)
area := clip.Rect(r).Push(ops)
pointer.InputOp{Tag: h}.Add(ops)
area.Pop()
Note that areas compound: the effective area of multiple area
operations is the intersection of the areas.
Note that hit areas behave similar to painting: the effective area of a stack
of multiple area operations is the intersection of the areas.
BUG: Clip operations other than clip.Rect and clip.Ellipse are approximated
with their bounding boxes.
Matching events
@@ -49,11 +53,11 @@ For example:
ops := new(op.Ops)
var h1, h2 *Handler
area := pointer.Rect(...).Push(ops)
area := clip.Rect(...).Push(ops)
pointer.InputOp{Tag: h1}.Add(Ops)
area.Pop()
area := pointer.Rect(...).Push(ops)
area := clip.Rect(...).Push(ops)
pointer.InputOp{Tag: h2}.Add(ops)
area.Pop()
@@ -66,9 +70,9 @@ parent areas all contain the event is considered.
Then, every handler attached to the area is matched with the event.
If all attached handlers are marked pass-through, the matching repeats with the
next foremost (sibling) area. Otherwise the matching repeats with the parent
area.
If all attached handlers are marked pass-through or if no handlers are
attached, the matching repeats with the next foremost (sibling) area. Otherwise
the matching repeats with the parent area.
In the example above, all events will go to h2 because it and h1 are siblings
and none are pass-through.
+20 -54
View File
@@ -14,6 +14,7 @@ import (
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/op"
"gioui.org/op/clip"
)
// Event is a pointer event.
@@ -42,20 +43,6 @@ type Event struct {
Modifiers key.Modifiers
}
// AreaOp pushes the current hit area to the stack and updates it to the
// intersection of the current hit area and the transformed area.
type AreaOp struct {
kind areaKind
rect image.Rectangle
}
// AreaStack represents an AreaOp on the stack of areas.
type AreaStack struct {
ops *ops.Ops
id ops.StackID
macroID int
}
// PassOp sets the pass-through mode. InputOps added while the pass-through
// mode is set don't block events to siblings.
type PassOp struct {
@@ -107,9 +94,6 @@ type Buttons uint8
// CursorName is the name of a cursor.
type CursorName string
// Must match app/internal/input.areaKind
type areaKind uint8
const (
// CursorDefault is the default cursor.
CursorDefault CursorName = ""
@@ -178,50 +162,32 @@ const (
ButtonTertiary
)
const (
areaRect areaKind = iota
areaEllipse
)
// Rect constructs a rectangular hit area.
func Rect(size image.Rectangle) AreaOp {
return AreaOp{
kind: areaRect,
rect: size,
}
//
// Deprecated: use clip.Rect instead.
func Rect(size image.Rectangle) clip.Op {
return clip.Rect(size).Op()
}
// Ellipse constructs an ellipsoid hit area.
func Ellipse(size image.Rectangle) AreaOp {
return AreaOp{
kind: areaEllipse,
rect: size,
//
// Deprecated: use clip.Ellipse instead.
func Ellipse(size image.Rectangle) clip.Ellipse {
return clip.Ellipse(frect(size))
}
// frect converts a rectangle to a f32.Rectangle.
func frect(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: fpt(r.Min), Max: fpt(r.Max),
}
}
// Push the current area to the stack and intersects the current area with the
// area represented by o.
func (a AreaOp) Push(o *op.Ops) AreaStack {
id, macroID := ops.PushOp(&o.Internal, ops.AreaStack)
a.add(o, true)
return AreaStack{ops: &o.Internal, id: id, macroID: macroID}
}
func (a AreaOp) add(o *op.Ops, push bool) {
data := ops.Write(&o.Internal, ops.TypeAreaLen)
data[0] = byte(ops.TypeArea)
data[1] = byte(a.kind)
bo := binary.LittleEndian
bo.PutUint32(data[2:], uint32(a.rect.Min.X))
bo.PutUint32(data[6:], uint32(a.rect.Min.Y))
bo.PutUint32(data[10:], uint32(a.rect.Max.X))
bo.PutUint32(data[14:], uint32(a.rect.Max.Y))
}
func (o AreaStack) Pop() {
ops.PopOp(o.ops, ops.AreaStack, o.id, o.macroID)
data := ops.Write(o.ops, ops.TypePopAreaLen)
data[0] = byte(ops.TypePopArea)
// fpt converts an point to a f32.Point.
func fpt(p image.Point) f32.Point {
return f32.Point{
X: float32(p.X), Y: float32(p.Y),
}
}
// Push the current pass mode to the pass stack and set the pass mode.
-14
View File
@@ -4,8 +4,6 @@ package pointer
import (
"testing"
"gioui.org/op"
)
func TestTypeString(t *testing.T) {
@@ -33,15 +31,3 @@ func TestTypeString(t *testing.T) {
})
}
}
func TestTransformChecks(t *testing.T) {
defer func() {
if err := recover(); err == nil {
t.Error("cross-macro Pop didn't panic")
}
}()
var ops op.Ops
area := AreaOp{}.Push(&ops)
op.Record(&ops)
area.Pop()
}