diff --git a/app/window.go b/app/window.go index 3d348b1f..9b4f9f29 100644 --- a/app/window.go +++ b/app/window.go @@ -339,8 +339,8 @@ func (w *Window) processFrame(d driver) { // If the window is inactive, the event is sent when the window becomes active. // // Note that Invalidate is intended for externally triggered updates, such as a -// response from a network request. InvalidateOp is more efficient for animation -// and similar internal updates. +// response from a network request. The [op.InvalidateCmd] command is more efficient +// for animation. // // Invalidate is safe for concurrent use. func (w *Window) Invalidate() { diff --git a/gesture/gesture.go b/gesture/gesture.go index 921440b1..5d9a1554 100644 --- a/gesture/gesture.go +++ b/gesture/gesture.go @@ -252,9 +252,6 @@ func (ClickEvent) ImplementsEvent() {} // as defined in io/pointer.InputOp. func (s *Scroll) Add(ops *op.Ops) { event.InputOp(ops, s) - if s.flinger.Active() { - op.InvalidateOp{}.Add(ops) - } } // Stop any remaining fling movement. @@ -336,6 +333,9 @@ func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, } } total += s.flinger.Tick(t) + if s.flinger.Active() { + q.Execute(op.InvalidateCmd{}) + } return total } diff --git a/internal/ops/ops.go b/internal/ops/ops.go index 78c1da35..e0cae3a7 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -55,7 +55,6 @@ const ( TypePopTransform TypePushOpacity TypePopOpacity - TypeInvalidate TypeImage TypePaint TypeColor @@ -405,7 +404,6 @@ var opProps = [0x100]opProp{ TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0}, TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0}, TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0}, - TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0}, TypeImage: {Size: TypeImageLen, NumRefs: 2}, TypePaint: {Size: TypePaintLen, NumRefs: 0}, TypeColor: {Size: TypeColorLen, NumRefs: 0}, @@ -459,8 +457,6 @@ func (t OpType) String() string { return "PushOpacity" case TypePopOpacity: return "PopOpacity" - case TypeInvalidate: - return "Invalidate" case TypeImage: return "Image" case TypePaint: diff --git a/io/input/router.go b/io/input/router.go index 545c14ba..698382c4 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -3,7 +3,6 @@ package input import ( - "encoding/binary" "image" "io" "strings" @@ -44,7 +43,7 @@ type Router struct { reader ops.Reader - // InvalidateOp summary. + // InvalidateCmd summary. wakeup bool wakeupTime time.Time @@ -261,7 +260,6 @@ func (q *Router) Frame(frame *op.Ops) { } } q.transfers = nil - q.wakeup = false q.deferring = false for _, h := range q.handlers { h.filter, h.nextFilter = h.nextFilter, h.filter @@ -470,6 +468,11 @@ func (q *Router) executeCommand(c Command) (event.Tag, stateChange) { case pointer.GrabCmd: tag = req.Tag state.pointerState, evts = q.pointer.queue.grab(state.pointerState, req) + case op.InvalidateCmd: + if !q.wakeup || req.At.Before(q.wakeupTime) { + q.wakeup = true + q.wakeupTime = req.At + } } return tag, stateChange{state: state, events: evts} } @@ -727,12 +730,6 @@ func (q *Router) collect() { var t f32.Affine2D for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { switch ops.OpType(encOp.Data[0]) { - case ops.TypeInvalidate: - op := decodeInvalidateOp(encOp.Data) - if !q.wakeup || op.At.Before(q.wakeupTime) { - q.wakeup = true - q.wakeupTime = op.At - } case ops.TypeSave: id := ops.DecodeSave(encOp.Data) if extra := id - len(q.savedTrans) + 1; extra > 0 { @@ -822,19 +819,9 @@ func (q *Router) collect() { // WakeupTime returns the most recent time for doing another frame, // as determined from the last call to Frame. func (q *Router) WakeupTime() (time.Time, bool) { - return q.wakeupTime, q.wakeup -} - -func decodeInvalidateOp(d []byte) op.InvalidateOp { - bo := binary.LittleEndian - if ops.OpType(d[0]) != ops.TypeInvalidate { - panic("invalid op") - } - var o op.InvalidateOp - if nanos := bo.Uint64(d[1:]); nanos > 0 { - o.At = time.Unix(0, int64(nanos)) - } - return o + w := q.wakeup + q.wakeup = false + return q.wakeupTime, w } func (s SemanticGestures) String() string { diff --git a/io/input/router_test.go b/io/input/router_test.go index 30929d32..0f856360 100644 --- a/io/input/router_test.go +++ b/io/input/router_test.go @@ -6,6 +6,7 @@ import ( "testing" "gioui.org/io/pointer" + "gioui.org/op" ) func TestNoFilterAllocs(t *testing.T) { @@ -22,3 +23,12 @@ func TestNoFilterAllocs(t *testing.T) { t.Fatalf("expected 0 AllocsPerOp, got %d", allocs) } } + +func TestRouterWakeup(t *testing.T) { + r := new(Router) + r.Source().Execute(op.InvalidateCmd{}) + r.Frame(new(op.Ops)) + if _, wake := r.WakeupTime(); !wake { + t.Errorf("InvalidateCmd did not trigger a redraw") + } +} diff --git a/op/op.go b/op/op.go index e183d271..e4af8f6c 100644 --- a/op/op.go +++ b/op/op.go @@ -53,7 +53,6 @@ The MacroOp records a list of operations to be executed later: ops := new(op.Ops) macro := op.Record(ops) // Record operations by adding them. - op.InvalidateOp{}.Add(ops) ... // End recording. call := macro.Stop() @@ -96,9 +95,9 @@ type CallOp struct { end ops.PC } -// InvalidateOp requests a redraw at the given time. Use +// InvalidateCmd requests a redraw at the given time. Use // the zero value to request an immediate redraw. -type InvalidateOp struct { +type InvalidateCmd struct { At time.Time } @@ -181,19 +180,6 @@ func (c CallOp) Add(o *Ops) { ops.AddCall(&o.Internal, c.ops, c.start, c.end) } -func (r InvalidateOp) Add(o *Ops) { - data := ops.Write(&o.Internal, ops.TypeRedrawLen) - data[0] = byte(ops.TypeInvalidate) - bo := binary.LittleEndian - // UnixNano cannot represent the zero time. - if t := r.At; !t.IsZero() { - nanos := t.UnixNano() - if nanos > 0 { - bo.PutUint64(data[1:], uint64(nanos)) - } - } -} - // Offset converts an offset to a TransformOp. func Offset(off image.Point) TransformOp { offf := f32.Pt(float32(off.X), float32(off.Y)) @@ -240,3 +226,5 @@ func (t TransformStack) Pop() { data := ops.Write(t.ops, ops.TypePopTransformLen) data[0] = byte(ops.TypePopTransform) } + +func (InvalidateCmd) ImplementsCommand() {} diff --git a/widget/editor.go b/widget/editor.go index eff06589..46175f05 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -692,8 +692,7 @@ func (e *Editor) layout(gtx layout.Context, textMaterial, selectMaterial op.Call const timePerBlink = time.Second / blinksPerSecond nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2)) if blinking { - redraw := op.InvalidateOp{At: nextBlink} - redraw.Add(gtx.Ops) + gtx.Execute(op.InvalidateCmd{At: nextBlink}) } e.showCaret = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2) } diff --git a/widget/material/button.go b/widget/material/button.go index d3c6278e..8221d6b9 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -258,7 +258,7 @@ func drawInk(gtx layout.Context, c widget.Press) { // Animate only ended presses, and presses that are fading in. if !c.End.IsZero() || sizet <= 1.0 { - op.InvalidateOp{}.Add(gtx.Ops) + gtx.Execute(op.InvalidateCmd{}) } if sizet > 1.0 { diff --git a/widget/material/loader.go b/widget/material/loader.go index 20f6ccde..58cfc14a 100644 --- a/widget/material/loader.go +++ b/widget/material/loader.go @@ -47,7 +47,7 @@ func (l LoaderStyle) Layout(gtx layout.Context) layout.Dimensions { }.Add(gtx.Ops) defer op.Offset(image.Pt(-radius, -radius)).Push(gtx.Ops).Pop() paint.PaintOp{}.Add(gtx.Ops) - op.InvalidateOp{}.Add(gtx.Ops) + gtx.Execute(op.InvalidateCmd{}) return layout.Dimensions{ Size: sz, }