diff options
| author | Pietro Gagliardi <[email protected]> | 2018-08-26 09:55:07 -0400 |
|---|---|---|
| committer | Pietro Gagliardi <[email protected]> | 2018-08-26 09:55:07 -0400 |
| commit | 62ac2527732a01dfa6bd2c9523215c0ba3816641 (patch) | |
| tree | 84244a69e048f79e4d9f134c121f4cf581200986 /BBB_GOFILES | |
| parent | a5a00c644c08a6e0f52740c3f2a280977929a285 (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')
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) +} |
