From 62ac2527732a01dfa6bd2c9523215c0ba3816641 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 26 Aug 2018 09:55:07 -0400 Subject: Moved all the Go files out of the way again, this time so we can migrate them to more proper cgo usage. --- BBB_GOFILES/area.go | 111 ++++++++ BBB_GOFILES/areahandler.go | 324 +++++++++++++++++++++++ BBB_GOFILES/box.go | 85 ++++++ BBB_GOFILES/button.go | 65 +++++ BBB_GOFILES/checkbox.go | 76 ++++++ BBB_GOFILES/colorbutton.go | 83 ++++++ BBB_GOFILES/combobox.go | 67 +++++ BBB_GOFILES/control.go | 145 ++++++++++ BBB_GOFILES/datetimepicker.go | 107 ++++++++ BBB_GOFILES/draw.go | 484 ++++++++++++++++++++++++++++++++++ BBB_GOFILES/drawtext.go | 554 +++++++++++++++++++++++++++++++++++++++ BBB_GOFILES/editablecombobox.go | 72 +++++ BBB_GOFILES/entry.go | 93 +++++++ BBB_GOFILES/fontbutton.go | 69 +++++ BBB_GOFILES/form.go | 71 +++++ BBB_GOFILES/grid.go | 96 +++++++ BBB_GOFILES/group.go | 80 ++++++ BBB_GOFILES/image.go | 58 ++++ BBB_GOFILES/label.go | 44 ++++ BBB_GOFILES/link_darwin_amd64.go | 7 + BBB_GOFILES/main.go | 132 ++++++++++ BBB_GOFILES/multilineentry.go | 97 +++++++ BBB_GOFILES/progressbar.go | 39 +++ BBB_GOFILES/radiobuttons.go | 66 +++++ BBB_GOFILES/separator.go | 37 +++ BBB_GOFILES/slider.go | 58 ++++ BBB_GOFILES/spinbox.go | 58 ++++ BBB_GOFILES/stddialogs.go | 41 +++ BBB_GOFILES/tab.go | 87 ++++++ BBB_GOFILES/tablemodel.go | 243 +++++++++++++++++ BBB_GOFILES/util.go | 45 ++++ BBB_GOFILES/window.go | 125 +++++++++ BBB_GOFILES/zz_controls.go | 222 ++++++++++++++++ BBB_GOFILES/zz_drawtext.go | 167 ++++++++++++ BBB_GOFILES/zz_histogram.go | 253 ++++++++++++++++++ area.go | 111 -------- areahandler.go | 324 ----------------------- box.go | 85 ------ button.go | 65 ----- checkbox.go | 76 ------ colorbutton.go | 83 ------ combobox.go | 67 ----- control.go | 145 ---------- datetimepicker.go | 107 -------- draw.go | 484 ---------------------------------- drawtext.go | 554 --------------------------------------- editablecombobox.go | 72 ----- entry.go | 93 ------- fontbutton.go | 69 ----- form.go | 71 ----- grid.go | 96 ------- group.go | 80 ------ image.go | 58 ---- label.go | 44 ---- link_darwin_amd64.go | 7 - main.go | 132 ---------- multilineentry.go | 97 ------- progressbar.go | 39 --- radiobuttons.go | 66 ----- separator.go | 37 --- slider.go | 58 ---- spinbox.go | 58 ---- stddialogs.go | 41 --- tab.go | 87 ------ tablemodel.go | 243 ----------------- util.go | 45 ---- window.go | 125 --------- zz_controls.go | 222 ---------------- zz_drawtext.go | 167 ------------ zz_histogram.go | 253 ------------------ 70 files changed, 4361 insertions(+), 4361 deletions(-) create mode 100644 BBB_GOFILES/area.go create mode 100644 BBB_GOFILES/areahandler.go create mode 100644 BBB_GOFILES/box.go create mode 100644 BBB_GOFILES/button.go create mode 100644 BBB_GOFILES/checkbox.go create mode 100644 BBB_GOFILES/colorbutton.go create mode 100644 BBB_GOFILES/combobox.go create mode 100644 BBB_GOFILES/control.go create mode 100644 BBB_GOFILES/datetimepicker.go create mode 100644 BBB_GOFILES/draw.go create mode 100644 BBB_GOFILES/drawtext.go create mode 100644 BBB_GOFILES/editablecombobox.go create mode 100644 BBB_GOFILES/entry.go create mode 100644 BBB_GOFILES/fontbutton.go create mode 100644 BBB_GOFILES/form.go create mode 100644 BBB_GOFILES/grid.go create mode 100644 BBB_GOFILES/group.go create mode 100644 BBB_GOFILES/image.go create mode 100644 BBB_GOFILES/label.go create mode 100644 BBB_GOFILES/link_darwin_amd64.go create mode 100644 BBB_GOFILES/main.go create mode 100644 BBB_GOFILES/multilineentry.go create mode 100644 BBB_GOFILES/progressbar.go create mode 100644 BBB_GOFILES/radiobuttons.go create mode 100644 BBB_GOFILES/separator.go create mode 100644 BBB_GOFILES/slider.go create mode 100644 BBB_GOFILES/spinbox.go create mode 100644 BBB_GOFILES/stddialogs.go create mode 100644 BBB_GOFILES/tab.go create mode 100644 BBB_GOFILES/tablemodel.go create mode 100644 BBB_GOFILES/util.go create mode 100644 BBB_GOFILES/window.go create mode 100644 BBB_GOFILES/zz_controls.go create mode 100644 BBB_GOFILES/zz_drawtext.go create mode 100644 BBB_GOFILES/zz_histogram.go delete mode 100644 area.go delete mode 100644 areahandler.go delete mode 100644 box.go delete mode 100644 button.go delete mode 100644 checkbox.go delete mode 100644 colorbutton.go delete mode 100644 combobox.go delete mode 100644 control.go delete mode 100644 datetimepicker.go delete mode 100644 draw.go delete mode 100644 drawtext.go delete mode 100644 editablecombobox.go delete mode 100644 entry.go delete mode 100644 fontbutton.go delete mode 100644 form.go delete mode 100644 grid.go delete mode 100644 group.go delete mode 100644 image.go delete mode 100644 label.go delete mode 100644 link_darwin_amd64.go delete mode 100644 main.go delete mode 100644 multilineentry.go delete mode 100644 progressbar.go delete mode 100644 radiobuttons.go delete mode 100644 separator.go delete mode 100644 slider.go delete mode 100644 spinbox.go delete mode 100644 stddialogs.go delete mode 100644 tab.go delete mode 100644 tablemodel.go delete mode 100644 util.go delete mode 100644 window.go delete mode 100644 zz_controls.go delete mode 100644 zz_drawtext.go delete mode 100644 zz_histogram.go 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 +// #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 +// #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 +// #include +// #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 +// #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 +// #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 +// #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 +// #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 +// #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) +} diff --git a/area.go b/area.go deleted file mode 100644 index f7ed41c..0000000 --- a/area.go +++ /dev/null @@ -1,111 +0,0 @@ -// 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/areahandler.go b/areahandler.go deleted file mode 100644 index dbe739e..0000000 --- a/areahandler.go +++ /dev/null @@ -1,324 +0,0 @@ -// 13 december 2015 - -package ui - -import ( - "unsafe" -) - -// #include -// #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/box.go b/box.go deleted file mode 100644 index 65421d4..0000000 --- a/box.go +++ /dev/null @@ -1,85 +0,0 @@ -// 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/button.go b/button.go deleted file mode 100644 index 630c684..0000000 --- a/button.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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/checkbox.go b/checkbox.go deleted file mode 100644 index 8177226..0000000 --- a/checkbox.go +++ /dev/null @@ -1,76 +0,0 @@ -// 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/colorbutton.go b/colorbutton.go deleted file mode 100644 index 0129a55..0000000 --- a/colorbutton.go +++ /dev/null @@ -1,83 +0,0 @@ -// 12 december 2015 - -package ui - -import ( - "unsafe" -) - -// #include -// #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/combobox.go b/combobox.go deleted file mode 100644 index 1e381de..0000000 --- a/combobox.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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/control.go b/control.go deleted file mode 100644 index 23a8ddf..0000000 --- a/control.go +++ /dev/null @@ -1,145 +0,0 @@ -// 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/datetimepicker.go b/datetimepicker.go deleted file mode 100644 index 3436b3c..0000000 --- a/datetimepicker.go +++ /dev/null @@ -1,107 +0,0 @@ -// 12 december 2015 - -package ui - -import ( - "time" - "unsafe" -) - -// #include -// #include -// #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/draw.go b/draw.go deleted file mode 100644 index fd21a75..0000000 --- a/draw.go +++ /dev/null @@ -1,484 +0,0 @@ -// 13 december 2015 - -package ui - -// #include -// #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/drawtext.go b/drawtext.go deleted file mode 100644 index 1cd438c..0000000 --- a/drawtext.go +++ /dev/null @@ -1,554 +0,0 @@ -// 12 august 2018 - -package ui - -// #include -// #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/editablecombobox.go b/editablecombobox.go deleted file mode 100644 index 1242928..0000000 --- a/editablecombobox.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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/entry.go b/entry.go deleted file mode 100644 index 52da537..0000000 --- a/entry.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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/fontbutton.go b/fontbutton.go deleted file mode 100644 index c228151..0000000 --- a/fontbutton.go +++ /dev/null @@ -1,69 +0,0 @@ -// 12 december 2015 - -package ui - -import ( - "unsafe" -) - -// #include -// #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/form.go b/form.go deleted file mode 100644 index 6f8a282..0000000 --- a/form.go +++ /dev/null @@ -1,71 +0,0 @@ -// 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/grid.go b/grid.go deleted file mode 100644 index a1985fc..0000000 --- a/grid.go +++ /dev/null @@ -1,96 +0,0 @@ -// 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/group.go b/group.go deleted file mode 100644 index 6992948..0000000 --- a/group.go +++ /dev/null @@ -1,80 +0,0 @@ -// 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/image.go b/image.go deleted file mode 100644 index 9b791fe..0000000 --- a/image.go +++ /dev/null @@ -1,58 +0,0 @@ -// 21 august 2018 - -package ui - -import ( - "image" -) - -// #include -// #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/label.go b/label.go deleted file mode 100644 index cac7680..0000000 --- a/label.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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/link_darwin_amd64.go b/link_darwin_amd64.go deleted file mode 100644 index ef9c2e5..0000000 --- a/link_darwin_amd64.go +++ /dev/null @@ -1,7 +0,0 @@ -// 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/main.go b/main.go deleted file mode 100644 index 6500345..0000000 --- a/main.go +++ /dev/null @@ -1,132 +0,0 @@ -// 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/multilineentry.go b/multilineentry.go deleted file mode 100644 index 88a75c4..0000000 --- a/multilineentry.go +++ /dev/null @@ -1,97 +0,0 @@ -// 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/progressbar.go b/progressbar.go deleted file mode 100644 index f58976f..0000000 --- a/progressbar.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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/radiobuttons.go b/radiobuttons.go deleted file mode 100644 index 2413544..0000000 --- a/radiobuttons.go +++ /dev/null @@ -1,66 +0,0 @@ -// 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/separator.go b/separator.go deleted file mode 100644 index 67dea4f..0000000 --- a/separator.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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/slider.go b/slider.go deleted file mode 100644 index 91656cf..0000000 --- a/slider.go +++ /dev/null @@ -1,58 +0,0 @@ -// 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/spinbox.go b/spinbox.go deleted file mode 100644 index 27dd287..0000000 --- a/spinbox.go +++ /dev/null @@ -1,58 +0,0 @@ -// 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/stddialogs.go b/stddialogs.go deleted file mode 100644 index 0ff8a01..0000000 --- a/stddialogs.go +++ /dev/null @@ -1,41 +0,0 @@ -// 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/tab.go b/tab.go deleted file mode 100644 index 0b0b0b9..0000000 --- a/tab.go +++ /dev/null @@ -1,87 +0,0 @@ -// 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/tablemodel.go b/tablemodel.go deleted file mode 100644 index b4ca7a4..0000000 --- a/tablemodel.go +++ /dev/null @@ -1,243 +0,0 @@ -// 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/util.go b/util.go deleted file mode 100644 index bcd4d33..0000000 --- a/util.go +++ /dev/null @@ -1,45 +0,0 @@ -// 12 december 2015 - -package ui - -import ( - "unsafe" -) - -// #include -// #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/window.go b/window.go deleted file mode 100644 index a40ddd9..0000000 --- a/window.go +++ /dev/null @@ -1,125 +0,0 @@ -// 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/zz_controls.go b/zz_controls.go deleted file mode 100644 index d77bebe..0000000 --- a/zz_controls.go +++ /dev/null @@ -1,222 +0,0 @@ -// 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/zz_drawtext.go b/zz_drawtext.go deleted file mode 100644 index a32b14b..0000000 --- a/zz_drawtext.go +++ /dev/null @@ -1,167 +0,0 @@ -// 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/zz_histogram.go b/zz_histogram.go deleted file mode 100644 index fae5aae..0000000 --- a/zz_histogram.go +++ /dev/null @@ -1,253 +0,0 @@ -// 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) -} -- cgit v1.2.3