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