summaryrefslogtreecommitdiff
path: root/BBB_GOFILES
diff options
context:
space:
mode:
authorPietro Gagliardi <[email protected]>2018-08-26 09:55:07 -0400
committerPietro Gagliardi <[email protected]>2018-08-26 09:55:07 -0400
commit62ac2527732a01dfa6bd2c9523215c0ba3816641 (patch)
tree84244a69e048f79e4d9f134c121f4cf581200986 /BBB_GOFILES
parenta5a00c644c08a6e0f52740c3f2a280977929a285 (diff)
Moved all the Go files out of the way again, this time so we can migrate them to more proper cgo usage.
Diffstat (limited to 'BBB_GOFILES')
-rw-r--r--BBB_GOFILES/area.go111
-rw-r--r--BBB_GOFILES/areahandler.go324
-rw-r--r--BBB_GOFILES/box.go85
-rw-r--r--BBB_GOFILES/button.go65
-rw-r--r--BBB_GOFILES/checkbox.go76
-rw-r--r--BBB_GOFILES/colorbutton.go83
-rw-r--r--BBB_GOFILES/combobox.go67
-rw-r--r--BBB_GOFILES/control.go145
-rw-r--r--BBB_GOFILES/datetimepicker.go107
-rw-r--r--BBB_GOFILES/draw.go484
-rw-r--r--BBB_GOFILES/drawtext.go554
-rw-r--r--BBB_GOFILES/editablecombobox.go72
-rw-r--r--BBB_GOFILES/entry.go93
-rw-r--r--BBB_GOFILES/fontbutton.go69
-rw-r--r--BBB_GOFILES/form.go71
-rw-r--r--BBB_GOFILES/grid.go96
-rw-r--r--BBB_GOFILES/group.go80
-rw-r--r--BBB_GOFILES/image.go58
-rw-r--r--BBB_GOFILES/label.go44
-rw-r--r--BBB_GOFILES/link_darwin_amd64.go7
-rw-r--r--BBB_GOFILES/main.go132
-rw-r--r--BBB_GOFILES/multilineentry.go97
-rw-r--r--BBB_GOFILES/progressbar.go39
-rw-r--r--BBB_GOFILES/radiobuttons.go66
-rw-r--r--BBB_GOFILES/separator.go37
-rw-r--r--BBB_GOFILES/slider.go58
-rw-r--r--BBB_GOFILES/spinbox.go58
-rw-r--r--BBB_GOFILES/stddialogs.go41
-rw-r--r--BBB_GOFILES/tab.go87
-rw-r--r--BBB_GOFILES/tablemodel.go243
-rw-r--r--BBB_GOFILES/util.go45
-rw-r--r--BBB_GOFILES/window.go125
-rw-r--r--BBB_GOFILES/zz_controls.go222
-rw-r--r--BBB_GOFILES/zz_drawtext.go167
-rw-r--r--BBB_GOFILES/zz_histogram.go253
35 files changed, 4361 insertions, 0 deletions
diff --git a/BBB_GOFILES/area.go b/BBB_GOFILES/area.go
new file mode 100644
index 0000000..f7ed41c
--- /dev/null
+++ b/BBB_GOFILES/area.go
@@ -0,0 +1,111 @@
+// 16 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Area is a Control that represents a blank canvas that a program
+// can draw on as it wishes. Areas also receive keyboard and mouse
+// events, and programs can react to those as they see fit. Drawing
+// and event handling are handled through an instance of a type
+// that implements AreaHandler that every Area has; see AreaHandler
+// for details.
+//
+// There are two types of areas. Non-scrolling areas are rectangular
+// and have no scrollbars. Programs can draw on and get mouse
+// events from any point in the Area, and the size of the Area is
+// decided by package ui itself, according to the layout of controls
+// in the Window the Area is located in and the size of said Window.
+// There is no way to query the Area's size or be notified when its
+// size changes; instead, you are given the area size as part of the
+// draw and mouse event handlers, for use solely within those
+// handlers.
+//
+// Scrolling areas have horziontal and vertical scrollbars. The amount
+// that can be scrolled is determined by the area's size, which is
+// decided by the programmer (both when creating the Area and by
+// a call to SetSize). Only a portion of the Area is visible at any time;
+// drawing and mouse events are automatically adjusted to match
+// what portion is visible, so you do not have to worry about scrolling
+// in your event handlers. AreaHandler has more information.
+//
+// The internal coordinate system of an Area is points, which are
+// floating-point and device-independent. For more details, see
+// AreaHandler. The size of a scrolling Area must be an exact integer
+// number of points (that is, you cannot have an Area that is 32.5
+// points tall) and thus the parameters to NewScrollingArea and
+// SetSize are ints. All other instances of points in parameters and
+// structures (including sizes of drawn objects) are float64s.
+type Area struct {
+ ControlBase
+ a *C.uiArea
+ ah *C.uiAreaHandler
+ scrolling bool
+}
+
+// NewArea creates a new non-scrolling Area.
+func NewArea(handler AreaHandler) *Area {
+ a := new(Area)
+ a.scrolling = false
+ a.ah = registerAreaHandler(handler)
+
+ a.a = C.uiNewArea(a.ah)
+
+ a.ControlBase = NewControlBase(a, uintptr(unsafe.Pointer(a.a)))
+ return a
+}
+
+// NewScrollingArea creates a new scrolling Area of the given size,
+// in points.
+func NewScrollingArea(handler AreaHandler, width int, height int) *Area {
+ a := new(Area)
+ a.scrolling = true
+ a.ah = registerAreaHandler(handler)
+
+ a.a = C.uiNewScrollingArea(a.ah, C.int(width), C.int(height))
+
+ a.ControlBase = NewControlBase(a, uintptr(unsafe.Pointer(a.a)))
+ return a
+}
+
+// Destroy destroys the Area.
+func (a *Area) Destroy() {
+ unregisterAreaHandler(a.ah)
+ a.ControlBase.Destroy()
+}
+
+// SetSize sets the size of a scrolling Area to the given size, in points.
+// SetSize panics if called on a non-scrolling Area.
+func (a *Area) SetSize(width int, height int) {
+ if !a.scrolling {
+ panic("attempt to call SetSize on non-scrolling Area")
+ }
+ C.uiAreaSetSize(a.a, C.int(width), C.int(height))
+}
+
+// QueueRedrawAll queues the entire Area for redraw.
+// The Area is not redrawn before this function returns; it is
+// redrawn when next possible.
+func (a *Area) QueueRedrawAll() {
+ C.uiAreaQueueRedrawAll(a.a)
+}
+
+// ScrollTo scrolls the Area to show the given rectangle; what this
+// means is implementation-defined, but you can safely assume
+// that as much of the given rectangle as possible will be visible
+// after this call. (TODO verify this on OS X) ScrollTo panics if called
+// on a non-scrolling Area.
+func (a *Area) ScrollTo(x float64, y float64, width float64, height float64) {
+ if !a.scrolling {
+ panic("attempt to call ScrollTo on non-scrolling Area")
+ }
+ C.uiAreaScrollTo(a.a, C.double(x), C.double(y), C.double(width), C.double(height))
+}
+
+// TODO BeginUserWindowMove
+// TODO BeginUserWindowResize
diff --git a/BBB_GOFILES/areahandler.go b/BBB_GOFILES/areahandler.go
new file mode 100644
index 0000000..dbe739e
--- /dev/null
+++ b/BBB_GOFILES/areahandler.go
@@ -0,0 +1,324 @@
+// 13 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include <stdlib.h>
+// #include "ui.h"
+// #include "util.h"
+// extern void doAreaHandlerDraw(uiAreaHandler *, uiArea *, uiAreaDrawParams *);
+// extern void doAreaHandlerMouseEvent(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);
+// extern void doAreaHandlerMouseCrossed(uiAreaHandler *, uiArea *, int);
+// extern void doAreaHandlerDragBroken(uiAreaHandler *, uiArea *);
+// extern int doAreaHandlerKeyEvent(uiAreaHandler *, uiArea *, uiAreaKeyEvent *);
+// static inline uiAreaHandler *allocAreaHandler(void)
+// {
+// uiAreaHandler *ah;
+//
+// ah = (uiAreaHandler *) pkguiAlloc(sizeof (uiAreaHandler));
+// ah->Draw = doAreaHandlerDraw;
+// ah->MouseEvent = doAreaHandlerMouseEvent;
+// ah->MouseCrossed = doAreaHandlerMouseCrossed;
+// ah->DragBroken = doAreaHandlerDragBroken;
+// ah->KeyEvent = doAreaHandlerKeyEvent;
+// return ah;
+// }
+// static inline void freeAreaHandler(uiAreaHandler *ah)
+// {
+// free(ah);
+// }
+import "C"
+
+// no need to lock this; only the GUI thread can access it
+var areahandlers = make(map[*C.uiAreaHandler]AreaHandler)
+
+// AreaHandler defines the functionality needed for handling events
+// from an Area. Each of the methods on AreaHandler is called from
+// the GUI thread, and every parameter (other than the Area itself)
+// should be assumed to only be valid during the life of the method
+// call (so for instance, do not save AreaDrawParams.AreaWidth, as
+// that might change without generating an event).
+//
+// Coordinates to Draw and MouseEvent are given in points. Points
+// are generic, floating-point, device-independent coordinates with
+// (0,0) at the top left corner. You never have to worry about the
+// mapping between points and pixels; simply draw everything using
+// points and you get nice effects like looking sharp on high-DPI
+// monitors for free. Proper documentation on the matter is being
+// written. In the meantime, there are several referenes to this kind of
+// drawing, most notably on Apple's website: https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html#//apple_ref/doc/uid/TP40012302-CH4-SW1
+//
+// For a scrolling Area, points are automatically offset by the scroll
+// position. So if the mouse moves to position (5,5) while the
+// horizontal scrollbar is at position 10 and the horizontal scrollbar is
+// at position 20, the coordinate stored in the AreaMouseEvent
+// structure is (15,25). The same applies to drawing.
+type AreaHandler interface {
+ // Draw is sent when a part of the Area needs to be drawn.
+ // dp will contain a drawing context to draw on, the rectangle
+ // that needs to be drawn in, and (for a non-scrolling area) the
+ // size of the area. The rectangle that needs to be drawn will
+ // have been cleared by the system prior to drawing, so you are
+ // always working on a clean slate.
+ //
+ // If you call Save on the drawing context, you must call Release
+ // before returning from Draw, and the number of calls to Save
+ // and Release must match. Failure to do so results in undefined
+ // behavior.
+ Draw(a *Area, dp *AreaDrawParams)
+
+ // MouseEvent is called when the mouse moves over the Area
+ // or when a mouse button is pressed or released. See
+ // AreaMouseEvent for more details.
+ //
+ // If a mouse button is being held, MouseEvents will continue to
+ // be generated, even if the mouse is not within the area. On
+ // some systems, the system can interrupt this behavior;
+ // see DragBroken.
+ MouseEvent(a *Area, me *AreaMouseEvent)
+
+ // MouseCrossed is called when the mouse either enters or
+ // leaves the Area. It is called even if the mouse buttons are being
+ // held (see MouseEvent above). If the mouse has entered the
+ // Area, left is false; if it has left the Area, left is true.
+ //
+ // If, when the Area is first shown, the mouse is already inside
+ // the Area, MouseCrossed will be called with left=false.
+ // TODO what about future shows?
+ MouseCrossed(a *Area, left bool)
+
+ // DragBroken is called if a mouse drag is interrupted by the
+ // system. As noted above, when a mouse button is held,
+ // MouseEvent will continue to be called, even if the mouse is
+ // outside the Area. On some systems, this behavior can be
+ // stopped by the system itself for a variety of reasons. This
+ // method is provided to allow your program to cope with the
+ // loss of the mouse in this case. You should cope by cancelling
+ // whatever drag-related operation you were doing.
+ //
+ // Note that this is only generated on some systems under
+ // specific conditions. Do not implement behavior that only
+ // takes effect when DragBroken is called.
+ DragBroken(a *Area)
+
+ // KeyEvent is called when a key is pressed while the Area has
+ // keyboard focus (if the Area has been tabbed into or if the
+ // mouse has been clicked on it). See AreaKeyEvent for specifics.
+ //
+ // Because some keyboard events are handled by the system
+ // (for instance, menu accelerators and global hotkeys), you
+ // must return whether you handled the key event; return true
+ // if you did or false if you did not. If you wish to ignore the
+ // keyboard outright, the correct implementation of KeyEvent is
+ // func (h *MyHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
+ // return false
+ // }
+ // DO NOT RETURN TRUE UNCONDITIONALLY FROM THIS
+ // METHOD. BAD THINGS WILL HAPPEN IF YOU DO.
+ KeyEvent(a *Area, ke *AreaKeyEvent) (handled bool)
+}
+
+func registerAreaHandler(ah AreaHandler) *C.uiAreaHandler {
+ uah := C.allocAreaHandler()
+ areahandlers[uah] = ah
+ return uah
+}
+
+func unregisterAreaHandler(uah *C.uiAreaHandler) {
+ delete(areahandlers, uah)
+ C.freeAreaHandler(uah)
+}
+
+// AreaDrawParams provides a drawing context that can be used
+// to draw on an Area and tells you where to draw. See AreaHandler
+// for introductory information.
+type AreaDrawParams struct {
+ // Context is the drawing context to draw on. See DrawContext
+ // for how to draw.
+ Context *DrawContext
+
+ // AreaWidth and AreaHeight provide the size of the Area for
+ // non-scrolling Areas. For scrolling Areas both values are zero.
+ //
+ // To reiterate the AreaHandler documentation, do NOT save
+ // these values for later; they can change without generating
+ // an event.
+ AreaWidth float64
+ AreaHeight float64
+
+ // These four fields define the rectangle that needs to be
+ // redrawn. The system will not draw anything outside this
+ // rectangle, but you can make your drawing faster if you
+ // also stay within the lines.
+ ClipX float64
+ ClipY float64
+ ClipWidth float64
+ ClipHeight float64
+}
+
+//export doAreaHandlerDraw
+func doAreaHandlerDraw(uah *C.uiAreaHandler, ua *C.uiArea, udp *C.uiAreaDrawParams) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ dp := &AreaDrawParams{
+ Context: &DrawContext{udp.Context},
+ AreaWidth: float64(udp.AreaWidth),
+ AreaHeight: float64(udp.AreaHeight),
+ ClipX: float64(udp.ClipX),
+ ClipY: float64(udp.ClipY),
+ ClipWidth: float64(udp.ClipWidth),
+ ClipHeight: float64(udp.ClipHeight),
+ }
+ ah.Draw(a, dp)
+}
+
+// TODO document all these
+//
+// TODO note that in the case of a drag, X and Y can be out of bounds, or in the event of a scrolling area, in places that are not visible
+type AreaMouseEvent struct {
+ X float64
+ Y float64
+
+ // AreaWidth and AreaHeight provide the size of the Area for
+ // non-scrolling Areas. For scrolling Areas both values are zero.
+ //
+ // To reiterate the AreaHandler documentation, do NOT save
+ // these values for later; they can change without generating
+ // an event.
+ AreaWidth float64
+ AreaHeight float64
+
+ Down uint
+ Up uint
+ Count uint
+ Modifiers Modifiers
+ Held []uint
+}
+
+func appendBits(out []uint, held C.uint64_t) []uint {
+ n := uint(1)
+ for i := 0; i < 64; i++ {
+ if held & 1 != 0 {
+ out = append(out, n)
+ }
+ held >>= 1
+ n++
+ }
+ return out
+}
+
+//export doAreaHandlerMouseEvent
+func doAreaHandlerMouseEvent(uah *C.uiAreaHandler, ua *C.uiArea, ume *C.uiAreaMouseEvent) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ me := &AreaMouseEvent{
+ X: float64(ume.X),
+ Y: float64(ume.Y),
+ AreaWidth: float64(ume.AreaWidth),
+ AreaHeight: float64(ume.AreaHeight),
+ Down: uint(ume.Down),
+ Up: uint(ume.Up),
+ Count: uint(ume.Count),
+ Modifiers: Modifiers(ume.Modifiers),
+ Held: make([]uint, 0, 64),
+ }
+ me.Held = appendBits(me.Held, ume.Held1To64)
+ ah.MouseEvent(a, me)
+}
+
+//export doAreaHandlerMouseCrossed
+func doAreaHandlerMouseCrossed(uah *C.uiAreaHandler, ua *C.uiArea, left C.int) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ah.MouseCrossed(a, tobool(left))
+}
+
+//export doAreaHandlerDragBroken
+func doAreaHandlerDragBroken(uah *C.uiAreaHandler, ua *C.uiArea) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ah.DragBroken(a)
+}
+
+// TODO document all these
+type AreaKeyEvent struct {
+ Key rune
+ ExtKey ExtKey
+ Modifier Modifiers
+ Modifiers Modifiers
+ Up bool
+}
+
+//export doAreaHandlerKeyEvent
+func doAreaHandlerKeyEvent(uah *C.uiAreaHandler, ua *C.uiArea, uke *C.uiAreaKeyEvent) C.int {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ke := &AreaKeyEvent{
+ Key: rune(uke.Key),
+ ExtKey: ExtKey(uke.ExtKey),
+ Modifier: Modifiers(uke.Modifier),
+ Modifiers: Modifiers(uke.Modifiers),
+ Up: tobool(uke.Up),
+ }
+ return frombool(ah.KeyEvent(a, ke))
+}
+
+// TODO document
+//
+// Note: these must be numerically identical to their libui equivalents.
+type Modifiers uint
+const (
+ Ctrl Modifiers = 1 << iota
+ Alt
+ Shift
+ Super
+)
+
+// TODO document
+//
+// Note: these must be numerically identical to their libui equivalents.
+type ExtKey int
+const (
+ Escape ExtKey = iota + 1
+ Insert // equivalent to "Help" on Apple keyboards
+ Delete
+ Home
+ End
+ PageUp
+ PageDown
+ Up
+ Down
+ Left
+ Right
+ F1 // F1..F12 are guaranteed to be consecutive
+ F2
+ F3
+ F4
+ F5
+ F6
+ F7
+ F8
+ F9
+ F10
+ F11
+ F12
+ N0 // numpad keys; independent of Num Lock state
+ N1 // N0..N9 are guaranteed to be consecutive
+ N2
+ N3
+ N4
+ N5
+ N6
+ N7
+ N8
+ N9
+ NDot
+ NEnter
+ NAdd
+ NSubtract
+ NMultiply
+ NDivide
+)
diff --git a/BBB_GOFILES/box.go b/BBB_GOFILES/box.go
new file mode 100644
index 0000000..65421d4
--- /dev/null
+++ b/BBB_GOFILES/box.go
@@ -0,0 +1,85 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Box is a Control that holds a group of Controls horizontally
+// or vertically. If horizontally, then all controls have the same
+// height. If vertically, then all controls have the same width.
+// By default, each control has its preferred width (horizontal)
+// or height (vertical); if a control is marked "stretchy", it will
+// take whatever space is left over. If multiple controls are marked
+// stretchy, they will be given equal shares of the leftover space.
+// There can also be space between each control ("padding").
+type Box struct {
+ ControlBase
+ b *C.uiBox
+ children []Control
+}
+
+// NewHorizontalBox creates a new horizontal Box.
+func NewHorizontalBox() *Box {
+ b := new(Box)
+
+ b.b = C.uiNewHorizontalBox()
+
+ b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b)))
+ return b
+}
+
+// NewVerticalBox creates a new vertical Box.
+func NewVerticalBox() *Box {
+ b := new(Box)
+
+ b.b = C.uiNewVerticalBox()
+
+ b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b)))
+ return b
+}
+
+// Destroy destroys the Box. If the Box has children,
+// Destroy calls Destroy on those Controls as well.
+func (b *Box) Destroy() {
+ for len(b.children) != 0 {
+ c := b.children[0]
+ b.Delete(0)
+ c.Destroy()
+ }
+ b.ControlBase.Destroy()
+}
+
+// Append adds the given control to the end of the Box.
+func (b *Box) Append(child Control, stretchy bool) {
+ c := (*C.uiControl)(nil)
+ // TODO this part is wrong for Box?
+ if child != nil {
+ c = touiControl(child.LibuiControl())
+ }
+ C.uiBoxAppend(b.b, c, frombool(stretchy))
+ b.children = append(b.children, child)
+}
+
+// Delete deletes the nth control of the Box.
+func (b *Box) Delete(n int) {
+ b.children = append(b.children[:n], b.children[n + 1:]...)
+ C.uiBoxDelete(b.b, C.int(n))
+}
+
+// Padded returns whether there is space between each control
+// of the Box.
+func (b *Box) Padded() bool {
+ return tobool(C.uiBoxPadded(b.b))
+}
+
+// SetPadded controls whether there is space between each control
+// of the Box. The size of the padding is determined by the OS and
+// its best practices.
+func (b *Box) SetPadded(padded bool) {
+ C.uiBoxSetPadded(b.b, frombool(padded))
+}
diff --git a/BBB_GOFILES/button.go b/BBB_GOFILES/button.go
new file mode 100644
index 0000000..630c684
--- /dev/null
+++ b/BBB_GOFILES/button.go
@@ -0,0 +1,65 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doButtonOnClicked(uiButton *, void *);
+// // see golang/go#19835
+// typedef void (*buttonCallback)(uiButton *, void *);
+import "C"
+
+// Button is a Control that represents a button that the user can
+// click to perform an action. A Button has a text label that should
+// describe what the button does.
+type Button struct {
+ ControlBase
+ b *C.uiButton
+ onClicked func(*Button)
+}
+
+// NewButton creates a new Button with the given text as its label.
+func NewButton(text string) *Button {
+ b := new(Button)
+
+ ctext := C.CString(text)
+ b.b = C.uiNewButton(ctext)
+ freestr(ctext)
+
+ C.uiButtonOnClicked(b.b, C.buttonCallback(C.doButtonOnClicked), nil)
+
+ b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b)))
+ return b
+}
+
+// Text returns the Button's text.
+func (b *Button) Text() string {
+ ctext := C.uiButtonText(b.b)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the Button's text to text.
+func (b *Button) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiButtonSetText(b.b, ctext)
+ freestr(ctext)
+}
+
+// OnClicked registers f to be run when the user clicks the Button.
+// Only one function can be registered at a time.
+func (b *Button) OnClicked(f func(*Button)) {
+ b.onClicked = f
+}
+
+//export doButtonOnClicked
+func doButtonOnClicked(bb *C.uiButton, data unsafe.Pointer) {
+ b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*Button)
+ if b.onClicked != nil {
+ b.onClicked(b)
+ }
+}
diff --git a/BBB_GOFILES/checkbox.go b/BBB_GOFILES/checkbox.go
new file mode 100644
index 0000000..8177226
--- /dev/null
+++ b/BBB_GOFILES/checkbox.go
@@ -0,0 +1,76 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doCheckboxOnToggled(uiCheckbox *, void *);
+// // see golang/go#19835
+// typedef void (*checkboxCallback)(uiCheckbox *, void *);
+import "C"
+
+// Checkbox is a Control that represents a box with a text label at its
+// side. When the user clicks the checkbox, a check mark will appear
+// in the box; clicking it again removes the check.
+type Checkbox struct {
+ ControlBase
+ c *C.uiCheckbox
+ onToggled func(*Checkbox)
+}
+
+// NewCheckbox creates a new Checkbox with the given text as its
+// label.
+func NewCheckbox(text string) *Checkbox {
+ c := new(Checkbox)
+
+ ctext := C.CString(text)
+ c.c = C.uiNewCheckbox(ctext)
+ freestr(ctext)
+
+ C.uiCheckboxOnToggled(c.c, C.checkboxCallback(C.doCheckboxOnToggled), nil)
+
+ c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c)))
+ return c
+}
+
+// Text returns the Checkbox's text.
+func (c *Checkbox) Text() string {
+ ctext := C.uiCheckboxText(c.c)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the Checkbox's text to text.
+func (c *Checkbox) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiCheckboxSetText(c.c, ctext)
+ freestr(ctext)
+}
+
+// OnToggled registers f to be run when the user clicks the Checkbox.
+// Only one function can be registered at a time.
+func (c *Checkbox) OnToggled(f func(*Checkbox)) {
+ c.onToggled = f
+}
+
+//export doCheckboxOnToggled
+func doCheckboxOnToggled(cc *C.uiCheckbox, data unsafe.Pointer) {
+ c := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*Checkbox)
+ if c.onToggled != nil {
+ c.onToggled(c)
+ }
+}
+
+// Checked returns whether the Checkbox is checked.
+func (c *Checkbox) Checked() bool {
+ return tobool(C.uiCheckboxChecked(c.c))
+}
+
+// SetChecked sets whether the Checkbox is checked.
+func (c *Checkbox) SetChecked(checked bool) {
+ C.uiCheckboxSetChecked(c.c, frombool(checked))
+}
diff --git a/BBB_GOFILES/colorbutton.go b/BBB_GOFILES/colorbutton.go
new file mode 100644
index 0000000..0129a55
--- /dev/null
+++ b/BBB_GOFILES/colorbutton.go
@@ -0,0 +1,83 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include <stdlib.h>
+// #include "ui.h"
+// #include "util.h"
+// extern void doColorButtonOnChanged(uiColorButton *, void *);
+// // see golang/go#19835
+// typedef void (*colorButtonCallback)(uiColorButton *, void *);
+// typedef struct pkguiCColor pkguiCColor;
+// struct pkguiCColor { double *r; double *g; double *b; double *a; };
+// static inline pkguiCColor pkguiNewCColor(void)
+// {
+// pkguiCColor c;
+//
+// c.r = (double *) pkguiAlloc(4 * sizeof (double));
+// c.g = c.r + 1;
+// c.b = c.g + 1;
+// c.a = c.b + 1;
+// return c;
+// }
+// static inline void pkguiFreeCColor(pkguiCColor c)
+// {
+// free(c.r);
+// }
+import "C"
+
+// ColorButton is a Control that represents a button that the user can
+// click to select a color.
+type ColorButton struct {
+ ControlBase
+ b *C.uiColorButton
+ onChanged func(*ColorButton)
+}
+
+// NewColorButton creates a new ColorButton.
+func NewColorButton() *ColorButton {
+ b := new(ColorButton)
+
+ b.b = C.uiNewColorButton()
+
+ C.uiColorButtonOnChanged(b.b, C.colorButtonCallback(C.doColorButtonOnChanged), nil)
+
+ b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b)))
+ return b
+}
+
+// Color returns the color currently selected in the ColorButton.
+// Colors are not alpha-premultiplied.
+// TODO rename b or bl
+func (b *ColorButton) Color() (r, g, bl, a float64) {
+ c := C.pkguiNewCColor()
+ defer C.pkguiFreeCColor(c)
+ C.uiColorButtonColor(b.b, c.r, c.g, c.b, c.a)
+ return float64(*(c.r)), float64(*(c.g)), float64(*(c.b)), float64(*(c.a))
+}
+
+// SetColor sets the currently selected color in the ColorButton.
+// Colors are not alpha-premultiplied.
+// TODO rename b or bl
+func (b *ColorButton) SetColor(r, g, bl, a float64) {
+ C.uiColorButtonSetColor(b.b, C.double(r), C.double(g), C.double(bl), C.double(a))
+}
+
+// OnChanged registers f to be run when the user changes the
+// currently selected color in the ColorButton. Only one function
+// can be registered at a time.
+func (b *ColorButton) OnChanged(f func(*ColorButton)) {
+ b.onChanged = f
+}
+
+//export doColorButtonOnChanged
+func doColorButtonOnChanged(bb *C.uiColorButton, data unsafe.Pointer) {
+ b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*ColorButton)
+ if b.onChanged != nil {
+ b.onChanged(b)
+ }
+}
diff --git a/BBB_GOFILES/combobox.go b/BBB_GOFILES/combobox.go
new file mode 100644
index 0000000..1e381de
--- /dev/null
+++ b/BBB_GOFILES/combobox.go
@@ -0,0 +1,67 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doComboboxOnSelected(uiCombobox *, void *);
+// // see golang/go#19835
+// typedef void (*comboboxCallback)(uiCombobox *, void *);
+import "C"
+
+// Combobox is a Control that represents a drop-down list of strings
+// that the user can choose one of at any time. For a Combobox that
+// users can type values into, see EditableCombobox.
+type Combobox struct {
+ ControlBase
+ c *C.uiCombobox
+ onSelected func(*Combobox)
+}
+
+// NewCombobox creates a new Combobox.
+func NewCombobox() *Combobox {
+ c := new(Combobox)
+
+ c.c = C.uiNewCombobox()
+
+ C.uiComboboxOnSelected(c.c, C.comboboxCallback(C.doComboboxOnSelected), nil)
+
+ c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c)))
+ return c
+}
+
+// Append adds the named item to the end of the Combobox.
+func (c *Combobox) Append(text string) {
+ ctext := C.CString(text)
+ C.uiComboboxAppend(c.c, ctext)
+ freestr(ctext)
+}
+
+// Selected returns the index of the currently selected item in the
+// Combobox, or -1 if nothing is selected.
+func (c *Combobox) Selected() int {
+ return int(C.uiComboboxSelected(c.c))
+}
+
+// SetSelected sets the currently selected item in the Combobox
+// to index. If index is -1 no item will be selected.
+func (c *Combobox) SetSelected(index int) {
+ C.uiComboboxSetSelected(c.c, C.int(index))
+}
+
+// OnSelected registers f to be run when the user selects an item in
+// the Combobox. Only one function can be registered at a time.
+func (c *Combobox) OnSelected(f func(*Combobox)) {
+ c.onSelected = f
+}
+
+//export doComboboxOnSelected
+func doComboboxOnSelected(cc *C.uiCombobox, data unsafe.Pointer) {
+ c := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*Combobox)
+ if c.onSelected != nil {
+ c.onSelected(c)
+ }
+}
diff --git a/BBB_GOFILES/control.go b/BBB_GOFILES/control.go
new file mode 100644
index 0000000..23a8ddf
--- /dev/null
+++ b/BBB_GOFILES/control.go
@@ -0,0 +1,145 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// no need to lock this; only the GUI thread can access it
+var controls = make(map[*C.uiControl]Control)
+
+// Control represents a GUI control. It provdes methods
+// common to all Controls.
+//
+// The preferred way to create new Controls is to use
+// ControlBase; see ControlBase below.
+type Control interface {
+ // LibuiControl returns the uiControl pointer for the Control.
+ // This is intended for use when adding a control to a
+ // container.
+ LibuiControl() uintptr
+
+ // Destroy destroys the Control.
+ Destroy()
+
+ // Handle returns the OS-level handle that backs the
+ // Control. On OSs that use reference counting for
+ // controls, Handle does not increment the reference
+ // count; you are sharing package ui's reference.
+ Handle() uintptr
+
+ // Visible returns whether the Control is visible.
+ Visible() bool
+
+ // Show shows the Control.
+ Show()
+
+ // Hide shows the Control. Hidden controls do not participate
+ // in layout (that is, Box, Grid, etc. does not reserve space for
+ // hidden controls).
+ Hide()
+
+ // Enabled returns whether the Control is enabled.
+ Enabled() bool
+
+ // Enable enables the Control.
+ Enable()
+
+ // Disable disables the Control.
+ Disable()
+}
+
+// ControlBase is an implementation of Control that provides
+// all the methods that Control requires. To use it, embed a
+// ControlBase (not a *ControlBase) into your structure, then
+// assign the result of NewControlBase to that field:
+//
+// type MyControl struct {
+// ui.ControlBase
+// c *C.MyControl
+// }
+//
+// func NewMyControl() *MyControl {
+// m := &NewMyControl{
+// c: C.newMyControl(),
+// }
+// m.ControlBase = ui.NewControlBase(m, uintptr(unsafe.Pointer(c)))
+// return m
+// }
+type ControlBase struct {
+ iface Control
+ c *C.uiControl
+}
+
+// NewControlBase creates a new ControlBase. See the
+// documentation of ControlBase for an example.
+// NewControl should only be called once per instance of Control.
+func NewControlBase(iface Control, c uintptr) ControlBase {
+ b := ControlBase{
+ iface: iface,
+ c: (*C.uiControl)(unsafe.Pointer(c)),
+ }
+ controls[b.c] = b.iface
+ return b
+}
+
+func (c *ControlBase) LibuiControl() uintptr {
+ return uintptr(unsafe.Pointer(c.c))
+}
+
+func (c *ControlBase) Destroy() {
+ delete(controls, c.c)
+ C.uiControlDestroy(c.c)
+}
+
+func (c *ControlBase) Handle() uintptr {
+ return uintptr(C.uiControlHandle(c.c))
+}
+
+func (c *ControlBase) Visible() bool {
+ return tobool(C.uiControlVisible(c.c))
+}
+
+func (c *ControlBase) Show() {
+ C.uiControlShow(c.c)
+}
+
+func (c *ControlBase) Hide() {
+ C.uiControlHide(c.c)
+}
+
+func (c *ControlBase) Enabled() bool {
+ return tobool(C.uiControlEnabled(c.c))
+}
+
+func (c *ControlBase) Enable() {
+ C.uiControlEnable(c.c)
+}
+
+func (c *ControlBase) Disable() {
+ C.uiControlDisable(c.c)
+}
+
+// ControlFromLibui returns the Control associated with a libui
+// uiControl. This is intended for implementing event handlers
+// on the Go side, to prevent sharing Go pointers with C.
+// This function only works on Controls that use ControlBase.
+func ControlFromLibui(c uintptr) Control {
+ // comma-ok form to avoid creating nil entries
+ cc, _ := controls[(*C.uiControl)(unsafe.Pointer(c))]
+ return cc
+}
+
+func touiControl(c uintptr) *C.uiControl {
+ return (*C.uiControl)(unsafe.Pointer(c))
+}
+
+// LibuiFreeText allows implementations of Control
+// to call the libui function uiFreeText.
+func LibuiFreeText(c uintptr) {
+ C.uiFreeText((*C.char)(unsafe.Pointer(c)))
+}
diff --git a/BBB_GOFILES/datetimepicker.go b/BBB_GOFILES/datetimepicker.go
new file mode 100644
index 0000000..3436b3c
--- /dev/null
+++ b/BBB_GOFILES/datetimepicker.go
@@ -0,0 +1,107 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "time"
+ "unsafe"
+)
+
+// #include <stdlib.h>
+// #include <time.h>
+// #include "ui.h"
+// #include "util.h"
+// static inline struct tm *allocTimeStruct(void)
+// {
+// return (struct tm *) pkguiAlloc(sizeof (struct tm));
+// }
+// extern void doDateTimePickerOnChanged(uiDateTimePicker *, void *);
+// // see golang/go#19835
+// typedef void (*dtpCallback)(uiDateTimePicker *, void *);
+import "C"
+
+// DateTimePicker is a Control that represents a field where the user
+// can enter a date and/or a time.
+type DateTimePicker struct {
+ ControlBase
+ d *C.uiDateTimePicker
+ onChanged func(*DateTimePicker)
+}
+
+func finishNewDateTimePicker(dd *C.uiDateTimePicker) *DateTimePicker {
+ d := new(DateTimePicker)
+
+ d.d = dd
+
+ C.uiDateTimePickerOnChanged(d.d, C.dtpCallback(C.doDateTimePickerOnChanged), nil)
+
+ d.ControlBase = NewControlBase(d, uintptr(unsafe.Pointer(d.d)))
+ return d
+}
+
+// NewDateTimePicker creates a new DateTimePicker that shows
+// both a date and a time.
+func NewDateTimePicker() *DateTimePicker {
+ return finishNewDateTimePicker(C.uiNewDateTimePicker())
+}
+
+// NewDatePicker creates a new DateTimePicker that shows
+// only a date.
+func NewDatePicker() *DateTimePicker {
+ return finishNewDateTimePicker(C.uiNewDatePicker())
+}
+
+// NewTimePicker creates a new DateTimePicker that shows
+// only a time.
+func NewTimePicker() *DateTimePicker {
+ return finishNewDateTimePicker(C.uiNewTimePicker())
+}
+
+// Time returns the time stored in the uiDateTimePicker.
+// The time is assumed to be local time.
+func (d *DateTimePicker) Time() time.Time {
+ tm := C.allocTimeStruct()
+ defer C.free(unsafe.Pointer(tm))
+ C.uiDateTimePickerTime(d.d, tm)
+ return time.Date(
+ int(tm.tm_year + 1900),
+ time.Month(tm.tm_mon + 1),
+ int(tm.tm_mday),
+ int(tm.tm_hour),
+ int(tm.tm_min),
+ int(tm.tm_sec),
+ 0, time.Local)
+}
+
+// SetTime sets the time in the DateTimePicker to t.
+// t's components are read as-is via t.Date() and t.Clock();
+// no time zone manipulations are done.
+func (d *DateTimePicker) SetTime(t time.Time) {
+ tm := C.allocTimeStruct()
+ defer C.free(unsafe.Pointer(tm))
+ year, mon, mday := t.Date()
+ tm.tm_year = C.int(year - 1900)
+ tm.tm_mon = C.int(mon - 1)
+ tm.tm_mday = C.int(mday)
+ hour, min, sec := t.Clock()
+ tm.tm_hour = C.int(hour)
+ tm.tm_min = C.int(min)
+ tm.tm_sec = C.int(sec)
+ tm.tm_isdst = -1
+ C.uiDateTimePickerSetTime(d.d, tm)
+}
+
+// OnChanged registers f to be run when the user changes the time
+// in the DateTimePicker. Only one function can be registered at a
+// time.
+func (d *DateTimePicker) OnChanged(f func(*DateTimePicker)) {
+ d.onChanged = f
+}
+
+//export doDateTimePickerOnChanged
+func doDateTimePickerOnChanged(dd *C.uiDateTimePicker, data unsafe.Pointer) {
+ d := ControlFromLibui(uintptr(unsafe.Pointer(dd))).(*DateTimePicker)
+ if d.onChanged != nil {
+ d.onChanged(d)
+ }
+}
diff --git a/BBB_GOFILES/draw.go b/BBB_GOFILES/draw.go
new file mode 100644
index 0000000..fd21a75
--- /dev/null
+++ b/BBB_GOFILES/draw.go
@@ -0,0 +1,484 @@
+// 13 december 2015
+
+package ui
+
+// #include <stdlib.h>
+// #include "ui.h"
+// #include "util.h"
+// static uiDrawBrush *newBrush(void)
+// {
+// return (uiDrawBrush *) pkguiAlloc(sizeof (uiDrawBrush));
+// }
+// static uiDrawBrushGradientStop *newStops(size_t n)
+// {
+// return (uiDrawBrushGradientStop *) pkguiAlloc(n * sizeof (uiDrawBrushGradientStop));
+// }
+// static void setStop(uiDrawBrushGradientStop *stops, size_t i, double pos, double r, double g, double b, double a)
+// {
+// stops[i].Pos = pos;
+// stops[i].R = r;
+// stops[i].G = g;
+// stops[i].B = b;
+// stops[i].A = a;
+// }
+// static void freeBrush(uiDrawBrush *b)
+// {
+// if (b->Type == uiDrawBrushTypeLinearGradient || b->Type == uiDrawBrushTypeRadialGradient)
+// free(b->Stops);
+// free(b);
+// }
+// static uiDrawStrokeParams *newStrokeParams(void)
+// {
+// return (uiDrawStrokeParams *) pkguiAlloc(sizeof (uiDrawStrokeParams));
+// }
+// static double *newDashes(size_t n)
+// {
+// return (double *) pkguiAlloc(n * sizeof (double));
+// }
+// static void setDash(double *dashes, size_t i, double dash)
+// {
+// dashes[i] = dash;
+// }
+// static void freeStrokeParams(uiDrawStrokeParams *sp)
+// {
+// if (sp->Dashes != NULL)
+// free(sp->Dashes);
+// free(sp);
+// }
+// static uiDrawMatrix *newMatrix(void)
+// {
+// return (uiDrawMatrix *) pkguiAlloc(sizeof (uiDrawMatrix));
+// }
+// static void freeMatrix(uiDrawMatrix *m)
+// {
+// free(m);
+// }
+import "C"
+
+// Path represents a geometric path in a drawing context.
+// This is the basic unit of drawing: all drawing operations consist of
+// forming a path, then stroking, filling, or clipping to that path.
+// A path is an OS resource; you must explicitly free it when finished.
+// Paths consist of multiple figures. Once you have added all the
+// figures to a path, you must "end" the path to make it ready to draw
+// with.
+// TODO rewrite all that
+//
+// Or more visually, the lifecycle of a Path is
+// p := NewPath()
+// for every figure {
+// p.NewFigure(...) // or NewFigureWithArc
+// p.LineTo(...) // any number of these in any order
+// p.ArcTo(...)
+// p.BezierTo(...)
+// if figure should be closed {
+// p.CloseFigure()
+// }
+// }
+// p.End()
+// // ...
+// dp.Context.Stroke(p, ...) // any number of these in any order
+// dp.Context.Fill(p, ...)
+// dp.Context.Clip(p)
+// // ...
+// p.Free() // when done with the path
+//
+// A Path also defines its fill mode. (This should ideally be a fill
+// parameter, but some implementations prevent it.)
+// TODO talk about fill modes
+type Path struct {
+ p *C.uiDrawPath
+}
+
+// TODO
+//
+// TODO disclaimer
+type FillMode uint
+const (
+ Winding FillMode = iota
+ Alternate
+)
+
+// NewPath creates a new Path with the given fill mode.
+func NewPath(fillMode FillMode) *Path {
+ var fm C.uiDrawFillMode
+
+ switch fillMode {
+ case Winding:
+ fm = C.uiDrawFillModeWinding
+ case Alternate:
+ fm = C.uiDrawFillModeAlternate
+ default:
+ panic("invalid fill mode passed to ui.NewPath()")
+ }
+ return &Path{
+ p: C.uiDrawNewPath(fm),
+ }
+}
+
+// Free destroys a Path. After calling Free the Path cannot be used.
+func (p *Path) Free() {
+ C.uiDrawFreePath(p.p)
+}
+
+// NewFigure starts a new figure in the Path. The current point
+// is set to the given point.
+func (p *Path) NewFigure(x float64, y float64) {
+ C.uiDrawPathNewFigure(p.p, C.double(x), C.double(y))
+}
+
+// NewFigureWithArc starts a new figure in the Path and adds an arc
+// as the first element of the figure. Unlike ArcTo, NewFigureWithArc
+// does not draw an initial line segment. Otherwise, see ArcTo.
+func (p *Path) NewFigureWithArc(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) {
+ C.uiDrawPathNewFigureWithArc(p.p,
+ C.double(xCenter), C.double(yCenter),
+ C.double(radius),
+ C.double(startAngle), C.double(sweep),
+ frombool(isNegative))
+}
+
+// LineTo adds a line to the current figure of the Path starting from
+// the current point and ending at the given point. The current point
+// is set to the ending point.
+func (p *Path) LineTo(x float64, y float64) {
+ C.uiDrawPathLineTo(p.p, C.double(x), C.double(y))
+}
+
+// ArcTo adds a circular arc to the current figure of the Path.
+// You pass it the center of the arc, its radius in radians, the starting
+// angle (couterclockwise) in radians, and the number of radians the
+// arc should sweep (counterclockwise). A line segment is drawn from
+// the current point to the start of the arc. The current point is set to
+// the end of the arc.
+func (p *Path) ArcTo(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) {
+ C.uiDrawPathArcTo(p.p,
+ C.double(xCenter), C.double(yCenter),
+ C.double(radius),
+ C.double(startAngle), C.double(sweep),
+ frombool(isNegative))
+}
+
+// BezierTo adds a cubic Bezier curve to the current figure of the Path.
+// Its start point is the current point. c1x and c1y are the first control
+// point. c2x and c2y are the second control point. endX and endY
+// are the end point. The current point is set to the end point.
+func (p *Path) BezierTo(c1x float64, c1y float64, c2x float64, c2y float64, endX float64, endY float64) {
+ C.uiDrawPathBezierTo(p.p,
+ C.double(c1x), C.double(c1y),
+ C.double(c2x), C.double(c2y),
+ C.double(endX), C.double(endY))
+}
+
+// CloseFigure draws a line segment from the current point of the
+// current figure of the Path back to its initial point. After calling this,
+// the current figure is over and you must either start a new figure
+// or end the Path. If this is not called and you start a new figure or
+// end the Path, then the current figure will not have this closing line
+// segment added to it (but the figure will still be over).
+func (p *Path) CloseFigure() {
+ C.uiDrawPathCloseFigure(p.p)
+}
+
+// AddRectangle creates a new figure in the Path that consists entirely
+// of a rectangle whose top-left corner is at the given point and whose
+// size is the given size. The rectangle is a closed figure; you must
+// either start a new figure or end the Path after calling this method.
+func (p *Path) AddRectangle(x float64, y float64, width float64, height float64) {
+ C.uiDrawPathAddRectangle(p.p, C.double(x), C.double(y), C.double(width), C.double(height))
+}
+
+// End ends the current Path. You cannot add figures to a Path that has
+// been ended. You cannot draw with a Path that has not been ended.
+func (p *Path) End() {
+ C.uiDrawPathEnd(p.p)
+}
+
+// DrawContext represents a drawing surface that you can draw to.
+// At present the only DrawContexts are surfaces associated with
+// Areas and are provided by package ui; see AreaDrawParams.
+type DrawContext struct {
+ c *C.uiDrawContext
+}
+
+// BrushType defines the various types of brushes.
+//
+// TODO disclaimer
+type BrushType int
+const (
+ BrushTypeSolid BrushType = iota
+ BrushTypeLinearGradient
+ BrushTypeRadialGradient
+ BrushTypeImage // presently unimplemented
+)
+
+// TODO
+//
+// TODO disclaimer
+// TODO rename these to put LineCap at the beginning? or just Cap?
+type LineCap int
+const (
+ FlatCap LineCap = iota
+ RoundCap
+ SquareCap
+)
+
+// TODO
+//
+// TODO disclaimer
+type LineJoin int
+const (
+ MiterJoin LineJoin = iota
+ RoundJoin
+ BevelJoin
+)
+
+// TODO document
+const DefaultMiterLimit = 10.0
+
+// TODO
+type Brush struct {
+ Type BrushType
+
+ // If Type is Solid.
+ // TODO
+ R float64
+ G float64
+ B float64
+ A float64
+
+ // If Type is LinearGradient or RadialGradient.
+ // TODO
+ X0 float64 // start point for both
+ Y0 float64
+ X1 float64 // linear: end point; radial: circle center
+ Y1 float64
+ OuterRadius float64 // for radial gradients only
+ Stops []GradientStop
+}
+
+// TODO
+type GradientStop struct {
+ Pos float64 // between 0 and 1 inclusive
+ R float64
+ G float64
+ B float64
+ A float64
+}
+
+func (b *Brush) toC() *C.uiDrawBrush {
+ cb := C.newBrush()
+ cb.Type = C.uiDrawBrushType(b.Type)
+ switch b.Type {
+ case BrushTypeSolid:
+ cb.R = C.double(b.R)
+ cb.G = C.double(b.G)
+ cb.B = C.double(b.B)
+ cb.A = C.double(b.A)
+ case BrushTypeLinearGradient, BrushTypeRadialGradient:
+ cb.X0 = C.double(b.X0)
+ cb.Y0 = C.double(b.Y0)
+ cb.X1 = C.double(b.X1)
+ cb.Y1 = C.double(b.Y1)
+ cb.OuterRadius = C.double(b.OuterRadius)
+ cb.NumStops = C.size_t(len(b.Stops))
+ cb.Stops = C.newStops(cb.NumStops)
+ for i, s := range b.Stops {
+ C.setStop(cb.Stops, C.size_t(i),
+ C.double(s.Pos),
+ C.double(s.R),
+ C.double(s.G),
+ C.double(s.B),
+ C.double(s.A))
+ }
+ case BrushTypeImage:
+ panic("unimplemented")
+ default:
+ panic("invalid brush type in Brush.toC()")
+ }
+ return cb
+}
+
+// TODO
+type StrokeParams struct {
+ Cap LineCap
+ Join LineJoin
+ Thickness float64
+ MiterLimit float64
+ Dashes []float64
+ DashPhase float64
+}
+
+func (sp *StrokeParams) toC() *C.uiDrawStrokeParams {
+ csp := C.newStrokeParams()
+ csp.Cap = C.uiDrawLineCap(sp.Cap)
+ csp.Join = C.uiDrawLineJoin(sp.Join)
+ csp.Thickness = C.double(sp.Thickness)
+ csp.MiterLimit = C.double(sp.MiterLimit)
+ csp.Dashes = nil
+ csp.NumDashes = C.size_t(len(sp.Dashes))
+ if csp.NumDashes != 0 {
+ csp.Dashes = C.newDashes(csp.NumDashes)
+ for i, d := range sp.Dashes {
+ C.setDash(csp.Dashes, C.size_t(i), C.double(d))
+ }
+ }
+ csp.DashPhase = C.double(sp.DashPhase)
+ return csp
+}
+
+// TODO
+func (c *DrawContext) Stroke(p *Path, b *Brush, sp *StrokeParams) {
+ cb := b.toC()
+ csp := sp.toC()
+ C.uiDrawStroke(c.c, p.p, cb, csp)
+ C.freeBrush(cb)
+ C.freeStrokeParams(csp)
+}
+
+// TODO
+func (c *DrawContext) Fill(p *Path, b *Brush) {
+ cb := b.toC()
+ C.uiDrawFill(c.c, p.p, cb)
+ C.freeBrush(cb)
+}
+
+// TODO
+// TODO should the methods of these return self for chaining?
+type Matrix struct {
+ M11 float64
+ M12 float64
+ M21 float64
+ M22 float64
+ M31 float64
+ M32 float64
+}
+
+// TODO identity matrix
+func NewMatrix() *Matrix {
+ m := new(Matrix)
+ m.SetIdentity()
+ return m
+}
+
+// TODO
+func (m *Matrix) SetIdentity() {
+ m.M11 = 1
+ m.M12 = 0
+ m.M21 = 0
+ m.M22 = 1
+ m.M31 = 0
+ m.M32 = 0
+}
+
+func (m *Matrix) toC() *C.uiDrawMatrix {
+ cm := C.newMatrix()
+ cm.M11 = C.double(m.M11)
+ cm.M12 = C.double(m.M12)
+ cm.M21 = C.double(m.M21)
+ cm.M22 = C.double(m.M22)
+ cm.M31 = C.double(m.M31)
+ cm.M32 = C.double(m.M32)
+ return cm
+}
+
+func (m *Matrix) fromC(cm *C.uiDrawMatrix) {
+ m.M11 = float64(cm.M11)
+ m.M12 = float64(cm.M12)
+ m.M21 = float64(cm.M21)
+ m.M22 = float64(cm.M22)
+ m.M31 = float64(cm.M31)
+ m.M32 = float64(cm.M32)
+ C.freeMatrix(cm)
+}
+
+// TODO
+func (m *Matrix) Translate(x float64, y float64) {
+ cm := m.toC()
+ C.uiDrawMatrixTranslate(cm, C.double(x), C.double(y))
+ m.fromC(cm)
+}
+
+// TODO
+func (m *Matrix) Scale(xCenter float64, yCenter float64, x float64, y float64) {
+ cm := m.toC()
+ C.uiDrawMatrixScale(cm,
+ C.double(xCenter), C.double(yCenter),
+ C.double(x), C.double(y))
+ m.fromC(cm)
+}
+
+// TODO
+func (m *Matrix) Rotate(x float64, y float64, amount float64) {
+ cm := m.toC()
+ C.uiDrawMatrixRotate(cm, C.double(x), C.double(y), C.double(amount))
+ m.fromC(cm)
+}
+
+// TODO
+func (m *Matrix) Skew(x float64, y float64, xamount float64, yamount float64) {
+ cm := m.toC()
+ C.uiDrawMatrixSkew(cm,
+ C.double(x), C.double(y),
+ C.double(xamount), C.double(yamount))
+ m.fromC(cm)
+}
+
+// TODO
+func (m *Matrix) Multiply(m2 *Matrix) {
+ cm := m.toC()
+ cm2 := m2.toC()
+ C.uiDrawMatrixMultiply(cm, cm2)
+ C.freeMatrix(cm2)
+ m.fromC(cm)
+}
+
+// TODO
+func (m *Matrix) Invertible() bool {
+ cm := m.toC()
+ res := C.uiDrawMatrixInvertible(cm)
+ C.freeMatrix(cm)
+ return tobool(res)
+}
+
+// TODO
+//
+// If m is not invertible, false is returned and m is left unchanged.
+func (m *Matrix) Invert() bool {
+ cm := m.toC()
+ res := C.uiDrawMatrixInvert(cm)
+ m.fromC(cm)
+ return tobool(res)
+}
+
+// TODO unimplemented
+func (m *Matrix) TransformPoint(x float64, y float64) (xout float64, yout float64) {
+ panic("TODO")
+}
+
+// TODO unimplemented
+func (m *Matrix) TransformSize(x float64, y float64) (xout float64, yout float64) {
+ panic("TODO")
+}
+
+// TODO
+func (c *DrawContext) Transform(m *Matrix) {
+ cm := m.toC()
+ C.uiDrawTransform(c.c, cm)
+ C.freeMatrix(cm)
+}
+
+// TODO
+func (c *DrawContext) Clip(p *Path) {
+ C.uiDrawClip(c.c, p.p)
+}
+
+// TODO
+func (c *DrawContext) Save() {
+ C.uiDrawSave(c.c)
+}
+
+// TODO
+func (c *DrawContext) Restore() {
+ C.uiDrawRestore(c.c)
+}
diff --git a/BBB_GOFILES/drawtext.go b/BBB_GOFILES/drawtext.go
new file mode 100644
index 0000000..1cd438c
--- /dev/null
+++ b/BBB_GOFILES/drawtext.go
@@ -0,0 +1,554 @@
+// 12 august 2018
+
+package ui
+
+// #include <stdlib.h>
+// #include "ui.h"
+// #include "util.h"
+// typedef struct pkguiCColor pkguiCColor;
+// struct pkguiCColor { double *r; double *g; double *b; double *a; };
+// static inline pkguiCColor pkguiNewCColor(void)
+// {
+// pkguiCColor c;
+//
+// c.r = (double *) pkguiAlloc(4 * sizeof (double));
+// c.g = c.r + 1;
+// c.b = c.g + 1;
+// c.a = c.b + 1;
+// return c;
+// }
+// static inline void pkguiFreeCColor(pkguiCColor c)
+// {
+// free(c.r);
+// }
+// static inline uiUnderlineColor *pkguiNewUnderlineColor(void)
+// {
+// return (uiUnderlineColor *) pkguiAlloc(sizeof (uiUnderlineColor));
+// }
+// static inline void pkguiFreeUnderlineColor(uiUnderlineColor *c)
+// {
+// free(c);
+// }
+// static inline uiFontDescriptor *pkguiNewFontDescriptor(void)
+// {
+// return (uiFontDescriptor *) pkguiAlloc(sizeof (uiFontDescriptor));
+// }
+// static inline void pkguiFreeFontDescriptor(uiFontDescriptor *fd)
+// {
+// free(fd);
+// }
+// static inline uiDrawTextLayoutParams *pkguiNewDrawTextLayoutParams(void)
+// {
+// return (uiDrawTextLayoutParams *) pkguiAlloc(sizeof (uiDrawTextLayoutParams));
+// }
+// static inline void pkguiFreeDrawTextLayoutParams(uiDrawTextLayoutParams *fd)
+// {
+// free(fd);
+// }
+import "C"
+
+// Attribute stores information about an attribute in an
+// AttributedString.
+//
+// The following types can be used as Attributes:
+//
+// - TextFamily
+// - TextSize
+// - TextWeight
+// - TextItalic
+// - TextStretch
+// - TextColor
+// - TextBackground
+// - Underline
+// - UnderlineColor
+// - UnderlineColorCustom
+// - OpenTypeFeatures
+//
+// For every Unicode codepoint in the AttributedString, at most one
+// value of each attribute type can be applied.
+type Attribute interface {
+ toLibui() *C.uiAttribute
+}
+
+// TextFamily is an Attribute that changes the font family of the text
+// it is applied to. Font family names are case-insensitive.
+type TextFamily string
+
+func (f TextFamily) toLibui() *C.uiAttribute {
+ fstr := C.CString(string(f))
+ defer freestr(fstr)
+ return C.uiNewFamilyAttribute(fstr)
+}
+
+// TextSize is an Attribute that changes the size of the text it is
+// applied to, in typographical points.
+type TextSize float64
+
+func (s TextSize) toLibui() *C.uiAttribute {
+ return C.uiNewSizeAttribute(C.double(s))
+}
+
+// TextWeight is an Attribute that changes the weight of the text
+// it is applied to. These roughly map to the OS/2 text weight field
+// of TrueType and OpenType fonts, or to CSS weight numbers. The
+// named constants are nominal values; the actual values may vary
+// by font and by OS, though this isn't particularly likely. Any value
+// between TextWeightMinimum and TextWeightMaximum,
+// inclusive, is allowed.
+//
+// Note that due to restrictions in early versions of Windows, some
+// fonts have "special" weights be exposed in many programs as
+// separate font families. This is perhaps most notable with
+// Arial Black. Package ui does not do this, even on Windows
+// (because the DirectWrite API libui uses on Windows does not do
+// this); to specify Arial Black, use family Arial and weight
+// TextWeightBlack.
+type TextWeight int
+const (
+ TextWeightMinimum TextWeight = 0
+ TextWeightThin TextWeight = 100
+ TextWeightUltraLight TextWeight = 200
+ TextWeightLight TextWeight = 300
+ TextWeightBook TextWeight = 350
+ TextWeightNormal TextWeight = 400
+ TextWeightMedium TextWeight = 500
+ TextWeightSemiBold TextWeight = 600
+ TextWeightBold TextWeight = 700
+ TextWeightUltraBold TextWeight = 800
+ TextWeightHeavy TextWeight = 900
+ TextWeightUltraHeavy TextWeight = 950
+ TextWeightMaximum TextWeight = 1000
+)
+
+func (w TextWeight) toLibui() *C.uiAttribute {
+ return C.uiNewWeightAttribute(C.uiTextWeight(w))
+}
+
+// TextItalic is an Attribute that changes the italic mode of the text
+// it is applied to. Italic represents "true" italics where the slanted
+// glyphs have custom shapes, whereas oblique represents italics
+// that are merely slanted versions of the normal glyphs. Most fonts
+// usually have one or the other.
+type TextItalic int
+const (
+ TextItalicNormal TextItalic = iota
+ TextItalicOblique
+ TextItalicItalic
+)
+
+func (i TextItalic) toLibui() *C.uiAttribute {
+ return C.uiNewItalicAttribute(C.uiTextItalic(i))
+}
+
+// TextStretch is an Attribute that changes the stretch (also called
+// "width") of the text it is applied to.
+//
+// Note that due to restrictions in early versions of Windows, some
+// fonts have "special" stretches be exposed in many programs as
+// separate font families. This is perhaps most notable with
+// Arial Condensed. Package ui does not do this, even on Windows
+// (because the DirectWrite API package ui uses on Windows does
+// not do this); to specify Arial Condensed, use family Arial and
+// stretch TextStretchCondensed.
+type TextStretch int
+const (
+ TextStretchUltraCondensed TextStretch = iota
+ TextStretchExtraCondensed
+ TextStretchCondensed
+ TextStretchSemiCondensed
+ TextStretchNormal
+ TextStretchSemiExpanded
+ TextStretchExpanded
+ TextStretchExtraExpanded
+ TextStretchUltraExpanded
+)
+
+func (s TextStretch) toLibui() *C.uiAttribute {
+ return C.uiNewStretchAttribute(C.uiTextStretch(s))
+}
+
+// TextColor is an Attribute that changes the color of the text it is
+// applied to.
+type TextColor struct {
+ R float64
+ G float64
+ B float64
+ A float64
+}
+
+func (c TextColor) toLibui() *C.uiAttribute {
+ return C.uiNewColorAttribute(C.double(c.R), C.double(c.G), C.double(c.B), C.double(c.A))
+}
+
+// TextBackground is an Attribute that changes the background
+// color of the text it is applied to.
+type TextBackground struct {
+ R float64
+ G float64
+ B float64
+ A float64
+}
+
+func (b TextBackground) toLibui() *C.uiAttribute {
+ return C.uiNewBackgroundAttribute(C.double(b.R), C.double(b.G), C.double(b.B), C.double(b.A))
+}
+
+// Underline is an Attribute that specifies a type of underline to use
+// on text.
+type Underline int
+const (
+ UnderlineNone Underline = iota
+ UnderlineSingle
+ UnderlineDouble
+ UnderlineSuggestion // wavy or dotted underlines used for spelling/grammar checkers
+)
+
+func (u Underline) toLibui() *C.uiAttribute {
+ return C.uiNewUnderlineAttribute(C.uiUnderline(u))
+}
+
+// UnderlineColor is an Attribute that changes the color of any
+// underline on the text it is applied to, regardless of the type of
+// underline. In addition to being able to specify the
+// platform-specific colors for suggestion underlines here, you can
+// also use a custom color with UnderlineColorCustom.
+//
+// To use the constants here correctly, pair them with
+// UnderlineSuggestion (though they can be used on other types of
+// underline as well).
+//
+// If an underline type is applied but no underline color is
+// specified, the text color is used instead. If an underline color
+// is specified without an underline type, the underline color
+// attribute is ignored, but not removed from the uiAttributedString.
+type UnderlineColor int
+const (
+ UnderlineColorSpelling UnderlineColor = iota + 1
+ UnderlineColorGrammar
+ UnderlineColorAuxiliary // for instance, the color used by smart replacements on macOS or in Microsoft Office
+)
+
+func (u UnderlineColor) toLibui() *C.uiAttribute {
+ return C.uiNewUnderlineColorAttribute(C.uiUnderlineColor(u), 0, 0, 0, 0)
+}
+
+// UnderlineColorCustom is an Attribute like UnderlineColor, except
+// it allows specifying a custom color.
+type UnderlineColorCustom struct {
+ R float64
+ G float64
+ B float64
+ A float64
+}
+
+func (u UnderlineColorCustom) toLibui() *C.uiAttribute {
+ return C.uiNewUnderlineColorAttribute(C.uiUnderlineColorCustom, C.double(u.R), C.double(u.G), C.double(u.B), C.double(u.A))
+}
+
+// OpenTypeFeatures is an Attribute that represents a set of
+// OpenType feature tag-value pairs, for applying OpenType
+// features to text. OpenType feature tags are four-character codes
+// defined by OpenType that cover things from design features like
+// small caps and swashes to language-specific glyph shapes and
+// beyond. Each tag may only appear once in any given
+// uiOpenTypeFeatures instance. Each value is a 32-bit integer,
+// often used as a Boolean flag, but sometimes as an index to choose
+// a glyph shape to use.
+//
+// If a font does not support a certain feature, that feature will be
+// ignored. (TODO verify this on all OSs)
+//
+// See the OpenType specification at
+// https://www.microsoft.com/typography/otspec/featuretags.htm
+// for the complete list of available features, information on specific
+// features, and how to use them.
+// TODO invalid features
+//
+// Note that if a feature is not present in a OpenTypeFeatures,
+// the feature is NOT treated as if its value was zero, unlike in Go.
+// Script-specific font shaping rules and font-specific feature
+// settings may use a different default value for a feature. You
+// should likewise NOT treat a missing feature as having a value of
+// zero either. Instead, a missing feature should be treated as
+// having some unspecified default value.
+//
+// Note that despite OpenTypeFeatures being a map, its contents
+// are copied by AttributedString. Modifying an OpenTypeFeatures
+// after giving it to an AttributedString, or modifying one that comes
+// out of an AttributedString, will have no effect.
+type OpenTypeFeatures map[OpenTypeTag]uint32
+
+func (o OpenTypeFeatures) toLibui() *C.uiAttribute {
+ otf := C.uiNewOpenTypeFeatures()
+ defer C.uiFreeOpenTypeFeatures(otf)
+ for tag, value := range o {
+ a := byte((tag >> 24) & 0xFF)
+ b := byte((tag >> 16) & 0xFF)
+ c := byte((tag >> 8) & 0xFF)
+ d := byte(tag & 0xFF)
+ C.uiOpenTypeFeaturesAdd(otf, C.char(a), C.char(b), C.char(c), C.char(d), C.uint32_t(value))
+ }
+ return C.uiNewFeaturesAttribute(otf)
+}
+
+// OpenTypeTag represents a four-byte OpenType feature tag.
+type OpenTypeTag uint32
+
+// ToOpenTypeTag converts the four characters a, b, c, and d into
+// an OpenTypeTag.
+func ToOpenTypeTag(a, b, c, d byte) OpenTypeTag {
+ return (OpenTypeTag(a) << 24) |
+ (OpenTypeTag(b) << 16) |
+ (OpenTypeTag(c) << 8) |
+ OpenTypeTag(d)
+}
+
+func attributeFromLibui(a *C.uiAttribute) Attribute {
+ switch C.uiAttributeGetType(a) {
+ case C.uiAttributeTypeFamily:
+ cf := C.uiAttributeFamily(a)
+ return TextFamily(C.GoString(cf))
+ case C.uiAttributeTypeSize:
+ return TextSize(C.uiAttributeSize(a))
+ case C.uiAttributeTypeWeight:
+ return TextWeight(C.uiAttributeWeight(a))
+ case C.uiAttributeTypeItalic:
+ return TextItalic(C.uiAttributeItalic(a))
+ case C.uiAttributeTypeStretch:
+ return TextStretch(C.uiAttributeStretch(a))
+ case C.uiAttributeTypeColor:
+ cc := C.pkguiNewCColor()
+ defer C.pkguiFreeCColor(cc)
+ C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a)
+ return TextColor{
+ R: float64(*(cc.r)),
+ G: float64(*(cc.g)),
+ B: float64(*(cc.b)),
+ A: float64(*(cc.a)),
+ }
+ case C.uiAttributeTypeBackground:
+ cc := C.pkguiNewCColor()
+ defer C.pkguiFreeCColor(cc)
+ C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a)
+ return TextBackground{
+ R: float64(*(cc.r)),
+ G: float64(*(cc.g)),
+ B: float64(*(cc.b)),
+ A: float64(*(cc.a)),
+ }
+ case C.uiAttributeTypeUnderline:
+ return Underline(C.uiAttributeUnderline(a))
+ case C.uiAttributeTypeUnderlineColor:
+ cu := C.pkguiNewUnderlineColor()
+ defer C.pkguiFreeUnderlineColor(cu)
+ cc := C.pkguiNewCColor()
+ defer C.pkguiFreeCColor(cc)
+ C.uiAttributeUnderlineColor(a, cu, cc.r, cc.g, cc.b, cc.a)
+ if *cu == C.uiUnderlineColorCustom {
+ return UnderlineColorCustom{
+ R: float64(*(cc.r)),
+ G: float64(*(cc.g)),
+ B: float64(*(cc.b)),
+ A: float64(*(cc.a)),
+ }
+ }
+ return UnderlineColor(*cu)
+ case C.uiAttributeTypeFeatures:
+ // TODO
+ }
+ panic("unreachable")
+}
+
+// AttributedString represents a string of UTF-8 text that can
+// optionally be embellished with formatting attributes. Package ui
+// provides the list of formatting attributes, which cover common
+// formatting traits like boldface and color as well as advanced
+// typographical features provided by OpenType like superscripts
+// and small caps. These attributes can be combined in a variety of
+// ways.
+//
+// Attributes are applied to runs of Unicode codepoints in the string.
+// Zero-length runs are elided. Consecutive runs that have the same
+// attribute type and value are merged. Each attribute is independent
+// of each other attribute; overlapping attributes of different types
+// do not split each other apart, but different values of the same
+// attribute type do.
+//
+// The empty string can also be represented by AttributedString,
+// but because of the no-zero-length-attribute rule, it will not have
+// attributes.
+//
+// Unlike Go strings, AttributedStrings are mutable.
+//
+// AttributedString allocates resources within libui, which package
+// ui sits on top of. As such, when you are finished with an
+// AttributedString, you must free it with Free. Like other things in
+// package ui, AttributedString must only be used from the main
+// goroutine.
+//
+// In addition, AttributedString provides facilities for moving
+// between grapheme clusters, which represent a character
+// from the point of view of the end user. The cursor of a text editor
+// is always placed on a grapheme boundary, so you can use these
+// features to move the cursor left or right by one "character".
+// TODO does uiAttributedString itself need this
+//
+// AttributedString does not provide enough information to be able
+// to draw itself onto a DrawContext or respond to user actions.
+// In order to do that, you'll need to use a DrawTextLayout, which
+// is built from the combination of an AttributedString and a set of
+// layout-specific properties.
+type AttributedString struct {
+ s *C.uiAttributedString
+}
+
+// NewAttributedString creates a new AttributedString from
+// initialString. The string will be entirely unattributed.
+func NewAttributedString(initialString string) *AttributedString {
+ cs := C.CString(initialString)
+ defer freestr(cs)
+ return &AttributedString{
+ s: C.uiNewAttributedString(cs),
+ }
+}
+
+// Free destroys s.
+func (s *AttributedString) Free() {
+ C.uiFreeAttributedString(s.s)
+}
+
+// String returns the textual content of s.
+func (s *AttributedString) String() string {
+ return C.GoString(C.uiAttributedStringString(s.s))
+}
+
+// AppendUnattributed adds str to the end of s. The new substring
+// will be unattributed.
+func (s *AttributedString) AppendUnattributed(str string) {
+ cs := C.CString(str)
+ defer freestr(cs)
+ C.uiAttributedStringAppendUnattributed(s.s, cs)
+}
+
+// InsertAtUnattributed adds str to s at the byte position specified by
+// at. The new substring will be unattributed; existing attributes will
+// be moved along with their text.
+func (s *AttributedString) InsertAtUnattributed(str string, at int) {
+ cs := C.CString(str)
+ defer freestr(cs)
+ C.uiAttributedStringInsertAtUnattributed(s.s, cs, C.size_t(at))
+}
+
+// Delete deletes the characters and attributes of s in the byte range
+// [start, end).
+func (s *AttributedString) Delete(start, end int) {
+ C.uiAttributedStringDelete(s.s, C.size_t(start), C.size_t(end))
+}
+
+// SetAttribute sets a in the byte range [start, end) of s. Any existing
+// attributes in that byte range of the same type are removed.
+func (s *AttributedString) SetAttribute(a Attribute, start, end int) {
+ C.uiAttributedStringSetAttribute(s.s, a.toLibui(), C.size_t(start), C.size_t(end))
+}
+
+// TODO uiAttributedStringForEachAttribute
+// TODO uiAttributedStringNumGraphemes
+// TODO uiAttributedStringByteIndexToGrapheme
+// TODO uiAttributedStringGraphemeToByteIndex
+
+// FontDescriptor provides a complete description of a font where
+// one is needed. Currently, this means as the default font of a
+// DrawTextLayout and as the data returned by FontButton.
+type FontDescriptor struct {
+ Family TextFamily
+ Size TextSize
+ Weight TextWeight
+ Italic TextItalic
+ Stretch TextStretch
+}
+
+func (d *FontDescriptor) fromLibui(fd *C.uiFontDescriptor) {
+ d.Family = TextFamily(C.GoString(fd.Family))
+ d.Size = TextSize(fd.Size)
+ d.Weight = TextWeight(fd.Weight)
+ d.Italic = TextItalic(fd.Italic)
+ d.Stretch = TextStretch(fd.Stretch)
+}
+
+func (d *FontDescriptor) toLibui() *C.uiFontDescriptor {
+ fd := C.pkguiNewFontDescriptor()
+ fd.Family = C.CString(string(d.Family))
+ fd.Size = C.double(d.Size)
+ fd.Weight = C.uiTextWeight(d.Weight)
+ fd.Italic = C.uiTextItalic(d.Italic)
+ fd.Stretch = C.uiTextStretch(d.Stretch)
+ return fd
+}
+
+func freeLibuiFontDescriptor(fd *C.uiFontDescriptor) {
+ freestr(fd.Family)
+ C.pkguiFreeFontDescriptor(fd)
+}
+
+// DrawTextLayout is a concrete representation of an
+// AttributedString that can be displayed in a DrawContext.
+// It includes information important for the drawing of a block of
+// text, including the bounding box to wrap the text within, the
+// alignment of lines of text within that box, areas to mark as
+// being selected, and other things.
+//
+// Unlike AttributedString, the content of a DrawTextLayout is
+// immutable once it has been created.
+//
+// TODO talk about OS-specific differences with text drawing that libui can't account for...
+type DrawTextLayout struct {
+ tl *C.uiDrawTextLayout
+}
+
+// DrawTextAlign specifies the alignment of lines of text in a
+// DrawTextLayout.
+// TODO should this really have Draw in the name?
+type DrawTextAlign int
+const (
+ DrawTextAlignLeft DrawTextAlign = iota
+ DrawTextAlignCenter
+ DrawTextAlignRight
+)
+
+// DrawTextLayoutParams describes a DrawTextLayout.
+// DefaultFont is used to render any text that is not attributed
+// sufficiently in String. Width determines the width of the bounding
+// box of the text; the height is determined automatically.
+type DrawTextLayoutParams struct {
+ String *AttributedString
+ DefaultFont *FontDescriptor
+ Width float64
+ Align DrawTextAlign
+}
+
+// DrawNewTextLayout() creates a new DrawTextLayout from
+// the given parameters.
+func DrawNewTextLayout(p *DrawTextLayoutParams) *DrawTextLayout {
+ dp := C.pkguiNewDrawTextLayoutParams()
+ defer C.pkguiFreeDrawTextLayoutParams(dp)
+ dp.String = p.String.s
+ dp.DefaultFont = p.DefaultFont.toLibui()
+ defer freeLibuiFontDescriptor(dp.DefaultFont)
+ dp.Width = C.double(p.Width)
+ dp.Align = C.uiDrawTextAlign(p.Align)
+ return &DrawTextLayout{
+ tl: C.uiDrawNewTextLayout(dp),
+ }
+}
+
+// Free frees tl. The underlying AttributedString is not freed.
+func (tl *DrawTextLayout) Free() {
+ C.uiDrawFreeTextLayout(tl.tl)
+}
+
+// Text draws tl in c with the top-left point of tl at (x, y).
+func (c *DrawContext) Text(tl *DrawTextLayout, x, y float64) {
+ C.uiDrawText(c.c, tl.tl, C.double(x), C.double(y))
+}
+
+// TODO uiDrawTextLayoutExtents
diff --git a/BBB_GOFILES/editablecombobox.go b/BBB_GOFILES/editablecombobox.go
new file mode 100644
index 0000000..1242928
--- /dev/null
+++ b/BBB_GOFILES/editablecombobox.go
@@ -0,0 +1,72 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doEditableComboboxOnChanged(uiEditableCombobox *, void *);
+// // see golang/go#19835
+// typedef void (*editableComboboxCallback)(uiEditableCombobox *, void *);
+import "C"
+
+// EditableCombobox is a Control that represents a drop-down list
+// of strings that the user can choose one of at any time. It also has
+// an entry field that the user can type an alternate choice into.
+type EditableCombobox struct {
+ ControlBase
+ c *C.uiEditableCombobox
+ onChanged func(*EditableCombobox)
+}
+
+// NewEditableCombobox creates a new EditableCombobox.
+func NewEditableCombobox() *EditableCombobox {
+ c := new(EditableCombobox)
+
+ c.c = C.uiNewEditableCombobox()
+
+ C.uiEditableComboboxOnChanged(c.c, C.editableComboboxCallback(C.doEditableComboboxOnChanged), nil)
+
+ c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c)))
+ return c
+}
+
+// Append adds the named item to the end of the EditableCombobox.
+func (e *EditableCombobox) Append(text string) {
+ ctext := C.CString(text)
+ C.uiEditableComboboxAppend(e.c, ctext)
+ freestr(ctext)
+}
+
+// Text returns the text in the entry of the EditableCombobox, which
+// could be one of the choices in the list if the user has selected one.
+func (e *EditableCombobox) Text() string {
+ ctext := C.uiEditableComboboxText(e.c)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the text in the entry of the EditableCombobox.
+func (e *EditableCombobox) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiEditableComboboxSetText(e.c, ctext)
+ freestr(ctext)
+}
+
+// OnChanged registers f to be run when the user either selects an
+// item or changes the text in the EditableCombobox. Only one
+// function can be registered at a time.
+func (e *EditableCombobox) OnChanged(f func(*EditableCombobox)) {
+ e.onChanged = f
+}
+
+//export doEditableComboboxOnChanged
+func doEditableComboboxOnChanged(cc *C.uiEditableCombobox, data unsafe.Pointer) {
+ e := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*EditableCombobox)
+ if e.onChanged != nil {
+ e.onChanged(e)
+ }
+}
diff --git a/BBB_GOFILES/entry.go b/BBB_GOFILES/entry.go
new file mode 100644
index 0000000..52da537
--- /dev/null
+++ b/BBB_GOFILES/entry.go
@@ -0,0 +1,93 @@
+// 12 december 2015
+
+// TODO typing in entry in OS X crashes libui
+// I've had similar issues with checkboxes on libui
+// something's wrong with NSMapTable
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doEntryOnChanged(uiEntry *, void *);
+// // see golang/go#19835
+// typedef void (*entryCallback)(uiEntry *, void *);
+import "C"
+
+// Entry is a Control that represents a space that the user can
+// type a single line of text into.
+type Entry struct {
+ ControlBase
+ e *C.uiEntry
+ onChanged func(*Entry)
+}
+
+func finishNewEntry(ee *C.uiEntry) *Entry {
+ e := new(Entry)
+
+ e.e = ee
+
+ C.uiEntryOnChanged(e.e, C.entryCallback(C.doEntryOnChanged), nil)
+
+ e.ControlBase = NewControlBase(e, uintptr(unsafe.Pointer(e.e)))
+ return e
+}
+
+// NewEntry creates a new Entry.
+func NewEntry() *Entry {
+ return finishNewEntry(C.uiNewEntry())
+}
+
+// NewPasswordEntry creates a new Entry whose contents are
+// visibly obfuscated, suitable for passwords.
+func NewPasswordEntry() *Entry {
+ return finishNewEntry(C.uiNewPasswordEntry())
+}
+
+// NewSearchEntry creates a new Entry suitable for searching with.
+// Changed events may, depending on the system, be delayed
+// with a search Entry, to produce a smoother user experience.
+func NewSearchEntry() *Entry {
+ return finishNewEntry(C.uiNewSearchEntry())
+}
+
+// Text returns the Entry's text.
+func (e *Entry) Text() string {
+ ctext := C.uiEntryText(e.e)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the Entry's text to text.
+func (e *Entry) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiEntrySetText(e.e, ctext)
+ freestr(ctext)
+}
+
+// OnChanged registers f to be run when the user makes a change to
+// the Entry. Only one function can be registered at a time.
+func (e *Entry) OnChanged(f func(*Entry)) {
+ e.onChanged = f
+}
+
+//export doEntryOnChanged
+func doEntryOnChanged(ee *C.uiEntry, data unsafe.Pointer) {
+ e := ControlFromLibui(uintptr(unsafe.Pointer(ee))).(*Entry)
+ if e.onChanged != nil {
+ e.onChanged(e)
+ }
+}
+
+// ReadOnly returns whether the Entry can be changed.
+func (e *Entry) ReadOnly() bool {
+ return tobool(C.uiEntryReadOnly(e.e))
+}
+
+// SetReadOnly sets whether the Entry can be changed.
+func (e *Entry) SetReadOnly(ro bool) {
+ C.uiEntrySetReadOnly(e.e, frombool(ro))
+}
diff --git a/BBB_GOFILES/fontbutton.go b/BBB_GOFILES/fontbutton.go
new file mode 100644
index 0000000..c228151
--- /dev/null
+++ b/BBB_GOFILES/fontbutton.go
@@ -0,0 +1,69 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include <stdlib.h>
+// #include "ui.h"
+// #include "util.h"
+// extern void doFontButtonOnChanged(uiFontButton *, void *);
+// // see golang/go#19835
+// typedef void (*fontButtonCallback)(uiFontButton *, void *);
+// static inline uiFontDescriptor *pkguiNewFontDescriptor(void)
+// {
+// return (uiFontDescriptor *) pkguiAlloc(sizeof (uiFontDescriptor));
+// }
+// static inline void pkguiFreeFontDescriptor(uiFontDescriptor *fd)
+// {
+// free(fd);
+// }
+import "C"
+
+// FontButton is a Control that represents a button that the user can
+// click to select a font.
+type FontButton struct {
+ ControlBase
+ b *C.uiFontButton
+ onChanged func(*FontButton)
+}
+
+// NewFontButton creates a new FontButton.
+func NewFontButton() *FontButton {
+ b := new(FontButton)
+
+ b.b = C.uiNewFontButton()
+
+ C.uiFontButtonOnChanged(b.b, C.fontButtonCallback(C.doFontButtonOnChanged), nil)
+
+ b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b)))
+ return b
+}
+
+// Font returns the font currently selected in the FontButton.
+func (b *FontButton) Font() *FontDescriptor {
+ cfd := C.pkguiNewFontDescriptor()
+ defer C.pkguiFreeFontDescriptor(cfd)
+ C.uiFontButtonFont(b.b, cfd)
+ defer C.uiFreeFontButtonFont(cfd)
+ fd := &FontDescriptor{}
+ fd.fromLibui(cfd)
+ return fd
+}
+
+// OnChanged registers f to be run when the user changes the
+// currently selected font in the FontButton. Only one function can
+// be registered at a time.
+func (b *FontButton) OnChanged(f func(*FontButton)) {
+ b.onChanged = f
+}
+
+//export doFontButtonOnChanged
+func doFontButtonOnChanged(bb *C.uiFontButton, data unsafe.Pointer) {
+ b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*FontButton)
+ if b.onChanged != nil {
+ b.onChanged(b)
+ }
+}
diff --git a/BBB_GOFILES/form.go b/BBB_GOFILES/form.go
new file mode 100644
index 0000000..6f8a282
--- /dev/null
+++ b/BBB_GOFILES/form.go
@@ -0,0 +1,71 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Form is a Control that holds a group of Controls vertically
+// with labels next to each. By default, each control has its
+// preferred height; if a control is marked "stretchy", it will take
+// whatever space is left over. If multiple controls are marked
+// stretchy, they will be given equal shares of the leftover space.
+// There can also be space between each control ("padding").
+type Form struct {
+ ControlBase
+ f *C.uiForm
+ children []Control
+}
+
+// NewForm creates a new horizontal Form.
+func NewForm() *Form {
+ f := new(Form)
+
+ f.f = C.uiNewForm()
+
+ f.ControlBase = NewControlBase(f, uintptr(unsafe.Pointer(f.f)))
+ return f
+}
+
+// Destroy destroys the Form. If the Form has children,
+// Destroy calls Destroy on those Controls as well.
+func (f *Form) Destroy() {
+ for len(f.children) != 0 {
+ c := f.children[0]
+ f.Delete(0)
+ c.Destroy()
+ }
+ f.ControlBase.Destroy()
+}
+
+// Append adds the given control to the end of the Form.
+func (f *Form) Append(label string, child Control, stretchy bool) {
+ clabel := C.CString(label)
+ defer freestr(clabel)
+ c := touiControl(child.LibuiControl())
+ C.uiFormAppend(f.f, clabel, c, frombool(stretchy))
+ f.children = append(f.children, child)
+}
+
+// Delete deletes the nth control of the Form.
+func (f *Form) Delete(n int) {
+ f.children = append(f.children[:n], f.children[n + 1:]...)
+ C.uiFormDelete(f.f, C.int(n))
+}
+
+// Padded returns whether there is space between each control
+// of the Form.
+func (f *Form) Padded() bool {
+ return tobool(C.uiFormPadded(f.f))
+}
+
+// SetPadded controls whether there is space between each control
+// of the Form. The size of the padding is determined by the OS and
+// its best practices.
+func (f *Form) SetPadded(padded bool) {
+ C.uiFormSetPadded(f.f, frombool(padded))
+}
diff --git a/BBB_GOFILES/grid.go b/BBB_GOFILES/grid.go
new file mode 100644
index 0000000..a1985fc
--- /dev/null
+++ b/BBB_GOFILES/grid.go
@@ -0,0 +1,96 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Grid is a Control that arranges other Controls in a grid.
+// Grid is a very powerful container: it can position and size each
+// Control in several ways and can (and must) have Controls added
+// to it in any direction. It can also have Controls spanning multiple
+// rows and columns.
+//
+// Each Control in a Grid has associated "expansion" and
+// "alignment" values in both the X and Y direction.
+// Expansion determines whether all cells in the same row/column
+// are given whatever space is left over after figuring out how big
+// the rest of the Grid should be. Alignment determines the position
+// of a Control relative to its cell after computing the above. The
+// special alignment Fill can be used to grow a Control to fit its cell.
+// Note that expansion and alignment are independent variables.
+// For more information on expansion and alignment, read
+// https://developer.gnome.org/gtk3/unstable/ch28s02.html.
+type Grid struct {
+ ControlBase
+ g *C.uiGrid
+ children []Control
+}
+
+// Align represents the alignment of a Control in its cell of a Grid.
+type Align int
+const (
+ AlignFill Align = iota
+ AlignStart
+ AlignCenter
+ AlignEnd
+)
+
+// At represents a side of a Control to add other Controls to a Grid to.
+type At int
+const (
+ Leading At = iota
+ Top
+ Trailing
+ Bottom
+)
+
+// NewGrid creates a new Grid.
+func NewGrid() *Grid {
+ g := new(Grid)
+
+ g.g = C.uiNewGrid()
+
+ g.ControlBase = NewControlBase(g, uintptr(unsafe.Pointer(g.g)))
+ return g
+}
+
+// TODO Destroy
+
+// Append adds the given control to the Grid, at the given coordinate.
+func (g *Grid) Append(child Control, left, top int, xspan, yspan int, hexpand bool, halign Align, vexpand bool, valign Align) {
+ C.uiGridAppend(g.g, touiControl(child.LibuiControl()),
+ C.int(left), C.int(top),
+ C.int(xspan), C.int(yspan),
+ frombool(hexpand), C.uiAlign(halign),
+ frombool(vexpand), C.uiAlign(valign))
+ g.children = append(g.children, child)
+}
+
+// InsertAt adds the given control to the Grid relative to an existing
+// control.
+func (g *Grid) InsertAt(child Control, existing Control, at At, xspan, yspan int, hexpand bool, halign Align, vexpand bool, valign Align) {
+ C.uiGridInsertAt(g.g, touiControl(child.LibuiControl()),
+ touiControl(existing.LibuiControl()), C.uiAt(at),
+ C.int(xspan), C.int(yspan),
+ frombool(hexpand), C.uiAlign(halign),
+ frombool(vexpand), C.uiAlign(valign))
+ g.children = append(g.children, child)
+}
+
+// Padded returns whether there is space between each control
+// of the Grid.
+func (g *Grid) Padded() bool {
+ return tobool(C.uiGridPadded(g.g))
+}
+
+// SetPadded controls whether there is space between each control
+// of the Grid. The size of the padding is determined by the OS and
+// its best practices.
+func (g *Grid) SetPadded(padded bool) {
+ C.uiGridSetPadded(g.g, frombool(padded))
+}
diff --git a/BBB_GOFILES/group.go b/BBB_GOFILES/group.go
new file mode 100644
index 0000000..6992948
--- /dev/null
+++ b/BBB_GOFILES/group.go
@@ -0,0 +1,80 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Group is a Control that holds another Control and wraps it around
+// a labelled box (though some systems make this box invisible).
+// You can use this to group related controls together.
+type Group struct {
+ ControlBase
+ g *C.uiGroup
+ child Control
+}
+
+// NewGroup creates a new Group.
+func NewGroup(title string) *Group {
+ g := new(Group)
+
+ ctitle := C.CString(title)
+ g.g = C.uiNewGroup(ctitle)
+ freestr(ctitle)
+
+ g.ControlBase = NewControlBase(g, uintptr(unsafe.Pointer(g.g)))
+ return g
+}
+
+// Destroy destroys the Group. If the Group has a child,
+// Destroy calls Destroy on that as well.
+func (g *Group) Destroy() {
+ if g.child != nil {
+ c := g.child
+ g.SetChild(nil)
+ c.Destroy()
+ }
+ g.ControlBase.Destroy()
+}
+
+// Title returns the Group's title.
+func (g *Group) Title() string {
+ ctitle := C.uiGroupTitle(g.g)
+ title := C.GoString(ctitle)
+ C.uiFreeText(ctitle)
+ return title
+}
+
+// SetTitle sets the Group's title to title.
+func (g *Group) SetTitle(title string) {
+ ctitle := C.CString(title)
+ C.uiGroupSetTitle(g.g, ctitle)
+ freestr(ctitle)
+}
+
+// SetChild sets the Group's child to child. If child is nil, the Group
+// will not have a child.
+func (g *Group) SetChild(child Control) {
+ g.child = child
+ c := (*C.uiControl)(nil)
+ if g.child != nil {
+ c = touiControl(g.child.LibuiControl())
+ }
+ C.uiGroupSetChild(g.g, c)
+}
+
+// Margined returns whether the Group has margins around its child.
+func (g *Group) Margined() bool {
+ return tobool(C.uiGroupMargined(g.g))
+}
+
+// SetMargined controls whether the Group has margins around its
+// child. The size of the margins are determined by the OS and its
+// best practices.
+func (g *Group) SetMargined(margined bool) {
+ C.uiGroupSetMargined(g.g, frombool(margined))
+}
diff --git a/BBB_GOFILES/image.go b/BBB_GOFILES/image.go
new file mode 100644
index 0000000..9b791fe
--- /dev/null
+++ b/BBB_GOFILES/image.go
@@ -0,0 +1,58 @@
+// 21 august 2018
+
+package ui
+
+import (
+ "image"
+)
+
+// #include <stdlib.h>
+// #include "ui.h"
+import "C"
+
+// Image stores an image for display on screen.
+//
+// Images are built from one or more representations, each with the
+// same aspect ratio but a different pixel size. Package ui
+// automatically selects the most appropriate representation for
+// drawing the image when it comes time to draw the image; what
+// this means depends on the pixel density of the target context.
+// Therefore, one can use Image to draw higher-detailed images on
+// higher-density displays. The typical use cases are either:
+//
+// - have just a single representation, at which point all screens
+// use the same image, and thus uiImage acts like a simple
+// bitmap image, or
+// - have two images, one at normal resolution and one at 2x
+// resolution; this matches the current expectations of some
+// desktop systems at the time of writing (mid-2018)
+//
+// Image allocates OS resources; you must explicitly free an Image
+// when you are finished with it.
+type Image struct {
+ i *C.uiImage
+}
+
+// NewImage creates a new Image with the given width and
+// height. This width and height should be the size in points of the
+// image in the device-independent case; typically this is the 1x size.
+func NewImage(width, height float64) *Image {
+ return &Image{
+ i: C.uiNewImage(C.double(width), C.double(height)),
+ }
+}
+
+// Free frees the Image.
+func (i *Image) Free() {
+ C.uiFreeImage(i.i)
+}
+
+// Append adds the given image as a representation of the Image.
+func (i *Image) Append(img *image.NRGBA) {
+ cpix := C.CBytes(img.Pix)
+ defer C.free(cpix)
+ C.uiImageAppend(i.i, cpix,
+ C.int(img.Rect.Dx()),
+ C.int(img.Rect.Dy()),
+ C.int(img.Stride))
+}
diff --git a/BBB_GOFILES/label.go b/BBB_GOFILES/label.go
new file mode 100644
index 0000000..cac7680
--- /dev/null
+++ b/BBB_GOFILES/label.go
@@ -0,0 +1,44 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Label is a Control that represents a line of text that cannot be
+// interacted with.
+type Label struct {
+ ControlBase
+ l *C.uiLabel
+}
+
+// NewLabel creates a new Label with the given text.
+func NewLabel(text string) *Label {
+ l := new(Label)
+
+ ctext := C.CString(text)
+ l.l = C.uiNewLabel(ctext)
+ freestr(ctext)
+
+ l.ControlBase = NewControlBase(l, uintptr(unsafe.Pointer(l.l)))
+ return l
+}
+
+// Text returns the Label's text.
+func (l *Label) Text() string {
+ ctext := C.uiLabelText(l.l)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the Label's text to text.
+func (l *Label) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiLabelSetText(l.l, ctext)
+ freestr(ctext)
+}
diff --git a/BBB_GOFILES/link_darwin_amd64.go b/BBB_GOFILES/link_darwin_amd64.go
new file mode 100644
index 0000000..ef9c2e5
--- /dev/null
+++ b/BBB_GOFILES/link_darwin_amd64.go
@@ -0,0 +1,7 @@
+// 13 december 2015
+
+package ui
+
+// #cgo CFLAGS: -mmacosx-version-min=10.8
+// #cgo LDFLAGS: ${SRCDIR}/libui_darwin_amd64.a -framework Foundation -framework AppKit -mmacosx-version-min=10.8
+import "C"
diff --git a/BBB_GOFILES/main.go b/BBB_GOFILES/main.go
new file mode 100644
index 0000000..6500345
--- /dev/null
+++ b/BBB_GOFILES/main.go
@@ -0,0 +1,132 @@
+// 11 december 2015
+
+package ui
+
+import (
+ "runtime"
+ "errors"
+ "sync"
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doQueueMain(void *);
+// extern int doOnShouldQuit(void *);
+// // see golang/go#19835
+// typedef void (*queueMainCallback)(void *);
+// typedef int (*onShouldQuitCallback)(void *);
+import "C"
+
+// make sure main() runs on the first thread created by the OS
+// if main() calls Main(), things will just work on macOS, where the first thread created by the OS is the only thread allowed to be the main GUI thread
+// we might as well lock the OS thread for the other platforms here too (though on those it doesn't matter *which* thread we lock to)
+// TODO describe the source of this trick
+func init() {
+ runtime.LockOSThread()
+}
+
+// Main initializes package ui, runs f to set up the program,
+// and executes the GUI main loop. f should set up the program's
+// initial state: open the main window, create controls, and set up
+// events. It should then return, at which point Main will
+// process events until Quit is called, at which point Main will return
+// nil. If package ui fails to initialize, Main returns an appropriate
+// error.
+func Main(f func()) error {
+ // TODO HEAP SAFETY
+ opts := C.uiInitOptions{}
+ estr := C.uiInit(&opts)
+ if estr != nil {
+ err := errors.New(C.GoString(estr))
+ C.uiFreeInitError(estr)
+ return err
+ }
+ C.uiOnShouldQuit(C.onShouldQuitCallback(C.doOnShouldQuit), nil)
+ QueueMain(f)
+ C.uiMain()
+ return nil
+}
+
+// Quit queues a return from Main. It does not exit the program.
+// It also does not immediately cause Main to return; Main will
+// return when it next can. Quit must be called from the GUI thread.
+func Quit() {
+ C.uiQuit()
+}
+
+// These prevent the passing of Go functions into C land.
+// TODO make an actual sparse list instead of this monotonic map thingy
+var (
+ qmmap = make(map[uintptr]func())
+ qmcurrent = uintptr(0)
+ qmlock sync.Mutex
+)
+
+// QueueMain queues f to be executed on the GUI thread when
+// next possible. It returns immediately; that is, it does not wait
+// for the function to actually be executed. QueueMain is the only
+// function that can be called from other goroutines, and its
+// primary purpose is to allow communication between other
+// goroutines and the GUI thread. Calling QueueMain after Quit
+// has been called results in undefined behavior.
+//
+// If you start a goroutine in f, it also cannot call package ui
+// functions. So for instance, the following will result in
+// undefined behavior:
+//
+// ui.QueueMain(func() {
+// go ui.MsgBox(...)
+// })
+func QueueMain(f func()) {
+ qmlock.Lock()
+ defer qmlock.Unlock()
+
+ n := uintptr(0)
+ for {
+ n = qmcurrent
+ qmcurrent++
+ if qmmap[n] == nil {
+ break
+ }
+ }
+ qmmap[n] = f
+ C.uiQueueMain(C.queueMainCallback(C.doQueueMain), unsafe.Pointer(n))
+}
+
+//export doQueueMain
+func doQueueMain(nn unsafe.Pointer) {
+ qmlock.Lock()
+
+ n := uintptr(nn)
+ f := qmmap[n]
+ delete(qmmap, n)
+
+ // allow uiQueueMain() to be called by a queued function
+ // TODO explicitly allow this in libui too
+ qmlock.Unlock()
+
+ f()
+}
+
+// no need to lock this; this API is only safe on the main thread
+var shouldQuitFunc func() bool
+
+// OnShouldQuit schedules f to be exeucted when the OS wants
+// the program to quit or when a Quit menu item has been clicked.
+// Only one function may be registered at a time. If the function
+// returns true, Quit will be called. If the function returns false, or
+// if OnShouldQuit is never called. Quit will not be called and the
+// OS will be told that the program needs to continue running.
+func OnShouldQuit(f func() bool) {
+ shouldQuitFunc = f
+}
+
+//export doOnShouldQuit
+func doOnShouldQuit(unused unsafe.Pointer) C.int {
+ if shouldQuitFunc == nil {
+ return 0
+ }
+ return frombool(shouldQuitFunc())
+}
+
+// TODO Timer?
diff --git a/BBB_GOFILES/multilineentry.go b/BBB_GOFILES/multilineentry.go
new file mode 100644
index 0000000..88a75c4
--- /dev/null
+++ b/BBB_GOFILES/multilineentry.go
@@ -0,0 +1,97 @@
+// 12 december 2015
+
+// TODO typing in entry in OS X crashes libui
+// I've had similar issues with checkboxes on libui
+// something's wrong with NSMapTable
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doMultilineEntryOnChanged(uiMultilineEntry *, void *);
+// // see golang/go#19835
+// typedef void (*multilineEntryCallback)(uiMultilineEntry *, void *);
+import "C"
+
+// MultilineEntry is a Control that represents a space that the user
+// can type multiple lines of text into.
+type MultilineEntry struct {
+ ControlBase
+ e *C.uiMultilineEntry
+ onChanged func(*MultilineEntry)
+}
+
+func finishNewMultilineEntry(ee *C.uiMultilineEntry) *MultilineEntry {
+ m := new(MultilineEntry)
+
+ m.e = ee
+
+ C.uiMultilineEntryOnChanged(m.e, C.multilineEntryCallback(C.doMultilineEntryOnChanged), nil)
+
+ m.ControlBase = NewControlBase(m, uintptr(unsafe.Pointer(m.e)))
+ return m
+}
+
+// NewMultilineEntry creates a new MultilineEntry.
+// The MultilineEntry soft-word-wraps and has no horizontal
+// scrollbar.
+func NewMultilineEntry() *MultilineEntry {
+ return finishNewMultilineEntry(C.uiNewMultilineEntry())
+}
+
+// NewNonWrappingMultilineEntry creates a new MultilineEntry.
+// The MultilineEntry does not word-wrap and thus has horizontal
+// scrollbar.
+func NewNonWrappingMultilineEntry() *MultilineEntry {
+ return finishNewMultilineEntry(C.uiNewNonWrappingMultilineEntry())
+}
+
+// Text returns the MultilineEntry's text.
+func (m *MultilineEntry) Text() string {
+ ctext := C.uiMultilineEntryText(m.e)
+ text := C.GoString(ctext)
+ C.uiFreeText(ctext)
+ return text
+}
+
+// SetText sets the MultilineEntry's text to text.
+func (m *MultilineEntry) SetText(text string) {
+ ctext := C.CString(text)
+ C.uiMultilineEntrySetText(m.e, ctext)
+ freestr(ctext)
+}
+
+// Append adds text to the end of the MultilineEntry's text.
+// TODO selection and scroll behavior
+func (m *MultilineEntry) Append(text string) {
+ ctext := C.CString(text)
+ C.uiMultilineEntryAppend(m.e, ctext)
+ freestr(ctext)
+}
+
+// OnChanged registers f to be run when the user makes a change to
+// the MultilineEntry. Only one function can be registered at a time.
+func (m *MultilineEntry) OnChanged(f func(*MultilineEntry)) {
+ m.onChanged = f
+}
+
+//export doMultilineEntryOnChanged
+func doMultilineEntryOnChanged(ee *C.uiMultilineEntry, data unsafe.Pointer) {
+ m := ControlFromLibui(uintptr(unsafe.Pointer(ee))).(*MultilineEntry)
+ if m.onChanged != nil {
+ m.onChanged(m)
+ }
+}
+
+// ReadOnly returns whether the MultilineEntry can be changed.
+func (m *MultilineEntry) ReadOnly() bool {
+ return tobool(C.uiMultilineEntryReadOnly(m.e))
+}
+
+// SetReadOnly sets whether the MultilineEntry can be changed.
+func (m *MultilineEntry) SetReadOnly(ro bool) {
+ C.uiMultilineEntrySetReadOnly(m.e, frombool(ro))
+}
diff --git a/BBB_GOFILES/progressbar.go b/BBB_GOFILES/progressbar.go
new file mode 100644
index 0000000..f58976f
--- /dev/null
+++ b/BBB_GOFILES/progressbar.go
@@ -0,0 +1,39 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// ProgressBar is a Control that represents a horizontal bar that
+// is filled in progressively over time as a process completes.
+type ProgressBar struct {
+ ControlBase
+ p *C.uiProgressBar
+}
+
+// NewProgressBar creates a new ProgressBar.
+func NewProgressBar() *ProgressBar {
+ p := new(ProgressBar)
+
+ p.p = C.uiNewProgressBar()
+
+ p.ControlBase = NewControlBase(p, uintptr(unsafe.Pointer(p.p)))
+ return p
+}
+
+// Value returns the value currently shown in the ProgressBar.
+func (p *ProgressBar) Value() int {
+ return int(C.uiProgressBarValue(p.p))
+}
+
+// SetValue sets the ProgressBar's currently displayed percentage
+// to value. value must be between 0 and 100 inclusive, or -1 for
+// an indeterminate progressbar.
+func (p *ProgressBar) SetValue(value int) {
+ C.uiProgressBarSetValue(p.p, C.int(value))
+}
diff --git a/BBB_GOFILES/radiobuttons.go b/BBB_GOFILES/radiobuttons.go
new file mode 100644
index 0000000..2413544
--- /dev/null
+++ b/BBB_GOFILES/radiobuttons.go
@@ -0,0 +1,66 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doRadioButtonsOnSelected(uiRadioButtons *, void *);
+// // see golang/go#19835
+// typedef void (*radioButtonsCallback)(uiRadioButtons *, void *);
+import "C"
+
+// RadioButtons is a Control that represents a set of checkable
+// buttons from which exactly one may be chosen by the user.
+type RadioButtons struct {
+ ControlBase
+ r *C.uiRadioButtons
+ onSelected func(*RadioButtons)
+}
+
+// NewRadioButtons creates a new RadioButtons.
+func NewRadioButtons() *RadioButtons {
+ r := new(RadioButtons)
+
+ r.r = C.uiNewRadioButtons()
+
+ C.uiRadioButtonsOnSelected(r.r, C.radioButtonsCallback(C.doRadioButtonsOnSelected), nil)
+
+ r.ControlBase = NewControlBase(r, uintptr(unsafe.Pointer(r.r)))
+ return r
+}
+
+// Append adds the named button to the end of the RadioButtons.
+func (r *RadioButtons) Append(text string) {
+ ctext := C.CString(text)
+ C.uiRadioButtonsAppend(r.r, ctext)
+ freestr(ctext)
+}
+
+// Selected returns the index of the currently selected option in the
+// RadioButtons, or -1 if no item is selected.
+func (r *RadioButtons) Selected() int {
+ return int(C.uiRadioButtonsSelected(r.r))
+}
+
+// SetSelected sets the currently selected option in the RadioButtons
+// to index.
+func (r *RadioButtons) SetSelected(index int) {
+ C.uiRadioButtonsSetSelected(r.r, C.int(index))
+}
+
+// OnSelected registers f to be run when the user selects an option in
+// the RadioButtons. Only one function can be registered at a time.
+func (r *RadioButtons) OnSelected(f func(*RadioButtons)) {
+ r.onSelected = f
+}
+
+//export doRadioButtonsOnSelected
+func doRadioButtonsOnSelected(rr *C.uiRadioButtons, data unsafe.Pointer) {
+ r := ControlFromLibui(uintptr(unsafe.Pointer(rr))).(*RadioButtons)
+ if r.onSelected != nil {
+ r.onSelected(r)
+ }
+}
diff --git a/BBB_GOFILES/separator.go b/BBB_GOFILES/separator.go
new file mode 100644
index 0000000..67dea4f
--- /dev/null
+++ b/BBB_GOFILES/separator.go
@@ -0,0 +1,37 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Separator is a Control that represents a horizontal line that
+// visually separates controls.
+type Separator struct {
+ ControlBase
+ s *C.uiSeparator
+}
+
+// NewHorizontalSeparator creates a new horizontal Separator.
+func NewHorizontalSeparator() *Separator {
+ s := new(Separator)
+
+ s.s = C.uiNewHorizontalSeparator()
+
+ s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s)))
+ return s
+}
+
+// NewVerticalSeparator creates a new vertical Separator.
+func NewVerticalSeparator() *Separator {
+ s := new(Separator)
+
+ s.s = C.uiNewVerticalSeparator()
+
+ s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s)))
+ return s
+}
diff --git a/BBB_GOFILES/slider.go b/BBB_GOFILES/slider.go
new file mode 100644
index 0000000..91656cf
--- /dev/null
+++ b/BBB_GOFILES/slider.go
@@ -0,0 +1,58 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doSliderOnChanged(uiSlider *, void *);
+// // see golang/go#19835
+// typedef void (*sliderCallback)(uiSlider *, void *);
+import "C"
+
+// Slider is a Control that represents a horizontal bar that represents
+// a range of integers. The user can drag a pointer on the bar to
+// select an integer.
+type Slider struct {
+ ControlBase
+ s *C.uiSlider
+ onChanged func(*Slider)
+}
+
+// NewSlider creates a new Slider. If min >= max, they are swapped.
+func NewSlider(min int, max int) *Slider {
+ s := new(Slider)
+
+ s.s = C.uiNewSlider(C.int(min), C.int(max))
+
+ C.uiSliderOnChanged(s.s, C.sliderCallback(C.doSliderOnChanged), nil)
+
+ s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s)))
+ return s
+}
+
+// Value returns the Slider's current value.
+func (s *Slider) Value() int {
+ return int(C.uiSliderValue(s.s))
+}
+
+// SetValue sets the Slider's current value to value.
+func (s *Slider) SetValue(value int) {
+ C.uiSliderSetValue(s.s, C.int(value))
+}
+
+// OnChanged registers f to be run when the user changes the value
+// of the Slider. Only one function can be registered at a time.
+func (s *Slider) OnChanged(f func(*Slider)) {
+ s.onChanged = f
+}
+
+//export doSliderOnChanged
+func doSliderOnChanged(ss *C.uiSlider, data unsafe.Pointer) {
+ s := ControlFromLibui(uintptr(unsafe.Pointer(ss))).(*Slider)
+ if s.onChanged != nil {
+ s.onChanged(s)
+ }
+}
diff --git a/BBB_GOFILES/spinbox.go b/BBB_GOFILES/spinbox.go
new file mode 100644
index 0000000..27dd287
--- /dev/null
+++ b/BBB_GOFILES/spinbox.go
@@ -0,0 +1,58 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern void doSpinboxOnChanged(uiSpinbox *, void *);
+// // see golang/go#19835
+// typedef void (*spinboxCallback)(uiSpinbox *, void *);
+import "C"
+
+// Spinbox is a Control that represents a space where the user can
+// enter integers. The space also comes with buttons to add or
+// subtract 1 from the integer.
+type Spinbox struct {
+ ControlBase
+ s *C.uiSpinbox
+ onChanged func(*Spinbox)
+}
+
+// NewSpinbox creates a new Spinbox. If min >= max, they are swapped.
+func NewSpinbox(min int, max int) *Spinbox {
+ s := new(Spinbox)
+
+ s.s = C.uiNewSpinbox(C.int(min), C.int(max))
+
+ C.uiSpinboxOnChanged(s.s, C.spinboxCallback(C.doSpinboxOnChanged), nil)
+
+ s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s)))
+ return s
+}
+
+// Value returns the Spinbox's current value.
+func (s *Spinbox) Value() int {
+ return int(C.uiSpinboxValue(s.s))
+}
+
+// SetValue sets the Spinbox's current value to value.
+func (s *Spinbox) SetValue(value int) {
+ C.uiSpinboxSetValue(s.s, C.int(value))
+}
+
+// OnChanged registers f to be run when the user changes the value
+// of the Spinbox. Only one function can be registered at a time.
+func (s *Spinbox) OnChanged(f func(*Spinbox)) {
+ s.onChanged = f
+}
+
+//export doSpinboxOnChanged
+func doSpinboxOnChanged(ss *C.uiSpinbox, data unsafe.Pointer) {
+ s := ControlFromLibui(uintptr(unsafe.Pointer(ss))).(*Spinbox)
+ if s.onChanged != nil {
+ s.onChanged(s)
+ }
+}
diff --git a/BBB_GOFILES/stddialogs.go b/BBB_GOFILES/stddialogs.go
new file mode 100644
index 0000000..0ff8a01
--- /dev/null
+++ b/BBB_GOFILES/stddialogs.go
@@ -0,0 +1,41 @@
+// 20 december 2015
+
+package ui
+
+// #include "ui.h"
+import "C"
+
+// TODO
+func MsgBoxError(w *Window, title string, description string) {
+ ctitle := C.CString(title)
+ defer freestr(ctitle)
+ cdescription := C.CString(description)
+ defer freestr(cdescription)
+ C.uiMsgBoxError(w.w, ctitle, cdescription)
+}
+
+func OpenFile(w *Window) string {
+ cname := C.uiOpenFile(w.w)
+ if cname == nil {
+ return ""
+ }
+ defer C.uiFreeText(cname)
+ return C.GoString(cname)
+}
+
+func SaveFile(w *Window) string {
+ cname := C.uiSaveFile(w.w)
+ if cname == nil {
+ return ""
+ }
+ defer C.uiFreeText(cname)
+ return C.GoString(cname)
+}
+
+func MsgBox(w *Window, title string, description string) {
+ ctitle := C.CString(title)
+ defer freestr(ctitle)
+ cdescription := C.CString(description)
+ defer freestr(cdescription)
+ C.uiMsgBox(w.w, ctitle, cdescription)
+}
diff --git a/BBB_GOFILES/tab.go b/BBB_GOFILES/tab.go
new file mode 100644
index 0000000..0b0b0b9
--- /dev/null
+++ b/BBB_GOFILES/tab.go
@@ -0,0 +1,87 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+import "C"
+
+// Tab is a Control that holds tabbed pages of Controls. Each tab
+// has a label. The user can click on the tabs themselves to switch
+// pages. Individual pages can also have margins.
+type Tab struct {
+ ControlBase
+ t *C.uiTab
+ children []Control
+}
+
+// NewTab creates a new Tab.
+func NewTab() *Tab {
+ t := new(Tab)
+
+ t.t = C.uiNewTab()
+
+ t.ControlBase = NewControlBase(t, uintptr(unsafe.Pointer(t.t)))
+ return t
+}
+
+// Destroy destroys the Tab. If the Tab has pages,
+// Destroy calls Destroy on the pages's Controls as well.
+func (t *Tab) Destroy() {
+ for len(t.children) != 0 {
+ c := t.children[0]
+ t.Delete(0)
+ c.Destroy()
+ }
+ t.ControlBase.Destroy()
+}
+
+// Append adds the given page to the end of the Tab.
+func (t *Tab) Append(name string, child Control) {
+ t.InsertAt(name, len(t.children), child)
+}
+
+// InsertAt adds the given page to the Tab such that it is the
+// nth page of the Tab (starting at 0).
+func (t *Tab) InsertAt(name string, n int, child Control) {
+ c := (*C.uiControl)(nil)
+ if child != nil {
+ c = touiControl(child.LibuiControl())
+ }
+ cname := C.CString(name)
+ C.uiTabInsertAt(t.t, cname, C.int(n), c)
+ freestr(cname)
+ ch := make([]Control, len(t.children) + 1)
+ // and insert into t.children at the right place
+ copy(ch[:n], t.children[:n])
+ ch[n] = child
+ copy(ch[n + 1:], t.children[n:])
+ t.children = ch
+}
+
+// Delete deletes the nth page of the Tab.
+func (t *Tab) Delete(n int) {
+ t.children = append(t.children[:n], t.children[n + 1:]...)
+ C.uiTabDelete(t.t, C.int(n))
+}
+
+// NumPages returns the number of pages in the Tab.
+func (t *Tab) NumPages() int {
+ return len(t.children)
+}
+
+// Margined returns whether page n (starting at 0) of the Tab
+// has margins around its child.
+func (t *Tab) Margined(n int) bool {
+ return tobool(C.uiTabMargined(t.t, C.int(n)))
+}
+
+// SetMargined controls whether page n (starting at 0) of the Tab
+// has margins around its child. The size of the margins are
+// determined by the OS and its best practices.
+func (t *Tab) SetMargined(n int, margined bool) {
+ C.uiTabSetMargined(t.t, C.int(n), frombool(margined))
+}
diff --git a/BBB_GOFILES/tablemodel.go b/BBB_GOFILES/tablemodel.go
new file mode 100644
index 0000000..b4ca7a4
--- /dev/null
+++ b/BBB_GOFILES/tablemodel.go
@@ -0,0 +1,243 @@
+// 24 august 2018
+
+package ui
+
+// #include "ui.h"
+// extern int doTableModelNumColumns(uiTableModelHandler *, uiTableModel *);
+// extern uiTableValueType doTableModelColumnType(uiTableModelHandler *, uiTableModel *, int);
+// extern int doTableModelNumRows(uiTableModelHandler *, uiTableModel *);
+// extern uiTableValue *doTableModelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int column);
+// extern void doTableModelSetCellValue(uiTableModelHandler *, uiTableModel *, int, int, uiTableValue *);
+// // deal with cgo being dumb
+// static inline void realDoTableModelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int column, const uiTableValue *value)
+// {
+// doTableModelSetCellValue(mh, m, row, column, (uiTableValue *) value);
+// }
+// // TODO why can't this be static?
+// const uiTableModelHandler pkguiTableModelHandler = {
+// .NumColumns = doTableModelNumColumns,
+// .ColumnType = doTableModelColumnType,
+// .NumRows = doTableModelNumRows,
+// .CellValue = doTableModelCellValue,
+// .SetCellValue = realDoTableModelSetCellValue,
+// };
+import "C"
+
+// TableValue is a type that represents a piece of data that can come
+// out of a TableModel.
+type TableValue interface {
+ toLibui() *C.uiTableValue
+}
+
+// TableString is a TableValue that stores a string. TableString is
+// used for displaying text in a Table.
+type TableString string
+
+func (s TableString) toLibui() *C.uiTableValue {
+ cs := C.CString(string(s))
+ defer freestr(cs)
+ return C.uiNewTableValueString(cs)
+}
+
+// TableImage is a TableValue that represents an Image. Ownership
+// of the Image is not copied; you must keep it alive alongside the
+// TableImage.
+type TableImage struct {
+ I *Image
+}
+
+func (i TableImage) toLibui() *C.uiTableValue {
+ return C.uiNewTableValueImage(i.I.i)
+}
+
+// TableInt is a TableValue that stores integers. These are used for
+// progressbars. Due to current limitations of libui, they also
+// represent checkbox states, via TableFalse and TableTrue.
+type TableInt int
+
+// TableFalse and TableTrue are the Boolean constants for TableInt.
+const (
+ TableFalse TableInt = 0
+ TableTrue TableInt = 1
+)
+
+func (i TableInt) toLibui() *C.uiTableValue {
+ return C.uiNewTableValueInt(C.int(i))
+}
+
+// TableColor is a TableValue that represents a color.
+type TableColor struct {
+ R float64
+ G float64
+ B float64
+ A float64
+}
+
+func (c TableColor) toLibui() *C.uiTableValue {
+ return C.uiNewTableValueColor(C.double(c.R), C.double(c.G), C.double(c.B), C.double(c.A))
+}
+
+func tableValueFromLibui(value *C.uiTableValue) TableValue {
+ if value == nil {
+ return nil
+ }
+ switch C.uiTableValueGetType(value) {
+ case C.uiTableValueTypeString:
+ cs := C.uiTableValueString(value)
+ return TableString(C.GoString(cs))
+ case C.uiTableValueTypeImage:
+ panic("TODO")
+ case C.uiTableValueTypeInt:
+ return TableInt(C.uiTableValueInt(value))
+ case C.uiTableValueTypeColor:
+ panic("TODO")
+ }
+ panic("unreachable")
+}
+
+// no need to lock these; only the GUI thread can access them
+var modelhandlers = make(map[*C.uiTableModel]TableModelHandler)
+var models = make(map[*C.uiTableModel]*TableModel)
+
+// TableModel is an object that provides the data for a Table.
+// This data is returned via methods you provide in the
+// TableModelHandler interface.
+//
+// TableModel represents data using a table, but this table does
+// not map directly to Table itself. Instead, you can have data
+// columns which provide instructions for how to render a given
+// Table's column — for instance, one model column can be used
+// to give certain rows of a Table a different background color.
+// Row numbers DO match with uiTable row numbers.
+//
+// Once created, the number and data types of columns of a
+// TableModel cannot change.
+//
+// Row and column numbers start at 0. A TableModel can be
+// associated with more than one Table at a time.
+type TableModel struct {
+ m *C.uiTableModel
+}
+
+// TableModelHandler defines the methods that TableModel
+// calls when it needs data.
+type TableModelHandler interface {
+ // ColumnTypes returns a slice of value types of the data
+ // stored in the model columns of the TableModel.
+ // Each entry in the slice should ideally be a zero value for
+ // the TableValue type of the column in question; the number
+ // of elements in the slice determines the number of model
+ // columns in the TableModel. The returned slice must remain
+ // constant through the lifetime of the TableModel. This
+ // method is not guaranteed to be called depending on the
+ // system.
+ ColumnTypes(m *TableModel) []TableValue
+
+ // NumRows returns the number or rows in the TableModel.
+ // This value must be non-negative.
+ NumRows(m *TableModel) int
+
+ // CellValue returns a TableValue corresponding to the model
+ // cell at (row, column). The type of the returned TableValue
+ // must match column's value type. Under some circumstances,
+ // nil may be returned; refer to the various methods that add
+ // columns to Table for details.
+ CellValue(m *TableModel, row, column int) TableValue
+
+ // SetCellValue changes the model cell value at (row, column)
+ // in the TableModel. Within this function, either do nothing
+ // to keep the current cell value or save the new cell value as
+ // appropriate. After SetCellValue is called, the Table will
+ // itself reload the table cell. Under certain conditions, the
+ // TableValue passed in can be nil; refer to the various
+ // methods that add columns to Table for details.
+ SetCellValue(m *TableModel, row, column int, value TableValue)
+}
+
+//export doTableModelNumColumns
+func doTableModelNumColumns(umh *C.uiTableModelHandler, um *C.uiTableModel) C.int {
+ mh := modelhandlers[um]
+ return C.int(len(mh.ColumnTypes(models[um])))
+}
+
+//export doTableModelColumnType
+func doTableModelColumnType(umh *C.uiTableModelHandler, um *C.uiTableModel, n C.int) C.uiTableValueType {
+ mh := modelhandlers[um]
+ c := mh.ColumnTypes(models[um])
+ switch c[n].(type) {
+ case TableString:
+ return C.uiTableValueTypeString
+ case TableImage:
+ return C.uiTableValueTypeImage
+ case TableInt:
+ return C.uiTableValueTypeInt
+ case TableColor:
+ return C.uiTableValueTypeColor
+ }
+ panic("unreachable")
+}
+
+//export doTableModelNumRows
+func doTableModelNumRows(umh *C.uiTableModelHandler, um *C.uiTableModel) C.int {
+ mh := modelhandlers[um]
+ return C.int(mh.NumRows(models[um]))
+}
+
+//export doTableModelCellValue
+func doTableModelCellValue(umh *C.uiTableModelHandler, um *C.uiTableModel, row, column C.int) *C.uiTableValue {
+ mh := modelhandlers[um]
+ v := mh.CellValue(models[um], int(row), int(column))
+ if v == nil {
+ return nil
+ }
+ return v.toLibui()
+}
+
+//export doTableModelSetCellValue
+func doTableModelSetCellValue(umh *C.uiTableModelHandler, um *C.uiTableModel, row, column C.int, value *C.uiTableValue) {
+ mh := modelhandlers[um]
+ v := tableValueFromLibui(value)
+ mh.SetCellValue(models[um], int(row), int(column), v)
+}
+
+// NewTableModel creates a new TableModel.
+func NewTableModel(handler TableModelHandler) *TableModel {
+ m := &TableModel{
+ m: C.uiNewTableModel(&C.pkguiTableModelHandler),
+ }
+ modelhandlers[m.m] = handler
+ models[m.m] = m
+ return m
+}
+
+// Free frees m. It is an error to Free any models associated with a
+// Table.
+func (m *TableModel) Free() {
+ delete(models, m.m)
+ delete(modelhandlers, m.m)
+ C.uiFreeTableModel(m.m)
+}
+
+// RowInserted tells any Tables associated with m that a new row
+// has been added to m at index index. You call this method when
+// the number of rows in your model has changed; after calling it,
+// NumRows should returm the new row count.
+func (m *TableModel) RowInserted(index int) {
+ C.uiTableModelRowInserted(m.m, C.int(index))
+}
+
+// RowChanged tells any Tables associated with m that the data in
+// the row at index has changed. You do not need to call this in
+// your SetCellValue handlers, but you do need to call this if your
+// data changes at some other point.
+func (m *TableModel) RowChanged(index int) {
+ C.uiTableModelRowChanged(m.m, C.int(index))
+}
+
+// RowDeleted tells any Tables associated with m that the row at
+// index index has been deleted. You call this function when the
+// number of rows in your model has changed; after calling it,
+// NumRows should returm the new row count.
+func (m *TableModel) RowDeleted(index int) {
+ C.uiTableModelRowDeleted(m.m, C.int(index))
+}
diff --git a/BBB_GOFILES/util.go b/BBB_GOFILES/util.go
new file mode 100644
index 0000000..bcd4d33
--- /dev/null
+++ b/BBB_GOFILES/util.go
@@ -0,0 +1,45 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include <stdlib.h>
+// #include "util.h"
+import "C"
+
+// We want Go itself to complain when we're out of memory.
+// The allocators in cgo *should* do this, but there isn't a
+// C.CMalloc(). There *is* a C.CBytes(), however, for transferring
+// binary blobs from Go to C. If we pass this an arbitrary slice
+// of the desired length, we get our C.CMalloc(). Using a slice
+// that's always initialized to zero gives us the memset(0)
+// (or ZeroMemory()) for free.
+var allocBytes = make([]byte, 1024) // 1024 bytes first
+
+//export pkguiAlloc
+func pkguiAlloc(n C.size_t) unsafe.Pointer {
+ if n > C.size_t(len(allocBytes)) {
+ // TODO round n up to a multiple of a power of 2?
+ // for instance 0x1234 bytes -> 0x1800 bytes
+ allocBytes = make([]byte, n)
+ }
+ return C.CBytes(allocBytes[:n])
+}
+
+func freestr(str *C.char) {
+ C.free(unsafe.Pointer(str))
+}
+
+func tobool(b C.int) bool {
+ return b != 0
+}
+
+func frombool(b bool) C.int {
+ if b {
+ return 1
+ }
+ return 0
+}
diff --git a/BBB_GOFILES/window.go b/BBB_GOFILES/window.go
new file mode 100644
index 0000000..a40ddd9
--- /dev/null
+++ b/BBB_GOFILES/window.go
@@ -0,0 +1,125 @@
+// 12 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "ui.h"
+// extern int doWindowOnClosing(uiWindow *, void *);
+// // see golang/go#19835
+// typedef int (*windowOnClosingCallback)(uiWindow *, void *);
+import "C"
+
+// Window is a Control that represents a top-level window.
+// A Window contains one child Control that occupies the
+// entirety of the window. Though a Window is a Control,
+// a Window cannot be the child of another Control.
+type Window struct {
+ ControlBase
+ w *C.uiWindow
+ child Control
+ onClosing func(w *Window) bool
+}
+
+// NewWindow creates a new Window.
+func NewWindow(title string, width int, height int, hasMenubar bool) *Window {
+ w := new(Window)
+
+ ctitle := C.CString(title)
+ w.w = C.uiNewWindow(ctitle, C.int(width), C.int(height), frombool(hasMenubar))
+ freestr(ctitle)
+
+ C.uiWindowOnClosing(w.w, C.windowOnClosingCallback(C.doWindowOnClosing), nil)
+
+ w.ControlBase = NewControlBase(w, uintptr(unsafe.Pointer(w.w)))
+ return w
+}
+
+// Destroy destroys the Window. If the Window has a child,
+// Destroy calls Destroy on that as well.
+func (w *Window) Destroy() {
+ w.Hide() // first hide the window, in case anything in the below if statement forces an immediate redraw
+ if w.child != nil {
+ c := w.child
+ w.SetChild(nil)
+ c.Destroy()
+ }
+ w.ControlBase.Destroy()
+}
+
+// Title returns the Window's title.
+func (w *Window) Title() string {
+ ctitle := C.uiWindowTitle(w.w)
+ title := C.GoString(ctitle)
+ C.uiFreeText(ctitle)
+ return title
+}
+
+// SetTitle sets the Window's title to title.
+func (w *Window) SetTitle(title string) {
+ ctitle := C.CString(title)
+ C.uiWindowSetTitle(w.w, ctitle)
+ freestr(ctitle)
+}
+
+// TODO ContentSize
+// TODO SetContentSize
+// TODO Fullscreen
+// TODO SetFullscreen
+// TODO OnContentSizeChanged
+
+// OnClosing registers f to be run when the user clicks the Window's
+// close button. Only one function can be registered at a time.
+// If f returns true, the window is destroyed with the Destroy method.
+// If f returns false, or if OnClosing is never called, the window is not
+// destroyed and is kept visible.
+func (w *Window) OnClosing(f func(*Window) bool) {
+ w.onClosing = f
+}
+
+//export doWindowOnClosing
+func doWindowOnClosing(ww *C.uiWindow, data unsafe.Pointer) C.int {
+ w := ControlFromLibui(uintptr(unsafe.Pointer(ww))).(*Window)
+ if w.onClosing == nil {
+ return 0
+ }
+ if w.onClosing(w) {
+ w.Destroy()
+ }
+ return 0
+}
+
+// Borderless returns whether the Window is borderless.
+func (w *Window) Borderless() bool {
+ return tobool(C.uiWindowBorderless(w.w))
+}
+
+// SetBorderless sets the Window to be borderless or not.
+func (w *Window) SetBorderless(borderless bool) {
+ C.uiWindowSetBorderless(w.w, frombool(borderless))
+}
+
+// SetChild sets the Window's child to child. If child is nil, the Window
+// will not have a child.
+func (w *Window) SetChild(child Control) {
+ w.child = child
+ c := (*C.uiControl)(nil)
+ if w.child != nil {
+ c = touiControl(w.child.LibuiControl())
+ }
+ C.uiWindowSetChild(w.w, c)
+}
+
+// Margined returns whether the Window has margins around its child.
+func (w *Window) Margined() bool {
+ return tobool(C.uiWindowMargined(w.w))
+}
+
+// SetMargined controls whether the Window has margins around its
+// child. The size of the margins are determined by the OS and its
+// best practices.
+func (w *Window) SetMargined(margined bool) {
+ C.uiWindowSetMargined(w.w, frombool(margined))
+}
diff --git a/BBB_GOFILES/zz_controls.go b/BBB_GOFILES/zz_controls.go
new file mode 100644
index 0000000..d77bebe
--- /dev/null
+++ b/BBB_GOFILES/zz_controls.go
@@ -0,0 +1,222 @@
+// 12 august 2018
+
+// +build OMIT
+
+package main
+
+import (
+ "github.com/andlabs/ui"
+)
+
+var mainwin *ui.Window
+
+func makeBasicControlsPage() ui.Control {
+ vbox := ui.NewVerticalBox()
+ vbox.SetPadded(true)
+
+ hbox := ui.NewHorizontalBox()
+ hbox.SetPadded(true)
+ vbox.Append(hbox, false)
+
+ hbox.Append(ui.NewButton("Button"), false)
+ hbox.Append(ui.NewCheckbox("Checkbox"), false)
+
+ vbox.Append(ui.NewLabel("This is a label. Right now, labels can only span one line."), false)
+
+ vbox.Append(ui.NewHorizontalSeparator(), false)
+
+ group := ui.NewGroup("Entries")
+ group.SetMargined(true)
+ vbox.Append(group, true)
+
+group.SetChild(ui.NewNonWrappingMultilineEntry())
+
+ entryForm := ui.NewForm()
+ entryForm.SetPadded(true)
+ group.SetChild(entryForm)
+
+ entryForm.Append("Entry", ui.NewEntry(), false)
+ entryForm.Append("Password Entry", ui.NewPasswordEntry(), false)
+ entryForm.Append("Search Entry", ui.NewSearchEntry(), false)
+ entryForm.Append("Multiline Entry", ui.NewMultilineEntry(), true)
+ entryForm.Append("Multiline Entry No Wrap", ui.NewNonWrappingMultilineEntry(), true)
+
+ return vbox
+}
+
+func makeNumbersPage() ui.Control {
+ hbox := ui.NewHorizontalBox()
+ hbox.SetPadded(true)
+
+ group := ui.NewGroup("Numbers")
+ group.SetMargined(true)
+ hbox.Append(group, true)
+
+ vbox := ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ group.SetChild(vbox)
+
+ spinbox := ui.NewSpinbox(0, 100)
+ slider := ui.NewSlider(0, 100)
+ pbar := ui.NewProgressBar()
+ spinbox.OnChanged(func(*ui.Spinbox) {
+ slider.SetValue(spinbox.Value())
+ pbar.SetValue(spinbox.Value())
+ })
+ slider.OnChanged(func(*ui.Slider) {
+ spinbox.SetValue(slider.Value())
+ pbar.SetValue(slider.Value())
+ })
+ vbox.Append(spinbox, false)
+ vbox.Append(slider, false)
+ vbox.Append(pbar, false)
+
+ ip := ui.NewProgressBar()
+ ip.SetValue(-1)
+ vbox.Append(ip, false)
+
+ group = ui.NewGroup("Lists")
+ group.SetMargined(true)
+ hbox.Append(group, true)
+
+ vbox = ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ group.SetChild(vbox)
+
+ cbox := ui.NewCombobox()
+ cbox.Append("Combobox Item 1")
+ cbox.Append("Combobox Item 2")
+ cbox.Append("Combobox Item 3")
+ vbox.Append(cbox, false)
+
+ ecbox := ui.NewEditableCombobox()
+ ecbox.Append("Editable Item 1")
+ ecbox.Append("Editable Item 2")
+ ecbox.Append("Editable Item 3")
+ vbox.Append(ecbox, false)
+
+ rb := ui.NewRadioButtons()
+ rb.Append("Radio Button 1")
+ rb.Append("Radio Button 2")
+ rb.Append("Radio Button 3")
+ vbox.Append(rb, false)
+
+ return hbox
+}
+
+func makeDataChoosersPage() ui.Control {
+ hbox := ui.NewHorizontalBox()
+ hbox.SetPadded(true)
+
+ vbox := ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ hbox.Append(vbox, false)
+
+ vbox.Append(ui.NewDatePicker(), false)
+ vbox.Append(ui.NewTimePicker(), false)
+ vbox.Append(ui.NewDateTimePicker(), false)
+ vbox.Append(ui.NewFontButton(), false)
+ vbox.Append(ui.NewColorButton(), false)
+
+ hbox.Append(ui.NewVerticalSeparator(), false)
+
+ vbox = ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ hbox.Append(vbox, true)
+
+ grid := ui.NewGrid()
+ grid.SetPadded(true)
+ vbox.Append(grid, false)
+
+ button := ui.NewButton("Open File")
+ entry := ui.NewEntry()
+ entry.SetReadOnly(true)
+ button.OnClicked(func(*ui.Button) {
+ filename := ui.OpenFile(mainwin)
+ if filename == "" {
+ filename = "(cancelled)"
+ }
+ entry.SetText(filename)
+ })
+ grid.Append(button,
+ 0, 0, 1, 1,
+ false, ui.AlignFill, false, ui.AlignFill)
+ grid.Append(entry,
+ 1, 0, 1, 1,
+ true, ui.AlignFill, false, ui.AlignFill)
+
+ button = ui.NewButton("Save File")
+ entry2 := ui.NewEntry()
+ entry2.SetReadOnly(true)
+ button.OnClicked(func(*ui.Button) {
+ filename := ui.SaveFile(mainwin)
+ if filename == "" {
+ filename = "(cancelled)"
+ }
+ entry2.SetText(filename)
+ })
+ grid.Append(button,
+ 0, 1, 1, 1,
+ false, ui.AlignFill, false, ui.AlignFill)
+ grid.Append(entry2,
+ 1, 1, 1, 1,
+ true, ui.AlignFill, false, ui.AlignFill)
+
+ msggrid := ui.NewGrid()
+ msggrid.SetPadded(true)
+ grid.Append(msggrid,
+ 0, 2, 2, 1,
+ false, ui.AlignCenter, false, ui.AlignStart)
+
+ button = ui.NewButton("Message Box")
+ button.OnClicked(func(*ui.Button) {
+ ui.MsgBox(mainwin,
+ "This is a normal message box.",
+ "More detailed information can be shown here.")
+ })
+ msggrid.Append(button,
+ 0, 0, 1, 1,
+ false, ui.AlignFill, false, ui.AlignFill)
+ button = ui.NewButton("Error Box")
+ button.OnClicked(func(*ui.Button) {
+ ui.MsgBoxError(mainwin,
+ "This message box describes an error.",
+ "More detailed information can be shown here.")
+ })
+ msggrid.Append(button,
+ 1, 0, 1, 1,
+ false, ui.AlignFill, false, ui.AlignFill)
+
+ return hbox
+}
+
+func setupUI() {
+ mainwin = ui.NewWindow("libui Control Gallery", 640, 480, true)
+ mainwin.OnClosing(func(*ui.Window) bool {
+ ui.Quit()
+ return true
+ })
+ ui.OnShouldQuit(func() bool {
+ mainwin.Destroy()
+ return true
+ })
+
+ tab := ui.NewTab()
+ mainwin.SetChild(tab)
+ mainwin.SetMargined(true)
+
+ tab.Append("Basic Controls", makeBasicControlsPage())
+ tab.SetMargined(0, true)
+
+ tab.Append("Numbers and Lists", makeNumbersPage())
+ tab.SetMargined(1, true)
+
+ tab.Append("Data Choosers", makeDataChoosersPage())
+ tab.SetMargined(2, true)
+
+ mainwin.Show()
+}
+
+func main() {
+ ui.Main(setupUI)
+}
diff --git a/BBB_GOFILES/zz_drawtext.go b/BBB_GOFILES/zz_drawtext.go
new file mode 100644
index 0000000..a32b14b
--- /dev/null
+++ b/BBB_GOFILES/zz_drawtext.go
@@ -0,0 +1,167 @@
+// 19 august 2018
+
+// +build OMIT
+
+package main
+
+// TODO probably a bug in libui: changing the font away from skia leads to a crash
+
+import (
+ "github.com/andlabs/ui"
+)
+
+var (
+ fontButton *ui.FontButton
+ alignment *ui.Combobox
+
+ attrstr *ui.AttributedString
+)
+
+func appendWithAttributes(what string, attrs ...ui.Attribute) {
+ start := len(attrstr.String())
+ end := start + len(what)
+ attrstr.AppendUnattributed(what)
+ for _, a := range attrs {
+ attrstr.SetAttribute(a, start, end)
+ }
+}
+
+func makeAttributedString() {
+ attrstr = ui.NewAttributedString(
+ "Drawing strings with package ui is done with the ui.AttributedString and ui.DrawTextLayout objects.\n" +
+ "ui.AttributedString lets you have a variety of attributes: ")
+
+ appendWithAttributes("font family", ui.TextFamily("Courier New"))
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("font size", ui.TextSize(18))
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("font weight", ui.TextWeightBold)
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("font italicness", ui.TextItalicItalic)
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("font stretch", ui.TextStretchCondensed)
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("text color", ui.TextColor{0.75, 0.25, 0.5, 0.75})
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("text background color", ui.TextBackground{0.5, 0.5, 0.25, 0.5})
+ attrstr.AppendUnattributed(", ")
+
+ appendWithAttributes("underline style", ui.UnderlineSingle)
+ attrstr.AppendUnattributed(", ")
+
+ attrstr.AppendUnattributed("and ")
+ appendWithAttributes("underline color",
+ ui.UnderlineDouble,
+ ui.UnderlineColorCustom{1.0, 0.0, 0.5, 1.0})
+ attrstr.AppendUnattributed(". ")
+
+ attrstr.AppendUnattributed("Furthermore, there are attributes allowing for ")
+ appendWithAttributes("special underlines for indicating spelling errors",
+ ui.UnderlineSuggestion,
+ ui.UnderlineColorSpelling)
+ attrstr.AppendUnattributed(" (and other types of errors) ")
+
+ attrstr.AppendUnattributed("and control over OpenType features such as ligatures (for instance, ")
+ appendWithAttributes("afford", ui.OpenTypeFeatures{
+ ui.ToOpenTypeTag('l', 'i', 'g', 'a'): 0,
+ })
+ attrstr.AppendUnattributed(" vs. ")
+ appendWithAttributes("afford", ui.OpenTypeFeatures{
+ ui.ToOpenTypeTag('l', 'i', 'g', 'a'): 1,
+ })
+ attrstr.AppendUnattributed(").\n")
+
+ attrstr.AppendUnattributed("Use the controls opposite to the text to control properties of the text.")
+}
+
+type areaHandler struct{}
+
+func (areaHandler) Draw(a *ui.Area, p *ui.AreaDrawParams) {
+ tl := ui.DrawNewTextLayout(&ui.DrawTextLayoutParams{
+ String: attrstr,
+ DefaultFont: fontButton.Font(),
+ Width: p.AreaWidth,
+ Align: ui.DrawTextAlign(alignment.Selected()),
+ })
+ defer tl.Free()
+ p.Context.Text(tl, 0, 0)
+}
+
+func (areaHandler) MouseEvent(a *ui.Area, me *ui.AreaMouseEvent) {
+ // do nothing
+}
+
+func (areaHandler) MouseCrossed(a *ui.Area, left bool) {
+ // do nothing
+}
+
+func (areaHandler) DragBroken(a *ui.Area) {
+ // do nothing
+}
+
+func (areaHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
+ // reject all keys
+ return false
+}
+
+func setupUI() {
+ makeAttributedString()
+
+ mainwin := ui.NewWindow("libui Text-Drawing Example", 640, 480, true)
+ mainwin.SetMargined(true)
+ mainwin.OnClosing(func(*ui.Window) bool {
+ mainwin.Destroy()
+ ui.Quit()
+ return false
+ })
+ ui.OnShouldQuit(func() bool {
+ mainwin.Destroy()
+ return true
+ })
+
+ hbox := ui.NewHorizontalBox()
+ hbox.SetPadded(true)
+ mainwin.SetChild(hbox)
+
+ vbox := ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ hbox.Append(vbox, false)
+
+ area := ui.NewArea(areaHandler{})
+
+ fontButton = ui.NewFontButton()
+ fontButton.OnChanged(func(*ui.FontButton) {
+ area.QueueRedrawAll()
+ })
+ vbox.Append(fontButton, false)
+
+ form := ui.NewForm()
+ form.SetPadded(true)
+ // TODO on OS X if this is set to 1 then the window can't resize; does the form not have the concept of stretchy trailing space?
+ vbox.Append(form, false)
+
+ alignment = ui.NewCombobox()
+ // note that the items match with the values of the uiDrawTextAlign values
+ alignment.Append("Left")
+ alignment.Append("Center")
+ alignment.Append("Right")
+ alignment.SetSelected(0) // start with left alignment
+ alignment.OnSelected(func(*ui.Combobox) {
+ area.QueueRedrawAll()
+ })
+ form.Append("Alignment", alignment, false)
+
+ hbox.Append(area, true)
+
+ mainwin.Show()
+}
+
+func main() {
+ ui.Main(setupUI)
+}
diff --git a/BBB_GOFILES/zz_histogram.go b/BBB_GOFILES/zz_histogram.go
new file mode 100644
index 0000000..fae5aae
--- /dev/null
+++ b/BBB_GOFILES/zz_histogram.go
@@ -0,0 +1,253 @@
+// 12 august 2018
+
+// +build OMIT
+
+package main
+
+import (
+ "math/rand"
+ "time"
+
+ "github.com/andlabs/ui"
+)
+
+var (
+ histogram *ui.Area
+ datapoints [10]*ui.Spinbox
+ colorButton *ui.ColorButton
+
+ currentPoint = -1
+)
+
+// some metrics
+const (
+ xoffLeft = 20 // histogram margins
+ yoffTop = 20
+ xoffRight = 20
+ yoffBottom = 20
+ pointRadius = 5
+)
+
+// helper to quickly set a brush color
+func mkSolidBrush(color uint32, alpha float64) *ui.Brush {
+ brush := new(ui.Brush)
+ brush.Type = ui.BrushTypeSolid
+ component := uint8((color >> 16) & 0xFF)
+ brush.R = float64(component) / 255
+ component = uint8((color >> 8) & 0xFF)
+ brush.G = float64(component) / 255
+ component = uint8(color & 0xFF)
+ brush.B = float64(component) / 255
+ brush.A = alpha
+ return brush
+}
+
+// and some colors
+// names and values from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370907%28v=vs.85%29.aspx
+const (
+ colorWhite = 0xFFFFFF
+ colorBlack = 0x000000
+ colorDodgerBlue = 0x1E90FF
+)
+
+func pointLocations(width, height float64) (xs, ys [10]float64) {
+ xincr := width / 9 // 10 - 1 to make the last point be at the end
+ yincr := height / 100
+ for i := 0; i < 10; i++ {
+ // get the value of the point
+ n := datapoints[i].Value()
+ // because y=0 is the top but n=0 is the bottom, we need to flip
+ n = 100 - n
+ xs[i] = xincr * float64(i)
+ ys[i] = yincr * float64(n)
+ }
+ return xs, ys
+}
+
+func constructGraph(width, height float64, extend bool) *ui.Path {
+ xs, ys := pointLocations(width, height)
+ path := ui.NewPath(ui.Winding)
+
+ path.NewFigure(xs[0], ys[0])
+ for i := 1; i < 10; i++ {
+ path.LineTo(xs[i], ys[i])
+ }
+
+ if extend {
+ path.LineTo(width, height)
+ path.LineTo(0, height)
+ path.CloseFigure()
+ }
+
+ path.End()
+ return path
+}
+
+func graphSize(clientWidth, clientHeight float64) (graphWidth, graphHeight float64) {
+ return clientWidth - xoffLeft - xoffRight,
+ clientHeight - yoffTop - yoffBottom
+}
+
+type areaHandler struct{}
+
+func (areaHandler) Draw(a *ui.Area, p *ui.AreaDrawParams) {
+ // fill the area with white
+ brush := mkSolidBrush(colorWhite, 1.0)
+ path := ui.NewPath(ui.Winding)
+ path.AddRectangle(0, 0, p.AreaWidth, p.AreaHeight)
+ path.End()
+ p.Context.Fill(path, brush)
+ path.Free()
+
+ graphWidth, graphHeight := graphSize(p.AreaWidth, p.AreaHeight)
+
+ sp := &ui.StrokeParams{
+ Cap: ui.FlatCap,
+ Join: ui.MiterJoin,
+ Thickness: 2,
+ MiterLimit: ui.DefaultMiterLimit,
+ }
+
+ // draw the axes
+ brush = mkSolidBrush(colorBlack, 1.0)
+ path = ui.NewPath(ui.Winding)
+ path.NewFigure(xoffLeft, yoffTop)
+ path.LineTo(xoffLeft, yoffTop + graphHeight)
+ path.LineTo(xoffLeft + graphWidth, yoffTop + graphHeight)
+ path.End()
+ p.Context.Stroke(path, brush, sp)
+ path.Free()
+
+ // now transform the coordinate space so (0, 0) is the top-left corner of the graph
+ m := ui.NewMatrix()
+ m.Translate(xoffLeft, yoffTop)
+ p.Context.Transform(m)
+
+ // now get the color for the graph itself and set up the brush
+ graphR, graphG, graphB, graphA := colorButton.Color()
+ brush.Type = ui.BrushTypeSolid
+ brush.R = graphR
+ brush.G = graphG
+ brush.B = graphB
+ // we set brush.A below to different values for the fill and stroke
+
+ // now create the fill for the graph below the graph line
+ path = constructGraph(graphWidth, graphHeight, true)
+ brush.A = graphA / 2
+ p.Context.Fill(path, brush)
+ path.Free()
+
+ // now draw the histogram line
+ path = constructGraph(graphWidth, graphHeight, false)
+ brush.A = graphA
+ p.Context.Stroke(path, brush, sp)
+ path.Free()
+
+ // now draw the point being hovered over
+ if currentPoint != -1 {
+ xs, ys := pointLocations(graphWidth, graphHeight)
+ path = ui.NewPath(ui.Winding)
+ path.NewFigureWithArc(
+ xs[currentPoint], ys[currentPoint],
+ pointRadius,
+ 0, 6.23, // TODO pi
+ false)
+ path.End()
+ // use the same brush as for the histogram lines
+ p.Context.Fill(path, brush)
+ path.Free()
+ }
+}
+
+func inPoint(x, y float64, xtest, ytest float64) bool {
+ // TODO switch to using a matrix
+ x -= xoffLeft
+ y -= yoffTop
+ return (x >= xtest - pointRadius) &&
+ (x <= xtest + pointRadius) &&
+ (y >= ytest - pointRadius) &&
+ (y <= ytest + pointRadius)
+}
+
+func (areaHandler) MouseEvent(a *ui.Area, me *ui.AreaMouseEvent) {
+ graphWidth, graphHeight := graphSize(me.AreaWidth, me.AreaHeight)
+ xs, ys := pointLocations(graphWidth, graphHeight)
+
+ currentPoint = -1
+ for i := 0; i < 10; i++ {
+ if inPoint(me.X, me.Y, xs[i], ys[i]) {
+ currentPoint = i
+ break
+ }
+ }
+
+ // TODO only redraw the relevant area
+ histogram.QueueRedrawAll()
+}
+
+func (areaHandler) MouseCrossed(a *ui.Area, left bool) {
+ // do nothing
+}
+
+func (areaHandler) DragBroken(a *ui.Area) {
+ // do nothing
+}
+
+func (areaHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
+ // reject all keys
+ return false
+}
+
+func setupUI() {
+ mainwin := ui.NewWindow("libui Histogram Example", 640, 480, true)
+ mainwin.SetMargined(true)
+ mainwin.OnClosing(func(*ui.Window) bool {
+ mainwin.Destroy()
+ ui.Quit()
+ return false
+ })
+ ui.OnShouldQuit(func() bool {
+ mainwin.Destroy()
+ return true
+ })
+
+ hbox := ui.NewHorizontalBox()
+ hbox.SetPadded(true)
+ mainwin.SetChild(hbox)
+
+ vbox := ui.NewVerticalBox()
+ vbox.SetPadded(true)
+ hbox.Append(vbox, false)
+
+ histogram = ui.NewArea(areaHandler{})
+
+ rand.Seed(time.Now().Unix())
+ for i := 0; i < 10; i++ {
+ datapoints[i] = ui.NewSpinbox(0, 100)
+ datapoints[i].SetValue(rand.Intn(101))
+ datapoints[i].OnChanged(func(*ui.Spinbox) {
+ histogram.QueueRedrawAll()
+ })
+ vbox.Append(datapoints[i], false)
+ }
+
+ colorButton = ui.NewColorButton()
+ // TODO inline these
+ brush := mkSolidBrush(colorDodgerBlue, 1.0)
+ colorButton.SetColor(brush.R,
+ brush.G,
+ brush.B,
+ brush.A)
+ colorButton.OnChanged(func(*ui.ColorButton) {
+ histogram.QueueRedrawAll()
+ })
+ vbox.Append(colorButton, false)
+
+ hbox.Append(histogram, true)
+
+ mainwin.Show()
+}
+
+func main() {
+ ui.Main(setupUI)
+}