blob: 2fe23a024d8cdcce44afe56b06680d03be4cb2f8 [file] [log] [blame]
// 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 provides graphical user interface widgets.
//
// TODO: give an overview and some example code.
package widget // import "golang.org/x/exp/shiny/widget"
import (
"image"
)
func max(x, y int) int {
if x > y {
return x
}
return y
}
// Arity is the number of children a class of nodes can have.
type Arity uint8
const (
Leaf = Arity(0) // Leaf nodes have no children.
Shell = Arity(1) // Shell nodes have at most one child.
Container = Arity(2) // Container nodes can have any number of children.
)
// Axis is zero, one or both of the horizontal and vertical axes. For example,
// a widget may be scrollable in one of the four AxisXxx values.
type Axis uint8
const (
AxisNone = Axis(0)
AxisHorizontal = Axis(1)
AxisVertical = Axis(2)
AxisBoth = Axis(3) // AxisBoth equals AxisHorizontal | AxisVertical.
)
// Class is a class of nodes. For example, all button widgets would be Nodes
// whose Class values have the buttonClass type.
type Class interface {
// Arity returns the number of children a specific node can have.
Arity(n *Node) Arity
// Measure sets n.MeasuredSize to the natural size, in pixels, of a
// specific node (and its children) of this class.
Measure(n *Node, t *Theme)
// Layout lays out a specific node (and its children) of this class,
// setting the Node.Rect fields of each child. The n.Rect field should have
// previously been set during the parent node's layout.
Layout(n *Node, t *Theme)
// Paint paints a specific node (and its children) of this class onto a
// destination image. origin is the parent widget's origin with respect to
// the dst image's origin; n.Rect.Add(origin) will be n's 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(n *Node, t *Theme, dst *image.RGBA, origin image.Point)
// TODO: OnXxxEvent methods.
}
// LeafClassEmbed is designed to be embedded in struct types that implement the
// Class interface and have Leaf arity. It provides default implementations of
// the Class interface's methods.
type LeafClassEmbed struct{}
func (LeafClassEmbed) Arity(n *Node) Arity { return Leaf }
func (LeafClassEmbed) Measure(n *Node, t *Theme) { n.MeasuredSize = image.Point{} }
func (LeafClassEmbed) Layout(n *Node, t *Theme) {}
func (LeafClassEmbed) Paint(n *Node, t *Theme, dst *image.RGBA, origin image.Point) {}
// ShellClassEmbed is designed to be embedded in struct types that implement
// the Class interface and have Shell arity. It provides default
// implementations of the Class interface's methods.
type ShellClassEmbed struct{}
func (ShellClassEmbed) Arity(n *Node) Arity { return Shell }
func (ShellClassEmbed) Measure(n *Node, t *Theme) {
if c := n.FirstChild; c != nil {
c.Measure(t)
n.MeasuredSize = c.MeasuredSize
} else {
n.MeasuredSize = image.Point{}
}
}
func (ShellClassEmbed) Layout(n *Node, t *Theme) {
if c := n.FirstChild; c != nil {
c.Rect = n.Rect.Sub(n.Rect.Min)
c.Layout(t)
}
}
func (ShellClassEmbed) Paint(n *Node, t *Theme, dst *image.RGBA, origin image.Point) {
if c := n.FirstChild; c != nil {
c.Paint(t, dst, origin.Add(n.Rect.Min))
}
}
// ContainerClassEmbed is designed to be embedded in struct types that
// implement the Class interface and have Container arity. It provides default
// implementations of the Class interface's methods.
type ContainerClassEmbed struct{}
func (ContainerClassEmbed) Arity(n *Node) Arity { return Container }
func (ContainerClassEmbed) Measure(n *Node, t *Theme) {
mSize := image.Point{}
for c := n.FirstChild; c != nil; c = c.NextSibling {
c.Measure(t)
if mSize.X < c.MeasuredSize.X {
mSize.X = c.MeasuredSize.X
}
if mSize.Y < c.MeasuredSize.Y {
mSize.Y = c.MeasuredSize.Y
}
}
n.MeasuredSize = mSize
}
func (ContainerClassEmbed) Layout(n *Node, t *Theme) {
for c := n.FirstChild; c != nil; c = c.NextSibling {
c.Rect = image.Rectangle{Max: c.MeasuredSize}
c.Layout(t)
}
}
func (ContainerClassEmbed) Paint(n *Node, t *Theme, dst *image.RGBA, origin image.Point) {
for c := n.FirstChild; c != nil; c = c.NextSibling {
c.Paint(t, dst, origin.Add(n.Rect.Min))
}
}
// Node is an element of a widget tree.
//
// Every element of a widget tree is a node, but nodes can be of different
// classes. For example, a Flow node (i.e. one whose Class is FlowClass) can
// contain two Button nodes and an Image node.
type Node struct {
// 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 AppendChild and
// RemoveChild methods, which keeps the tree structure consistent.
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
// Class is class-specific data and behavior for this node. For example, a
// buttonClass-typed value may store an image and some text in this field.
// A progressBarClass-typed value may store a numerical percentage.
// Different class types would paint their nodes differently.
Class Class
// LayoutData is layout-specific data for this node. Its type is determined
// by its parent node's class. 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 Class.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 Class.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
}
// Arity calls n.Class.Arity with n as its first argument.
func (n *Node) Arity() Arity {
return n.Class.Arity(n)
}
// Measure calls n.Class.Measure with n as its first argument.
func (n *Node) Measure(t *Theme) {
n.Class.Measure(n, t)
}
// Layout calls n.Class.Layout with n as its first argument.
func (n *Node) Layout(t *Theme) {
n.Class.Layout(n, t)
}
// Paint calls n.Class.Paint with n as its first argument.
func (n *Node) Paint(t *Theme, dst *image.RGBA, origin image.Point) {
n.Class.Paint(n, t, dst, origin)
}
// AppendChild adds a node c as a child of n.
//
// It will panic if c already has a parent or siblings.
func (n *Node) AppendChild(c *Node) {
if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil {
panic("widget: AppendChild called for an attached child Node")
}
switch n.Arity() {
case Leaf:
panic("widget: AppendChild called for a leaf parent Node")
case Shell:
if n.FirstChild != nil {
panic("widget: AppendChild called for a shell parent Node that already has a child Node")
}
}
last := n.LastChild
if last != nil {
last.NextSibling = c
} else {
n.FirstChild = c
}
n.LastChild = c
c.Parent = n
c.PrevSibling = last
}
// RemoveChild removes a node c that is a child of n. Afterwards, c will have
// no parent and no siblings.
//
// It will panic if c's parent is not n.
func (n *Node) RemoveChild(c *Node) {
if c.Parent != n {
panic("widget: RemoveChild called for a non-child Node")
}
if n.FirstChild == c {
n.FirstChild = c.NextSibling
}
if c.NextSibling != nil {
c.NextSibling.PrevSibling = c.PrevSibling
}
if n.LastChild == c {
n.LastChild = c.PrevSibling
}
if c.PrevSibling != nil {
c.PrevSibling.NextSibling = c.NextSibling
}
c.Parent = nil
c.PrevSibling = nil
c.NextSibling = nil
}