blob: 0dcbf639aab119aea4c68fe797bc3ff9d6667c7c [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"
"golang.org/x/exp/shiny/gesture"
"golang.org/x/exp/shiny/screen"
"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"
"golang.org/x/mobile/event/size"
)
// 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.
)
func (a Axis) Horizontal() bool { return a&AxisHorizontal != 0 }
func (a Axis) Vertical() bool { return a&AxisVertical != 0 }
// WithLayoutData returns the given node after setting its embedded LayoutData
// field.
func WithLayoutData(n node.Node, layoutData interface{}) node.Node {
n.Wrappee().LayoutData = layoutData
return n
}
// RunWindowOptions are optional arguments to RunWindow.
type RunWindowOptions struct {
NewWindowOptions screen.NewWindowOptions
Theme theme.Theme
// TODO: some mechanism to process, filter and inject events. Perhaps a
// screen.EventFilter interface, and note that the zero value in this
// RunWindowOptions implicitly includes the gesture.EventFilter?
}
// TODO: how does RunWindow's caller inject or process events (whether general
// like lifecycle events or app-specific)? How does it stop the event loop when
// the app's work is done?
// TODO: how do widgets signal that they need repaint or relayout?
// TODO: propagate keyboard / mouse / touch events.
// RunWindow creates a new window for s, with the given widget tree, and runs
// its event loop.
//
// 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 (
nwo *screen.NewWindowOptions
t *theme.Theme
)
if opts != nil {
nwo = &opts.NewWindowOptions
t = &opts.Theme
}
w, err := s.NewWindow(nwo)
if err != nil {
return err
}
defer w.Release()
// paintPending batches up multiple NeedsPaint observations so that we
// paint only once (which can be relatively expensive) even when there are
// multiple input events in the queue, such as from a rapidly moving mouse
// or from the user typing many keys.
//
// TODO: determine somehow if there's an external paint event in the queue,
// not just internal paint events?
//
// TODO: if every package that uses package screen should basically
// throttle like this, should it be provided at a lower level?
paintPending := false
gef := gesture.EventFilter{EventDeque: w}
for {
e := w.NextEvent()
if e = gef.Filter(e); e == nil {
continue
}
switch e := e.(type) {
case lifecycle.Event:
root.OnLifecycleEvent(e)
if e.To == lifecycle.StageDead {
return nil
}
case gesture.Event, mouse.Event:
root.OnInputEvent(e, image.Point{})
case paint.Event:
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 dpi := float64(e.PixelsPerPt) * unit.PointsPerInch; dpi != t.GetDPI() {
newT := new(theme.Theme)
if t != nil {
*newT = *t
}
newT.DPI = dpi
t = newT
}
size := e.Size()
root.Measure(t, size.X, size.Y)
root.Wrappee().Rect = e.Bounds()
root.Layout(t)
// TODO: call Mark(node.MarkNeedsPaint)?
case error:
return e
}
if !paintPending && root.Wrappee().Marks.NeedsPaint() {
paintPending = true
w.Send(paint.Event{})
}
}
}