Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
509 lines (434 sloc) 17 KB
// 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 node provides the structure for a tree of heterogenous widget nodes.
//
// Most programmers should not need to import this package, only the top-level
// widget package. Only those that write custom widgets need to explicitly
// refer to the Node, Embed and related types.
//
// The Node interface is usually implemented by struct types that embed one of
// LeafEmbed, ShellEmbed or ContainerEmbed (all of which themselves embed an
// Embed), providing default implementations of all of Node's methods.
//
// The split between an outer wrapper (Node) interface type and an inner
// wrappee (Embed) struct type enables heterogenous nodes, such as a buttons
// and labels, in a widget tree where every node contains common fields such as
// position, size and tree structure links (parent, siblings and children).
//
// In a traditional object-oriented type system, this might be represented by
// the Button and Label types both subclassing the Node type. Go does not have
// inheritance, so the outer / inner split is composed explicitly. For example,
// the concrete Button type is a struct type that embeds an XxxEmbed (such as
// LeafEmbed), and the NewButton function sets the inner Embed's Wrapper field
// to point back to the outer value.
//
// There are three layers here (Button embeds LeafEmbed embeds Embed) instead
// of two. The intermediate layer exists because there needs to be a place to
// provide default implementations of methods like Measure, but that place
// shouldn't be the inner-most type (Embed), otherwise it'd be too easy to
// write subtly incorrect code like:
//
// for c := w.FirstChild; c != nil; c = c.NextSibling {
// c.Measure(etc) // This should instead be c.Wrapper.Measure(etc).
// }
//
// In any case, most programmers that want to construct a widget tree should
// not need to know this detail. It usually suffices to call functions such as
// widget.NewButton or widget.NewLabel, and then parent.Insert(button, nil).
//
// See the example/gallery program for some example code for a custom widget.
package node // import "golang.org/x/exp/shiny/widget/node"
import (
"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/lifecycle"
"golang.org/x/mobile/event/mouse"
)
// EventHandled is whether or not an input event (a key, mouse, touch or
// gesture event) was handled by a widget. If it was not handled, the event is
// propagated along the widget tree.
type EventHandled bool
const (
NotHandled = EventHandled(false)
Handled = EventHandled(true)
)
// NoHint means that there is no width or height hint in a Measure call.
const NoHint = -1
// Node is a node in the widget tree.
type Node interface {
// Wrappee returns the inner (embedded) type that is wrapped by this type.
Wrappee() *Embed
// Insert adds a node c as a child of this node. If nextSibling is nil, c
// will be inserted at the end of this node's children. Otherwise, c will
// be inserted such that its next sibling is nextSibling.
//
// It will panic if c already has a parent or siblings.
Insert(c, nextSibling Node)
// Remove removes a node c that is a child of this node. Afterwards, c will
// have no parent and no siblings.
//
// It will panic if c's parent is not this node.
Remove(c Node)
// Measure sets this node's Embed.MeasuredSize to its natural size, taking
// its children into account.
//
// Some nodes' natural height might depend on their imposed width, such as
// a text widget word-wrapping its contents. The caller may provide hints
// that the parent can override the child's natural size in the width,
// height or both directions. A negative value means that there is no hint.
// For example, a container might lay out its children to all have the same
// width, and could pass that width as the widthHint argument.
Measure(t *theme.Theme, widthHint, heightHint int)
// Layout lays out this node (and its children), setting the Embed.Rect
// fields of each child. This node's Embed.Rect field should have
// previously been set during the parent node's layout.
Layout(t *theme.Theme)
// 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.
//
// 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 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.
Mark(m Marks)
// OnChildMarked handles a child being given new marks. By default, marks
// are propagated up the node tree towards the root. For example, a child
// being marked for needing paint will cause the parent being marked for
// needing paint.
OnChildMarked(child Node, newMarks Marks)
// OnLifecycleEvent propagates a lifecycle event to a node (and its
// children).
OnLifecycleEvent(e lifecycle.Event)
// OnInputEvent handles a key, mouse, touch or gesture event.
//
// origin is the parent widget's origin with respect to the event origin;
// this node's Embed.Rect.Add(origin) will be its position and size in
// event coordinate space.
OnInputEvent(e interface{}, origin image.Point) EventHandled
// TODO: other OnXxxEvent methods?
}
// 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 }
func (m *LeafEmbed) Insert(c, nextSibling Node) {
panic("node: Insert called for a leaf parent")
}
func (m *LeafEmbed) Remove(c Node) { m.remove(c) }
func (m *LeafEmbed) Measure(t *theme.Theme, widthHint, heightHint int) { m.MeasuredSize = image.Point{} }
func (m *LeafEmbed) Layout(t *theme.Theme) {}
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) {}
func (m *LeafEmbed) OnLifecycleEvent(e lifecycle.Event) {}
func (m *LeafEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled { return NotHandled }
// ShellEmbed is designed to be embedded in struct types for nodes with at most
// one child.
type ShellEmbed struct{ Embed }
func (m *ShellEmbed) Insert(c, nextSibling Node) {
if m.FirstChild != nil {
panic("node: Insert called for a shell parent that already has a child")
}
m.insert(c, nextSibling)
}
func (m *ShellEmbed) Remove(c Node) { m.remove(c) }
func (m *ShellEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
if c := m.FirstChild; c != nil {
c.Wrapper.Measure(t, widthHint, heightHint)
m.MeasuredSize = c.MeasuredSize
} else {
m.MeasuredSize = image.Point{}
}
}
func (m *ShellEmbed) Layout(t *theme.Theme) {
if c := m.FirstChild; c != nil {
c.Rect = m.Rect.Sub(m.Rect.Min)
c.Wrapper.Layout(t)
}
}
func (m *ShellEmbed) Paint(ctx *PaintContext, origin image.Point) error {
m.Marks.UnmarkNeedsPaint()
if c := m.FirstChild; c != nil {
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) {
m.Mark(newMarks)
}
func (m *ShellEmbed) OnLifecycleEvent(e lifecycle.Event) {
if c := m.FirstChild; c != nil {
c.Wrapper.OnLifecycleEvent(e)
}
}
func (m *ShellEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled {
if c := m.FirstChild; c != nil {
return c.Wrapper.OnInputEvent(e, origin.Add(m.Rect.Min))
}
return NotHandled
}
// ContainerEmbed is designed to be embedded in struct types for nodes with any
// number of children.
type ContainerEmbed struct{ Embed }
func (m *ContainerEmbed) Insert(c, nextSibling Node) { m.insert(c, nextSibling) }
func (m *ContainerEmbed) Remove(c Node) { m.remove(c) }
func (m *ContainerEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
mSize := image.Point{}
for c := m.FirstChild; c != nil; c = c.NextSibling {
c.Wrapper.Measure(t, NoHint, NoHint)
if mSize.X < c.MeasuredSize.X {
mSize.X = c.MeasuredSize.X
}
if mSize.Y < c.MeasuredSize.Y {
mSize.Y = c.MeasuredSize.Y
}
}
m.MeasuredSize = mSize
}
func (m *ContainerEmbed) Layout(t *theme.Theme) {
for c := m.FirstChild; c != nil; c = c.NextSibling {
c.Rect = image.Rectangle{Max: c.MeasuredSize}
c.Wrapper.Layout(t)
}
}
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 {
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) {
m.Mark(newMarks)
}
func (m *ContainerEmbed) OnLifecycleEvent(e lifecycle.Event) {
for c := m.FirstChild; c != nil; c = c.NextSibling {
c.Wrapper.OnLifecycleEvent(e)
}
}
func (m *ContainerEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled {
origin = origin.Add(m.Rect.Min)
var p image.Point
switch e := e.(type) {
case gesture.Event:
p = image.Point{
X: int(e.CurrentPos.X) - origin.X,
Y: int(e.CurrentPos.Y) - origin.Y,
}
case mouse.Event:
p = image.Point{
X: int(e.X) - origin.X,
Y: int(e.Y) - origin.Y,
}
}
// Iterate backwards. Later children have priority over earlier children,
// as later ones are usually drawn over earlier ones.
for c := m.LastChild; c != nil; c = c.PrevSibling {
if p.In(c.Rect) && c.Wrapper.OnInputEvent(e, origin) == Handled {
return Handled
}
}
return NotHandled
}
// Embed is the common data structure for each node in a widget tree.
type Embed struct {
// Wrapper is the outer type that wraps (embeds) this type. It should not
// be nil.
Wrapper Node
// Parent, FirstChild, LastChild, PrevSibling and NextSibling describe the
// widget tree structure.
//
// These fields are exported to enable walking the node tree, but they
// should not be modified directly. Instead, call the Insert and Remove
// methods, which keeps the tree structure consistent.
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Embed
// LayoutData is layout-specific data for this node. Its type is determined
// by its parent node's type. For example, each child of a Flow may hold a
// FlowLayoutData in this field.
LayoutData interface{}
// TODO: add commentary about the Measure / Layout / Paint model, and about
// the lifetime of the MeasuredSize and Rect fields, and when user code can
// access and/or modify them. At some point a new cycle begins, a call to
// measure is necessary, and using MeasuredSize is incorrect (unless you're
// trying to recall something about the past).
// MeasuredSize is the widget's natural size, in pixels, as calculated by
// the most recent Measure call.
MeasuredSize image.Point
// Rect is the widget's position and actual (as opposed to natural) size,
// in pixels, as calculated by the most recent Layout call on its parent
// node. A parent may lay out a child at a size different to its natural
// size in order to satisfy a layout constraint, such as a row of buttons
// expanding to fill a panel's width.
//
// The position (Rectangle.Min) is relative to its parent node. This is not
// necessarily the same as relative to the screen's, window's or image
// buffer's origin.
Rect image.Rectangle
// Marks are a bitfield of node state, such as whether it needs measure,
// layout or paint.
Marks Marks
}
func (m *Embed) Wrappee() *Embed { return m }
// TODO: should insert and remove call Mark(MarkNeedsMeasureLayout | MarkNeedsPaint)?
func (m *Embed) insert(c, nextSibling Node) {
n := c.Wrappee()
if n.Parent != nil || n.PrevSibling != nil || n.NextSibling != nil {
panic("node: Insert called for an attached child")
}
n.Parent = m
if nextSibling == nil {
last := m.LastChild
if last != nil {
last.NextSibling = n
} else {
m.FirstChild = n
}
m.LastChild = n
n.PrevSibling = last
return
}
o := nextSibling.Wrappee()
if o.Parent != m {
panic("node: Insert called for a non-sibling nextSibling node")
}
if o.PrevSibling == nil {
o.PrevSibling = n
n.NextSibling = o
m.FirstChild = n
return
}
o.PrevSibling.NextSibling = n
n.PrevSibling = o.PrevSibling
n.NextSibling = o
o.PrevSibling = n
}
func (m *Embed) remove(c Node) {
n := c.Wrappee()
if n.Parent != m {
panic("node: Remove called for a non-child node")
}
if m.FirstChild == n {
m.FirstChild = n.NextSibling
}
if n.NextSibling != nil {
n.NextSibling.PrevSibling = n.PrevSibling
}
if m.LastChild == n {
m.LastChild = n.PrevSibling
}
if n.PrevSibling != nil {
n.PrevSibling.NextSibling = n.NextSibling
}
n.Parent = nil
n.PrevSibling = nil
n.NextSibling = nil
}
func (m *Embed) Mark(marks Marks) {
oldMarks := m.Marks
m.Marks |= marks
changedMarks := m.Marks ^ oldMarks
if changedMarks != 0 && m.Parent != nil {
m.Parent.Wrapper.OnChildMarked(m.Wrapper, changedMarks)
}
}
// Marks are a bitfield of node state, such as whether it needs measure, layout
// or paint.
type Marks uint32
const (
// MarkNeedsMeasureLayout marks this node as needing Measure and Layout
// calls.
MarkNeedsMeasureLayout = Marks(1 << 0)
// TODO: use this.
// MarkNeedsPaint marks this node as needing a Paint call.
MarkNeedsPaint = Marks(1 << 1)
// 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 }
You can’t perform that action at this time.