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