1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// 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{})
}
}
}
|