1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package widget
import (
"image"
"image/draw"
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget/node"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/lifecycle"
)
// TODO: scrolling.
// Sheet is a shell widget that provides *image.RGBA pixel buffers (analogous
// to blank sheets of paper) for its descendent widgets to paint on, via their
// PaintBase methods. Such buffers may be cached and their contents re-used for
// multiple paints, which can make scrolling and animation smoother and more
// efficient.
//
// A simple app may have only one Sheet, near the root of its widget tree. A
// more complicated app may have multiple Sheets. For example, consider a text
// editor consisting of a small header bar and a large text widget. Those two
// nodes may be backed by two separate Sheets, since scrolling the latter
// should not scroll the former.
type Sheet struct {
node.ShellEmbed
buf screen.Buffer
tex screen.Texture
}
// NewSheet returns a new Sheet widget.
func NewSheet(inner node.Node) *Sheet {
w := &Sheet{}
w.Wrapper = w
if inner != nil {
w.Insert(inner, nil)
}
return w
}
func (w *Sheet) release() {
if w.buf != nil {
w.buf.Release()
w.buf = nil
}
if w.tex != nil {
w.tex.Release()
w.tex = nil
}
}
func (w *Sheet) Paint(ctx *node.PaintContext, origin image.Point) (retErr error) {
w.Marks.UnmarkNeedsPaint()
c := w.FirstChild
if c == nil {
w.release()
return nil
}
fresh, size := false, w.Rect.Size()
if w.buf != nil && w.buf.Size() != size {
w.release()
}
if w.buf == nil {
w.buf, retErr = ctx.Screen.NewBuffer(size)
if retErr != nil {
w.release()
return retErr
}
w.tex, retErr = ctx.Screen.NewTexture(size)
if retErr != nil {
w.release()
return retErr
}
fresh = true
}
if fresh || c.Marks.NeedsPaintBase() {
c.Wrapper.PaintBase(&node.PaintBaseContext{
Theme: ctx.Theme,
Dst: w.buf.RGBA(),
}, image.Point{})
}
w.tex.Upload(image.Point{}, w.buf, w.buf.Bounds())
src2dst := ctx.Src2Dst
translate(&src2dst,
float64(origin.X+w.Rect.Min.X),
float64(origin.Y+w.Rect.Min.Y),
)
// TODO: should draw.Over be configurable?
ctx.Drawer.Draw(src2dst, w.tex, w.tex.Bounds(), draw.Over, nil)
return c.Wrapper.Paint(ctx, origin.Add(w.Rect.Min))
}
func translate(a *f64.Aff3, tx, ty float64) {
a[2] += a[0]*tx + a[1]*ty
a[5] += a[3]*tx + a[4]*ty
}
func (w *Sheet) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
w.Marks.UnmarkNeedsPaintBase()
// Do not recursively call PaintBase on our children. We create our own
// buffers, and Sheet.Paint will call PaintBase with our PaintBaseContext
// instead of our ancestor's.
return nil
}
func (w *Sheet) OnChildMarked(child node.Node, newMarks node.Marks) {
if newMarks&node.MarkNeedsPaintBase != 0 {
newMarks &^= node.MarkNeedsPaintBase
newMarks |= node.MarkNeedsPaint
}
w.Mark(newMarks)
}
func (w *Sheet) OnLifecycleEvent(e lifecycle.Event) {
if e.Crosses(lifecycle.StageVisible) == lifecycle.CrossOff {
w.release()
}
}
|