summaryrefslogtreecommitdiffstats
path: root/shiny/widget/sheet.go
blob: f55696b3de4078cf3715aac14f1d04012d88af9d (plain)
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()
	}
}