shiny/widget/node: split Paint into Paint and PaintBase.
Also introduce the Sheet widget.
Follow-up changes will implement smooth scrolling, where cached textures
are simply re-drawn at different offsets, instead of a window-sized
buffer being drawn on and uploaded from scratch on every paint cycle.
Change-Id: Iea291a064200cb658004846cd6cf075131644464
Reviewed-on: https://go-review.googlesource.com/25321
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/example/basicgl/main.go b/shiny/example/basicgl/main.go
index 3542e37..38442e6 100644
--- a/shiny/example/basicgl/main.go
+++ b/shiny/example/basicgl/main.go
@@ -40,13 +40,13 @@
defer t1.cleanup()
defer t2.cleanup()
- body := flex.NewFlex(
+ body := widget.NewSheet(flex.NewFlex(
colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)),
widget.WithLayoutData(t1.w, flex.LayoutData{Grow: 1, Align: flex.AlignItemStretch}),
colorPatch(colornames.Blue, unit.Pixels(50), unit.Pixels(50)),
widget.WithLayoutData(t2.w, flex.LayoutData{MinSize: image.Point{80, 80}}),
colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)),
- )
+ ))
if err := widget.RunWindow(s, body, nil); err != nil {
log.Fatal(err)
diff --git a/shiny/example/gallery/main.go b/shiny/example/gallery/main.go
index f7cb66d..5cb247f 100644
--- a/shiny/example/gallery/main.go
+++ b/shiny/example/gallery/main.go
@@ -22,7 +22,6 @@
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget"
"golang.org/x/exp/shiny/widget/node"
- "golang.org/x/exp/shiny/widget/theme"
)
var uniforms = [...]*image.Uniform{
@@ -56,21 +55,22 @@
if w.index == len(uniforms) {
w.index = 0
}
- w.Mark(node.MarkNeedsPaint)
+ w.Mark(node.MarkNeedsPaintBase)
}
return node.Handled
}
-func (w *custom) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
- w.Marks.UnmarkNeedsPaint()
- draw.Draw(dst, w.Rect.Add(origin), uniforms[w.index], image.Point{}, draw.Src)
+func (w *custom) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ draw.Draw(ctx.Dst, w.Rect.Add(origin), uniforms[w.index], image.Point{}, draw.Src)
+ return nil
}
func main() {
log.SetFlags(0)
driver.Main(func(s screen.Screen) {
// TODO: create a bunch of standard widgets: buttons, labels, etc.
- w := newCustom()
+ w := widget.NewSheet(newCustom())
if err := widget.RunWindow(s, w, nil); err != nil {
log.Fatal(err)
}
diff --git a/shiny/example/imageview/main.go b/shiny/example/imageview/main.go
index 4811ec8..a756d8d 100644
--- a/shiny/example/imageview/main.go
+++ b/shiny/example/imageview/main.go
@@ -57,8 +57,8 @@
if err != nil {
log.Fatal(err)
}
- m := widget.NewImage(src, src.Bounds())
- if err := widget.RunWindow(s, m, nil); err != nil {
+ w := widget.NewSheet(widget.NewImage(src, src.Bounds()))
+ if err := widget.RunWindow(s, w, nil); err != nil {
log.Fatal(err)
}
})
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
index a04cd83..8b6d8a6 100644
--- a/shiny/example/layout/main.go
+++ b/shiny/example/layout/main.go
@@ -28,6 +28,7 @@
"golang.org/x/exp/shiny/unit"
"golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
"golang.org/x/exp/shiny/widget/theme"
)
@@ -82,7 +83,10 @@
vf.Measure(t)
vf.Rect = rgba.Bounds()
vf.Layout(t)
- vf.Paint(t, rgba, image.Point{})
+ vf.PaintBase(&node.PaintBaseContext{
+ Theme: t,
+ Dst: rgba,
+ }, image.Point{})
// Encode to PNG.
out, err := os.Create("out.png")
diff --git a/shiny/example/textedit/main.go b/shiny/example/textedit/main.go
index e8bb9fb..d36152b 100644
--- a/shiny/example/textedit/main.go
+++ b/shiny/example/textedit/main.go
@@ -25,7 +25,7 @@
func main() {
log.SetFlags(0)
driver.Main(func(s screen.Screen) {
- w := widget.NewText(prideAndPrejudice)
+ w := widget.NewSheet(widget.NewText(prideAndPrejudice))
if err := widget.RunWindow(s, w, nil); err != nil {
log.Fatal(err)
}
diff --git a/shiny/screen/screen.go b/shiny/screen/screen.go
index cbc1cb4..c4b326b 100644
--- a/shiny/screen/screen.go
+++ b/shiny/screen/screen.go
@@ -248,6 +248,10 @@
// contents can be further modified, once all outstanding calls to Upload
// have returned.
//
+ // TODO: make it optional that a Buffer's contents is preserved after
+ // Upload? Undoing a swizzle is a non-trivial amount of work, and can be
+ // redundant if the next paint cycle starts by clearing the buffer.
+ //
// When uploading to a Window, there will not be any visible effect until
// Publish is called.
Upload(dp image.Point, src Buffer, sr image.Rectangle)
diff --git a/shiny/widget/glwidget/glwidget.go b/shiny/widget/glwidget/glwidget.go
index f598502..c80ea6e 100644
--- a/shiny/widget/glwidget/glwidget.go
+++ b/shiny/widget/glwidget/glwidget.go
@@ -12,7 +12,6 @@
"golang.org/x/exp/shiny/driver/gldriver"
"golang.org/x/exp/shiny/widget/node"
- "golang.org/x/exp/shiny/widget/theme"
"golang.org/x/mobile/gl"
)
@@ -81,14 +80,16 @@
return w
}
-func (w *GL) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
+func (w *GL) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
if w.Rect.Empty() {
- return
+ return nil
}
- w.dst = dst
+ w.dst = ctx.Dst
w.origin = origin
w.draw(w)
w.dst = nil
+ return nil
}
// Publish renders the default framebuffer of Ctx onto the area of the
diff --git a/shiny/widget/image.go b/shiny/widget/image.go
index b7e9e98..8a085ff 100644
--- a/shiny/widget/image.go
+++ b/shiny/widget/image.go
@@ -43,10 +43,10 @@
w.MeasuredSize = w.SrcRect.Size()
}
-func (w *Image) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
- w.Marks.UnmarkNeedsPaint()
+func (w *Image) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
if w.Src == nil {
- return
+ return nil
}
// wRect is the widget's layout rectangle, in dst's coordinate space.
@@ -57,5 +57,6 @@
// upper-left corner of wRect.
sRect := w.SrcRect.Add(wRect.Min.Sub(w.SrcRect.Min))
- draw.Draw(dst, wRect.Intersect(sRect), w.Src, w.SrcRect.Min, draw.Over)
+ draw.Draw(ctx.Dst, wRect.Intersect(sRect), w.Src, w.SrcRect.Min, draw.Over)
+ return nil
}
diff --git a/shiny/widget/label.go b/shiny/widget/label.go
index 858ba15..5fca977 100644
--- a/shiny/widget/label.go
+++ b/shiny/widget/label.go
@@ -40,15 +40,15 @@
w.MeasuredSize.Y = m.Ascent.Ceil() + m.Descent.Ceil()
}
-func (w *Label) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
- w.Marks.UnmarkNeedsPaint()
- dst = dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
+func (w *Label) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ dst := ctx.Dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
if dst.Bounds().Empty() {
- return
+ return nil
}
- face := t.AcquireFontFace(theme.FontFaceOptions{})
- defer t.ReleaseFontFace(theme.FontFaceOptions{}, face)
+ face := ctx.Theme.AcquireFontFace(theme.FontFaceOptions{})
+ defer ctx.Theme.ReleaseFontFace(theme.FontFaceOptions{}, face)
m := face.Metrics()
ascent := m.Ascent.Ceil()
@@ -59,7 +59,7 @@
d := font.Drawer{
Dst: dst,
- Src: tc.Uniform(t),
+ Src: tc.Uniform(ctx.Theme),
Face: face,
Dot: fixed.Point26_6{
X: fixed.I(origin.X + w.Rect.Min.X),
@@ -67,4 +67,5 @@
},
}
d.DrawString(w.Text)
+ return nil
}
diff --git a/shiny/widget/node/node.go b/shiny/widget/node/node.go
index dcb77ec..03ebfff 100644
--- a/shiny/widget/node/node.go
+++ b/shiny/widget/node/node.go
@@ -45,7 +45,9 @@
"image"
"golang.org/x/exp/shiny/gesture"
+ "golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/math/f64"
"golang.org/x/mobile/event/mouse"
)
@@ -88,15 +90,49 @@
// previously been set during the parent node's layout.
Layout(t *theme.Theme)
- // Paint paints this node (and its children) onto a destination image.
+ // Paint paints this node (and its children). Painting is split into two
+ // passes: a base pass and an effects pass. The effects pass is often a
+ // no-op, and the bulk of the work is typically done in the base pass.
//
- // origin is the parent widget's origin with respect to the dst image's
+ // The base pass paints onto an *image.RGBA pixel buffer and ancestor nodes
+ // may choose to re-use the result. For example, re-painting a text widget
+ // after scrolling may copy cached buffers at different offsets, instead of
+ // painting the text's glyphs onto a fresh buffer. Similarly, animating the
+ // scale and opacity of an overlay can re-use the buffer from a previous
+ // base pass.
+ //
+ // The effects pass paints that part of the widget that can not or should
+ // not be cached. For example, the border of a text widget shouldn't move
+ // on the screen when that text widget is scrolled. The effects pass does
+ // not have a destination RGBA pixel buffer, and is limited to what a
+ // screen.Drawer provides: affine-transformed textures and uniform fills.
+ //
+ // TODO: app-specific OpenGL, if available, should be part of the effects
+ // pass. Is that exposed via the screen.Drawer or by another mechanism?
+ //
+ // The Paint method may create base pass RGBA pixel buffers, by calling
+ // ctx.Screen.NewBuffer. Many implementations won't, and instead assume
+ // that PaintBase is recursively triggered by an ancestor node such as a
+ // widget.Sheet. If it does create those RGBA pixel buffers, it is also
+ // responsible for calling PaintBase on this node (and its children). In
+ // any case, the Paint method should then paint any effects. Many widgets
+ // will neither create their own buffers nor have any effects, so their
+ // Paint methods will simply be the default implemention: do nothing except
+ // call Paint on its children. As mentioned above, the bulk of the work is
+ // typically done in PaintBase.
+ //
+ // origin is the parent widget's origin with respect to the ctx.Src2Dst
+ // transformation matrix; this node's Embed.Rect.Add(origin) will be its
+ // position and size in pre-transformed coordinate space.
+ Paint(ctx *PaintContext, origin image.Point) error
+
+ // PaintBase paints the base pass of this node (and its children) onto an
+ // RGBA pixel buffer.
+ //
+ // origin is the parent widget's origin with respect to the ctx.Dst image's
// origin; this node's Embed.Rect.Add(origin) will be its position and size
- // in dst's coordinate space.
- //
- // TODO: add a clip rectangle? Or rely on the RGBA.SubImage method to pass
- // smaller dst images?
- Paint(t *theme.Theme, dst *image.RGBA, origin image.Point)
+ // in ctx.Dst's coordinate space.
+ PaintBase(ctx *PaintBaseContext, origin image.Point) error
// Mark adds the given marks to this node. It calls OnChildMarked on its
// parent if new marks were added.
@@ -119,6 +155,27 @@
}
+// PaintContext is the context for the Node.Paint method.
+type PaintContext struct {
+ Theme *theme.Theme
+ Screen screen.Screen
+ Drawer screen.Drawer
+ Src2Dst f64.Aff3
+
+ // TODO: add a clip rectangle?
+
+ // TODO: add the DrawContext from the lifecycle event?
+}
+
+// PaintBaseContext is the context for the Node.PaintBase method.
+type PaintBaseContext struct {
+ Theme *theme.Theme
+ Dst *image.RGBA
+
+ // TODO: add a clip rectangle? Or rely on the RGBA.SubImage method to pass
+ // smaller Dst images?
+}
+
// LeafEmbed is designed to be embedded in struct types for nodes with no
// children.
type LeafEmbed struct{ Embed }
@@ -133,8 +190,14 @@
func (m *LeafEmbed) Layout(t *theme.Theme) {}
-func (m *LeafEmbed) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
+func (m *LeafEmbed) Paint(ctx *PaintContext, origin image.Point) error {
m.Marks.UnmarkNeedsPaint()
+ return nil
+}
+
+func (m *LeafEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ return nil
}
func (m *LeafEmbed) OnChildMarked(child Node, newMarks Marks) {}
@@ -170,11 +233,20 @@
}
}
-func (m *ShellEmbed) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
+func (m *ShellEmbed) Paint(ctx *PaintContext, origin image.Point) error {
m.Marks.UnmarkNeedsPaint()
if c := m.FirstChild; c != nil {
- c.Wrapper.Paint(t, dst, origin.Add(m.Rect.Min))
+ return c.Wrapper.Paint(ctx, origin.Add(m.Rect.Min))
}
+ return nil
+}
+
+func (m *ShellEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ if c := m.FirstChild; c != nil {
+ return c.Wrapper.PaintBase(ctx, origin.Add(m.Rect.Min))
+ }
+ return nil
}
func (m *ShellEmbed) OnChildMarked(child Node, newMarks Marks) {
@@ -217,12 +289,26 @@
}
}
-func (m *ContainerEmbed) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
+func (m *ContainerEmbed) Paint(ctx *PaintContext, origin image.Point) error {
m.Marks.UnmarkNeedsPaint()
origin = origin.Add(m.Rect.Min)
for c := m.FirstChild; c != nil; c = c.NextSibling {
- c.Wrapper.Paint(t, dst, origin)
+ if err := c.Wrapper.Paint(ctx, origin); err != nil {
+ return err
+ }
}
+ return nil
+}
+
+func (m *ContainerEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ origin = origin.Add(m.Rect.Min)
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ if err := c.Wrapper.PaintBase(ctx, origin); err != nil {
+ return err
+ }
+ }
+ return nil
}
func (m *ContainerEmbed) OnChildMarked(child Node, newMarks Marks) {
@@ -382,11 +468,15 @@
// MarkNeedsPaint marks this node as needing a Paint call.
MarkNeedsPaint = Marks(1 << 1)
- // TODO: have separate notions of 'base' and 'top' paint passes.
+
+ // MarkNeedsPaintBase marks this node as needing a PaintBase call.
+ MarkNeedsPaintBase = Marks(1 << 2)
)
func (m Marks) NeedsMeasureLayout() bool { return m&MarkNeedsMeasureLayout != 0 }
func (m Marks) NeedsPaint() bool { return m&MarkNeedsPaint != 0 }
+func (m Marks) NeedsPaintBase() bool { return m&MarkNeedsPaintBase != 0 }
func (m *Marks) UnmarkNeedsMeasureLayout() { *m &^= MarkNeedsMeasureLayout }
func (m *Marks) UnmarkNeedsPaint() { *m &^= MarkNeedsPaint }
+func (m *Marks) UnmarkNeedsPaintBase() { *m &^= MarkNeedsPaintBase }
diff --git a/shiny/widget/sheet.go b/shiny/widget/sheet.go
new file mode 100644
index 0000000..407a9de
--- /dev/null
+++ b/shiny/widget/sheet.go
@@ -0,0 +1,105 @@
+// 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"
+)
+
+// 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) Paint(ctx *node.PaintContext, origin image.Point) (retErr error) {
+ w.Marks.UnmarkNeedsPaint()
+ c := w.FirstChild
+ if c == nil {
+ if w.buf != nil {
+ w.buf.Release()
+ w.buf = nil
+ w.tex.Release()
+ w.tex = nil
+ }
+ return nil
+ }
+
+ fresh, size := false, w.Rect.Size()
+ if w.buf != nil && w.buf.Size() != size {
+ w.buf.Release()
+ w.buf = nil
+ w.tex.Release()
+ w.tex = nil
+ }
+ if w.buf == nil {
+ w.buf, retErr = ctx.Screen.NewBuffer(size)
+ if retErr != nil {
+ return retErr
+ }
+ w.tex, retErr = ctx.Screen.NewTexture(size)
+ if retErr != nil {
+ w.buf.Release()
+ w.buf = nil
+ 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())
+ // TODO: should draw.Over be configurable?
+ ctx.Drawer.Draw(ctx.Src2Dst, w.tex, w.tex.Bounds(), draw.Over, nil)
+
+ return c.Wrapper.Paint(ctx, origin.Add(w.Rect.Min))
+}
+
+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)
+}
diff --git a/shiny/widget/text.go b/shiny/widget/text.go
index e5b386e..9b87053 100644
--- a/shiny/widget/text.go
+++ b/shiny/widget/text.go
@@ -64,23 +64,23 @@
w.frame.SetMaxWidth(fixed.I(w.Rect.Dx() - 2*padding))
}
-func (w *Text) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
- w.Marks.UnmarkNeedsPaint()
- dst = dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
+func (w *Text) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ dst := ctx.Dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
if dst.Bounds().Empty() {
- return
+ return nil
}
- face := t.AcquireFontFace(theme.FontFaceOptions{})
- defer t.ReleaseFontFace(theme.FontFaceOptions{}, face)
+ face := ctx.Theme.AcquireFontFace(theme.FontFaceOptions{})
+ defer ctx.Theme.ReleaseFontFace(theme.FontFaceOptions{}, face)
m := face.Metrics()
ascent := m.Ascent.Ceil()
descent := m.Descent.Ceil()
height := m.Height.Ceil()
- padding := t.Pixels(unit.Ems(0.5)).Ceil()
+ padding := ctx.Theme.Pixels(unit.Ems(0.5)).Ceil()
- draw.Draw(dst, dst.Bounds(), t.GetPalette().Background(), image.Point{}, draw.Src)
+ draw.Draw(dst, dst.Bounds(), ctx.Theme.GetPalette().Background(), image.Point{}, draw.Src)
minDotY := fixed.I(dst.Bounds().Min.Y - descent)
maxDotY := fixed.I(dst.Bounds().Max.Y + ascent)
@@ -88,7 +88,7 @@
x0 := fixed.I(origin.X + w.Rect.Min.X + padding)
d := font.Drawer{
Dst: dst,
- Src: t.GetPalette().Foreground(),
+ Src: ctx.Theme.GetPalette().Foreground(),
Face: face,
Dot: fixed.Point26_6{
X: x0,
@@ -100,7 +100,7 @@
for l := p.FirstLine(f); l != nil; l = l.Next(f) {
if d.Dot.Y > minDotY {
if d.Dot.Y >= maxDotY {
- return
+ return nil
}
for b := l.FirstBox(f); b != nil; b = b.Next(f) {
d.DrawBytes(b.TrimmedText(f))
@@ -111,4 +111,11 @@
d.Dot.Y += fixed.I(height)
}
}
+ return nil
+}
+
+func (w *Text) Paint(ctx *node.PaintContext, origin image.Point) error {
+ // TODO: draw an optional border, whose color depends on whether w has the
+ // keyboard focus.
+ return w.LeafEmbed.Paint(ctx, origin)
}
diff --git a/shiny/widget/uniform.go b/shiny/widget/uniform.go
index 175aad9..c8fadec 100644
--- a/shiny/widget/uniform.go
+++ b/shiny/widget/uniform.go
@@ -31,13 +31,15 @@
return w
}
-func (w *Uniform) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
- w.Marks.UnmarkNeedsPaint()
+func (w *Uniform) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
if w.ThemeColor != nil {
+ src := w.ThemeColor.Uniform(ctx.Theme)
// TODO: should draw.Src be draw.Over?
- draw.Draw(dst, w.Rect.Add(origin), w.ThemeColor.Uniform(t), image.Point{}, draw.Src)
+ draw.Draw(ctx.Dst, w.Rect.Add(origin), src, image.Point{}, draw.Src)
}
if c := w.FirstChild; c != nil {
- c.Wrapper.Paint(t, dst, origin.Add(w.Rect.Min))
+ return c.Wrapper.PaintBase(ctx, origin.Add(w.Rect.Min))
}
+ return nil
}
diff --git a/shiny/widget/widget.go b/shiny/widget/widget.go
index 374be03..4c0f3ce 100644
--- a/shiny/widget/widget.go
+++ b/shiny/widget/widget.go
@@ -15,6 +15,7 @@
"golang.org/x/exp/shiny/unit"
"golang.org/x/exp/shiny/widget/node"
"golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/math/f64"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
@@ -73,7 +74,6 @@
// A nil opts is valid and means to use the default option values.
func RunWindow(s screen.Screen, root node.Node, opts *RunWindowOptions) error {
var (
- buf screen.Buffer
nwo *screen.NewWindowOptions
t *theme.Theme
)
@@ -81,12 +81,6 @@
nwo = &opts.NewWindowOptions
t = &opts.Theme
}
- defer func() {
- if buf != nil {
- buf.Release()
- }
- }()
-
w, err := s.NewWindow(nwo)
if err != nil {
return err
@@ -115,6 +109,7 @@
switch e := e.(type) {
case lifecycle.Event:
+ // TODO: drop buffers and textures when we're not visible.
if e.To == lifecycle.StageDead {
return nil
}
@@ -123,23 +118,22 @@
root.OnInputEvent(e, image.Point{})
case paint.Event:
- if buf != nil {
- root.Paint(t, buf.RGBA(), image.Point{})
- w.Upload(image.Point{}, buf, buf.Bounds())
+ ctx := &node.PaintContext{
+ Theme: t,
+ Screen: s,
+ Drawer: w,
+ Src2Dst: f64.Aff3{
+ 1, 0, 0,
+ 0, 1, 0,
+ },
+ }
+ if err := root.Paint(ctx, image.Point{}); err != nil {
+ return err
}
w.Publish()
paintPending = false
case size.Event:
- if buf != nil {
- buf.Release()
- }
- var err error
- buf, err = s.NewBuffer(e.Size())
- if err != nil {
- return err
- }
-
if dpi := float64(e.PixelsPerPt) * unit.PointsPerInch; dpi != t.GetDPI() {
newT := new(theme.Theme)
if t != nil {