// 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() } }