├── LICENSE ├── README.md ├── TODO.md ├── area.go ├── areahandler.go ├── box.go ├── button.go ├── checkbox.go ├── colorbutton.go ├── combobox.go ├── control.go ├── datetimepicker.go ├── draw.go ├── drawtext.go ├── dummy_windows.cpp ├── editablecombobox.go ├── entry.go ├── examples ├── controlgallery.go ├── drawtext.go ├── histogram.go ├── table.go └── updateui.go ├── fontbutton.go ├── form.go ├── grid.go ├── group.go ├── image.go ├── label.go ├── libui_darwin_amd64.a ├── libui_linux_386.a ├── libui_linux_amd64.a ├── libui_windows_386.a ├── libui_windows_amd64.a ├── link_darwin_amd64.go ├── link_linux_386.go ├── link_linux_amd64.go ├── link_windows_386.go ├── link_windows_amd64.go ├── main.go ├── multilineentry.go ├── pkgui.c ├── pkgui.h ├── progressbar.go ├── radiobuttons.go ├── separator.go ├── slider.go ├── spinbox.go ├── stddialogs.go ├── tab.go ├── table.go ├── tablemodel.go ├── ui.h ├── util.go ├── window.go └── winmanifest ├── doc.go ├── resources.rc ├── ui.manifest ├── winmanifest_windows_386.syso └── winmanifest_windows_amd64.syso /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Pietro Gagliardi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | (this is called the MIT License or Expat License; see http://www.opensource.org/licenses/MIT) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ui: platform-native GUI library for Go 2 | 3 | This is a library that aims to provide simple GUI software development in Go. It is based on my [libui](https://github.com/andlabs/libui), a simple cross-platform library that does the same thing, but written in C. 4 | 5 | It runs on/requires: 6 | 7 | - Windows: cgo, Windows Vista SP2 with Platform Update and newer 8 | - Mac OS X: cgo, Mac OS X 10.8 and newer 9 | - other Unixes: cgo, GTK+ 3.10 and newer 10 | - Debian, Ubuntu, etc.: `sudo apt-get install libgtk-3-dev` 11 | - Red Hat/Fedora, etc.: `sudo dnf install gtk3-devel` 12 | 13 | It also requires Go 1.8 or newer. 14 | 15 | It currently aligns to libui's Alpha 4.1, with only a small handful of functions not available. 16 | 17 | # Status 18 | 19 | Package ui is currently **mid-alpha** software. Much of what is currently present runs stabily enough for the examples and perhaps some small programs to work, but the stability is still a work-in-progress, much of what is already there is not feature-complete, some of it will be buggy on certain platforms, and there's a lot of stuff missing. The libui README has more information. 20 | 21 | # Installation 22 | 23 | Once you have the dependencies installed, a simple 24 | 25 | ``` 26 | go get github.com/andlabs/ui/... 27 | ``` 28 | 29 | should suffice. 30 | 31 | # Documentation 32 | 33 | The in-code documentation is sufficient to get started, but needs improvement. 34 | 35 | Some simple example programs are in the `examples` directory. You can `go build` each of them individually. 36 | 37 | ## Windows manifests 38 | 39 | Package ui requires a manifest that specifies Common Controls v6 to run on Windows. It should at least also state as supported Windows Vista and Windows 7, though to avoid surprises with other packages (or with Go itself; see [this issue](https://github.com/golang/go/issues/17835)) you should state compatibility with higher versions of Windows too. 40 | 41 | The simplest option is provided as a subpackage `winmanifest`; you can simply import it without a name, and it'll set things up properly: 42 | 43 | ```go 44 | import _ "github.com/andlabs/ui/winmanifest" 45 | ``` 46 | 47 | You do not have to worry about importing this in non-Windows-only files; it does nothing on non-Windows platforms. 48 | 49 | If you wish to use your own manifest instead, you can use the one in `winmanifest` as a template to see what's required and how. You'll need to specify the template in a `.rc` file and use `windres` in MinGW-w64 to generate a `.syso` file as follows: 50 | 51 | ``` 52 | windres -i resources.rc -o winmanifest_windows_GOARCH.syso -O coff 53 | ``` 54 | 55 | You may also be interested in the `github.com/akavel/rsrc` and `github.com/josephspurrier/goversioninfo` packages, which provide other Go-like options for embedding the manifest. 56 | 57 | Note that if you choose to ship a manifest as a separate `.exe.manifest` file instead of embedding it in your binary, and you use Cygwin or MSYS2 as the source of your MinGW-w64, Cygwin and MSYS2 instruct gcc to embed a default manifest of its own if none is specified. **This default will override your manifest file!** See [this issue](https://github.com/Alexpux/MSYS2-packages/issues/454) for more details, including workaround instructions. 58 | 59 | ## macOS program execution 60 | 61 | If you run a macOS program binary directly from the command line, it will start in the background. This is intentional; see [this](https://github.com/andlabs/libui#why-does-my-program-start-in-the-background-on-os-x-if-i-run-from-the-command-line) for more details. 62 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - document that Destroy cannot be called on Controls that have a parent 2 | - identify earliest tag for https://github.com/golang/go/commit/dba926d7a37bd6b3d740c132e8d6346214b6355c 3 | -------------------------------------------------------------------------------- /area.go: -------------------------------------------------------------------------------- 1 | // 16 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Area is a Control that represents a blank canvas that a program 13 | // can draw on as it wishes. Areas also receive keyboard and mouse 14 | // events, and programs can react to those as they see fit. Drawing 15 | // and event handling are handled through an instance of a type 16 | // that implements AreaHandler that every Area has; see AreaHandler 17 | // for details. 18 | // 19 | // There are two types of areas. Non-scrolling areas are rectangular 20 | // and have no scrollbars. Programs can draw on and get mouse 21 | // events from any point in the Area, and the size of the Area is 22 | // decided by package ui itself, according to the layout of controls 23 | // in the Window the Area is located in and the size of said Window. 24 | // There is no way to query the Area's size or be notified when its 25 | // size changes; instead, you are given the area size as part of the 26 | // draw and mouse event handlers, for use solely within those 27 | // handlers. 28 | // 29 | // Scrolling areas have horziontal and vertical scrollbars. The amount 30 | // that can be scrolled is determined by the area's size, which is 31 | // decided by the programmer (both when creating the Area and by 32 | // a call to SetSize). Only a portion of the Area is visible at any time; 33 | // drawing and mouse events are automatically adjusted to match 34 | // what portion is visible, so you do not have to worry about scrolling 35 | // in your event handlers. AreaHandler has more information. 36 | // 37 | // The internal coordinate system of an Area is points, which are 38 | // floating-point and device-independent. For more details, see 39 | // AreaHandler. The size of a scrolling Area must be an exact integer 40 | // number of points (that is, you cannot have an Area that is 32.5 41 | // points tall) and thus the parameters to NewScrollingArea and 42 | // SetSize are ints. All other instances of points in parameters and 43 | // structures (including sizes of drawn objects) are float64s. 44 | type Area struct { 45 | ControlBase 46 | a *C.uiArea 47 | ah *C.uiAreaHandler 48 | scrolling bool 49 | } 50 | 51 | // NewArea creates a new non-scrolling Area. 52 | func NewArea(handler AreaHandler) *Area { 53 | a := new(Area) 54 | a.scrolling = false 55 | a.ah = registerAreaHandler(handler) 56 | 57 | a.a = C.uiNewArea(a.ah) 58 | 59 | a.ControlBase = NewControlBase(a, uintptr(unsafe.Pointer(a.a))) 60 | return a 61 | } 62 | 63 | // NewScrollingArea creates a new scrolling Area of the given size, 64 | // in points. 65 | func NewScrollingArea(handler AreaHandler, width int, height int) *Area { 66 | a := new(Area) 67 | a.scrolling = true 68 | a.ah = registerAreaHandler(handler) 69 | 70 | a.a = C.uiNewScrollingArea(a.ah, C.int(width), C.int(height)) 71 | 72 | a.ControlBase = NewControlBase(a, uintptr(unsafe.Pointer(a.a))) 73 | return a 74 | } 75 | 76 | // Destroy destroys the Area. 77 | func (a *Area) Destroy() { 78 | unregisterAreaHandler(a.ah) 79 | a.ControlBase.Destroy() 80 | } 81 | 82 | // SetSize sets the size of a scrolling Area to the given size, in points. 83 | // SetSize panics if called on a non-scrolling Area. 84 | func (a *Area) SetSize(width int, height int) { 85 | if !a.scrolling { 86 | panic("attempt to call SetSize on non-scrolling Area") 87 | } 88 | C.uiAreaSetSize(a.a, C.int(width), C.int(height)) 89 | } 90 | 91 | // QueueRedrawAll queues the entire Area for redraw. 92 | // The Area is not redrawn before this function returns; it is 93 | // redrawn when next possible. 94 | func (a *Area) QueueRedrawAll() { 95 | C.uiAreaQueueRedrawAll(a.a) 96 | } 97 | 98 | // ScrollTo scrolls the Area to show the given rectangle; what this 99 | // means is implementation-defined, but you can safely assume 100 | // that as much of the given rectangle as possible will be visible 101 | // after this call. (TODO verify this on OS X) ScrollTo panics if called 102 | // on a non-scrolling Area. 103 | func (a *Area) ScrollTo(x float64, y float64, width float64, height float64) { 104 | if !a.scrolling { 105 | panic("attempt to call ScrollTo on non-scrolling Area") 106 | } 107 | C.uiAreaScrollTo(a.a, C.double(x), C.double(y), C.double(width), C.double(height)) 108 | } 109 | 110 | // TODO BeginUserWindowMove 111 | // TODO BeginUserWindowResize 112 | -------------------------------------------------------------------------------- /areahandler.go: -------------------------------------------------------------------------------- 1 | // 13 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // no need to lock this; only the GUI thread can access it 13 | var areahandlers = make(map[*C.uiAreaHandler]AreaHandler) 14 | 15 | // AreaHandler defines the functionality needed for handling events 16 | // from an Area. Each of the methods on AreaHandler is called from 17 | // the GUI thread, and every parameter (other than the Area itself) 18 | // should be assumed to only be valid during the life of the method 19 | // call (so for instance, do not save AreaDrawParams.AreaWidth, as 20 | // that might change without generating an event). 21 | // 22 | // Coordinates to Draw and MouseEvent are given in points. Points 23 | // are generic, floating-point, device-independent coordinates with 24 | // (0,0) at the top left corner. You never have to worry about the 25 | // mapping between points and pixels; simply draw everything using 26 | // points and you get nice effects like looking sharp on high-DPI 27 | // monitors for free. Proper documentation on the matter is being 28 | // written. In the meantime, there are several referenes to this kind of 29 | // 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 30 | // 31 | // For a scrolling Area, points are automatically offset by the scroll 32 | // position. So if the mouse moves to position (5,5) while the 33 | // horizontal scrollbar is at position 10 and the horizontal scrollbar is 34 | // at position 20, the coordinate stored in the AreaMouseEvent 35 | // structure is (15,25). The same applies to drawing. 36 | type AreaHandler interface { 37 | // Draw is sent when a part of the Area needs to be drawn. 38 | // dp will contain a drawing context to draw on, the rectangle 39 | // that needs to be drawn in, and (for a non-scrolling area) the 40 | // size of the area. The rectangle that needs to be drawn will 41 | // have been cleared by the system prior to drawing, so you are 42 | // always working on a clean slate. 43 | // 44 | // If you call Save on the drawing context, you must call Release 45 | // before returning from Draw, and the number of calls to Save 46 | // and Release must match. Failure to do so results in undefined 47 | // behavior. 48 | Draw(a *Area, dp *AreaDrawParams) 49 | 50 | // MouseEvent is called when the mouse moves over the Area 51 | // or when a mouse button is pressed or released. See 52 | // AreaMouseEvent for more details. 53 | // 54 | // If a mouse button is being held, MouseEvents will continue to 55 | // be generated, even if the mouse is not within the area. On 56 | // some systems, the system can interrupt this behavior; 57 | // see DragBroken. 58 | MouseEvent(a *Area, me *AreaMouseEvent) 59 | 60 | // MouseCrossed is called when the mouse either enters or 61 | // leaves the Area. It is called even if the mouse buttons are being 62 | // held (see MouseEvent above). If the mouse has entered the 63 | // Area, left is false; if it has left the Area, left is true. 64 | // 65 | // If, when the Area is first shown, the mouse is already inside 66 | // the Area, MouseCrossed will be called with left=false. 67 | // TODO what about future shows? 68 | MouseCrossed(a *Area, left bool) 69 | 70 | // DragBroken is called if a mouse drag is interrupted by the 71 | // system. As noted above, when a mouse button is held, 72 | // MouseEvent will continue to be called, even if the mouse is 73 | // outside the Area. On some systems, this behavior can be 74 | // stopped by the system itself for a variety of reasons. This 75 | // method is provided to allow your program to cope with the 76 | // loss of the mouse in this case. You should cope by cancelling 77 | // whatever drag-related operation you were doing. 78 | // 79 | // Note that this is only generated on some systems under 80 | // specific conditions. Do not implement behavior that only 81 | // takes effect when DragBroken is called. 82 | DragBroken(a *Area) 83 | 84 | // KeyEvent is called when a key is pressed while the Area has 85 | // keyboard focus (if the Area has been tabbed into or if the 86 | // mouse has been clicked on it). See AreaKeyEvent for specifics. 87 | // 88 | // Because some keyboard events are handled by the system 89 | // (for instance, menu accelerators and global hotkeys), you 90 | // must return whether you handled the key event; return true 91 | // if you did or false if you did not. If you wish to ignore the 92 | // keyboard outright, the correct implementation of KeyEvent is 93 | // func (h *MyHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) { 94 | // return false 95 | // } 96 | // DO NOT RETURN TRUE UNCONDITIONALLY FROM THIS 97 | // METHOD. BAD THINGS WILL HAPPEN IF YOU DO. 98 | KeyEvent(a *Area, ke *AreaKeyEvent) (handled bool) 99 | } 100 | 101 | func registerAreaHandler(ah AreaHandler) *C.uiAreaHandler { 102 | uah := C.pkguiAllocAreaHandler() 103 | areahandlers[uah] = ah 104 | return uah 105 | } 106 | 107 | func unregisterAreaHandler(uah *C.uiAreaHandler) { 108 | delete(areahandlers, uah) 109 | C.pkguiFreeAreaHandler(uah) 110 | } 111 | 112 | // AreaDrawParams provides a drawing context that can be used 113 | // to draw on an Area and tells you where to draw. See AreaHandler 114 | // for introductory information. 115 | type AreaDrawParams struct { 116 | // Context is the drawing context to draw on. See DrawContext 117 | // for how to draw. 118 | Context *DrawContext 119 | 120 | // AreaWidth and AreaHeight provide the size of the Area for 121 | // non-scrolling Areas. For scrolling Areas both values are zero. 122 | // 123 | // To reiterate the AreaHandler documentation, do NOT save 124 | // these values for later; they can change without generating 125 | // an event. 126 | AreaWidth float64 127 | AreaHeight float64 128 | 129 | // These four fields define the rectangle that needs to be 130 | // redrawn. The system will not draw anything outside this 131 | // rectangle, but you can make your drawing faster if you 132 | // also stay within the lines. 133 | ClipX float64 134 | ClipY float64 135 | ClipWidth float64 136 | ClipHeight float64 137 | } 138 | 139 | //export pkguiDoAreaHandlerDraw 140 | func pkguiDoAreaHandlerDraw(uah *C.uiAreaHandler, ua *C.uiArea, udp *C.uiAreaDrawParams) { 141 | ah := areahandlers[uah] 142 | a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area) 143 | dp := &AreaDrawParams{ 144 | Context: &DrawContext{udp.Context}, 145 | AreaWidth: float64(udp.AreaWidth), 146 | AreaHeight: float64(udp.AreaHeight), 147 | ClipX: float64(udp.ClipX), 148 | ClipY: float64(udp.ClipY), 149 | ClipWidth: float64(udp.ClipWidth), 150 | ClipHeight: float64(udp.ClipHeight), 151 | } 152 | ah.Draw(a, dp) 153 | } 154 | 155 | // TODO document all these 156 | // 157 | // 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 158 | type AreaMouseEvent struct { 159 | X float64 160 | Y float64 161 | 162 | // AreaWidth and AreaHeight provide the size of the Area for 163 | // non-scrolling Areas. For scrolling Areas both values are zero. 164 | // 165 | // To reiterate the AreaHandler documentation, do NOT save 166 | // these values for later; they can change without generating 167 | // an event. 168 | AreaWidth float64 169 | AreaHeight float64 170 | 171 | Down uint 172 | Up uint 173 | Count uint 174 | Modifiers Modifiers 175 | Held []uint 176 | } 177 | 178 | func appendBits(out []uint, held C.uint64_t) []uint { 179 | n := uint(1) 180 | for i := 0; i < 64; i++ { 181 | if held & 1 != 0 { 182 | out = append(out, n) 183 | } 184 | held >>= 1 185 | n++ 186 | } 187 | return out 188 | } 189 | 190 | //export pkguiDoAreaHandlerMouseEvent 191 | func pkguiDoAreaHandlerMouseEvent(uah *C.uiAreaHandler, ua *C.uiArea, ume *C.uiAreaMouseEvent) { 192 | ah := areahandlers[uah] 193 | a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area) 194 | me := &AreaMouseEvent{ 195 | X: float64(ume.X), 196 | Y: float64(ume.Y), 197 | AreaWidth: float64(ume.AreaWidth), 198 | AreaHeight: float64(ume.AreaHeight), 199 | Down: uint(ume.Down), 200 | Up: uint(ume.Up), 201 | Count: uint(ume.Count), 202 | Modifiers: Modifiers(ume.Modifiers), 203 | Held: make([]uint, 0, 64), 204 | } 205 | me.Held = appendBits(me.Held, ume.Held1To64) 206 | ah.MouseEvent(a, me) 207 | } 208 | 209 | //export pkguiDoAreaHandlerMouseCrossed 210 | func pkguiDoAreaHandlerMouseCrossed(uah *C.uiAreaHandler, ua *C.uiArea, left C.int) { 211 | ah := areahandlers[uah] 212 | a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area) 213 | ah.MouseCrossed(a, tobool(left)) 214 | } 215 | 216 | //export pkguiDoAreaHandlerDragBroken 217 | func pkguiDoAreaHandlerDragBroken(uah *C.uiAreaHandler, ua *C.uiArea) { 218 | ah := areahandlers[uah] 219 | a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area) 220 | ah.DragBroken(a) 221 | } 222 | 223 | // TODO document all these 224 | type AreaKeyEvent struct { 225 | Key rune 226 | ExtKey ExtKey 227 | Modifier Modifiers 228 | Modifiers Modifiers 229 | Up bool 230 | } 231 | 232 | //export pkguiDoAreaHandlerKeyEvent 233 | func pkguiDoAreaHandlerKeyEvent(uah *C.uiAreaHandler, ua *C.uiArea, uke *C.uiAreaKeyEvent) C.int { 234 | ah := areahandlers[uah] 235 | a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area) 236 | ke := &AreaKeyEvent{ 237 | Key: rune(uke.Key), 238 | ExtKey: ExtKey(uke.ExtKey), 239 | Modifier: Modifiers(uke.Modifier), 240 | Modifiers: Modifiers(uke.Modifiers), 241 | Up: tobool(uke.Up), 242 | } 243 | return frombool(ah.KeyEvent(a, ke)) 244 | } 245 | 246 | // TODO document 247 | // 248 | // Note: these must be numerically identical to their libui equivalents. 249 | type Modifiers uint 250 | const ( 251 | Ctrl Modifiers = 1 << iota 252 | Alt 253 | Shift 254 | Super 255 | ) 256 | 257 | // TODO document 258 | // 259 | // Note: these must be numerically identical to their libui equivalents. 260 | type ExtKey int 261 | const ( 262 | Escape ExtKey = iota + 1 263 | Insert // equivalent to "Help" on Apple keyboards 264 | Delete 265 | Home 266 | End 267 | PageUp 268 | PageDown 269 | Up 270 | Down 271 | Left 272 | Right 273 | F1 // F1..F12 are guaranteed to be consecutive 274 | F2 275 | F3 276 | F4 277 | F5 278 | F6 279 | F7 280 | F8 281 | F9 282 | F10 283 | F11 284 | F12 285 | N0 // numpad keys; independent of Num Lock state 286 | N1 // N0..N9 are guaranteed to be consecutive 287 | N2 288 | N3 289 | N4 290 | N5 291 | N6 292 | N7 293 | N8 294 | N9 295 | NDot 296 | NEnter 297 | NAdd 298 | NSubtract 299 | NMultiply 300 | NDivide 301 | ) 302 | -------------------------------------------------------------------------------- /box.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Box is a Control that holds a group of Controls horizontally 13 | // or vertically. If horizontally, then all controls have the same 14 | // height. If vertically, then all controls have the same width. 15 | // By default, each control has its preferred width (horizontal) 16 | // or height (vertical); if a control is marked "stretchy", it will 17 | // take whatever space is left over. If multiple controls are marked 18 | // stretchy, they will be given equal shares of the leftover space. 19 | // There can also be space between each control ("padding"). 20 | type Box struct { 21 | ControlBase 22 | b *C.uiBox 23 | children []Control 24 | } 25 | 26 | // NewHorizontalBox creates a new horizontal Box. 27 | func NewHorizontalBox() *Box { 28 | b := new(Box) 29 | 30 | b.b = C.uiNewHorizontalBox() 31 | 32 | b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b))) 33 | return b 34 | } 35 | 36 | // NewVerticalBox creates a new vertical Box. 37 | func NewVerticalBox() *Box { 38 | b := new(Box) 39 | 40 | b.b = C.uiNewVerticalBox() 41 | 42 | b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b))) 43 | return b 44 | } 45 | 46 | // Destroy destroys the Box. If the Box has children, 47 | // Destroy calls Destroy on those Controls as well. 48 | func (b *Box) Destroy() { 49 | for len(b.children) != 0 { 50 | c := b.children[0] 51 | b.Delete(0) 52 | c.Destroy() 53 | } 54 | b.ControlBase.Destroy() 55 | } 56 | 57 | // Append adds the given control to the end of the Box. 58 | func (b *Box) Append(child Control, stretchy bool) { 59 | c := (*C.uiControl)(nil) 60 | // TODO this part is wrong for Box? 61 | if child != nil { 62 | c = touiControl(child.LibuiControl()) 63 | } 64 | C.uiBoxAppend(b.b, c, frombool(stretchy)) 65 | b.children = append(b.children, child) 66 | } 67 | 68 | // Delete deletes the nth control of the Box. 69 | func (b *Box) Delete(n int) { 70 | b.children = append(b.children[:n], b.children[n + 1:]...) 71 | C.uiBoxDelete(b.b, C.int(n)) 72 | } 73 | 74 | // Padded returns whether there is space between each control 75 | // of the Box. 76 | func (b *Box) Padded() bool { 77 | return tobool(C.uiBoxPadded(b.b)) 78 | } 79 | 80 | // SetPadded controls whether there is space between each control 81 | // of the Box. The size of the padding is determined by the OS and 82 | // its best practices. 83 | func (b *Box) SetPadded(padded bool) { 84 | C.uiBoxSetPadded(b.b, frombool(padded)) 85 | } 86 | -------------------------------------------------------------------------------- /button.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Button is a Control that represents a button that the user can 13 | // click to perform an action. A Button has a text label that should 14 | // describe what the button does. 15 | type Button struct { 16 | ControlBase 17 | b *C.uiButton 18 | onClicked func(*Button) 19 | } 20 | 21 | // NewButton creates a new Button with the given text as its label. 22 | func NewButton(text string) *Button { 23 | b := new(Button) 24 | 25 | ctext := C.CString(text) 26 | b.b = C.uiNewButton(ctext) 27 | freestr(ctext) 28 | 29 | C.pkguiButtonOnClicked(b.b) 30 | 31 | b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b))) 32 | return b 33 | } 34 | 35 | // Text returns the Button's text. 36 | func (b *Button) Text() string { 37 | ctext := C.uiButtonText(b.b) 38 | text := C.GoString(ctext) 39 | C.uiFreeText(ctext) 40 | return text 41 | } 42 | 43 | // SetText sets the Button's text to text. 44 | func (b *Button) SetText(text string) { 45 | ctext := C.CString(text) 46 | C.uiButtonSetText(b.b, ctext) 47 | freestr(ctext) 48 | } 49 | 50 | // OnClicked registers f to be run when the user clicks the Button. 51 | // Only one function can be registered at a time. 52 | func (b *Button) OnClicked(f func(*Button)) { 53 | b.onClicked = f 54 | } 55 | 56 | //export pkguiDoButtonOnClicked 57 | func pkguiDoButtonOnClicked(bb *C.uiButton, data unsafe.Pointer) { 58 | b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*Button) 59 | if b.onClicked != nil { 60 | b.onClicked(b) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /checkbox.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Checkbox is a Control that represents a box with a text label at its 13 | // side. When the user clicks the checkbox, a check mark will appear 14 | // in the box; clicking it again removes the check. 15 | type Checkbox struct { 16 | ControlBase 17 | c *C.uiCheckbox 18 | onToggled func(*Checkbox) 19 | } 20 | 21 | // NewCheckbox creates a new Checkbox with the given text as its 22 | // label. 23 | func NewCheckbox(text string) *Checkbox { 24 | c := new(Checkbox) 25 | 26 | ctext := C.CString(text) 27 | c.c = C.uiNewCheckbox(ctext) 28 | freestr(ctext) 29 | 30 | C.pkguiCheckboxOnToggled(c.c) 31 | 32 | c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c))) 33 | return c 34 | } 35 | 36 | // Text returns the Checkbox's text. 37 | func (c *Checkbox) Text() string { 38 | ctext := C.uiCheckboxText(c.c) 39 | text := C.GoString(ctext) 40 | C.uiFreeText(ctext) 41 | return text 42 | } 43 | 44 | // SetText sets the Checkbox's text to text. 45 | func (c *Checkbox) SetText(text string) { 46 | ctext := C.CString(text) 47 | C.uiCheckboxSetText(c.c, ctext) 48 | freestr(ctext) 49 | } 50 | 51 | // OnToggled registers f to be run when the user clicks the Checkbox. 52 | // Only one function can be registered at a time. 53 | func (c *Checkbox) OnToggled(f func(*Checkbox)) { 54 | c.onToggled = f 55 | } 56 | 57 | //export pkguiDoCheckboxOnToggled 58 | func pkguiDoCheckboxOnToggled(cc *C.uiCheckbox, data unsafe.Pointer) { 59 | c := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*Checkbox) 60 | if c.onToggled != nil { 61 | c.onToggled(c) 62 | } 63 | } 64 | 65 | // Checked returns whether the Checkbox is checked. 66 | func (c *Checkbox) Checked() bool { 67 | return tobool(C.uiCheckboxChecked(c.c)) 68 | } 69 | 70 | // SetChecked sets whether the Checkbox is checked. 71 | func (c *Checkbox) SetChecked(checked bool) { 72 | C.uiCheckboxSetChecked(c.c, frombool(checked)) 73 | } 74 | -------------------------------------------------------------------------------- /colorbutton.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // ColorButton is a Control that represents a button that the user can 13 | // click to select a color. 14 | type ColorButton struct { 15 | ControlBase 16 | b *C.uiColorButton 17 | onChanged func(*ColorButton) 18 | } 19 | 20 | // NewColorButton creates a new ColorButton. 21 | func NewColorButton() *ColorButton { 22 | b := new(ColorButton) 23 | 24 | b.b = C.uiNewColorButton() 25 | 26 | C.pkguiColorButtonOnChanged(b.b) 27 | 28 | b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b))) 29 | return b 30 | } 31 | 32 | // Color returns the color currently selected in the ColorButton. 33 | // Colors are not alpha-premultiplied. 34 | // TODO rename b or bl 35 | func (b *ColorButton) Color() (r, g, bl, a float64) { 36 | c := C.pkguiAllocColorDoubles() 37 | defer C.pkguiFreeColorDoubles(c) 38 | C.uiColorButtonColor(b.b, c.r, c.g, c.b, c.a) 39 | return float64(*(c.r)), float64(*(c.g)), float64(*(c.b)), float64(*(c.a)) 40 | } 41 | 42 | // SetColor sets the currently selected color in the ColorButton. 43 | // Colors are not alpha-premultiplied. 44 | // TODO rename b or bl 45 | func (b *ColorButton) SetColor(r, g, bl, a float64) { 46 | C.uiColorButtonSetColor(b.b, C.double(r), C.double(g), C.double(bl), C.double(a)) 47 | } 48 | 49 | // OnChanged registers f to be run when the user changes the 50 | // currently selected color in the ColorButton. Only one function 51 | // can be registered at a time. 52 | func (b *ColorButton) OnChanged(f func(*ColorButton)) { 53 | b.onChanged = f 54 | } 55 | 56 | //export pkguiDoColorButtonOnChanged 57 | func pkguiDoColorButtonOnChanged(bb *C.uiColorButton, data unsafe.Pointer) { 58 | b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*ColorButton) 59 | if b.onChanged != nil { 60 | b.onChanged(b) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /combobox.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Combobox is a Control that represents a drop-down list of strings 13 | // that the user can choose one of at any time. For a Combobox that 14 | // users can type values into, see EditableCombobox. 15 | type Combobox struct { 16 | ControlBase 17 | c *C.uiCombobox 18 | onSelected func(*Combobox) 19 | } 20 | 21 | // NewCombobox creates a new Combobox. 22 | func NewCombobox() *Combobox { 23 | c := new(Combobox) 24 | 25 | c.c = C.uiNewCombobox() 26 | 27 | C.pkguiComboboxOnSelected(c.c) 28 | 29 | c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c))) 30 | return c 31 | } 32 | 33 | // Append adds the named item to the end of the Combobox. 34 | func (c *Combobox) Append(text string) { 35 | ctext := C.CString(text) 36 | C.uiComboboxAppend(c.c, ctext) 37 | freestr(ctext) 38 | } 39 | 40 | // Selected returns the index of the currently selected item in the 41 | // Combobox, or -1 if nothing is selected. 42 | func (c *Combobox) Selected() int { 43 | return int(C.uiComboboxSelected(c.c)) 44 | } 45 | 46 | // SetSelected sets the currently selected item in the Combobox 47 | // to index. If index is -1 no item will be selected. 48 | func (c *Combobox) SetSelected(index int) { 49 | C.uiComboboxSetSelected(c.c, C.int(index)) 50 | } 51 | 52 | // OnSelected registers f to be run when the user selects an item in 53 | // the Combobox. Only one function can be registered at a time. 54 | func (c *Combobox) OnSelected(f func(*Combobox)) { 55 | c.onSelected = f 56 | } 57 | 58 | //export pkguiDoComboboxOnSelected 59 | func pkguiDoComboboxOnSelected(cc *C.uiCombobox, data unsafe.Pointer) { 60 | c := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*Combobox) 61 | if c.onSelected != nil { 62 | c.onSelected(c) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /control.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // no need to lock this; only the GUI thread can access it 13 | var controls = make(map[*C.uiControl]Control) 14 | 15 | // Control represents a GUI control. It provdes methods 16 | // common to all Controls. 17 | // 18 | // The preferred way to create new Controls is to use 19 | // ControlBase; see ControlBase below. 20 | type Control interface { 21 | // LibuiControl returns the uiControl pointer for the Control. 22 | // This is intended for use when adding a control to a 23 | // container. 24 | LibuiControl() uintptr 25 | 26 | // Destroy destroys the Control. 27 | Destroy() 28 | 29 | // Handle returns the OS-level handle that backs the 30 | // Control. On OSs that use reference counting for 31 | // controls, Handle does not increment the reference 32 | // count; you are sharing package ui's reference. 33 | Handle() uintptr 34 | 35 | // Visible returns whether the Control is visible. 36 | Visible() bool 37 | 38 | // Show shows the Control. 39 | Show() 40 | 41 | // Hide shows the Control. Hidden controls do not participate 42 | // in layout (that is, Box, Grid, etc. does not reserve space for 43 | // hidden controls). 44 | Hide() 45 | 46 | // Enabled returns whether the Control is enabled. 47 | Enabled() bool 48 | 49 | // Enable enables the Control. 50 | Enable() 51 | 52 | // Disable disables the Control. 53 | Disable() 54 | } 55 | 56 | // ControlBase is an implementation of Control that provides 57 | // all the methods that Control requires. To use it, embed a 58 | // ControlBase (not a *ControlBase) into your structure, then 59 | // assign the result of NewControlBase to that field: 60 | // 61 | // type MyControl struct { 62 | // ui.ControlBase 63 | // c *C.MyControl 64 | // } 65 | // 66 | // func NewMyControl() *MyControl { 67 | // m := &NewMyControl{ 68 | // c: C.newMyControl(), 69 | // } 70 | // m.ControlBase = ui.NewControlBase(m, uintptr(unsafe.Pointer(c))) 71 | // return m 72 | // } 73 | type ControlBase struct { 74 | iface Control 75 | c *C.uiControl 76 | } 77 | 78 | // NewControlBase creates a new ControlBase. See the 79 | // documentation of ControlBase for an example. 80 | // NewControl should only be called once per instance of Control. 81 | func NewControlBase(iface Control, c uintptr) ControlBase { 82 | b := ControlBase{ 83 | iface: iface, 84 | c: (*C.uiControl)(unsafe.Pointer(c)), 85 | } 86 | controls[b.c] = b.iface 87 | return b 88 | } 89 | 90 | func (c *ControlBase) LibuiControl() uintptr { 91 | return uintptr(unsafe.Pointer(c.c)) 92 | } 93 | 94 | func (c *ControlBase) Destroy() { 95 | delete(controls, c.c) 96 | C.uiControlDestroy(c.c) 97 | } 98 | 99 | func (c *ControlBase) Handle() uintptr { 100 | return uintptr(C.uiControlHandle(c.c)) 101 | } 102 | 103 | func (c *ControlBase) Visible() bool { 104 | return tobool(C.uiControlVisible(c.c)) 105 | } 106 | 107 | func (c *ControlBase) Show() { 108 | C.uiControlShow(c.c) 109 | } 110 | 111 | func (c *ControlBase) Hide() { 112 | C.uiControlHide(c.c) 113 | } 114 | 115 | func (c *ControlBase) Enabled() bool { 116 | return tobool(C.uiControlEnabled(c.c)) 117 | } 118 | 119 | func (c *ControlBase) Enable() { 120 | C.uiControlEnable(c.c) 121 | } 122 | 123 | func (c *ControlBase) Disable() { 124 | C.uiControlDisable(c.c) 125 | } 126 | 127 | // ControlFromLibui returns the Control associated with a libui 128 | // uiControl. This is intended for implementing event handlers 129 | // on the Go side, to prevent sharing Go pointers with C. 130 | // This function only works on Controls that use ControlBase. 131 | func ControlFromLibui(c uintptr) Control { 132 | // comma-ok form to avoid creating nil entries 133 | cc, _ := controls[(*C.uiControl)(unsafe.Pointer(c))] 134 | return cc 135 | } 136 | 137 | func touiControl(c uintptr) *C.uiControl { 138 | return (*C.uiControl)(unsafe.Pointer(c)) 139 | } 140 | 141 | // LibuiFreeText allows implementations of Control 142 | // to call the libui function uiFreeText. 143 | func LibuiFreeText(c uintptr) { 144 | C.uiFreeText((*C.char)(unsafe.Pointer(c))) 145 | } 146 | -------------------------------------------------------------------------------- /datetimepicker.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | // #include "pkgui.h" 11 | import "C" 12 | 13 | // DateTimePicker is a Control that represents a field where the user 14 | // can enter a date and/or a time. 15 | type DateTimePicker struct { 16 | ControlBase 17 | d *C.uiDateTimePicker 18 | onChanged func(*DateTimePicker) 19 | } 20 | 21 | func finishNewDateTimePicker(dd *C.uiDateTimePicker) *DateTimePicker { 22 | d := new(DateTimePicker) 23 | 24 | d.d = dd 25 | 26 | C.pkguiDateTimePickerOnChanged(d.d) 27 | 28 | d.ControlBase = NewControlBase(d, uintptr(unsafe.Pointer(d.d))) 29 | return d 30 | } 31 | 32 | // NewDateTimePicker creates a new DateTimePicker that shows 33 | // both a date and a time. 34 | func NewDateTimePicker() *DateTimePicker { 35 | return finishNewDateTimePicker(C.uiNewDateTimePicker()) 36 | } 37 | 38 | // NewDatePicker creates a new DateTimePicker that shows 39 | // only a date. 40 | func NewDatePicker() *DateTimePicker { 41 | return finishNewDateTimePicker(C.uiNewDatePicker()) 42 | } 43 | 44 | // NewTimePicker creates a new DateTimePicker that shows 45 | // only a time. 46 | func NewTimePicker() *DateTimePicker { 47 | return finishNewDateTimePicker(C.uiNewTimePicker()) 48 | } 49 | 50 | // Time returns the time stored in the uiDateTimePicker. 51 | // The time is assumed to be local time. 52 | func (d *DateTimePicker) Time() time.Time { 53 | tm := C.pkguiAllocTime() 54 | defer C.pkguiFreeTime(tm) 55 | C.uiDateTimePickerTime(d.d, tm) 56 | return time.Date( 57 | int(tm.tm_year + 1900), 58 | time.Month(tm.tm_mon + 1), 59 | int(tm.tm_mday), 60 | int(tm.tm_hour), 61 | int(tm.tm_min), 62 | int(tm.tm_sec), 63 | 0, time.Local) 64 | } 65 | 66 | // SetTime sets the time in the DateTimePicker to t. 67 | // t's components are read as-is via t.Date() and t.Clock(); 68 | // no time zone manipulations are done. 69 | func (d *DateTimePicker) SetTime(t time.Time) { 70 | tm := C.pkguiAllocTime() 71 | defer C.pkguiFreeTime(tm) 72 | year, mon, mday := t.Date() 73 | tm.tm_year = C.int(year - 1900) 74 | tm.tm_mon = C.int(mon - 1) 75 | tm.tm_mday = C.int(mday) 76 | hour, min, sec := t.Clock() 77 | tm.tm_hour = C.int(hour) 78 | tm.tm_min = C.int(min) 79 | tm.tm_sec = C.int(sec) 80 | tm.tm_isdst = -1 81 | C.uiDateTimePickerSetTime(d.d, tm) 82 | } 83 | 84 | // OnChanged registers f to be run when the user changes the time 85 | // in the DateTimePicker. Only one function can be registered at a 86 | // time. 87 | func (d *DateTimePicker) OnChanged(f func(*DateTimePicker)) { 88 | d.onChanged = f 89 | } 90 | 91 | //export pkguiDoDateTimePickerOnChanged 92 | func pkguiDoDateTimePickerOnChanged(dd *C.uiDateTimePicker, data unsafe.Pointer) { 93 | d := ControlFromLibui(uintptr(unsafe.Pointer(dd))).(*DateTimePicker) 94 | if d.onChanged != nil { 95 | d.onChanged(d) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /draw.go: -------------------------------------------------------------------------------- 1 | // 13 december 2015 2 | 3 | package ui 4 | 5 | // #include "pkgui.h" 6 | import "C" 7 | 8 | // DrawPath represents a geometric path in a drawing context. 9 | // This is the basic unit of drawing: all drawing operations consist of 10 | // forming a path, then stroking, filling, or clipping to that path. 11 | // A path is an OS resource; you must explicitly free it when finished. 12 | // Paths consist of multiple figures. Once you have added all the 13 | // figures to a path, you must "end" the path to make it ready to draw 14 | // with. 15 | // TODO rewrite all that 16 | // 17 | // Or more visually, the lifecycle of a Path is 18 | // p := DrawNewPath() 19 | // for every figure { 20 | // p.NewFigure(...) // or NewFigureWithArc 21 | // p.LineTo(...) // any number of these in any order 22 | // p.ArcTo(...) 23 | // p.BezierTo(...) 24 | // if figure should be closed { 25 | // p.CloseFigure() 26 | // } 27 | // } 28 | // p.End() 29 | // // ... 30 | // dp.Context.Stroke(p, ...) // any number of these in any order 31 | // dp.Context.Fill(p, ...) 32 | // dp.Context.Clip(p) 33 | // // ... 34 | // p.Free() // when done with the path 35 | // 36 | // A DrawPath also defines its fill mode. (This should ideally be a fill 37 | // parameter, but some implementations prevent it.) 38 | // TODO talk about fill modes 39 | type DrawPath struct { 40 | p *C.uiDrawPath 41 | } 42 | 43 | // TODO 44 | // 45 | // TODO disclaimer 46 | type DrawFillMode uint 47 | const ( 48 | DrawFillModeWinding DrawFillMode = iota 49 | DrawFillModeAlternate 50 | ) 51 | 52 | // DrawNewPath creates a new DrawPath with the given fill mode. 53 | func DrawNewPath(fillMode DrawFillMode) *DrawPath { 54 | var fm C.uiDrawFillMode 55 | 56 | switch fillMode { 57 | case DrawFillModeWinding: 58 | fm = C.uiDrawFillModeWinding 59 | case DrawFillModeAlternate: 60 | fm = C.uiDrawFillModeAlternate 61 | default: 62 | panic("invalid fill mode passed to ui.NewPath()") 63 | } 64 | return &DrawPath{ 65 | p: C.uiDrawNewPath(fm), 66 | } 67 | } 68 | 69 | // Free destroys a DrawPath. After calling Free the DrawPath cannot 70 | // be used. 71 | func (p *DrawPath) Free() { 72 | C.uiDrawFreePath(p.p) 73 | } 74 | 75 | // NewFigure starts a new figure in the DrawPath. The current point 76 | // is set to the given point. 77 | func (p *DrawPath) NewFigure(x float64, y float64) { 78 | C.uiDrawPathNewFigure(p.p, C.double(x), C.double(y)) 79 | } 80 | 81 | // NewFigureWithArc starts a new figure in the DrawPath and adds 82 | // an arc as the first element of the figure. Unlike ArcTo, 83 | // NewFigureWithArc does not draw an initial line segment. 84 | // Otherwise, see ArcTo. 85 | func (p *DrawPath) NewFigureWithArc(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) { 86 | C.uiDrawPathNewFigureWithArc(p.p, 87 | C.double(xCenter), C.double(yCenter), 88 | C.double(radius), 89 | C.double(startAngle), C.double(sweep), 90 | frombool(isNegative)) 91 | } 92 | 93 | // LineTo adds a line to the current figure of the DrawPath starting 94 | // from the current point and ending at the given point. The current 95 | // point is set to the ending point. 96 | func (p *DrawPath) LineTo(x float64, y float64) { 97 | C.uiDrawPathLineTo(p.p, C.double(x), C.double(y)) 98 | } 99 | 100 | // ArcTo adds a circular arc to the current figure of the DrawPath. 101 | // You pass it the center of the arc, its radius in radians, the starting 102 | // angle (couterclockwise) in radians, and the number of radians the 103 | // arc should sweep (counterclockwise). A line segment is drawn from 104 | // the current point to the start of the arc. The current point is set to 105 | // the end of the arc. 106 | func (p *DrawPath) ArcTo(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) { 107 | C.uiDrawPathArcTo(p.p, 108 | C.double(xCenter), C.double(yCenter), 109 | C.double(radius), 110 | C.double(startAngle), C.double(sweep), 111 | frombool(isNegative)) 112 | } 113 | 114 | // BezierTo adds a cubic Bezier curve to the current figure of the 115 | // DrawPath. Its start point is the current point. c1x and c1y are the 116 | // first control point. c2x and c2y are the second control point. endX 117 | // and endY are the end point. The current point is set to the end 118 | // point. 119 | func (p *DrawPath) BezierTo(c1x float64, c1y float64, c2x float64, c2y float64, endX float64, endY float64) { 120 | C.uiDrawPathBezierTo(p.p, 121 | C.double(c1x), C.double(c1y), 122 | C.double(c2x), C.double(c2y), 123 | C.double(endX), C.double(endY)) 124 | } 125 | 126 | // CloseFigure draws a line segment from the current point of the 127 | // current figure of the DrawPath back to its initial point. After calling 128 | // this, the current figure is over and you must either start a new 129 | // figure or end the DrawPath. If this is not called and you start a 130 | // new figure or end the DrawPath, then the current figure will not 131 | // have this closing line segment added to it (but the figure will still 132 | // be over). 133 | func (p *DrawPath) CloseFigure() { 134 | C.uiDrawPathCloseFigure(p.p) 135 | } 136 | 137 | // AddRectangle creates a new figure in the DrawPath that consists 138 | // entirely of a rectangle whose top-left corner is at the given point 139 | // and whose size is the given size. The rectangle is a closed figure; 140 | // you must either start a new figure or end the Path after calling 141 | // this method. 142 | func (p *DrawPath) AddRectangle(x float64, y float64, width float64, height float64) { 143 | C.uiDrawPathAddRectangle(p.p, C.double(x), C.double(y), C.double(width), C.double(height)) 144 | } 145 | 146 | // End ends the current DrawPath. You cannot add figures to a 147 | // DrawPath that has been ended. You cannot draw with a 148 | // DrawPath that has not been ended. 149 | func (p *DrawPath) End() { 150 | C.uiDrawPathEnd(p.p) 151 | } 152 | 153 | // DrawContext represents a drawing surface that you can draw to. 154 | // At present the only DrawContexts are surfaces associated with 155 | // Areas and are provided by package ui; see AreaDrawParams. 156 | type DrawContext struct { 157 | c *C.uiDrawContext 158 | } 159 | 160 | // DrawBrushType defines the various types of brushes. 161 | // 162 | // TODO disclaimer 163 | type DrawBrushType int 164 | const ( 165 | DrawBrushTypeSolid DrawBrushType = iota 166 | DrawBrushTypeLinearGradient 167 | DrawBrushTypeRadialGradient 168 | DrawBrushTypeImage // presently unimplemented 169 | ) 170 | 171 | // TODO 172 | // 173 | // TODO disclaimer 174 | // TODO rename these to put LineCap at the beginning? or just Cap? 175 | type DrawLineCap int 176 | const ( 177 | DrawLineCapFlat DrawLineCap = iota 178 | DrawLineCapRound 179 | DrawLineCapSquare 180 | ) 181 | 182 | // TODO 183 | // 184 | // TODO disclaimer 185 | type DrawLineJoin int 186 | const ( 187 | DrawLineJoinMiter DrawLineJoin = iota 188 | DrawLineJoinRound 189 | DrawLineJoinBevel 190 | ) 191 | 192 | // TODO document 193 | const DrawDefaultMiterLimit = 10.0 194 | 195 | // TODO 196 | type DrawBrush struct { 197 | Type DrawBrushType 198 | 199 | // If Type is Solid. 200 | // TODO 201 | R float64 202 | G float64 203 | B float64 204 | A float64 205 | 206 | // If Type is LinearGradient or RadialGradient. 207 | // TODO 208 | X0 float64 // start point for both 209 | Y0 float64 210 | X1 float64 // linear: end point; radial: circle center 211 | Y1 float64 212 | OuterRadius float64 // for radial gradients only 213 | Stops []DrawGradientStop 214 | } 215 | 216 | // TODO 217 | type DrawGradientStop struct { 218 | Pos float64 // between 0 and 1 inclusive 219 | R float64 220 | G float64 221 | B float64 222 | A float64 223 | } 224 | 225 | func (b *DrawBrush) toLibui() *C.uiDrawBrush { 226 | cb := C.pkguiAllocBrush() 227 | cb.Type = C.uiDrawBrushType(b.Type) 228 | switch b.Type { 229 | case DrawBrushTypeSolid: 230 | cb.R = C.double(b.R) 231 | cb.G = C.double(b.G) 232 | cb.B = C.double(b.B) 233 | cb.A = C.double(b.A) 234 | case DrawBrushTypeLinearGradient, DrawBrushTypeRadialGradient: 235 | cb.X0 = C.double(b.X0) 236 | cb.Y0 = C.double(b.Y0) 237 | cb.X1 = C.double(b.X1) 238 | cb.Y1 = C.double(b.Y1) 239 | cb.OuterRadius = C.double(b.OuterRadius) 240 | cb.NumStops = C.size_t(len(b.Stops)) 241 | cb.Stops = C.pkguiAllocGradientStops(cb.NumStops) 242 | for i, s := range b.Stops { 243 | C.pkguiSetGradientStop(cb.Stops, C.size_t(i), 244 | C.double(s.Pos), 245 | C.double(s.R), 246 | C.double(s.G), 247 | C.double(s.B), 248 | C.double(s.A)) 249 | } 250 | case DrawBrushTypeImage: 251 | panic("unimplemented") 252 | default: 253 | panic("invalid brush type in Brush.toLibui()") 254 | } 255 | return cb 256 | } 257 | 258 | func freeBrush(cb *C.uiDrawBrush) { 259 | if cb.Type == C.uiDrawBrushTypeLinearGradient || cb.Type == C.uiDrawBrushTypeRadialGradient { 260 | C.pkguiFreeGradientStops(cb.Stops) 261 | } 262 | C.pkguiFreeBrush(cb) 263 | } 264 | 265 | // TODO 266 | type DrawStrokeParams struct { 267 | Cap DrawLineCap 268 | Join DrawLineJoin 269 | Thickness float64 270 | MiterLimit float64 271 | Dashes []float64 272 | DashPhase float64 273 | } 274 | 275 | func (sp *DrawStrokeParams) toLibui() *C.uiDrawStrokeParams { 276 | csp := C.pkguiAllocStrokeParams() 277 | csp.Cap = C.uiDrawLineCap(sp.Cap) 278 | csp.Join = C.uiDrawLineJoin(sp.Join) 279 | csp.Thickness = C.double(sp.Thickness) 280 | csp.MiterLimit = C.double(sp.MiterLimit) 281 | csp.Dashes = nil 282 | csp.NumDashes = C.size_t(len(sp.Dashes)) 283 | if csp.NumDashes != 0 { 284 | csp.Dashes = C.pkguiAllocDashes(csp.NumDashes) 285 | for i, d := range sp.Dashes { 286 | C.pkguiSetDash(csp.Dashes, C.size_t(i), C.double(d)) 287 | } 288 | } 289 | csp.DashPhase = C.double(sp.DashPhase) 290 | return csp 291 | } 292 | 293 | func freeStrokeParams(csp *C.uiDrawStrokeParams) { 294 | if csp.Dashes != nil { 295 | C.pkguiFreeDashes(csp.Dashes) 296 | } 297 | C.pkguiFreeStrokeParams(csp) 298 | } 299 | 300 | // TODO 301 | func (c *DrawContext) Stroke(p *DrawPath, b *DrawBrush, sp *DrawStrokeParams) { 302 | cb := b.toLibui() 303 | defer freeBrush(cb) 304 | csp := sp.toLibui() 305 | defer freeStrokeParams(csp) 306 | C.uiDrawStroke(c.c, p.p, cb, csp) 307 | } 308 | 309 | // TODO 310 | func (c *DrawContext) Fill(p *DrawPath, b *DrawBrush) { 311 | cb := b.toLibui() 312 | defer freeBrush(cb) 313 | C.uiDrawFill(c.c, p.p, cb) 314 | } 315 | 316 | // TODO 317 | // TODO should the methods of these return self for chaining? 318 | type DrawMatrix struct { 319 | M11 float64 320 | M12 float64 321 | M21 float64 322 | M22 float64 323 | M31 float64 324 | M32 float64 325 | } 326 | 327 | // TODO identity matrix 328 | func DrawNewMatrix() *DrawMatrix { 329 | m := new(DrawMatrix) 330 | m.SetIdentity() 331 | return m 332 | } 333 | 334 | // TODO 335 | func (m *DrawMatrix) SetIdentity() { 336 | m.M11 = 1 337 | m.M12 = 0 338 | m.M21 = 0 339 | m.M22 = 1 340 | m.M31 = 0 341 | m.M32 = 0 342 | } 343 | 344 | func (m *DrawMatrix) toLibui() *C.uiDrawMatrix { 345 | cm := C.pkguiAllocMatrix() 346 | cm.M11 = C.double(m.M11) 347 | cm.M12 = C.double(m.M12) 348 | cm.M21 = C.double(m.M21) 349 | cm.M22 = C.double(m.M22) 350 | cm.M31 = C.double(m.M31) 351 | cm.M32 = C.double(m.M32) 352 | return cm 353 | } 354 | 355 | func (m *DrawMatrix) fromLibui(cm *C.uiDrawMatrix) { 356 | m.M11 = float64(cm.M11) 357 | m.M12 = float64(cm.M12) 358 | m.M21 = float64(cm.M21) 359 | m.M22 = float64(cm.M22) 360 | m.M31 = float64(cm.M31) 361 | m.M32 = float64(cm.M32) 362 | C.pkguiFreeMatrix(cm) 363 | } 364 | 365 | // TODO 366 | func (m *DrawMatrix) Translate(x float64, y float64) { 367 | cm := m.toLibui() 368 | C.uiDrawMatrixTranslate(cm, C.double(x), C.double(y)) 369 | m.fromLibui(cm) 370 | } 371 | 372 | // TODO 373 | func (m *DrawMatrix) Scale(xCenter float64, yCenter float64, x float64, y float64) { 374 | cm := m.toLibui() 375 | C.uiDrawMatrixScale(cm, 376 | C.double(xCenter), C.double(yCenter), 377 | C.double(x), C.double(y)) 378 | m.fromLibui(cm) 379 | } 380 | 381 | // TODO 382 | func (m *DrawMatrix) Rotate(x float64, y float64, amount float64) { 383 | cm := m.toLibui() 384 | C.uiDrawMatrixRotate(cm, C.double(x), C.double(y), C.double(amount)) 385 | m.fromLibui(cm) 386 | } 387 | 388 | // TODO 389 | func (m *DrawMatrix) Skew(x float64, y float64, xamount float64, yamount float64) { 390 | cm := m.toLibui() 391 | C.uiDrawMatrixSkew(cm, 392 | C.double(x), C.double(y), 393 | C.double(xamount), C.double(yamount)) 394 | m.fromLibui(cm) 395 | } 396 | 397 | // TODO 398 | func (m *DrawMatrix) Multiply(m2 *DrawMatrix) { 399 | cm := m.toLibui() 400 | cm2 := m2.toLibui() 401 | C.uiDrawMatrixMultiply(cm, cm2) 402 | C.pkguiFreeMatrix(cm2) 403 | m.fromLibui(cm) 404 | } 405 | 406 | // TODO 407 | func (m *DrawMatrix) Invertible() bool { 408 | cm := m.toLibui() 409 | res := C.uiDrawMatrixInvertible(cm) 410 | C.pkguiFreeMatrix(cm) 411 | return tobool(res) 412 | } 413 | 414 | // TODO 415 | // 416 | // If m is not invertible, false is returned and m is left unchanged. 417 | func (m *DrawMatrix) Invert() bool { 418 | cm := m.toLibui() 419 | res := C.uiDrawMatrixInvert(cm) 420 | m.fromLibui(cm) 421 | return tobool(res) 422 | } 423 | 424 | // TODO unimplemented 425 | func (m *DrawMatrix) TransformPoint(x float64, y float64) (xout float64, yout float64) { 426 | panic("TODO") 427 | } 428 | 429 | // TODO unimplemented 430 | func (m *DrawMatrix) TransformSize(x float64, y float64) (xout float64, yout float64) { 431 | panic("TODO") 432 | } 433 | 434 | // TODO 435 | func (c *DrawContext) Transform(m *DrawMatrix) { 436 | cm := m.toLibui() 437 | C.uiDrawTransform(c.c, cm) 438 | C.pkguiFreeMatrix(cm) 439 | } 440 | 441 | // TODO 442 | func (c *DrawContext) Clip(p *DrawPath) { 443 | C.uiDrawClip(c.c, p.p) 444 | } 445 | 446 | // TODO 447 | func (c *DrawContext) Save() { 448 | C.uiDrawSave(c.c) 449 | } 450 | 451 | // TODO 452 | func (c *DrawContext) Restore() { 453 | C.uiDrawRestore(c.c) 454 | } 455 | -------------------------------------------------------------------------------- /drawtext.go: -------------------------------------------------------------------------------- 1 | // 12 august 2018 2 | 3 | package ui 4 | 5 | // #include "pkgui.h" 6 | import "C" 7 | 8 | // Attribute stores information about an attribute in an 9 | // AttributedString. 10 | // 11 | // The following types can be used as Attributes: 12 | // 13 | // - TextFamily 14 | // - TextSize 15 | // - TextWeight 16 | // - TextItalic 17 | // - TextStretch 18 | // - TextColor 19 | // - TextBackground 20 | // - Underline 21 | // - UnderlineColor 22 | // - UnderlineColorCustom 23 | // - OpenTypeFeatures 24 | // 25 | // For every Unicode codepoint in the AttributedString, at most one 26 | // value of each attribute type can be applied. 27 | type Attribute interface { 28 | toLibui() *C.uiAttribute 29 | } 30 | 31 | // TextFamily is an Attribute that changes the font family of the text 32 | // it is applied to. Font family names are case-insensitive. 33 | type TextFamily string 34 | 35 | func (f TextFamily) toLibui() *C.uiAttribute { 36 | fstr := C.CString(string(f)) 37 | defer freestr(fstr) 38 | return C.uiNewFamilyAttribute(fstr) 39 | } 40 | 41 | // TextSize is an Attribute that changes the size of the text it is 42 | // applied to, in typographical points. 43 | type TextSize float64 44 | 45 | func (s TextSize) toLibui() *C.uiAttribute { 46 | return C.uiNewSizeAttribute(C.double(s)) 47 | } 48 | 49 | // TextWeight is an Attribute that changes the weight of the text 50 | // it is applied to. These roughly map to the OS/2 text weight field 51 | // of TrueType and OpenType fonts, or to CSS weight numbers. The 52 | // named constants are nominal values; the actual values may vary 53 | // by font and by OS, though this isn't particularly likely. Any value 54 | // between TextWeightMinimum and TextWeightMaximum, 55 | // inclusive, is allowed. 56 | // 57 | // Note that due to restrictions in early versions of Windows, some 58 | // fonts have "special" weights be exposed in many programs as 59 | // separate font families. This is perhaps most notable with 60 | // Arial Black. Package ui does not do this, even on Windows 61 | // (because the DirectWrite API libui uses on Windows does not do 62 | // this); to specify Arial Black, use family Arial and weight 63 | // TextWeightBlack. 64 | type TextWeight int 65 | const ( 66 | TextWeightMinimum TextWeight = 0 67 | TextWeightThin TextWeight = 100 68 | TextWeightUltraLight TextWeight = 200 69 | TextWeightLight TextWeight = 300 70 | TextWeightBook TextWeight = 350 71 | TextWeightNormal TextWeight = 400 72 | TextWeightMedium TextWeight = 500 73 | TextWeightSemiBold TextWeight = 600 74 | TextWeightBold TextWeight = 700 75 | TextWeightUltraBold TextWeight = 800 76 | TextWeightHeavy TextWeight = 900 77 | TextWeightUltraHeavy TextWeight = 950 78 | TextWeightMaximum TextWeight = 1000 79 | ) 80 | 81 | func (w TextWeight) toLibui() *C.uiAttribute { 82 | return C.uiNewWeightAttribute(C.uiTextWeight(w)) 83 | } 84 | 85 | // TextItalic is an Attribute that changes the italic mode of the text 86 | // it is applied to. Italic represents "true" italics where the slanted 87 | // glyphs have custom shapes, whereas oblique represents italics 88 | // that are merely slanted versions of the normal glyphs. Most fonts 89 | // usually have one or the other. 90 | type TextItalic int 91 | const ( 92 | TextItalicNormal TextItalic = iota 93 | TextItalicOblique 94 | TextItalicItalic 95 | ) 96 | 97 | func (i TextItalic) toLibui() *C.uiAttribute { 98 | return C.uiNewItalicAttribute(C.uiTextItalic(i)) 99 | } 100 | 101 | // TextStretch is an Attribute that changes the stretch (also called 102 | // "width") of the text it is applied to. 103 | // 104 | // Note that due to restrictions in early versions of Windows, some 105 | // fonts have "special" stretches be exposed in many programs as 106 | // separate font families. This is perhaps most notable with 107 | // Arial Condensed. Package ui does not do this, even on Windows 108 | // (because the DirectWrite API package ui uses on Windows does 109 | // not do this); to specify Arial Condensed, use family Arial and 110 | // stretch TextStretchCondensed. 111 | type TextStretch int 112 | const ( 113 | TextStretchUltraCondensed TextStretch = iota 114 | TextStretchExtraCondensed 115 | TextStretchCondensed 116 | TextStretchSemiCondensed 117 | TextStretchNormal 118 | TextStretchSemiExpanded 119 | TextStretchExpanded 120 | TextStretchExtraExpanded 121 | TextStretchUltraExpanded 122 | ) 123 | 124 | func (s TextStretch) toLibui() *C.uiAttribute { 125 | return C.uiNewStretchAttribute(C.uiTextStretch(s)) 126 | } 127 | 128 | // TextColor is an Attribute that changes the color of the text it is 129 | // applied to. 130 | type TextColor struct { 131 | R float64 132 | G float64 133 | B float64 134 | A float64 135 | } 136 | 137 | func (c TextColor) toLibui() *C.uiAttribute { 138 | return C.uiNewColorAttribute(C.double(c.R), C.double(c.G), C.double(c.B), C.double(c.A)) 139 | } 140 | 141 | // TextBackground is an Attribute that changes the background 142 | // color of the text it is applied to. 143 | type TextBackground struct { 144 | R float64 145 | G float64 146 | B float64 147 | A float64 148 | } 149 | 150 | func (b TextBackground) toLibui() *C.uiAttribute { 151 | return C.uiNewBackgroundAttribute(C.double(b.R), C.double(b.G), C.double(b.B), C.double(b.A)) 152 | } 153 | 154 | // Underline is an Attribute that specifies a type of underline to use 155 | // on text. 156 | type Underline int 157 | const ( 158 | UnderlineNone Underline = iota 159 | UnderlineSingle 160 | UnderlineDouble 161 | UnderlineSuggestion // wavy or dotted underlines used for spelling/grammar checkers 162 | ) 163 | 164 | func (u Underline) toLibui() *C.uiAttribute { 165 | return C.uiNewUnderlineAttribute(C.uiUnderline(u)) 166 | } 167 | 168 | // UnderlineColor is an Attribute that changes the color of any 169 | // underline on the text it is applied to, regardless of the type of 170 | // underline. In addition to being able to specify the 171 | // platform-specific colors for suggestion underlines here, you can 172 | // also use a custom color with UnderlineColorCustom. 173 | // 174 | // To use the constants here correctly, pair them with 175 | // UnderlineSuggestion (though they can be used on other types of 176 | // underline as well). 177 | // 178 | // If an underline type is applied but no underline color is 179 | // specified, the text color is used instead. If an underline color 180 | // is specified without an underline type, the underline color 181 | // attribute is ignored, but not removed from the uiAttributedString. 182 | type UnderlineColor int 183 | const ( 184 | UnderlineColorSpelling UnderlineColor = iota + 1 185 | UnderlineColorGrammar 186 | UnderlineColorAuxiliary // for instance, the color used by smart replacements on macOS or in Microsoft Office 187 | ) 188 | 189 | func (u UnderlineColor) toLibui() *C.uiAttribute { 190 | return C.uiNewUnderlineColorAttribute(C.uiUnderlineColor(u), 0, 0, 0, 0) 191 | } 192 | 193 | // UnderlineColorCustom is an Attribute like UnderlineColor, except 194 | // it allows specifying a custom color. 195 | type UnderlineColorCustom struct { 196 | R float64 197 | G float64 198 | B float64 199 | A float64 200 | } 201 | 202 | func (u UnderlineColorCustom) toLibui() *C.uiAttribute { 203 | return C.uiNewUnderlineColorAttribute(C.uiUnderlineColorCustom, C.double(u.R), C.double(u.G), C.double(u.B), C.double(u.A)) 204 | } 205 | 206 | // OpenTypeFeatures is an Attribute that represents a set of 207 | // OpenType feature tag-value pairs, for applying OpenType 208 | // features to text. OpenType feature tags are four-character codes 209 | // defined by OpenType that cover things from design features like 210 | // small caps and swashes to language-specific glyph shapes and 211 | // beyond. Each tag may only appear once in any given 212 | // uiOpenTypeFeatures instance. Each value is a 32-bit integer, 213 | // often used as a Boolean flag, but sometimes as an index to choose 214 | // a glyph shape to use. 215 | // 216 | // If a font does not support a certain feature, that feature will be 217 | // ignored. (TODO verify this on all OSs) 218 | // 219 | // See the OpenType specification at 220 | // https://www.microsoft.com/typography/otspec/featuretags.htm 221 | // for the complete list of available features, information on specific 222 | // features, and how to use them. 223 | // TODO invalid features 224 | // 225 | // Note that if a feature is not present in a OpenTypeFeatures, 226 | // the feature is NOT treated as if its value was zero, unlike in Go. 227 | // Script-specific font shaping rules and font-specific feature 228 | // settings may use a different default value for a feature. You 229 | // should likewise NOT treat a missing feature as having a value of 230 | // zero either. Instead, a missing feature should be treated as 231 | // having some unspecified default value. 232 | // 233 | // Note that despite OpenTypeFeatures being a map, its contents 234 | // are copied by AttributedString. Modifying an OpenTypeFeatures 235 | // after giving it to an AttributedString, or modifying one that comes 236 | // out of an AttributedString, will have no effect. 237 | type OpenTypeFeatures map[OpenTypeTag]uint32 238 | 239 | func (o OpenTypeFeatures) toLibui() *C.uiAttribute { 240 | otf := C.uiNewOpenTypeFeatures() 241 | defer C.uiFreeOpenTypeFeatures(otf) 242 | for tag, value := range o { 243 | a := byte((tag >> 24) & 0xFF) 244 | b := byte((tag >> 16) & 0xFF) 245 | c := byte((tag >> 8) & 0xFF) 246 | d := byte(tag & 0xFF) 247 | C.uiOpenTypeFeaturesAdd(otf, C.char(a), C.char(b), C.char(c), C.char(d), C.uint32_t(value)) 248 | } 249 | return C.uiNewFeaturesAttribute(otf) 250 | } 251 | 252 | // OpenTypeTag represents a four-byte OpenType feature tag. 253 | type OpenTypeTag uint32 254 | 255 | // ToOpenTypeTag converts the four characters a, b, c, and d into 256 | // an OpenTypeTag. 257 | func ToOpenTypeTag(a, b, c, d byte) OpenTypeTag { 258 | return (OpenTypeTag(a) << 24) | 259 | (OpenTypeTag(b) << 16) | 260 | (OpenTypeTag(c) << 8) | 261 | OpenTypeTag(d) 262 | } 263 | 264 | func attributeFromLibui(a *C.uiAttribute) Attribute { 265 | switch C.uiAttributeGetType(a) { 266 | case C.uiAttributeTypeFamily: 267 | cf := C.uiAttributeFamily(a) 268 | return TextFamily(C.GoString(cf)) 269 | case C.uiAttributeTypeSize: 270 | return TextSize(C.uiAttributeSize(a)) 271 | case C.uiAttributeTypeWeight: 272 | return TextWeight(C.uiAttributeWeight(a)) 273 | case C.uiAttributeTypeItalic: 274 | return TextItalic(C.uiAttributeItalic(a)) 275 | case C.uiAttributeTypeStretch: 276 | return TextStretch(C.uiAttributeStretch(a)) 277 | case C.uiAttributeTypeColor: 278 | cc := C.pkguiAllocColorDoubles() 279 | defer C.pkguiFreeColorDoubles(cc) 280 | C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a) 281 | return TextColor{ 282 | R: float64(*(cc.r)), 283 | G: float64(*(cc.g)), 284 | B: float64(*(cc.b)), 285 | A: float64(*(cc.a)), 286 | } 287 | case C.uiAttributeTypeBackground: 288 | cc := C.pkguiAllocColorDoubles() 289 | defer C.pkguiFreeColorDoubles(cc) 290 | C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a) 291 | return TextBackground{ 292 | R: float64(*(cc.r)), 293 | G: float64(*(cc.g)), 294 | B: float64(*(cc.b)), 295 | A: float64(*(cc.a)), 296 | } 297 | case C.uiAttributeTypeUnderline: 298 | return Underline(C.uiAttributeUnderline(a)) 299 | case C.uiAttributeTypeUnderlineColor: 300 | cu := C.pkguiNewUnderlineColor() 301 | defer C.pkguiFreeUnderlineColor(cu) 302 | cc := C.pkguiAllocColorDoubles() 303 | defer C.pkguiFreeColorDoubles(cc) 304 | C.uiAttributeUnderlineColor(a, cu, cc.r, cc.g, cc.b, cc.a) 305 | if *cu == C.uiUnderlineColorCustom { 306 | return UnderlineColorCustom{ 307 | R: float64(*(cc.r)), 308 | G: float64(*(cc.g)), 309 | B: float64(*(cc.b)), 310 | A: float64(*(cc.a)), 311 | } 312 | } 313 | return UnderlineColor(*cu) 314 | case C.uiAttributeTypeFeatures: 315 | // TODO 316 | } 317 | panic("unreachable") 318 | } 319 | 320 | // AttributedString represents a string of UTF-8 text that can 321 | // optionally be embellished with formatting attributes. Package ui 322 | // provides the list of formatting attributes, which cover common 323 | // formatting traits like boldface and color as well as advanced 324 | // typographical features provided by OpenType like superscripts 325 | // and small caps. These attributes can be combined in a variety of 326 | // ways. 327 | // 328 | // Attributes are applied to runs of Unicode codepoints in the string. 329 | // Zero-length runs are elided. Consecutive runs that have the same 330 | // attribute type and value are merged. Each attribute is independent 331 | // of each other attribute; overlapping attributes of different types 332 | // do not split each other apart, but different values of the same 333 | // attribute type do. 334 | // 335 | // The empty string can also be represented by AttributedString, 336 | // but because of the no-zero-length-attribute rule, it will not have 337 | // attributes. 338 | // 339 | // Unlike Go strings, AttributedStrings are mutable. 340 | // 341 | // AttributedString allocates resources within libui, which package 342 | // ui sits on top of. As such, when you are finished with an 343 | // AttributedString, you must free it with Free. Like other things in 344 | // package ui, AttributedString must only be used from the main 345 | // goroutine. 346 | // 347 | // In addition, AttributedString provides facilities for moving 348 | // between grapheme clusters, which represent a character 349 | // from the point of view of the end user. The cursor of a text editor 350 | // is always placed on a grapheme boundary, so you can use these 351 | // features to move the cursor left or right by one "character". 352 | // TODO does uiAttributedString itself need this 353 | // 354 | // AttributedString does not provide enough information to be able 355 | // to draw itself onto a DrawContext or respond to user actions. 356 | // In order to do that, you'll need to use a DrawTextLayout, which 357 | // is built from the combination of an AttributedString and a set of 358 | // layout-specific properties. 359 | type AttributedString struct { 360 | s *C.uiAttributedString 361 | } 362 | 363 | // NewAttributedString creates a new AttributedString from 364 | // initialString. The string will be entirely unattributed. 365 | func NewAttributedString(initialString string) *AttributedString { 366 | cs := C.CString(initialString) 367 | defer freestr(cs) 368 | return &AttributedString{ 369 | s: C.uiNewAttributedString(cs), 370 | } 371 | } 372 | 373 | // Free destroys s. 374 | func (s *AttributedString) Free() { 375 | C.uiFreeAttributedString(s.s) 376 | } 377 | 378 | // String returns the textual content of s. 379 | func (s *AttributedString) String() string { 380 | return C.GoString(C.uiAttributedStringString(s.s)) 381 | } 382 | 383 | // AppendUnattributed adds str to the end of s. The new substring 384 | // will be unattributed. 385 | func (s *AttributedString) AppendUnattributed(str string) { 386 | cs := C.CString(str) 387 | defer freestr(cs) 388 | C.uiAttributedStringAppendUnattributed(s.s, cs) 389 | } 390 | 391 | // InsertAtUnattributed adds str to s at the byte position specified by 392 | // at. The new substring will be unattributed; existing attributes will 393 | // be moved along with their text. 394 | func (s *AttributedString) InsertAtUnattributed(str string, at int) { 395 | cs := C.CString(str) 396 | defer freestr(cs) 397 | C.uiAttributedStringInsertAtUnattributed(s.s, cs, C.size_t(at)) 398 | } 399 | 400 | // Delete deletes the characters and attributes of s in the byte range 401 | // [start, end). 402 | func (s *AttributedString) Delete(start, end int) { 403 | C.uiAttributedStringDelete(s.s, C.size_t(start), C.size_t(end)) 404 | } 405 | 406 | // SetAttribute sets a in the byte range [start, end) of s. Any existing 407 | // attributes in that byte range of the same type are removed. 408 | func (s *AttributedString) SetAttribute(a Attribute, start, end int) { 409 | C.uiAttributedStringSetAttribute(s.s, a.toLibui(), C.size_t(start), C.size_t(end)) 410 | } 411 | 412 | // TODO uiAttributedStringForEachAttribute 413 | // TODO uiAttributedStringNumGraphemes 414 | // TODO uiAttributedStringByteIndexToGrapheme 415 | // TODO uiAttributedStringGraphemeToByteIndex 416 | 417 | // FontDescriptor provides a complete description of a font where 418 | // one is needed. Currently, this means as the default font of a 419 | // DrawTextLayout and as the data returned by FontButton. 420 | type FontDescriptor struct { 421 | Family TextFamily 422 | Size TextSize 423 | Weight TextWeight 424 | Italic TextItalic 425 | Stretch TextStretch 426 | } 427 | 428 | func (d *FontDescriptor) fromLibui(fd *C.uiFontDescriptor) { 429 | d.Family = TextFamily(C.GoString(fd.Family)) 430 | d.Size = TextSize(fd.Size) 431 | d.Weight = TextWeight(fd.Weight) 432 | d.Italic = TextItalic(fd.Italic) 433 | d.Stretch = TextStretch(fd.Stretch) 434 | } 435 | 436 | func (d *FontDescriptor) toLibui() *C.uiFontDescriptor { 437 | fd := C.pkguiNewFontDescriptor() 438 | fd.Family = C.CString(string(d.Family)) 439 | fd.Size = C.double(d.Size) 440 | fd.Weight = C.uiTextWeight(d.Weight) 441 | fd.Italic = C.uiTextItalic(d.Italic) 442 | fd.Stretch = C.uiTextStretch(d.Stretch) 443 | return fd 444 | } 445 | 446 | func freeLibuiFontDescriptor(fd *C.uiFontDescriptor) { 447 | freestr(fd.Family) 448 | C.pkguiFreeFontDescriptor(fd) 449 | } 450 | 451 | // DrawTextLayout is a concrete representation of an 452 | // AttributedString that can be displayed in a DrawContext. 453 | // It includes information important for the drawing of a block of 454 | // text, including the bounding box to wrap the text within, the 455 | // alignment of lines of text within that box, areas to mark as 456 | // being selected, and other things. 457 | // 458 | // Unlike AttributedString, the content of a DrawTextLayout is 459 | // immutable once it has been created. 460 | // 461 | // TODO talk about OS-specific differences with text drawing that libui can't account for... 462 | type DrawTextLayout struct { 463 | tl *C.uiDrawTextLayout 464 | } 465 | 466 | // DrawTextAlign specifies the alignment of lines of text in a 467 | // DrawTextLayout. 468 | // TODO should this really have Draw in the name? 469 | type DrawTextAlign int 470 | const ( 471 | DrawTextAlignLeft DrawTextAlign = iota 472 | DrawTextAlignCenter 473 | DrawTextAlignRight 474 | ) 475 | 476 | // DrawTextLayoutParams describes a DrawTextLayout. 477 | // DefaultFont is used to render any text that is not attributed 478 | // sufficiently in String. Width determines the width of the bounding 479 | // box of the text; the height is determined automatically. 480 | type DrawTextLayoutParams struct { 481 | String *AttributedString 482 | DefaultFont *FontDescriptor 483 | Width float64 484 | Align DrawTextAlign 485 | } 486 | 487 | // DrawNewTextLayout() creates a new DrawTextLayout from 488 | // the given parameters. 489 | func DrawNewTextLayout(p *DrawTextLayoutParams) *DrawTextLayout { 490 | dp := C.pkguiNewDrawTextLayoutParams() 491 | defer C.pkguiFreeDrawTextLayoutParams(dp) 492 | dp.String = p.String.s 493 | dp.DefaultFont = p.DefaultFont.toLibui() 494 | defer freeLibuiFontDescriptor(dp.DefaultFont) 495 | dp.Width = C.double(p.Width) 496 | dp.Align = C.uiDrawTextAlign(p.Align) 497 | return &DrawTextLayout{ 498 | tl: C.uiDrawNewTextLayout(dp), 499 | } 500 | } 501 | 502 | // Free frees tl. The underlying AttributedString is not freed. 503 | func (tl *DrawTextLayout) Free() { 504 | C.uiDrawFreeTextLayout(tl.tl) 505 | } 506 | 507 | // Text draws tl in c with the top-left point of tl at (x, y). 508 | func (c *DrawContext) Text(tl *DrawTextLayout, x, y float64) { 509 | C.uiDrawText(c.c, tl.tl, C.double(x), C.double(y)) 510 | } 511 | 512 | // TODO uiDrawTextLayoutExtents 513 | -------------------------------------------------------------------------------- /dummy_windows.cpp: -------------------------------------------------------------------------------- 1 | // 5 june 2016 2 | // This file is only present to force cgo to use the C++ linker instead of the C linker on Windows. 3 | -------------------------------------------------------------------------------- /editablecombobox.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // EditableCombobox is a Control that represents a drop-down list 13 | // of strings that the user can choose one of at any time. It also has 14 | // an entry field that the user can type an alternate choice into. 15 | type EditableCombobox struct { 16 | ControlBase 17 | c *C.uiEditableCombobox 18 | onChanged func(*EditableCombobox) 19 | } 20 | 21 | // NewEditableCombobox creates a new EditableCombobox. 22 | func NewEditableCombobox() *EditableCombobox { 23 | c := new(EditableCombobox) 24 | 25 | c.c = C.uiNewEditableCombobox() 26 | 27 | C.pkguiEditableComboboxOnChanged(c.c) 28 | 29 | c.ControlBase = NewControlBase(c, uintptr(unsafe.Pointer(c.c))) 30 | return c 31 | } 32 | 33 | // Append adds the named item to the end of the EditableCombobox. 34 | func (e *EditableCombobox) Append(text string) { 35 | ctext := C.CString(text) 36 | C.uiEditableComboboxAppend(e.c, ctext) 37 | freestr(ctext) 38 | } 39 | 40 | // Text returns the text in the entry of the EditableCombobox, which 41 | // could be one of the choices in the list if the user has selected one. 42 | func (e *EditableCombobox) Text() string { 43 | ctext := C.uiEditableComboboxText(e.c) 44 | text := C.GoString(ctext) 45 | C.uiFreeText(ctext) 46 | return text 47 | } 48 | 49 | // SetText sets the text in the entry of the EditableCombobox. 50 | func (e *EditableCombobox) SetText(text string) { 51 | ctext := C.CString(text) 52 | C.uiEditableComboboxSetText(e.c, ctext) 53 | freestr(ctext) 54 | } 55 | 56 | // OnChanged registers f to be run when the user either selects an 57 | // item or changes the text in the EditableCombobox. Only one 58 | // function can be registered at a time. 59 | func (e *EditableCombobox) OnChanged(f func(*EditableCombobox)) { 60 | e.onChanged = f 61 | } 62 | 63 | //export pkguiDoEditableComboboxOnChanged 64 | func pkguiDoEditableComboboxOnChanged(cc *C.uiEditableCombobox, data unsafe.Pointer) { 65 | e := ControlFromLibui(uintptr(unsafe.Pointer(cc))).(*EditableCombobox) 66 | if e.onChanged != nil { 67 | e.onChanged(e) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | // TODO typing in entry in OS X crashes libui 4 | // I've had similar issues with checkboxes on libui 5 | // something's wrong with NSMapTable 6 | 7 | package ui 8 | 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | // #include "pkgui.h" 14 | import "C" 15 | 16 | // Entry is a Control that represents a space that the user can 17 | // type a single line of text into. 18 | type Entry struct { 19 | ControlBase 20 | e *C.uiEntry 21 | onChanged func(*Entry) 22 | } 23 | 24 | func finishNewEntry(ee *C.uiEntry) *Entry { 25 | e := new(Entry) 26 | 27 | e.e = ee 28 | 29 | C.pkguiEntryOnChanged(e.e) 30 | 31 | e.ControlBase = NewControlBase(e, uintptr(unsafe.Pointer(e.e))) 32 | return e 33 | } 34 | 35 | // NewEntry creates a new Entry. 36 | func NewEntry() *Entry { 37 | return finishNewEntry(C.uiNewEntry()) 38 | } 39 | 40 | // NewPasswordEntry creates a new Entry whose contents are 41 | // visibly obfuscated, suitable for passwords. 42 | func NewPasswordEntry() *Entry { 43 | return finishNewEntry(C.uiNewPasswordEntry()) 44 | } 45 | 46 | // NewSearchEntry creates a new Entry suitable for searching with. 47 | // Changed events may, depending on the system, be delayed 48 | // with a search Entry, to produce a smoother user experience. 49 | func NewSearchEntry() *Entry { 50 | return finishNewEntry(C.uiNewSearchEntry()) 51 | } 52 | 53 | // Text returns the Entry's text. 54 | func (e *Entry) Text() string { 55 | ctext := C.uiEntryText(e.e) 56 | text := C.GoString(ctext) 57 | C.uiFreeText(ctext) 58 | return text 59 | } 60 | 61 | // SetText sets the Entry's text to text. 62 | func (e *Entry) SetText(text string) { 63 | ctext := C.CString(text) 64 | C.uiEntrySetText(e.e, ctext) 65 | freestr(ctext) 66 | } 67 | 68 | // OnChanged registers f to be run when the user makes a change to 69 | // the Entry. Only one function can be registered at a time. 70 | func (e *Entry) OnChanged(f func(*Entry)) { 71 | e.onChanged = f 72 | } 73 | 74 | //export pkguiDoEntryOnChanged 75 | func pkguiDoEntryOnChanged(ee *C.uiEntry, data unsafe.Pointer) { 76 | e := ControlFromLibui(uintptr(unsafe.Pointer(ee))).(*Entry) 77 | if e.onChanged != nil { 78 | e.onChanged(e) 79 | } 80 | } 81 | 82 | // ReadOnly returns whether the Entry can be changed. 83 | func (e *Entry) ReadOnly() bool { 84 | return tobool(C.uiEntryReadOnly(e.e)) 85 | } 86 | 87 | // SetReadOnly sets whether the Entry can be changed. 88 | func (e *Entry) SetReadOnly(ro bool) { 89 | C.uiEntrySetReadOnly(e.e, frombool(ro)) 90 | } 91 | -------------------------------------------------------------------------------- /examples/controlgallery.go: -------------------------------------------------------------------------------- 1 | // 12 august 2018 2 | 3 | // +build OMIT 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/andlabs/ui" 9 | _ "github.com/andlabs/ui/winmanifest" 10 | ) 11 | 12 | var mainwin *ui.Window 13 | 14 | func makeBasicControlsPage() ui.Control { 15 | vbox := ui.NewVerticalBox() 16 | vbox.SetPadded(true) 17 | 18 | hbox := ui.NewHorizontalBox() 19 | hbox.SetPadded(true) 20 | vbox.Append(hbox, false) 21 | 22 | hbox.Append(ui.NewButton("Button"), false) 23 | hbox.Append(ui.NewCheckbox("Checkbox"), false) 24 | 25 | vbox.Append(ui.NewLabel("This is a label. Right now, labels can only span one line."), false) 26 | 27 | vbox.Append(ui.NewHorizontalSeparator(), false) 28 | 29 | group := ui.NewGroup("Entries") 30 | group.SetMargined(true) 31 | vbox.Append(group, true) 32 | 33 | group.SetChild(ui.NewNonWrappingMultilineEntry()) 34 | 35 | entryForm := ui.NewForm() 36 | entryForm.SetPadded(true) 37 | group.SetChild(entryForm) 38 | 39 | entryForm.Append("Entry", ui.NewEntry(), false) 40 | entryForm.Append("Password Entry", ui.NewPasswordEntry(), false) 41 | entryForm.Append("Search Entry", ui.NewSearchEntry(), false) 42 | entryForm.Append("Multiline Entry", ui.NewMultilineEntry(), true) 43 | entryForm.Append("Multiline Entry No Wrap", ui.NewNonWrappingMultilineEntry(), true) 44 | 45 | return vbox 46 | } 47 | 48 | func makeNumbersPage() ui.Control { 49 | hbox := ui.NewHorizontalBox() 50 | hbox.SetPadded(true) 51 | 52 | group := ui.NewGroup("Numbers") 53 | group.SetMargined(true) 54 | hbox.Append(group, true) 55 | 56 | vbox := ui.NewVerticalBox() 57 | vbox.SetPadded(true) 58 | group.SetChild(vbox) 59 | 60 | spinbox := ui.NewSpinbox(0, 100) 61 | slider := ui.NewSlider(0, 100) 62 | pbar := ui.NewProgressBar() 63 | spinbox.OnChanged(func(*ui.Spinbox) { 64 | slider.SetValue(spinbox.Value()) 65 | pbar.SetValue(spinbox.Value()) 66 | }) 67 | slider.OnChanged(func(*ui.Slider) { 68 | spinbox.SetValue(slider.Value()) 69 | pbar.SetValue(slider.Value()) 70 | }) 71 | vbox.Append(spinbox, false) 72 | vbox.Append(slider, false) 73 | vbox.Append(pbar, false) 74 | 75 | ip := ui.NewProgressBar() 76 | ip.SetValue(-1) 77 | vbox.Append(ip, false) 78 | 79 | group = ui.NewGroup("Lists") 80 | group.SetMargined(true) 81 | hbox.Append(group, true) 82 | 83 | vbox = ui.NewVerticalBox() 84 | vbox.SetPadded(true) 85 | group.SetChild(vbox) 86 | 87 | cbox := ui.NewCombobox() 88 | cbox.Append("Combobox Item 1") 89 | cbox.Append("Combobox Item 2") 90 | cbox.Append("Combobox Item 3") 91 | vbox.Append(cbox, false) 92 | 93 | ecbox := ui.NewEditableCombobox() 94 | ecbox.Append("Editable Item 1") 95 | ecbox.Append("Editable Item 2") 96 | ecbox.Append("Editable Item 3") 97 | vbox.Append(ecbox, false) 98 | 99 | rb := ui.NewRadioButtons() 100 | rb.Append("Radio Button 1") 101 | rb.Append("Radio Button 2") 102 | rb.Append("Radio Button 3") 103 | vbox.Append(rb, false) 104 | 105 | return hbox 106 | } 107 | 108 | func makeDataChoosersPage() ui.Control { 109 | hbox := ui.NewHorizontalBox() 110 | hbox.SetPadded(true) 111 | 112 | vbox := ui.NewVerticalBox() 113 | vbox.SetPadded(true) 114 | hbox.Append(vbox, false) 115 | 116 | vbox.Append(ui.NewDatePicker(), false) 117 | vbox.Append(ui.NewTimePicker(), false) 118 | vbox.Append(ui.NewDateTimePicker(), false) 119 | vbox.Append(ui.NewFontButton(), false) 120 | vbox.Append(ui.NewColorButton(), false) 121 | 122 | hbox.Append(ui.NewVerticalSeparator(), false) 123 | 124 | vbox = ui.NewVerticalBox() 125 | vbox.SetPadded(true) 126 | hbox.Append(vbox, true) 127 | 128 | grid := ui.NewGrid() 129 | grid.SetPadded(true) 130 | vbox.Append(grid, false) 131 | 132 | button := ui.NewButton("Open File") 133 | entry := ui.NewEntry() 134 | entry.SetReadOnly(true) 135 | button.OnClicked(func(*ui.Button) { 136 | filename := ui.OpenFile(mainwin) 137 | if filename == "" { 138 | filename = "(cancelled)" 139 | } 140 | entry.SetText(filename) 141 | }) 142 | grid.Append(button, 143 | 0, 0, 1, 1, 144 | false, ui.AlignFill, false, ui.AlignFill) 145 | grid.Append(entry, 146 | 1, 0, 1, 1, 147 | true, ui.AlignFill, false, ui.AlignFill) 148 | 149 | button = ui.NewButton("Save File") 150 | entry2 := ui.NewEntry() 151 | entry2.SetReadOnly(true) 152 | button.OnClicked(func(*ui.Button) { 153 | filename := ui.SaveFile(mainwin) 154 | if filename == "" { 155 | filename = "(cancelled)" 156 | } 157 | entry2.SetText(filename) 158 | }) 159 | grid.Append(button, 160 | 0, 1, 1, 1, 161 | false, ui.AlignFill, false, ui.AlignFill) 162 | grid.Append(entry2, 163 | 1, 1, 1, 1, 164 | true, ui.AlignFill, false, ui.AlignFill) 165 | 166 | msggrid := ui.NewGrid() 167 | msggrid.SetPadded(true) 168 | grid.Append(msggrid, 169 | 0, 2, 2, 1, 170 | false, ui.AlignCenter, false, ui.AlignStart) 171 | 172 | button = ui.NewButton("Message Box") 173 | button.OnClicked(func(*ui.Button) { 174 | ui.MsgBox(mainwin, 175 | "This is a normal message box.", 176 | "More detailed information can be shown here.") 177 | }) 178 | msggrid.Append(button, 179 | 0, 0, 1, 1, 180 | false, ui.AlignFill, false, ui.AlignFill) 181 | button = ui.NewButton("Error Box") 182 | button.OnClicked(func(*ui.Button) { 183 | ui.MsgBoxError(mainwin, 184 | "This message box describes an error.", 185 | "More detailed information can be shown here.") 186 | }) 187 | msggrid.Append(button, 188 | 1, 0, 1, 1, 189 | false, ui.AlignFill, false, ui.AlignFill) 190 | 191 | return hbox 192 | } 193 | 194 | func setupUI() { 195 | mainwin = ui.NewWindow("libui Control Gallery", 640, 480, true) 196 | mainwin.OnClosing(func(*ui.Window) bool { 197 | ui.Quit() 198 | return true 199 | }) 200 | ui.OnShouldQuit(func() bool { 201 | mainwin.Destroy() 202 | return true 203 | }) 204 | 205 | tab := ui.NewTab() 206 | mainwin.SetChild(tab) 207 | mainwin.SetMargined(true) 208 | 209 | tab.Append("Basic Controls", makeBasicControlsPage()) 210 | tab.SetMargined(0, true) 211 | 212 | tab.Append("Numbers and Lists", makeNumbersPage()) 213 | tab.SetMargined(1, true) 214 | 215 | tab.Append("Data Choosers", makeDataChoosersPage()) 216 | tab.SetMargined(2, true) 217 | 218 | mainwin.Show() 219 | } 220 | 221 | func main() { 222 | ui.Main(setupUI) 223 | } 224 | -------------------------------------------------------------------------------- /examples/drawtext.go: -------------------------------------------------------------------------------- 1 | // 19 august 2018 2 | 3 | // +build OMIT 4 | 5 | package main 6 | 7 | // TODO probably a bug in libui: changing the font away from skia leads to a crash 8 | 9 | import ( 10 | "github.com/andlabs/ui" 11 | _ "github.com/andlabs/ui/winmanifest" 12 | ) 13 | 14 | var ( 15 | fontButton *ui.FontButton 16 | alignment *ui.Combobox 17 | 18 | attrstr *ui.AttributedString 19 | ) 20 | 21 | func appendWithAttributes(what string, attrs ...ui.Attribute) { 22 | start := len(attrstr.String()) 23 | end := start + len(what) 24 | attrstr.AppendUnattributed(what) 25 | for _, a := range attrs { 26 | attrstr.SetAttribute(a, start, end) 27 | } 28 | } 29 | 30 | func makeAttributedString() { 31 | attrstr = ui.NewAttributedString( 32 | "Drawing strings with package ui is done with the ui.AttributedString and ui.DrawTextLayout objects.\n" + 33 | "ui.AttributedString lets you have a variety of attributes: ") 34 | 35 | appendWithAttributes("font family", ui.TextFamily("Courier New")) 36 | attrstr.AppendUnattributed(", ") 37 | 38 | appendWithAttributes("font size", ui.TextSize(18)) 39 | attrstr.AppendUnattributed(", ") 40 | 41 | appendWithAttributes("font weight", ui.TextWeightBold) 42 | attrstr.AppendUnattributed(", ") 43 | 44 | appendWithAttributes("font italicness", ui.TextItalicItalic) 45 | attrstr.AppendUnattributed(", ") 46 | 47 | appendWithAttributes("font stretch", ui.TextStretchCondensed) 48 | attrstr.AppendUnattributed(", ") 49 | 50 | appendWithAttributes("text color", ui.TextColor{0.75, 0.25, 0.5, 0.75}) 51 | attrstr.AppendUnattributed(", ") 52 | 53 | appendWithAttributes("text background color", ui.TextBackground{0.5, 0.5, 0.25, 0.5}) 54 | attrstr.AppendUnattributed(", ") 55 | 56 | appendWithAttributes("underline style", ui.UnderlineSingle) 57 | attrstr.AppendUnattributed(", ") 58 | 59 | attrstr.AppendUnattributed("and ") 60 | appendWithAttributes("underline color", 61 | ui.UnderlineDouble, 62 | ui.UnderlineColorCustom{1.0, 0.0, 0.5, 1.0}) 63 | attrstr.AppendUnattributed(". ") 64 | 65 | attrstr.AppendUnattributed("Furthermore, there are attributes allowing for ") 66 | appendWithAttributes("special underlines for indicating spelling errors", 67 | ui.UnderlineSuggestion, 68 | ui.UnderlineColorSpelling) 69 | attrstr.AppendUnattributed(" (and other types of errors) ") 70 | 71 | attrstr.AppendUnattributed("and control over OpenType features such as ligatures (for instance, ") 72 | appendWithAttributes("afford", ui.OpenTypeFeatures{ 73 | ui.ToOpenTypeTag('l', 'i', 'g', 'a'): 0, 74 | }) 75 | attrstr.AppendUnattributed(" vs. ") 76 | appendWithAttributes("afford", ui.OpenTypeFeatures{ 77 | ui.ToOpenTypeTag('l', 'i', 'g', 'a'): 1, 78 | }) 79 | attrstr.AppendUnattributed(").\n") 80 | 81 | attrstr.AppendUnattributed("Use the controls opposite to the text to control properties of the text.") 82 | } 83 | 84 | type areaHandler struct{} 85 | 86 | func (areaHandler) Draw(a *ui.Area, p *ui.AreaDrawParams) { 87 | tl := ui.DrawNewTextLayout(&ui.DrawTextLayoutParams{ 88 | String: attrstr, 89 | DefaultFont: fontButton.Font(), 90 | Width: p.AreaWidth, 91 | Align: ui.DrawTextAlign(alignment.Selected()), 92 | }) 93 | defer tl.Free() 94 | p.Context.Text(tl, 0, 0) 95 | } 96 | 97 | func (areaHandler) MouseEvent(a *ui.Area, me *ui.AreaMouseEvent) { 98 | // do nothing 99 | } 100 | 101 | func (areaHandler) MouseCrossed(a *ui.Area, left bool) { 102 | // do nothing 103 | } 104 | 105 | func (areaHandler) DragBroken(a *ui.Area) { 106 | // do nothing 107 | } 108 | 109 | func (areaHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) { 110 | // reject all keys 111 | return false 112 | } 113 | 114 | func setupUI() { 115 | makeAttributedString() 116 | 117 | mainwin := ui.NewWindow("libui Text-Drawing Example", 640, 480, true) 118 | mainwin.SetMargined(true) 119 | mainwin.OnClosing(func(*ui.Window) bool { 120 | mainwin.Destroy() 121 | ui.Quit() 122 | return false 123 | }) 124 | ui.OnShouldQuit(func() bool { 125 | mainwin.Destroy() 126 | return true 127 | }) 128 | 129 | hbox := ui.NewHorizontalBox() 130 | hbox.SetPadded(true) 131 | mainwin.SetChild(hbox) 132 | 133 | vbox := ui.NewVerticalBox() 134 | vbox.SetPadded(true) 135 | hbox.Append(vbox, false) 136 | 137 | area := ui.NewArea(areaHandler{}) 138 | 139 | fontButton = ui.NewFontButton() 140 | fontButton.OnChanged(func(*ui.FontButton) { 141 | area.QueueRedrawAll() 142 | }) 143 | vbox.Append(fontButton, false) 144 | 145 | form := ui.NewForm() 146 | form.SetPadded(true) 147 | // 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? 148 | vbox.Append(form, false) 149 | 150 | alignment = ui.NewCombobox() 151 | // note that the items match with the values of the uiDrawTextAlign values 152 | alignment.Append("Left") 153 | alignment.Append("Center") 154 | alignment.Append("Right") 155 | alignment.SetSelected(0) // start with left alignment 156 | alignment.OnSelected(func(*ui.Combobox) { 157 | area.QueueRedrawAll() 158 | }) 159 | form.Append("Alignment", alignment, false) 160 | 161 | hbox.Append(area, true) 162 | 163 | mainwin.Show() 164 | } 165 | 166 | func main() { 167 | ui.Main(setupUI) 168 | } 169 | -------------------------------------------------------------------------------- /examples/histogram.go: -------------------------------------------------------------------------------- 1 | // 12 august 2018 2 | 3 | // +build OMIT 4 | 5 | package main 6 | 7 | import ( 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/andlabs/ui" 12 | _ "github.com/andlabs/ui/winmanifest" 13 | ) 14 | 15 | var ( 16 | histogram *ui.Area 17 | datapoints [10]*ui.Spinbox 18 | colorButton *ui.ColorButton 19 | 20 | currentPoint = -1 21 | ) 22 | 23 | // some metrics 24 | const ( 25 | xoffLeft = 20 // histogram margins 26 | yoffTop = 20 27 | xoffRight = 20 28 | yoffBottom = 20 29 | pointRadius = 5 30 | ) 31 | 32 | // helper to quickly set a brush color 33 | func mkSolidBrush(color uint32, alpha float64) *ui.DrawBrush { 34 | brush := new(ui.DrawBrush) 35 | brush.Type = ui.DrawBrushTypeSolid 36 | component := uint8((color >> 16) & 0xFF) 37 | brush.R = float64(component) / 255 38 | component = uint8((color >> 8) & 0xFF) 39 | brush.G = float64(component) / 255 40 | component = uint8(color & 0xFF) 41 | brush.B = float64(component) / 255 42 | brush.A = alpha 43 | return brush 44 | } 45 | 46 | // and some colors 47 | // names and values from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370907%28v=vs.85%29.aspx 48 | const ( 49 | colorWhite = 0xFFFFFF 50 | colorBlack = 0x000000 51 | colorDodgerBlue = 0x1E90FF 52 | ) 53 | 54 | func pointLocations(width, height float64) (xs, ys [10]float64) { 55 | xincr := width / 9 // 10 - 1 to make the last point be at the end 56 | yincr := height / 100 57 | for i := 0; i < 10; i++ { 58 | // get the value of the point 59 | n := datapoints[i].Value() 60 | // because y=0 is the top but n=0 is the bottom, we need to flip 61 | n = 100 - n 62 | xs[i] = xincr * float64(i) 63 | ys[i] = yincr * float64(n) 64 | } 65 | return xs, ys 66 | } 67 | 68 | func constructGraph(width, height float64, extend bool) *ui.DrawPath { 69 | xs, ys := pointLocations(width, height) 70 | path := ui.DrawNewPath(ui.DrawFillModeWinding) 71 | 72 | path.NewFigure(xs[0], ys[0]) 73 | for i := 1; i < 10; i++ { 74 | path.LineTo(xs[i], ys[i]) 75 | } 76 | 77 | if extend { 78 | path.LineTo(width, height) 79 | path.LineTo(0, height) 80 | path.CloseFigure() 81 | } 82 | 83 | path.End() 84 | return path 85 | } 86 | 87 | func graphSize(clientWidth, clientHeight float64) (graphWidth, graphHeight float64) { 88 | return clientWidth - xoffLeft - xoffRight, 89 | clientHeight - yoffTop - yoffBottom 90 | } 91 | 92 | type areaHandler struct{} 93 | 94 | func (areaHandler) Draw(a *ui.Area, p *ui.AreaDrawParams) { 95 | // fill the area with white 96 | brush := mkSolidBrush(colorWhite, 1.0) 97 | path := ui.DrawNewPath(ui.DrawFillModeWinding) 98 | path.AddRectangle(0, 0, p.AreaWidth, p.AreaHeight) 99 | path.End() 100 | p.Context.Fill(path, brush) 101 | path.Free() 102 | 103 | graphWidth, graphHeight := graphSize(p.AreaWidth, p.AreaHeight) 104 | 105 | sp := &ui.DrawStrokeParams{ 106 | Cap: ui.DrawLineCapFlat, 107 | Join: ui.DrawLineJoinMiter, 108 | Thickness: 2, 109 | MiterLimit: ui.DrawDefaultMiterLimit, 110 | } 111 | 112 | // draw the axes 113 | brush = mkSolidBrush(colorBlack, 1.0) 114 | path = ui.DrawNewPath(ui.DrawFillModeWinding) 115 | path.NewFigure(xoffLeft, yoffTop) 116 | path.LineTo(xoffLeft, yoffTop + graphHeight) 117 | path.LineTo(xoffLeft + graphWidth, yoffTop + graphHeight) 118 | path.End() 119 | p.Context.Stroke(path, brush, sp) 120 | path.Free() 121 | 122 | // now transform the coordinate space so (0, 0) is the top-left corner of the graph 123 | m := ui.DrawNewMatrix() 124 | m.Translate(xoffLeft, yoffTop) 125 | p.Context.Transform(m) 126 | 127 | // now get the color for the graph itself and set up the brush 128 | graphR, graphG, graphB, graphA := colorButton.Color() 129 | brush.Type = ui.DrawBrushTypeSolid 130 | brush.R = graphR 131 | brush.G = graphG 132 | brush.B = graphB 133 | // we set brush.A below to different values for the fill and stroke 134 | 135 | // now create the fill for the graph below the graph line 136 | path = constructGraph(graphWidth, graphHeight, true) 137 | brush.A = graphA / 2 138 | p.Context.Fill(path, brush) 139 | path.Free() 140 | 141 | // now draw the histogram line 142 | path = constructGraph(graphWidth, graphHeight, false) 143 | brush.A = graphA 144 | p.Context.Stroke(path, brush, sp) 145 | path.Free() 146 | 147 | // now draw the point being hovered over 148 | if currentPoint != -1 { 149 | xs, ys := pointLocations(graphWidth, graphHeight) 150 | path = ui.DrawNewPath(ui.DrawFillModeWinding) 151 | path.NewFigureWithArc( 152 | xs[currentPoint], ys[currentPoint], 153 | pointRadius, 154 | 0, 6.23, // TODO pi 155 | false) 156 | path.End() 157 | // use the same brush as for the histogram lines 158 | p.Context.Fill(path, brush) 159 | path.Free() 160 | } 161 | } 162 | 163 | func inPoint(x, y float64, xtest, ytest float64) bool { 164 | // TODO switch to using a matrix 165 | x -= xoffLeft 166 | y -= yoffTop 167 | return (x >= xtest - pointRadius) && 168 | (x <= xtest + pointRadius) && 169 | (y >= ytest - pointRadius) && 170 | (y <= ytest + pointRadius) 171 | } 172 | 173 | func (areaHandler) MouseEvent(a *ui.Area, me *ui.AreaMouseEvent) { 174 | graphWidth, graphHeight := graphSize(me.AreaWidth, me.AreaHeight) 175 | xs, ys := pointLocations(graphWidth, graphHeight) 176 | 177 | currentPoint = -1 178 | for i := 0; i < 10; i++ { 179 | if inPoint(me.X, me.Y, xs[i], ys[i]) { 180 | currentPoint = i 181 | break 182 | } 183 | } 184 | 185 | // TODO only redraw the relevant area 186 | histogram.QueueRedrawAll() 187 | } 188 | 189 | func (areaHandler) MouseCrossed(a *ui.Area, left bool) { 190 | // do nothing 191 | } 192 | 193 | func (areaHandler) DragBroken(a *ui.Area) { 194 | // do nothing 195 | } 196 | 197 | func (areaHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) { 198 | // reject all keys 199 | return false 200 | } 201 | 202 | func setupUI() { 203 | mainwin := ui.NewWindow("libui Histogram Example", 640, 480, true) 204 | mainwin.SetMargined(true) 205 | mainwin.OnClosing(func(*ui.Window) bool { 206 | mainwin.Destroy() 207 | ui.Quit() 208 | return false 209 | }) 210 | ui.OnShouldQuit(func() bool { 211 | mainwin.Destroy() 212 | return true 213 | }) 214 | 215 | hbox := ui.NewHorizontalBox() 216 | hbox.SetPadded(true) 217 | mainwin.SetChild(hbox) 218 | 219 | vbox := ui.NewVerticalBox() 220 | vbox.SetPadded(true) 221 | hbox.Append(vbox, false) 222 | 223 | histogram = ui.NewArea(areaHandler{}) 224 | 225 | rand.Seed(time.Now().Unix()) 226 | for i := 0; i < 10; i++ { 227 | datapoints[i] = ui.NewSpinbox(0, 100) 228 | datapoints[i].SetValue(rand.Intn(101)) 229 | datapoints[i].OnChanged(func(*ui.Spinbox) { 230 | histogram.QueueRedrawAll() 231 | }) 232 | vbox.Append(datapoints[i], false) 233 | } 234 | 235 | colorButton = ui.NewColorButton() 236 | // TODO inline these 237 | brush := mkSolidBrush(colorDodgerBlue, 1.0) 238 | colorButton.SetColor(brush.R, 239 | brush.G, 240 | brush.B, 241 | brush.A) 242 | colorButton.OnChanged(func(*ui.ColorButton) { 243 | histogram.QueueRedrawAll() 244 | }) 245 | vbox.Append(colorButton, false) 246 | 247 | hbox.Append(histogram, true) 248 | 249 | mainwin.Show() 250 | } 251 | 252 | func main() { 253 | ui.Main(setupUI) 254 | } 255 | -------------------------------------------------------------------------------- /examples/table.go: -------------------------------------------------------------------------------- 1 | // 26 august 2018 2 | 3 | // +build OMIT 4 | 5 | // TODO possible bugs in libui: 6 | // - the checkboxes on macOS retain their values when they shouldn't 7 | // - the table on GTK+ is very thin; the scrolled window needs hexpand=TRUE 8 | 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "image" 14 | _ "image/png" 15 | "image/draw" 16 | "bytes" 17 | 18 | "github.com/andlabs/ui" 19 | _ "github.com/andlabs/ui/winmanifest" 20 | ) 21 | 22 | type modelHandler struct { 23 | row9Text string 24 | yellowRow int 25 | checkStates [15]int 26 | } 27 | 28 | func newModelHandler() *modelHandler { 29 | m := new(modelHandler) 30 | m.row9Text = "You can edit this one" 31 | m.yellowRow = -1 32 | return m 33 | } 34 | 35 | func (mh *modelHandler) ColumnTypes(m *ui.TableModel) []ui.TableValue { 36 | return []ui.TableValue{ 37 | ui.TableString(""), // column 0 text 38 | ui.TableString(""), // column 1 text 39 | ui.TableString(""), // column 2 text 40 | ui.TableColor{}, // row background color 41 | ui.TableColor{}, // column 1 text color 42 | ui.TableImage{}, // column 1 image 43 | ui.TableString(""), // column 4 button text 44 | ui.TableInt(0), // column 3 checkbox state 45 | ui.TableInt(0), // column 5 progress 46 | } 47 | } 48 | 49 | func (mh *modelHandler) NumRows(m *ui.TableModel) int { 50 | return 15 51 | } 52 | 53 | var img [2]*ui.Image 54 | 55 | func (mh *modelHandler) CellValue(m *ui.TableModel, row, column int) ui.TableValue { 56 | if column == 3 { 57 | if row == mh.yellowRow { 58 | return ui.TableColor{1, 1, 0, 1} 59 | } 60 | if row == 3 { 61 | return ui.TableColor{1, 0, 0, 1} 62 | } 63 | if row == 11 { 64 | return ui.TableColor{0, 0.5, 1, 0.5} 65 | } 66 | return nil 67 | } 68 | if column == 4 { 69 | if (row % 2) == 1 { 70 | return ui.TableColor{0.5, 0, 0.75, 1} 71 | } 72 | return nil 73 | } 74 | if column == 5 { 75 | if row < 8 { 76 | return ui.TableImage{img[0]} 77 | } 78 | return ui.TableImage{img[1]} 79 | } 80 | if column == 7 { 81 | return ui.TableInt(mh.checkStates[row]) 82 | } 83 | if column == 8 { 84 | if row == 0 { 85 | return ui.TableInt(0) 86 | } 87 | if row == 13 { 88 | return ui.TableInt(100) 89 | } 90 | if row == 14 { 91 | return ui.TableInt(-1) 92 | } 93 | return ui.TableInt(50) 94 | } 95 | switch column { 96 | case 0: 97 | return ui.TableString(fmt.Sprintf("Row %d", row)) 98 | case 2: 99 | if row == 9 { 100 | return ui.TableString(mh.row9Text) 101 | } 102 | return ui.TableString("Editing this won't change anything") 103 | case 1: 104 | return ui.TableString("Colors!") 105 | case 6: 106 | return ui.TableString("Make Yellow") 107 | } 108 | panic("unreachable") 109 | } 110 | 111 | func (mh *modelHandler) SetCellValue(m *ui.TableModel, row, column int, value ui.TableValue) { 112 | if row == 9 && column == 2 { 113 | mh.row9Text = string(value.(ui.TableString)) 114 | } 115 | if column == 6 { // row background color 116 | prevYellowRow := mh.yellowRow 117 | mh.yellowRow = row 118 | if prevYellowRow != -1 { 119 | m.RowChanged(prevYellowRow) 120 | } 121 | m.RowChanged(mh.yellowRow) 122 | } 123 | if column == 7 { // checkboxes 124 | mh.checkStates[row] = int(value.(ui.TableInt)) 125 | } 126 | } 127 | 128 | func appendImageNamed(i *ui.Image, which string) { 129 | b := rawImages[which] 130 | img, _, err := image.Decode(bytes.NewReader(b)) 131 | if err != nil { 132 | panic(err) 133 | } 134 | nr, ok := img.(*image.RGBA) 135 | if !ok { 136 | i2 := image.NewRGBA(img.Bounds()) 137 | draw.Draw(i2, i2.Bounds(), img, img.Bounds().Min, draw.Src) 138 | nr = i2 139 | } 140 | i.Append(nr) 141 | } 142 | 143 | func setupUI() { 144 | mainwin := ui.NewWindow("libui Control Gallery", 640, 480, true) 145 | mainwin.OnClosing(func(*ui.Window) bool { 146 | ui.Quit() 147 | return true 148 | }) 149 | ui.OnShouldQuit(func() bool { 150 | mainwin.Destroy() 151 | return true 152 | }) 153 | 154 | img[0] = ui.NewImage(16, 16) 155 | appendImageNamed(img[0], "andlabs_16x16test_24june2016.png") 156 | appendImageNamed(img[0], "andlabs_32x32test_24june2016.png") 157 | img[1] = ui.NewImage(16, 16) 158 | appendImageNamed(img[1], "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png") 159 | appendImageNamed(img[1], "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png") 160 | 161 | mh := newModelHandler() 162 | model := ui.NewTableModel(mh) 163 | 164 | table := ui.NewTable(&ui.TableParams{ 165 | Model: model, 166 | RowBackgroundColorModelColumn: 3, 167 | }) 168 | mainwin.SetChild(table) 169 | mainwin.SetMargined(true) 170 | 171 | table.AppendTextColumn("Column 1", 172 | 0, ui.TableModelColumnNeverEditable, nil) 173 | 174 | table.AppendImageTextColumn("Column 2", 175 | 5, 176 | 1, ui.TableModelColumnNeverEditable, &ui.TableTextColumnOptionalParams{ 177 | ColorModelColumn: 4, 178 | }); 179 | table.AppendTextColumn("Editable", 180 | 2, ui.TableModelColumnAlwaysEditable, nil) 181 | 182 | table.AppendCheckboxColumn("Checkboxes", 183 | 7, ui.TableModelColumnAlwaysEditable) 184 | table.AppendButtonColumn("Buttons", 185 | 6, ui.TableModelColumnAlwaysEditable) 186 | 187 | table.AppendProgressBarColumn("Progress Bar", 188 | 8) 189 | 190 | mainwin.Show() 191 | } 192 | 193 | func main() { 194 | ui.Main(setupUI) 195 | } 196 | 197 | var rawImages = map[string][]byte{ 198 | "andlabs_16x16test_24june2016.png": []byte{ 199 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 200 | 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 201 | 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00, 202 | 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 203 | 0x00, 0xca, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0xa5, 0x93, 0xb1, 0x0d, 204 | 0xc2, 0x40, 0x0c, 0x45, 0x1d, 0xc4, 0x14, 0x0c, 0x12, 0x41, 0x0f, 0x62, 205 | 0x12, 0x46, 0x80, 0x8a, 0x2e, 0x15, 0x30, 0x02, 0x93, 0x20, 0x68, 0x11, 206 | 0x51, 0x06, 0x61, 0x0d, 0x88, 0x2d, 0x7f, 0xdb, 0x07, 0x87, 0x08, 0xdc, 207 | 0x49, 0x91, 0x7d, 0xf6, 0xf7, 0xf3, 0x4f, 0xa4, 0x54, 0xbb, 0xeb, 0xf6, 208 | 0x41, 0x05, 0x67, 0xcc, 0xb3, 0x9b, 0xfa, 0xf6, 0x17, 0x62, 0xdf, 0xcd, 209 | 0x48, 0x00, 0x32, 0xbd, 0xa8, 0x1d, 0x72, 0xee, 0x3c, 0x47, 0x16, 0xfb, 210 | 0x5c, 0x53, 0x8d, 0x03, 0x30, 0x14, 0x84, 0xf7, 0xd5, 0x89, 0x26, 0xc7, 211 | 0x25, 0x10, 0x36, 0xe4, 0x05, 0xa2, 0x51, 0xbc, 0xc4, 0x1c, 0xc3, 0x1c, 212 | 0xed, 0x30, 0x1c, 0x8f, 0x16, 0x3f, 0x02, 0x78, 0x33, 0x20, 0x06, 0x60, 213 | 0x97, 0x70, 0xaa, 0x45, 0x7f, 0x85, 0x60, 0x5d, 0xb6, 0xf4, 0xc2, 0xc4, 214 | 0x3e, 0x0f, 0x44, 0xcd, 0x1b, 0x20, 0x90, 0x0f, 0xed, 0x85, 0xa8, 0x55, 215 | 0x05, 0x42, 0x43, 0xb4, 0x9e, 0xce, 0x71, 0xb3, 0xe8, 0x0e, 0xb4, 0xc4, 216 | 0xc3, 0x39, 0x21, 0xb7, 0x73, 0xbd, 0xe4, 0x1b, 0xe4, 0x04, 0xb6, 0xaa, 217 | 0x4f, 0x18, 0x2c, 0xee, 0x42, 0x31, 0x01, 0x84, 0xfa, 0xe0, 0xd4, 0x00, 218 | 0xdf, 0xb6, 0x83, 0xf8, 0xea, 0xc2, 0x00, 0x10, 0xfc, 0x1a, 0x05, 0x30, 219 | 0x74, 0x3b, 0xe0, 0xd1, 0x45, 0xb1, 0x83, 0xaa, 0xf4, 0x77, 0x7e, 0x02, 220 | 0x87, 0x1f, 0x42, 0x7f, 0x9e, 0x2b, 0xe8, 0xdf, 0x00, 0x00, 0x00, 0x00, 221 | 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 222 | }, 223 | "andlabs_32x32test_24june2016.png": []byte{ 224 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 225 | 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 226 | 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00, 227 | 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 228 | 0x01, 0x6a, 0x49, 0x44, 0x41, 0x54, 0x58, 0x09, 0xc5, 0x97, 0xc1, 0x0d, 229 | 0xc2, 0x30, 0x0c, 0x45, 0x03, 0x62, 0x0a, 0x06, 0x41, 0x70, 0x07, 0x31, 230 | 0x09, 0x23, 0xc0, 0x89, 0x05, 0x80, 0x11, 0x98, 0x04, 0xc1, 0x15, 0x81, 231 | 0x18, 0x84, 0x35, 0x00, 0x57, 0xfd, 0x8d, 0x13, 0x92, 0x3a, 0x4e, 0x03, 232 | 0x8d, 0x54, 0x39, 0x35, 0xb1, 0xff, 0x8b, 0xed, 0x1e, 0x18, 0xec, 0xae, 233 | 0xdb, 0x97, 0xe9, 0x71, 0x8d, 0x48, 0x7b, 0x33, 0xb9, 0xf5, 0x82, 0xb0, 234 | 0x7f, 0xcc, 0x4c, 0x05, 0x50, 0xa9, 0x2f, 0x26, 0x32, 0xc4, 0xf9, 0x21, 235 | 0x9f, 0xa1, 0x13, 0x8a, 0x5c, 0x16, 0x40, 0x4a, 0x9e, 0x92, 0x14, 0x78, 236 | 0x8a, 0x5c, 0x43, 0xc4, 0xf4, 0x65, 0x3b, 0x01, 0x3c, 0x57, 0x27, 0x43, 237 | 0x4f, 0x97, 0x95, 0x0d, 0x40, 0xc2, 0xe3, 0xe3, 0xb2, 0x7a, 0xba, 0x40, 238 | 0xd8, 0x19, 0x50, 0x5c, 0x03, 0xe2, 0x08, 0x21, 0x10, 0xdf, 0xd7, 0x3a, 239 | 0x88, 0x6c, 0x46, 0xd4, 0x00, 0x5f, 0x42, 0x35, 0x85, 0x03, 0x41, 0x03, 240 | 0xcb, 0x44, 0x00, 0x1a, 0xb2, 0x2e, 0x80, 0x66, 0xd2, 0x43, 0xd9, 0x32, 241 | 0x7c, 0x2e, 0x40, 0x1b, 0x75, 0x0d, 0xe7, 0xdc, 0x94, 0x09, 0xc6, 0x2a, 242 | 0xc3, 0x8e, 0x04, 0xb7, 0x59, 0x43, 0x08, 0x08, 0x64, 0xcc, 0x15, 0xa7, 243 | 0x78, 0x5b, 0x01, 0x45, 0xdf, 0x28, 0x90, 0x43, 0xd0, 0x3e, 0x77, 0x59, 244 | 0x80, 0x8c, 0x0c, 0x5d, 0x84, 0x21, 0xe7, 0x02, 0x94, 0x1c, 0x42, 0x29, 245 | 0x57, 0x3d, 0x6f, 0x16, 0xa0, 0x6d, 0x00, 0x81, 0xcb, 0xec, 0xe1, 0x7e, 246 | 0x61, 0x6f, 0xc6, 0xac, 0xa7, 0x73, 0xfb, 0xae, 0xc8, 0x65, 0x01, 0x6c, 247 | 0xb8, 0xb8, 0x23, 0x71, 0x47, 0xf0, 0x13, 0x11, 0xf2, 0x89, 0x89, 0x3e, 248 | 0x07, 0xd4, 0x5f, 0x41, 0x4c, 0x88, 0x80, 0xfc, 0xaa, 0x14, 0x07, 0x88, 249 | 0x89, 0x43, 0x28, 0x07, 0x42, 0x5d, 0x01, 0x88, 0x95, 0xb2, 0xc9, 0x00, 250 | 0xd2, 0xed, 0x01, 0xa4, 0xad, 0x82, 0x38, 0x84, 0xe8, 0xab, 0x3f, 0x74, 251 | 0x10, 0x0c, 0x59, 0x0e, 0x21, 0xc5, 0xb5, 0x02, 0xa4, 0xde, 0x3a, 0x06, 252 | 0x41, 0x7e, 0x29, 0x47, 0x72, 0x0b, 0x42, 0x22, 0x25, 0x7c, 0x51, 0x00, 253 | 0x89, 0x3c, 0x55, 0x9c, 0xb7, 0x23, 0x14, 0xf3, 0xd5, 0x82, 0x9c, 0x9e, 254 | 0x87, 0x12, 0x73, 0x1f, 0x87, 0xf0, 0x67, 0xc2, 0x01, 0x28, 0x75, 0x6b, 255 | 0x2e, 0x8e, 0x3d, 0x84, 0x7d, 0x8d, 0x68, 0x0b, 0x10, 0xf8, 0x6b, 0xdb, 256 | 0x00, 0xf8, 0x64, 0xbf, 0x12, 0xe6, 0xed, 0x20, 0x8d, 0x0a, 0xe0, 0x5f, 257 | 0xe2, 0xb8, 0x14, 0x87, 0x68, 0x2a, 0x80, 0x1f, 0xff, 0x6d, 0x07, 0x7d, 258 | 0xff, 0x3d, 0x7f, 0x03, 0x93, 0xca, 0x91, 0xa9, 0x89, 0x2a, 0x2e, 0xd2, 259 | 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 260 | }, 261 | "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png": []byte{ 262 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 263 | 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 264 | 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00, 265 | 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0, 266 | 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 267 | 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 268 | 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x16, 269 | 0x14, 0x0d, 0x09, 0xd9, 0x88, 0x44, 0xfa, 0x00, 0x00, 0x02, 0x4d, 0x49, 270 | 0x44, 0x41, 0x54, 0x38, 0xcb, 0x95, 0x92, 0xdf, 0x4b, 0x53, 0x61, 0x18, 271 | 0xc7, 0x3f, 0x67, 0x9b, 0x6e, 0x7a, 0xd6, 0xfc, 0x55, 0xe0, 0x59, 0x8a, 272 | 0x95, 0x71, 0x08, 0x52, 0x21, 0x91, 0x2c, 0x8a, 0x0a, 0x94, 0x11, 0x14, 273 | 0x78, 0x21, 0x99, 0x14, 0x81, 0xdd, 0x48, 0x7f, 0x45, 0x63, 0x5d, 0x75, 274 | 0x1b, 0x34, 0xd0, 0x9b, 0x52, 0x83, 0x0a, 0xad, 0x0b, 0x43, 0x72, 0xd0, 275 | 0x85, 0x14, 0x14, 0x68, 0x77, 0x39, 0xcd, 0x6c, 0x94, 0x0c, 0xf4, 0x48, 276 | 0x4d, 0x5b, 0x5b, 0x4b, 0xcf, 0x39, 0x3b, 0xef, 0xe9, 0x62, 0x3f, 0xec, 277 | 0xd7, 0x2e, 0x7c, 0xae, 0xbe, 0x2f, 0xef, 0xf3, 0xfd, 0xbc, 0xdf, 0xf7, 278 | 0x7d, 0x5e, 0x69, 0x78, 0x78, 0xf8, 0xc9, 0xfa, 0xfa, 0x7a, 0x2f, 0xbb, 279 | 0xab, 0xcb, 0xc1, 0x60, 0x70, 0x1c, 0x80, 0x50, 0x28, 0x64, 0xef, 0xb6, 280 | 0x42, 0xa1, 0x90, 0x5d, 0x20, 0xb9, 0x0a, 0x22, 0x12, 0x89, 0xe4, 0x95, 281 | 0x84, 0xa6, 0x69, 0xf8, 0xfd, 0x0a, 0x00, 0x9a, 0xa6, 0xa1, 0x28, 0xfe, 282 | 0xbc, 0x5e, 0x63, 0x60, 0x60, 0xe0, 0x8f, 0x28, 0x45, 0x80, 0xa6, 0x69, 283 | 0x48, 0x52, 0x0e, 0x20, 0x49, 0xb9, 0xf5, 0xce, 0xde, 0x5a, 0xc9, 0xbb, 284 | 0x14, 0x01, 0x8a, 0x5f, 0xc1, 0x2b, 0x7b, 0x01, 0x88, 0xc5, 0x62, 0xf4, 285 | 0xf4, 0xf4, 0x00, 0x10, 0x8d, 0x46, 0x69, 0x69, 0x6d, 0x41, 0xb2, 0x25, 286 | 0xe6, 0xa3, 0xf3, 0xa5, 0x01, 0x86, 0x6e, 0x90, 0x21, 0x83, 0x84, 0x04, 287 | 0x40, 0x2a, 0x95, 0x22, 0x2f, 0x49, 0x7f, 0x4f, 0x91, 0x8f, 0x57, 0x1a, 288 | 0xe0, 0x71, 0xbb, 0x91, 0xbd, 0x5e, 0x0a, 0xaf, 0xe3, 0xf3, 0x55, 0x15, 289 | 0xfc, 0xf8, 0xaa, 0x76, 0x74, 0x49, 0xc0, 0xb6, 0xae, 0xe7, 0x4f, 0xcc, 290 | 0xb5, 0x7e, 0x8c, 0x3c, 0x67, 0xf5, 0xee, 0x1d, 0xb6, 0x16, 0x16, 0x89, 291 | 0xa7, 0x33, 0xb9, 0x26, 0xb9, 0x82, 0xc9, 0xb6, 0x56, 0xbc, 0x6d, 0xed, 292 | 0xff, 0x49, 0xe0, 0xf1, 0x20, 0xcb, 0x32, 0x00, 0xf6, 0xe8, 0x3d, 0x3e, 293 | 0xcd, 0xcd, 0xd1, 0x74, 0xe5, 0x22, 0x65, 0x9d, 0x2a, 0xc2, 0x21, 0x91, 294 | 0x15, 0x02, 0xc3, 0x14, 0x48, 0x0e, 0x0f, 0x4d, 0xcf, 0xa6, 0x78, 0x74, 295 | 0xbc, 0xe3, 0x65, 0xff, 0xec, 0xdb, 0xfe, 0x22, 0x40, 0xd7, 0x75, 0x00, 296 | 0x36, 0x1e, 0x8c, 0x51, 0xb3, 0xb2, 0x4c, 0xc3, 0x8d, 0x3e, 0xb2, 0xe9, 297 | 0x24, 0xc9, 0xf8, 0x2a, 0xa6, 0x10, 0x18, 0x96, 0x8d, 0xb3, 0xaa, 0x16, 298 | 0x53, 0x08, 0xac, 0x4e, 0x15, 0xe7, 0xd2, 0xea, 0x99, 0x11, 0xf5, 0xf0, 299 | 0xed, 0x22, 0xc0, 0xed, 0x76, 0x23, 0xcb, 0x32, 0x89, 0xc8, 0x34, 0xfb, 300 | 0xaf, 0x9e, 0xe7, 0x47, 0x3c, 0x56, 0x34, 0x9a, 0x42, 0xf0, 0xa1, 0xfe, 301 | 0x10, 0x9b, 0x86, 0x0b, 0x53, 0x64, 0x31, 0x24, 0x0b, 0xef, 0xb1, 0x7a, 302 | 0xd4, 0xa7, 0x93, 0x7d, 0xff, 0x24, 0xb0, 0x37, 0xbf, 0x61, 0x19, 0x5b, 303 | 0xe8, 0xd6, 0x8e, 0x59, 0x52, 0xca, 0xd9, 0xd3, 0xe1, 0x26, 0xfc, 0xb5, 304 | 0x0b, 0xc3, 0xd4, 0x51, 0x6b, 0xbd, 0x08, 0xcb, 0xe4, 0xe8, 0xe8, 0x43, 305 | 0x8f, 0xe3, 0xef, 0x04, 0x66, 0x75, 0x0d, 0xce, 0x7d, 0x0d, 0xe0, 0xab, 306 | 0x41, 0x17, 0x16, 0xba, 0x25, 0x70, 0xaa, 0x95, 0x34, 0x37, 0xa7, 0xa9, 307 | 0xcf, 0xae, 0x71, 0xb2, 0x71, 0x2f, 0x5f, 0x52, 0x29, 0x7c, 0x2b, 0xef, 308 | 0xc9, 0x78, 0x3c, 0xdb, 0xc5, 0x04, 0x81, 0x40, 0x20, 0x37, 0x8d, 0xc1, 309 | 0x41, 0xde, 0xdd, 0x1f, 0xe3, 0xe0, 0xf5, 0x5e, 0xea, 0x0e, 0x1c, 0xc1, 310 | 0x76, 0xb9, 0xa8, 0x3e, 0x5d, 0x87, 0xa8, 0x70, 0x10, 0xec, 0x90, 0x78, 311 | 0xfc, 0x39, 0x4d, 0x63, 0x7c, 0x91, 0xf6, 0xc9, 0x09, 0x2a, 0x55, 0x75, 312 | 0x5c, 0x0a, 0x87, 0xc3, 0x53, 0x89, 0x44, 0xe2, 0xc2, 0xef, 0xb3, 0xad, 313 | 0x9d, 0x99, 0x41, 0x7e, 0xf3, 0x1a, 0xf3, 0xdc, 0x09, 0xca, 0x1a, 0xaa, 314 | 0xf1, 0x9c, 0xb5, 0xd1, 0x0d, 0x41, 0x66, 0xc3, 0x64, 0x7a, 0x4a, 0xd0, 315 | 0x33, 0xfb, 0x8a, 0x0a, 0x55, 0x7d, 0x71, 0x6d, 0x61, 0x21, 0x50, 0xea, 316 | 0x7f, 0x30, 0xd2, 0xdd, 0x7d, 0x49, 0x5f, 0x5a, 0xba, 0xe9, 0x4e, 0x26, 317 | 0x55, 0xe7, 0xcf, 0x4c, 0xb9, 0x6d, 0x83, 0x90, 0x65, 0xc3, 0xa5, 0x28, 318 | 0xcb, 0x07, 0xbb, 0xba, 0x6e, 0x9d, 0x1a, 0x1a, 0x9a, 0x00, 0xf8, 0x05, 319 | 0xf4, 0xf5, 0x23, 0xe9, 0x30, 0xeb, 0x2d, 0xf9, 0x00, 0x00, 0x00, 0x00, 320 | 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 321 | }, 322 | "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png": []byte{ 323 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 324 | 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 325 | 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a, 0xf4, 0x00, 0x00, 0x00, 326 | 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7c, 0x08, 0x64, 327 | 0x88, 0x00, 0x00, 0x05, 0xa5, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xe5, 328 | 0x97, 0xcd, 0x6b, 0x54, 0x57, 0x18, 0xc6, 0x7f, 0xe7, 0xde, 0x3b, 0x93, 329 | 0x51, 0x67, 0x92, 0x8c, 0x66, 0x32, 0x6a, 0x26, 0x4d, 0x94, 0x18, 0x27, 330 | 0xa9, 0x50, 0xbf, 0x5a, 0x2d, 0x94, 0x76, 0xd1, 0x85, 0x05, 0x41, 0x8b, 331 | 0x60, 0x75, 0x53, 0x70, 0x21, 0x0a, 0x55, 0xba, 0x71, 0x51, 0xa8, 0xb4, 332 | 0x7f, 0x40, 0xd7, 0xd5, 0x45, 0xa5, 0x90, 0x6e, 0x5a, 0xa8, 0x20, 0x34, 333 | 0x15, 0x0a, 0x6d, 0xa1, 0x25, 0x58, 0x2c, 0xa4, 0xd6, 0x42, 0x4d, 0xa2, 334 | 0xf9, 0x20, 0xc9, 0xc4, 0x64, 0x3e, 0x4c, 0xa2, 0x13, 0x93, 0xcc, 0xdc, 335 | 0xef, 0x2e, 0x32, 0xf7, 0xf4, 0xde, 0xc9, 0x97, 0x59, 0x74, 0xd5, 0x03, 336 | 0x87, 0x7b, 0xde, 0xf3, 0x9e, 0x73, 0xde, 0xe7, 0x3c, 0xef, 0xf3, 0xde, 337 | 0xb9, 0x03, 0xff, 0xf7, 0x26, 0xaa, 0x27, 0xae, 0x5d, 0xbb, 0x76, 0x4a, 338 | 0xd3, 0xb4, 0x2e, 0x20, 0xa6, 0xaa, 0x2a, 0x8a, 0xa2, 0xe0, 0x38, 0x0e, 339 | 0xb6, 0x6d, 0xcb, 0x6e, 0x59, 0x96, 0x7c, 0xfa, 0xc7, 0x6b, 0xcd, 0x01, 340 | 0xcf, 0x2d, 0xcb, 0x3a, 0xd7, 0xd5, 0xd5, 0x75, 0xcb, 0x1f, 0x4f, 0x5b, 341 | 0x86, 0x48, 0x88, 0x2f, 0xcf, 0x9f, 0x3f, 0x1f, 0xab, 0x8c, 0xe5, 0xbc, 342 | 0xeb, 0xba, 0x81, 0x75, 0x7e, 0xfb, 0x05, 0xc7, 0xb1, 0x4b, 0x97, 0x2e, 343 | 0x7d, 0x09, 0xac, 0x0d, 0xc0, 0xb6, 0xed, 0x7a, 0x80, 0xd1, 0xd1, 0x51, 344 | 0x84, 0x10, 0x12, 0x84, 0xff, 0xe9, 0x07, 0xe6, 0x1f, 0xaf, 0x64, 0x7b, 345 | 0x40, 0x1a, 0x1b, 0x1b, 0x31, 0x0c, 0xa3, 0xbe, 0xda, 0xb7, 0x0c, 0x80, 346 | 0xeb, 0xba, 0x32, 0xc8, 0x8d, 0x1b, 0x37, 0x48, 0x24, 0x12, 0x81, 0xa0, 347 | 0xf9, 0x7c, 0x9e, 0x64, 0x32, 0x29, 0xed, 0x42, 0xa1, 0x40, 0x32, 0x99, 348 | 0x94, 0xfb, 0x0b, 0x85, 0x02, 0x8d, 0x8d, 0x8d, 0xd2, 0x9e, 0x9e, 0x9e, 349 | 0xe6, 0xc2, 0x85, 0x0b, 0xcb, 0x40, 0xad, 0x0a, 0xc0, 0x71, 0x1c, 0x79, 350 | 0x93, 0x54, 0x2a, 0x45, 0x2a, 0x95, 0x0a, 0xdc, 0xaa, 0xa6, 0xa6, 0x26, 351 | 0x30, 0x17, 0x89, 0x44, 0x48, 0xa5, 0x52, 0x72, 0x8f, 0xdf, 0xf6, 0xfc, 352 | 0xde, 0xda, 0xea, 0x34, 0xae, 0x0a, 0xc0, 0x63, 0xc1, 0x3b, 0xb4, 0xfa, 353 | 0x59, 0x9d, 0x9a, 0x95, 0x68, 0xf7, 0xb7, 0xb5, 0xfc, 0x2b, 0x69, 0x40, 354 | 0x8e, 0xa7, 0xa6, 0xa6, 0xb0, 0x6d, 0x3b, 0x10, 0xac, 0x50, 0x28, 0x04, 355 | 0xd6, 0xe4, 0xf3, 0x79, 0x4c, 0xd3, 0x94, 0xfe, 0x7c, 0x3e, 0xef, 0xa9, 356 | 0x5e, 0xfa, 0x37, 0xcc, 0x80, 0x77, 0xd8, 0xce, 0x9d, 0x3b, 0x03, 0xf4, 357 | 0x0a, 0x21, 0xd0, 0x34, 0x8d, 0xe3, 0xc7, 0x8f, 0xcb, 0x43, 0x47, 0x46, 358 | 0x46, 0x68, 0x6b, 0x6b, 0x93, 0x6b, 0x86, 0x87, 0x87, 0xa5, 0x0d, 0x30, 359 | 0x3c, 0x3c, 0x8c, 0xa2, 0x28, 0x2f, 0x0e, 0xc0, 0xbb, 0x9d, 0x10, 0x82, 360 | 0x6c, 0x36, 0x8b, 0xe3, 0x38, 0xcb, 0x44, 0x38, 0x34, 0x34, 0x24, 0xd7, 361 | 0x4f, 0x4c, 0x4c, 0x04, 0x40, 0x67, 0x32, 0x19, 0x69, 0x03, 0x64, 0x32, 362 | 0x19, 0xd2, 0xe9, 0xf4, 0xc6, 0x01, 0x00, 0xec, 0xd8, 0xb1, 0x83, 0xe6, 363 | 0xe6, 0xe6, 0x00, 0x00, 0x55, 0x55, 0xd9, 0xbb, 0x77, 0xaf, 0xb4, 0x43, 364 | 0xa1, 0x50, 0x80, 0x01, 0x4d, 0xd3, 0x02, 0x0c, 0x68, 0x9a, 0xb6, 0xb1, 365 | 0x14, 0xd8, 0xb6, 0x2d, 0x45, 0x98, 0xcb, 0xe5, 0x96, 0x6d, 0x28, 0x14, 366 | 0x0a, 0x0c, 0x0e, 0x0e, 0xca, 0x80, 0xff, 0x19, 0x03, 0x9e, 0x06, 0x9a, 367 | 0x9b, 0x9b, 0xa5, 0x4f, 0x08, 0x41, 0x28, 0x14, 0x92, 0x07, 0x7a, 0xb6, 368 | 0x9f, 0x01, 0x55, 0x55, 0xd9, 0xb3, 0x67, 0x8f, 0xdc, 0xa3, 0xaa, 0xaa, 369 | 0x64, 0xc0, 0x0f, 0x6c, 0x5d, 0x00, 0x80, 0xd4, 0x80, 0x77, 0xb8, 0xa7, 370 | 0xf2, 0x47, 0x8f, 0x1e, 0x49, 0x3b, 0x93, 0xc9, 0x04, 0x40, 0xaf, 0xc4, 371 | 0x40, 0x47, 0x47, 0xc7, 0xc6, 0x18, 0xf0, 0x52, 0xe0, 0x55, 0x81, 0xbf, 372 | 0xf6, 0x55, 0x55, 0x25, 0x9d, 0x4e, 0x07, 0xec, 0xf6, 0xf6, 0x76, 0x09, 373 | 0x40, 0xd3, 0xb4, 0x00, 0x03, 0x7e, 0x0d, 0xbc, 0x10, 0x03, 0xfe, 0x1a, 374 | 0xf6, 0xbf, 0x07, 0xbc, 0x9e, 0xcb, 0xe5, 0x02, 0x1a, 0x18, 0x1f, 0x1b, 375 | 0x23, 0x7b, 0xef, 0x1e, 0xf9, 0xfb, 0xf7, 0x79, 0x36, 0x32, 0xc2, 0x7c, 376 | 0x2e, 0x47, 0x44, 0x51, 0x70, 0x34, 0x8d, 0xf0, 0xd6, 0xad, 0x28, 0x89, 377 | 0x04, 0xe1, 0x73, 0xe7, 0xe8, 0x7c, 0xfb, 0xed, 0x8d, 0xa5, 0xc0, 0xaf, 378 | 0x81, 0xea, 0x2a, 0x48, 0xa7, 0xd3, 0xd8, 0xa6, 0x49, 0xef, 0x57, 0x5f, 379 | 0x31, 0x70, 0xfd, 0x3a, 0xfb, 0x1a, 0x1a, 0x78, 0x6b, 0xd7, 0x2e, 0x5a, 380 | 0x3a, 0x3b, 0x89, 0x1d, 0x3a, 0x84, 0xeb, 0x38, 0x38, 0xa6, 0x49, 0x71, 381 | 0x6e, 0x8e, 0xb1, 0x5c, 0x8e, 0xbe, 0x2b, 0x57, 0xe8, 0x2e, 0x97, 0x89, 382 | 0xd4, 0xd5, 0x71, 0x1a, 0xc2, 0x37, 0xc1, 0x58, 0x17, 0x80, 0xc7, 0xc0, 383 | 0x4a, 0xef, 0x81, 0x3f, 0x7b, 0x7a, 0xb8, 0xf3, 0xc9, 0x27, 0x1c, 0x88, 384 | 0x46, 0xb9, 0x7a, 0xf2, 0x24, 0x9b, 0xc3, 0x61, 0x60, 0x29, 0xc7, 0xb6, 385 | 0x61, 0xe0, 0x98, 0x26, 0x8e, 0x69, 0x12, 0x11, 0x82, 0xf6, 0x86, 0x06, 386 | 0xda, 0xea, 0xea, 0x58, 0x58, 0x5c, 0xe4, 0xa7, 0x81, 0x01, 0xee, 0xc2, 387 | 0x6f, 0x1f, 0xc0, 0xa9, 0xeb, 0x30, 0xb1, 0x2a, 0x00, 0x4f, 0x03, 0x4d, 388 | 0x4d, 0x4d, 0x92, 0x01, 0x8f, 0x15, 0x61, 0x9a, 0xdc, 0xbd, 0x7a, 0x95, 389 | 0x77, 0x3b, 0x3b, 0x39, 0xdc, 0xd6, 0x06, 0x95, 0x94, 0x39, 0xa6, 0x89, 390 | 0x6d, 0x59, 0x32, 0xb8, 0x63, 0x9a, 0xd8, 0xbe, 0x71, 0xc8, 0xb2, 0x78, 391 | 0xa7, 0xb5, 0x95, 0x44, 0x28, 0x74, 0xf8, 0xbb, 0xe1, 0xe1, 0x1f, 0x2f, 392 | 0xc0, 0xd1, 0x2f, 0xa0, 0xb8, 0xae, 0x06, 0x3c, 0x06, 0x3c, 0x00, 0x43, 393 | 0x37, 0x6f, 0x72, 0x28, 0x1e, 0xe7, 0x70, 0x47, 0x07, 0xe8, 0x3a, 0x8e, 394 | 0x6d, 0xaf, 0x18, 0x30, 0x30, 0xe7, 0x03, 0xb6, 0x2f, 0x1e, 0x67, 0x68, 395 | 0xdb, 0xb6, 0x74, 0xff, 0xcc, 0xcc, 0x47, 0xc0, 0xc7, 0x6b, 0xa6, 0xa0, 396 | 0x5a, 0x03, 0x42, 0x08, 0xfa, 0x1f, 0x3e, 0xe4, 0xf5, 0xb3, 0x67, 0x71, 397 | 0x2d, 0x0b, 0xbb, 0x54, 0x5a, 0x37, 0xe0, 0x32, 0x50, 0xae, 0xcb, 0x81, 398 | 0x44, 0x82, 0xbe, 0x99, 0x99, 0xb3, 0xab, 0x02, 0xf0, 0xea, 0x75, 0x6a, 399 | 0x6a, 0x4a, 0x8e, 0x3d, 0x16, 0xf4, 0xe9, 0x69, 0x6a, 0x6b, 0x6b, 0xb1, 400 | 0x9e, 0x3e, 0xc5, 0x29, 0x95, 0xfe, 0x0d, 0x50, 0x09, 0x6a, 0x9b, 0x26, 401 | 0xe3, 0x6f, 0xec, 0xe7, 0xdb, 0xf2, 0x6b, 0xe0, 0xba, 0x98, 0x86, 0x81, 402 | 0x6e, 0x18, 0x58, 0x86, 0x81, 0x6e, 0x98, 0x38, 0xb6, 0x49, 0xab, 0x53, 403 | 0x44, 0x3c, 0xfc, 0xf4, 0x25, 0x40, 0x59, 0x93, 0x01, 0x4f, 0x03, 0x32, 404 | 0xff, 0x42, 0x70, 0xdf, 0x75, 0x79, 0x92, 0xcd, 0xb2, 0x79, 0xcb, 0x16, 405 | 0xec, 0x72, 0x79, 0xd9, 0x0d, 0x6d, 0xd7, 0xa6, 0xfc, 0x7e, 0x03, 0xdb, 406 | 0xba, 0x8b, 0xfc, 0xb9, 0x98, 0x46, 0xd7, 0x0d, 0x0c, 0x43, 0x47, 0xd7, 407 | 0x0d, 0x74, 0xdd, 0x20, 0xa2, 0xb9, 0x44, 0x67, 0x17, 0x01, 0x54, 0x20, 408 | 0xba, 0xaa, 0x06, 0x84, 0x10, 0x4c, 0x4e, 0x4e, 0x06, 0xde, 0x84, 0x00, 409 | 0x4a, 0x2c, 0x46, 0x6f, 0x4f, 0x0f, 0xc9, 0x33, 0x67, 0x50, 0x66, 0x66, 410 | 0xb0, 0xab, 0x58, 0x28, 0xbe, 0x12, 0x27, 0x17, 0xce, 0xb2, 0x6b, 0xff, 411 | 0xdf, 0xf4, 0xf6, 0xec, 0xc2, 0xb2, 0xec, 0x4a, 0xb7, 0x08, 0x6b, 0xd0, 412 | 0x1e, 0xd1, 0xc9, 0x67, 0xc6, 0x88, 0x43, 0x1e, 0x88, 0x28, 0x6b, 0xa5, 413 | 0xa0, 0xa9, 0xa9, 0x89, 0x96, 0x96, 0x16, 0xd9, 0x5b, 0x5b, 0x5b, 0xa9, 414 | 0xdd, 0xb7, 0x8f, 0x81, 0xc9, 0x49, 0xfe, 0xf8, 0xe1, 0x07, 0x4a, 0xc9, 415 | 0x24, 0x6e, 0x32, 0xb9, 0xf4, 0xe9, 0x5d, 0x2e, 0x63, 0x95, 0x4a, 0x64, 416 | 0x0e, 0x0b, 0xa6, 0xf5, 0x31, 0x1e, 0xef, 0x78, 0xcc, 0x2b, 0xa1, 0xaf, 417 | 0x97, 0x2e, 0xe4, 0xda, 0x6c, 0x8f, 0x6f, 0xe2, 0xb5, 0x58, 0x99, 0xc1, 418 | 0xc7, 0x05, 0xe2, 0x7d, 0x3f, 0x53, 0x82, 0x3b, 0x80, 0xbd, 0x6a, 0x0a, 419 | 0x5c, 0xd7, 0x5d, 0x91, 0x01, 0xf7, 0xe0, 0x41, 0x9e, 0xf7, 0xf5, 0x71, 420 | 0x6f, 0x74, 0x94, 0x85, 0x62, 0x91, 0x9d, 0xbb, 0x77, 0xb3, 0xbd, 0xad, 421 | 0x8d, 0x70, 0xa9, 0x84, 0x3d, 0x37, 0x4b, 0xf6, 0x48, 0x84, 0xb2, 0x55, 422 | 0xc4, 0xb2, 0x75, 0xa2, 0x6f, 0x3d, 0xe0, 0xe5, 0xde, 0xd3, 0xd4, 0x1b, 423 | 0x30, 0x3d, 0xd8, 0x47, 0xcf, 0xa2, 0xc6, 0x9e, 0xde, 0x6e, 0xc8, 0x8f, 424 | 0x3c, 0xfb, 0x0b, 0x3e, 0x07, 0x16, 0xd6, 0x2c, 0xc3, 0x8b, 0x17, 0x2f, 425 | 0x06, 0x2a, 0x40, 0x08, 0x81, 0x7d, 0xe2, 0x04, 0x03, 0x6f, 0xbe, 0xc9, 426 | 0xed, 0xcb, 0x97, 0x79, 0x9e, 0xcf, 0xb3, 0xd7, 0x30, 0x98, 0xe8, 0xef, 427 | 0x67, 0x53, 0x34, 0x8a, 0x95, 0x8e, 0x32, 0xf7, 0x57, 0x8c, 0x90, 0x02, 428 | 0xb8, 0xb0, 0x50, 0xd6, 0x31, 0xfe, 0xee, 0xe2, 0xfb, 0x85, 0xa3, 0x34, 429 | 0x17, 0xb3, 0xec, 0xef, 0xbd, 0x85, 0x9e, 0x1b, 0x99, 0x7e, 0x00, 0x1f, 430 | 0x3e, 0x80, 0x7e, 0xa0, 0x5c, 0x0d, 0x20, 0xa6, 0x28, 0xca, 0x9c, 0xeb, 431 | 0xba, 0xb5, 0xf5, 0xf5, 0xff, 0x7e, 0xc2, 0x57, 0x7f, 0x54, 0x1e, 0x3a, 432 | 0x76, 0x8c, 0x86, 0xdb, 0xb7, 0xf9, 0xe5, 0xb3, 0xcf, 0xf8, 0xbd, 0xbb, 433 | 0x9b, 0x58, 0x28, 0x44, 0x52, 0xd7, 0x51, 0x1f, 0x2f, 0x30, 0x3e, 0xfe, 434 | 0x0c, 0x67, 0x93, 0xc0, 0x71, 0x5d, 0x42, 0x33, 0x16, 0x4a, 0x5f, 0x3f, 435 | 0xaf, 0x3e, 0xfc, 0x15, 0xa7, 0x58, 0xa0, 0xb0, 0x75, 0xab, 0x75, 0x07, 436 | 0x2e, 0x66, 0xa1, 0x17, 0x78, 0x02, 0xc1, 0xbf, 0x66, 0x31, 0x20, 0x71, 437 | 0xe4, 0xc8, 0x91, 0xf7, 0x5a, 0x5a, 0x5a, 0x3e, 0x05, 0x36, 0x55, 0xb3, 438 | 0xe3, 0x6f, 0xae, 0xeb, 0x62, 0x18, 0x06, 0xd6, 0xfc, 0x3c, 0x35, 0x93, 439 | 0x93, 0x44, 0x66, 0x67, 0xa9, 0x99, 0x9f, 0x47, 0x33, 0x0c, 0x14, 0xcb, 440 | 0xc2, 0xd1, 0x34, 0xcc, 0x50, 0x08, 0x3d, 0x1a, 0xa5, 0x14, 0x8f, 0x53, 441 | 0xda, 0xbe, 0xdd, 0x18, 0xcd, 0xe5, 0xbe, 0x18, 0x1c, 0x1c, 0xfc, 0x06, 442 | 0x18, 0x07, 0xb2, 0x80, 0xe3, 0x07, 0x10, 0x06, 0x92, 0xc0, 0x36, 0xa0, 443 | 0x1e, 0xa8, 0x61, 0xa9, 0x54, 0xd6, 0x6b, 0x0a, 0x10, 0x01, 0x36, 0x57, 444 | 0xf6, 0x68, 0x95, 0x39, 0x00, 0x87, 0xa5, 0x1f, 0x9e, 0xa7, 0x80, 0x0e, 445 | 0x3c, 0x03, 0x66, 0x59, 0xfa, 0x1d, 0x98, 0xaf, 0x66, 0xc0, 0x6b, 0x35, 446 | 0x95, 0x03, 0xc3, 0xab, 0xf8, 0xd7, 0x6b, 0xa2, 0xd2, 0xbd, 0xaf, 0x0f, 447 | 0xb7, 0x02, 0xc4, 0xac, 0x80, 0xd0, 0x7d, 0x3e, 0xfe, 0x01, 0x9f, 0x1e, 448 | 0x98, 0x64, 0x1e, 0x77, 0xb2, 0x47, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 449 | 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 450 | }, 451 | } 452 | -------------------------------------------------------------------------------- /examples/updateui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/andlabs/ui" 8 | ) 9 | 10 | // Example showing how to update the UI using the QueueMain function 11 | // especially if the update is coming from another goroutine 12 | // 13 | // see QueueMain in 'main.go' for detailed description 14 | 15 | var countLabel *ui.Label 16 | var count int 17 | 18 | func setupUI() { 19 | mainWindow := ui.NewWindow("libui Updating UI", 640, 480, true) 20 | mainWindow.OnClosing(func(*ui.Window) bool { 21 | ui.Quit() 22 | return true 23 | }) 24 | ui.OnShouldQuit(func() bool { 25 | mainWindow.Destroy() 26 | return true 27 | }) 28 | 29 | vbContainer := ui.NewVerticalBox() 30 | vbContainer.SetPadded(true) 31 | 32 | inputGroup := ui.NewGroup("Input") 33 | inputGroup.SetMargined(true) 34 | 35 | vbInput := ui.NewVerticalBox() 36 | vbInput.SetPadded(true) 37 | 38 | inputForm := ui.NewForm() 39 | inputForm.SetPadded(true) 40 | 41 | message := ui.NewEntry() 42 | message.SetText("Hello World") 43 | inputForm.Append("What message do you want to show?", message, false) 44 | 45 | showMessageButton := ui.NewButton("Show message") 46 | clearMessageButton := ui.NewButton("Clear message") 47 | 48 | vbInput.Append(inputForm, false) 49 | vbInput.Append(showMessageButton, false) 50 | vbInput.Append(clearMessageButton, false) 51 | 52 | inputGroup.SetChild(vbInput) 53 | 54 | messageGroup := ui.NewGroup("Message") 55 | messageGroup.SetMargined(true) 56 | 57 | vbMessage := ui.NewVerticalBox() 58 | vbMessage.SetPadded(true) 59 | 60 | messageLabel := ui.NewLabel("") 61 | 62 | vbMessage.Append(messageLabel, false) 63 | 64 | messageGroup.SetChild(vbMessage) 65 | 66 | countGroup := ui.NewGroup("Counter") 67 | countGroup.SetMargined(true) 68 | 69 | vbCounter := ui.NewVerticalBox() 70 | vbCounter.SetPadded(true) 71 | 72 | countLabel = ui.NewLabel(fmt.Sprintf("%d", count)) 73 | 74 | vbCounter.Append(countLabel, false) 75 | countGroup.SetChild(vbCounter) 76 | 77 | vbContainer.Append(inputGroup, false) 78 | vbContainer.Append(messageGroup, false) 79 | vbContainer.Append(countGroup, false) 80 | 81 | mainWindow.SetChild(vbContainer) 82 | 83 | showMessageButton.OnClicked(func(*ui.Button) { 84 | // Update the UI directly as it is called from the main thread 85 | messageLabel.SetText(message.Text()) 86 | }) 87 | 88 | clearMessageButton.OnClicked(func(*ui.Button) { 89 | // Update the UI directly as it is called from the main thread 90 | messageLabel.SetText("") 91 | }) 92 | 93 | mainWindow.Show() 94 | 95 | // Counting and updating the UI from another goroutine 96 | go counter() 97 | } 98 | 99 | func counter() { 100 | for { 101 | time.Sleep(1 * time.Second) 102 | count++ 103 | 104 | // Update the UI using the QueueMain function 105 | ui.QueueMain(func() { 106 | countLabel.SetText(fmt.Sprintf("%d", count)) 107 | }) 108 | } 109 | } 110 | 111 | func main() { 112 | count = 0 113 | 114 | ui.Main(setupUI) 115 | } 116 | -------------------------------------------------------------------------------- /fontbutton.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // FontButton is a Control that represents a button that the user can 13 | // click to select a font. 14 | type FontButton struct { 15 | ControlBase 16 | b *C.uiFontButton 17 | onChanged func(*FontButton) 18 | } 19 | 20 | // NewFontButton creates a new FontButton. 21 | func NewFontButton() *FontButton { 22 | b := new(FontButton) 23 | 24 | b.b = C.uiNewFontButton() 25 | 26 | C.pkguiFontButtonOnChanged(b.b) 27 | 28 | b.ControlBase = NewControlBase(b, uintptr(unsafe.Pointer(b.b))) 29 | return b 30 | } 31 | 32 | // Font returns the font currently selected in the FontButton. 33 | func (b *FontButton) Font() *FontDescriptor { 34 | cfd := C.pkguiNewFontDescriptor() 35 | defer C.pkguiFreeFontDescriptor(cfd) 36 | C.uiFontButtonFont(b.b, cfd) 37 | defer C.uiFreeFontButtonFont(cfd) 38 | fd := &FontDescriptor{} 39 | fd.fromLibui(cfd) 40 | return fd 41 | } 42 | 43 | // OnChanged registers f to be run when the user changes the 44 | // currently selected font in the FontButton. Only one function can 45 | // be registered at a time. 46 | func (b *FontButton) OnChanged(f func(*FontButton)) { 47 | b.onChanged = f 48 | } 49 | 50 | //export pkguiDoFontButtonOnChanged 51 | func pkguiDoFontButtonOnChanged(bb *C.uiFontButton, data unsafe.Pointer) { 52 | b := ControlFromLibui(uintptr(unsafe.Pointer(bb))).(*FontButton) 53 | if b.onChanged != nil { 54 | b.onChanged(b) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /form.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Form is a Control that holds a group of Controls vertically 13 | // with labels next to each. By default, each control has its 14 | // preferred height; if a control is marked "stretchy", it will take 15 | // whatever space is left over. If multiple controls are marked 16 | // stretchy, they will be given equal shares of the leftover space. 17 | // There can also be space between each control ("padding"). 18 | type Form struct { 19 | ControlBase 20 | f *C.uiForm 21 | children []Control 22 | } 23 | 24 | // NewForm creates a new horizontal Form. 25 | func NewForm() *Form { 26 | f := new(Form) 27 | 28 | f.f = C.uiNewForm() 29 | 30 | f.ControlBase = NewControlBase(f, uintptr(unsafe.Pointer(f.f))) 31 | return f 32 | } 33 | 34 | // Destroy destroys the Form. If the Form has children, 35 | // Destroy calls Destroy on those Controls as well. 36 | func (f *Form) Destroy() { 37 | for len(f.children) != 0 { 38 | c := f.children[0] 39 | f.Delete(0) 40 | c.Destroy() 41 | } 42 | f.ControlBase.Destroy() 43 | } 44 | 45 | // Append adds the given control to the end of the Form. 46 | func (f *Form) Append(label string, child Control, stretchy bool) { 47 | clabel := C.CString(label) 48 | defer freestr(clabel) 49 | c := touiControl(child.LibuiControl()) 50 | C.uiFormAppend(f.f, clabel, c, frombool(stretchy)) 51 | f.children = append(f.children, child) 52 | } 53 | 54 | // Delete deletes the nth control of the Form. 55 | func (f *Form) Delete(n int) { 56 | f.children = append(f.children[:n], f.children[n + 1:]...) 57 | C.uiFormDelete(f.f, C.int(n)) 58 | } 59 | 60 | // Padded returns whether there is space between each control 61 | // of the Form. 62 | func (f *Form) Padded() bool { 63 | return tobool(C.uiFormPadded(f.f)) 64 | } 65 | 66 | // SetPadded controls whether there is space between each control 67 | // of the Form. The size of the padding is determined by the OS and 68 | // its best practices. 69 | func (f *Form) SetPadded(padded bool) { 70 | C.uiFormSetPadded(f.f, frombool(padded)) 71 | } 72 | -------------------------------------------------------------------------------- /grid.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Grid is a Control that arranges other Controls in a grid. 13 | // Grid is a very powerful container: it can position and size each 14 | // Control in several ways and can (and must) have Controls added 15 | // to it in any direction. It can also have Controls spanning multiple 16 | // rows and columns. 17 | // 18 | // Each Control in a Grid has associated "expansion" and 19 | // "alignment" values in both the X and Y direction. 20 | // Expansion determines whether all cells in the same row/column 21 | // are given whatever space is left over after figuring out how big 22 | // the rest of the Grid should be. Alignment determines the position 23 | // of a Control relative to its cell after computing the above. The 24 | // special alignment Fill can be used to grow a Control to fit its cell. 25 | // Note that expansion and alignment are independent variables. 26 | // For more information on expansion and alignment, read 27 | // https://developer.gnome.org/gtk3/unstable/ch28s02.html. 28 | type Grid struct { 29 | ControlBase 30 | g *C.uiGrid 31 | children []Control 32 | } 33 | 34 | // Align represents the alignment of a Control in its cell of a Grid. 35 | type Align int 36 | const ( 37 | AlignFill Align = iota 38 | AlignStart 39 | AlignCenter 40 | AlignEnd 41 | ) 42 | 43 | // At represents a side of a Control to add other Controls to a Grid to. 44 | type At int 45 | const ( 46 | Leading At = iota 47 | Top 48 | Trailing 49 | Bottom 50 | ) 51 | 52 | // NewGrid creates a new Grid. 53 | func NewGrid() *Grid { 54 | g := new(Grid) 55 | 56 | g.g = C.uiNewGrid() 57 | 58 | g.ControlBase = NewControlBase(g, uintptr(unsafe.Pointer(g.g))) 59 | return g 60 | } 61 | 62 | // TODO Destroy 63 | 64 | // Append adds the given control to the Grid, at the given coordinate. 65 | func (g *Grid) Append(child Control, left, top int, xspan, yspan int, hexpand bool, halign Align, vexpand bool, valign Align) { 66 | C.uiGridAppend(g.g, touiControl(child.LibuiControl()), 67 | C.int(left), C.int(top), 68 | C.int(xspan), C.int(yspan), 69 | frombool(hexpand), C.uiAlign(halign), 70 | frombool(vexpand), C.uiAlign(valign)) 71 | g.children = append(g.children, child) 72 | } 73 | 74 | // InsertAt adds the given control to the Grid relative to an existing 75 | // control. 76 | func (g *Grid) InsertAt(child Control, existing Control, at At, xspan, yspan int, hexpand bool, halign Align, vexpand bool, valign Align) { 77 | C.uiGridInsertAt(g.g, touiControl(child.LibuiControl()), 78 | touiControl(existing.LibuiControl()), C.uiAt(at), 79 | C.int(xspan), C.int(yspan), 80 | frombool(hexpand), C.uiAlign(halign), 81 | frombool(vexpand), C.uiAlign(valign)) 82 | g.children = append(g.children, child) 83 | } 84 | 85 | // Padded returns whether there is space between each control 86 | // of the Grid. 87 | func (g *Grid) Padded() bool { 88 | return tobool(C.uiGridPadded(g.g)) 89 | } 90 | 91 | // SetPadded controls whether there is space between each control 92 | // of the Grid. The size of the padding is determined by the OS and 93 | // its best practices. 94 | func (g *Grid) SetPadded(padded bool) { 95 | C.uiGridSetPadded(g.g, frombool(padded)) 96 | } 97 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Group is a Control that holds another Control and wraps it around 13 | // a labelled box (though some systems make this box invisible). 14 | // You can use this to group related controls together. 15 | type Group struct { 16 | ControlBase 17 | g *C.uiGroup 18 | child Control 19 | } 20 | 21 | // NewGroup creates a new Group. 22 | func NewGroup(title string) *Group { 23 | g := new(Group) 24 | 25 | ctitle := C.CString(title) 26 | g.g = C.uiNewGroup(ctitle) 27 | freestr(ctitle) 28 | 29 | g.ControlBase = NewControlBase(g, uintptr(unsafe.Pointer(g.g))) 30 | return g 31 | } 32 | 33 | // Destroy destroys the Group. If the Group has a child, 34 | // Destroy calls Destroy on that as well. 35 | func (g *Group) Destroy() { 36 | if g.child != nil { 37 | c := g.child 38 | g.SetChild(nil) 39 | c.Destroy() 40 | } 41 | g.ControlBase.Destroy() 42 | } 43 | 44 | // Title returns the Group's title. 45 | func (g *Group) Title() string { 46 | ctitle := C.uiGroupTitle(g.g) 47 | title := C.GoString(ctitle) 48 | C.uiFreeText(ctitle) 49 | return title 50 | } 51 | 52 | // SetTitle sets the Group's title to title. 53 | func (g *Group) SetTitle(title string) { 54 | ctitle := C.CString(title) 55 | C.uiGroupSetTitle(g.g, ctitle) 56 | freestr(ctitle) 57 | } 58 | 59 | // SetChild sets the Group's child to child. If child is nil, the Group 60 | // will not have a child. 61 | func (g *Group) SetChild(child Control) { 62 | g.child = child 63 | c := (*C.uiControl)(nil) 64 | if g.child != nil { 65 | c = touiControl(g.child.LibuiControl()) 66 | } 67 | C.uiGroupSetChild(g.g, c) 68 | } 69 | 70 | // Margined returns whether the Group has margins around its child. 71 | func (g *Group) Margined() bool { 72 | return tobool(C.uiGroupMargined(g.g)) 73 | } 74 | 75 | // SetMargined controls whether the Group has margins around its 76 | // child. The size of the margins are determined by the OS and its 77 | // best practices. 78 | func (g *Group) SetMargined(margined bool) { 79 | C.uiGroupSetMargined(g.g, frombool(margined)) 80 | } 81 | -------------------------------------------------------------------------------- /image.go: -------------------------------------------------------------------------------- 1 | // 21 august 2018 2 | 3 | package ui 4 | 5 | import ( 6 | "image" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Image stores an image for display on screen. 13 | // 14 | // Images are built from one or more representations, each with the 15 | // same aspect ratio but a different pixel size. Package ui 16 | // automatically selects the most appropriate representation for 17 | // drawing the image when it comes time to draw the image; what 18 | // this means depends on the pixel density of the target context. 19 | // Therefore, one can use Image to draw higher-detailed images on 20 | // higher-density displays. The typical use cases are either: 21 | // 22 | // - have just a single representation, at which point all screens 23 | // use the same image, and thus uiImage acts like a simple 24 | // bitmap image, or 25 | // - have two images, one at normal resolution and one at 2x 26 | // resolution; this matches the current expectations of some 27 | // desktop systems at the time of writing (mid-2018) 28 | // 29 | // Image allocates OS resources; you must explicitly free an Image 30 | // when you are finished with it. 31 | type Image struct { 32 | i *C.uiImage 33 | } 34 | 35 | // NewImage creates a new Image with the given width and 36 | // height. This width and height should be the size in points of the 37 | // image in the device-independent case; typically this is the 1x size. 38 | func NewImage(width, height float64) *Image { 39 | return &Image{ 40 | i: C.uiNewImage(C.double(width), C.double(height)), 41 | } 42 | } 43 | 44 | // Free frees the Image. 45 | func (i *Image) Free() { 46 | C.uiFreeImage(i.i) 47 | } 48 | 49 | // Append adds the given image as a representation of the Image. 50 | func (i *Image) Append(img *image.RGBA) { 51 | cpix := C.CBytes(img.Pix) 52 | defer C.free(cpix) 53 | C.uiImageAppend(i.i, cpix, 54 | C.int(img.Rect.Dx()), 55 | C.int(img.Rect.Dy()), 56 | C.int(img.Stride)) 57 | } 58 | -------------------------------------------------------------------------------- /label.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Label is a Control that represents a line of text that cannot be 13 | // interacted with. 14 | type Label struct { 15 | ControlBase 16 | l *C.uiLabel 17 | } 18 | 19 | // NewLabel creates a new Label with the given text. 20 | func NewLabel(text string) *Label { 21 | l := new(Label) 22 | 23 | ctext := C.CString(text) 24 | l.l = C.uiNewLabel(ctext) 25 | freestr(ctext) 26 | 27 | l.ControlBase = NewControlBase(l, uintptr(unsafe.Pointer(l.l))) 28 | return l 29 | } 30 | 31 | // Text returns the Label's text. 32 | func (l *Label) Text() string { 33 | ctext := C.uiLabelText(l.l) 34 | text := C.GoString(ctext) 35 | C.uiFreeText(ctext) 36 | return text 37 | } 38 | 39 | // SetText sets the Label's text to text. 40 | func (l *Label) SetText(text string) { 41 | ctext := C.CString(text) 42 | C.uiLabelSetText(l.l, ctext) 43 | freestr(ctext) 44 | } 45 | -------------------------------------------------------------------------------- /libui_darwin_amd64.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/libui_darwin_amd64.a -------------------------------------------------------------------------------- /libui_linux_386.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/libui_linux_386.a -------------------------------------------------------------------------------- /libui_linux_amd64.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/libui_linux_amd64.a -------------------------------------------------------------------------------- /libui_windows_386.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/libui_windows_386.a -------------------------------------------------------------------------------- /libui_windows_amd64.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/libui_windows_amd64.a -------------------------------------------------------------------------------- /link_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // 13 december 2015 2 | 3 | package ui 4 | 5 | // #cgo CFLAGS: -mmacosx-version-min=10.8 6 | // #cgo LDFLAGS: ${SRCDIR}/libui_darwin_amd64.a -framework Foundation -framework AppKit -mmacosx-version-min=10.8 7 | import "C" 8 | -------------------------------------------------------------------------------- /link_linux_386.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | // +build !darwin 3 | 4 | // 11 december 2015 5 | 6 | package ui 7 | 8 | // #cgo LDFLAGS: ${SRCDIR}/libui_linux_386.a -lm -ldl 9 | // #cgo pkg-config: gtk+-3.0 10 | import "C" 11 | -------------------------------------------------------------------------------- /link_linux_amd64.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | // +build !darwin 3 | 4 | // 11 december 2015 5 | 6 | package ui 7 | 8 | // #cgo LDFLAGS: ${SRCDIR}/libui_linux_amd64.a -lm -ldl 9 | // #cgo pkg-config: gtk+-3.0 10 | import "C" 11 | -------------------------------------------------------------------------------- /link_windows_386.go: -------------------------------------------------------------------------------- 1 | // 13 december 2015 2 | 3 | package ui 4 | 5 | // #cgo LDFLAGS: ${SRCDIR}/libui_windows_386.a 6 | // #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lmsimg32 -lcomdlg32 -ld2d1 -ldwrite -lole32 -loleaut32 -loleacc -luuid -lwindowscodecs -static -static-libgcc -static-libstdc++ 7 | import "C" 8 | -------------------------------------------------------------------------------- /link_windows_amd64.go: -------------------------------------------------------------------------------- 1 | // 13 december 2015 2 | 3 | package ui 4 | 5 | // #cgo LDFLAGS: ${SRCDIR}/libui_windows_amd64.a 6 | // #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lmsimg32 -lcomdlg32 -ld2d1 -ldwrite -lole32 -loleaut32 -loleacc -luuid -lwindowscodecs -static -static-libgcc -static-libstdc++ 7 | import "C" 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 11 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "runtime" 7 | "errors" 8 | "sync" 9 | "unsafe" 10 | ) 11 | 12 | // #include "pkgui.h" 13 | import "C" 14 | 15 | // make sure main() runs on the first thread created by the OS 16 | // 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 17 | // 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) 18 | // TODO describe the source of this trick 19 | func init() { 20 | runtime.LockOSThread() 21 | } 22 | 23 | // Main initializes package ui, runs f to set up the program, 24 | // and executes the GUI main loop. f should set up the program's 25 | // initial state: open the main window, create controls, and set up 26 | // events. It should then return, at which point Main will 27 | // process events until Quit is called, at which point Main will return 28 | // nil. If package ui fails to initialize, Main returns an appropriate 29 | // error. 30 | func Main(f func()) error { 31 | opts := C.pkguiAllocInitOptions() 32 | estr := C.uiInit(opts) 33 | C.pkguiFreeInitOptions(opts) 34 | if estr != nil { 35 | err := errors.New(C.GoString(estr)) 36 | C.uiFreeInitError(estr) 37 | return err 38 | } 39 | C.pkguiOnShouldQuit() 40 | QueueMain(f) 41 | C.uiMain() 42 | return nil 43 | } 44 | 45 | // Quit queues a return from Main. It does not exit the program. 46 | // It also does not immediately cause Main to return; Main will 47 | // return when it next can. Quit must be called from the GUI thread. 48 | func Quit() { 49 | C.uiQuit() 50 | } 51 | 52 | // These prevent the passing of Go functions into C land. 53 | // TODO make an actual sparse list instead of this monotonic map thingy 54 | var ( 55 | qmmap = make(map[uintptr]func()) 56 | qmcurrent = uintptr(0) 57 | qmlock sync.Mutex 58 | ) 59 | 60 | // QueueMain queues f to be executed on the GUI thread when 61 | // next possible. It returns immediately; that is, it does not wait 62 | // for the function to actually be executed. QueueMain is the only 63 | // function that can be called from other goroutines, and its 64 | // primary purpose is to allow communication between other 65 | // goroutines and the GUI thread. Calling QueueMain after Quit 66 | // has been called results in undefined behavior. 67 | // 68 | // If you start a goroutine in f, it also cannot call package ui 69 | // functions. So for instance, the following will result in 70 | // undefined behavior: 71 | // 72 | // ui.QueueMain(func() { 73 | // go ui.MsgBox(...) 74 | // }) 75 | func QueueMain(f func()) { 76 | qmlock.Lock() 77 | defer qmlock.Unlock() 78 | 79 | n := uintptr(0) 80 | for { 81 | n = qmcurrent 82 | qmcurrent++ 83 | if qmmap[n] == nil { 84 | break 85 | } 86 | } 87 | qmmap[n] = f 88 | C.pkguiQueueMain(C.uintptr_t(n)) 89 | } 90 | 91 | //export pkguiDoQueueMain 92 | func pkguiDoQueueMain(nn unsafe.Pointer) { 93 | qmlock.Lock() 94 | 95 | n := uintptr(nn) 96 | f := qmmap[n] 97 | delete(qmmap, n) 98 | 99 | // allow uiQueueMain() to be called by a queued function 100 | // TODO explicitly allow this in libui too 101 | qmlock.Unlock() 102 | 103 | f() 104 | } 105 | 106 | // no need to lock this; this API is only safe on the main thread 107 | var shouldQuitFunc func() bool 108 | 109 | // OnShouldQuit schedules f to be exeucted when the OS wants 110 | // the program to quit or when a Quit menu item has been clicked. 111 | // Only one function may be registered at a time. If the function 112 | // returns true, Quit will be called. If the function returns false, or 113 | // if OnShouldQuit is never called. Quit will not be called and the 114 | // OS will be told that the program needs to continue running. 115 | func OnShouldQuit(f func() bool) { 116 | shouldQuitFunc = f 117 | } 118 | 119 | //export pkguiDoOnShouldQuit 120 | func pkguiDoOnShouldQuit(unused unsafe.Pointer) C.int { 121 | if shouldQuitFunc == nil { 122 | return 0 123 | } 124 | return frombool(shouldQuitFunc()) 125 | } 126 | 127 | // TODO Timer? 128 | -------------------------------------------------------------------------------- /multilineentry.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | // TODO typing in entry in OS X crashes libui 4 | // I've had similar issues with checkboxes on libui 5 | // something's wrong with NSMapTable 6 | 7 | package ui 8 | 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | // #include "pkgui.h" 14 | import "C" 15 | 16 | // MultilineEntry is a Control that represents a space that the user 17 | // can type multiple lines of text into. 18 | type MultilineEntry struct { 19 | ControlBase 20 | e *C.uiMultilineEntry 21 | onChanged func(*MultilineEntry) 22 | } 23 | 24 | func finishNewMultilineEntry(ee *C.uiMultilineEntry) *MultilineEntry { 25 | m := new(MultilineEntry) 26 | 27 | m.e = ee 28 | 29 | C.pkguiMultilineEntryOnChanged(m.e) 30 | 31 | m.ControlBase = NewControlBase(m, uintptr(unsafe.Pointer(m.e))) 32 | return m 33 | } 34 | 35 | // NewMultilineEntry creates a new MultilineEntry. 36 | // The MultilineEntry soft-word-wraps and has no horizontal 37 | // scrollbar. 38 | func NewMultilineEntry() *MultilineEntry { 39 | return finishNewMultilineEntry(C.uiNewMultilineEntry()) 40 | } 41 | 42 | // NewNonWrappingMultilineEntry creates a new MultilineEntry. 43 | // The MultilineEntry does not word-wrap and thus has horizontal 44 | // scrollbar. 45 | func NewNonWrappingMultilineEntry() *MultilineEntry { 46 | return finishNewMultilineEntry(C.uiNewNonWrappingMultilineEntry()) 47 | } 48 | 49 | // Text returns the MultilineEntry's text. 50 | func (m *MultilineEntry) Text() string { 51 | ctext := C.uiMultilineEntryText(m.e) 52 | text := C.GoString(ctext) 53 | C.uiFreeText(ctext) 54 | return text 55 | } 56 | 57 | // SetText sets the MultilineEntry's text to text. 58 | func (m *MultilineEntry) SetText(text string) { 59 | ctext := C.CString(text) 60 | C.uiMultilineEntrySetText(m.e, ctext) 61 | freestr(ctext) 62 | } 63 | 64 | // Append adds text to the end of the MultilineEntry's text. 65 | // TODO selection and scroll behavior 66 | func (m *MultilineEntry) Append(text string) { 67 | ctext := C.CString(text) 68 | C.uiMultilineEntryAppend(m.e, ctext) 69 | freestr(ctext) 70 | } 71 | 72 | // OnChanged registers f to be run when the user makes a change to 73 | // the MultilineEntry. Only one function can be registered at a time. 74 | func (m *MultilineEntry) OnChanged(f func(*MultilineEntry)) { 75 | m.onChanged = f 76 | } 77 | 78 | //export pkguiDoMultilineEntryOnChanged 79 | func pkguiDoMultilineEntryOnChanged(ee *C.uiMultilineEntry, data unsafe.Pointer) { 80 | m := ControlFromLibui(uintptr(unsafe.Pointer(ee))).(*MultilineEntry) 81 | if m.onChanged != nil { 82 | m.onChanged(m) 83 | } 84 | } 85 | 86 | // ReadOnly returns whether the MultilineEntry can be changed. 87 | func (m *MultilineEntry) ReadOnly() bool { 88 | return tobool(C.uiMultilineEntryReadOnly(m.e)) 89 | } 90 | 91 | // SetReadOnly sets whether the MultilineEntry can be changed. 92 | func (m *MultilineEntry) SetReadOnly(ro bool) { 93 | C.uiMultilineEntrySetReadOnly(m.e, frombool(ro)) 94 | } 95 | -------------------------------------------------------------------------------- /pkgui.c: -------------------------------------------------------------------------------- 1 | // 26 august 2018 2 | #include "pkgui.h" 3 | #include "_cgo_export.h" 4 | 5 | uiInitOptions *pkguiAllocInitOptions(void) 6 | { 7 | return (uiInitOptions *) pkguiAlloc(sizeof (uiInitOptions)); 8 | } 9 | 10 | void pkguiFreeInitOptions(uiInitOptions *o) 11 | { 12 | free(o); 13 | } 14 | 15 | void pkguiQueueMain(uintptr_t n) 16 | { 17 | uiQueueMain(pkguiDoQueueMain, (void *) n); 18 | } 19 | 20 | void pkguiOnShouldQuit(void) 21 | { 22 | uiOnShouldQuit(pkguiDoOnShouldQuit, NULL); 23 | } 24 | 25 | void pkguiWindowOnClosing(uiWindow *w) 26 | { 27 | uiWindowOnClosing(w, pkguiDoWindowOnClosing, NULL); 28 | } 29 | 30 | void pkguiButtonOnClicked(uiButton *b) 31 | { 32 | uiButtonOnClicked(b, pkguiDoButtonOnClicked, NULL); 33 | } 34 | 35 | void pkguiCheckboxOnToggled(uiCheckbox *c) 36 | { 37 | uiCheckboxOnToggled(c, pkguiDoCheckboxOnToggled, NULL); 38 | } 39 | 40 | void pkguiColorButtonOnChanged(uiColorButton *c) 41 | { 42 | uiColorButtonOnChanged(c, pkguiDoColorButtonOnChanged, NULL); 43 | } 44 | 45 | pkguiColorDoubles pkguiAllocColorDoubles(void) 46 | { 47 | pkguiColorDoubles c; 48 | 49 | c.r = (double *) pkguiAlloc(4 * sizeof (double)); 50 | c.g = c.r + 1; 51 | c.b = c.g + 1; 52 | c.a = c.b + 1; 53 | return c; 54 | } 55 | 56 | void pkguiFreeColorDoubles(pkguiColorDoubles c) 57 | { 58 | free(c.r); 59 | } 60 | 61 | void pkguiComboboxOnSelected(uiCombobox *c) 62 | { 63 | uiComboboxOnSelected(c, pkguiDoComboboxOnSelected, NULL); 64 | } 65 | 66 | void pkguiDateTimePickerOnChanged(uiDateTimePicker *d) 67 | { 68 | uiDateTimePickerOnChanged(d, pkguiDoDateTimePickerOnChanged, NULL); 69 | } 70 | 71 | struct tm *pkguiAllocTime(void) 72 | { 73 | return (struct tm *) pkguiAlloc(sizeof (struct tm)); 74 | } 75 | 76 | void pkguiFreeTime(struct tm *t) 77 | { 78 | free(t); 79 | } 80 | 81 | void pkguiEditableComboboxOnChanged(uiEditableCombobox *c) 82 | { 83 | uiEditableComboboxOnChanged(c, pkguiDoEditableComboboxOnChanged, NULL); 84 | } 85 | 86 | void pkguiEntryOnChanged(uiEntry *e) 87 | { 88 | uiEntryOnChanged(e, pkguiDoEntryOnChanged, NULL); 89 | } 90 | 91 | void pkguiFontButtonOnChanged(uiFontButton *b) 92 | { 93 | uiFontButtonOnChanged(b, pkguiDoFontButtonOnChanged, NULL); 94 | } 95 | 96 | void pkguiMultilineEntryOnChanged(uiMultilineEntry *e) 97 | { 98 | uiMultilineEntryOnChanged(e, pkguiDoMultilineEntryOnChanged, NULL); 99 | } 100 | 101 | void pkguiRadioButtonsOnSelected(uiRadioButtons *r) 102 | { 103 | uiRadioButtonsOnSelected(r, pkguiDoRadioButtonsOnSelected, NULL); 104 | } 105 | 106 | void pkguiSliderOnChanged(uiSlider *s) 107 | { 108 | uiSliderOnChanged(s, pkguiDoSliderOnChanged, NULL); 109 | } 110 | 111 | void pkguiSpinboxOnChanged(uiSpinbox *s) 112 | { 113 | uiSpinboxOnChanged(s, pkguiDoSpinboxOnChanged, NULL); 114 | } 115 | 116 | uiDrawBrush *pkguiAllocBrush(void) 117 | { 118 | return (uiDrawBrush *) pkguiAlloc(sizeof (uiDrawBrush)); 119 | } 120 | 121 | void pkguiFreeBrush(uiDrawBrush *b) 122 | { 123 | free(b); 124 | } 125 | 126 | uiDrawBrushGradientStop *pkguiAllocGradientStops(size_t n) 127 | { 128 | return (uiDrawBrushGradientStop *) pkguiAlloc(n * sizeof (uiDrawBrushGradientStop)); 129 | } 130 | 131 | void pkguiFreeGradientStops(uiDrawBrushGradientStop *stops) 132 | { 133 | free(stops); 134 | } 135 | 136 | void pkguiSetGradientStop(uiDrawBrushGradientStop *stops, size_t i, double pos, double r, double g, double b, double a) 137 | { 138 | stops[i].Pos = pos; 139 | stops[i].R = r; 140 | stops[i].G = g; 141 | stops[i].B = b; 142 | stops[i].A = a; 143 | } 144 | 145 | uiDrawStrokeParams *pkguiAllocStrokeParams(void) 146 | { 147 | return (uiDrawStrokeParams *) pkguiAlloc(sizeof (uiDrawStrokeParams)); 148 | } 149 | 150 | void pkguiFreeStrokeParams(uiDrawStrokeParams *p) 151 | { 152 | free(p); 153 | } 154 | 155 | double *pkguiAllocDashes(size_t n) 156 | { 157 | return (double *) pkguiAlloc(n * sizeof (double)); 158 | } 159 | 160 | void pkguiFreeDashes(double *dashes) 161 | { 162 | free(dashes); 163 | } 164 | 165 | void pkguiSetDash(double *dashes, size_t i, double dash) 166 | { 167 | dashes[i] = dash; 168 | } 169 | 170 | uiDrawMatrix *pkguiAllocMatrix(void) 171 | { 172 | return (uiDrawMatrix *) pkguiAlloc(sizeof (uiDrawMatrix)); 173 | } 174 | 175 | void pkguiFreeMatrix(uiDrawMatrix *m) 176 | { 177 | free(m); 178 | } 179 | 180 | uiUnderlineColor *pkguiNewUnderlineColor(void) 181 | { 182 | return (uiUnderlineColor *) pkguiAlloc(sizeof (uiUnderlineColor)); 183 | } 184 | 185 | void pkguiFreeUnderlineColor(uiUnderlineColor *c) 186 | { 187 | free(c); 188 | } 189 | 190 | uiFontDescriptor *pkguiNewFontDescriptor(void) 191 | { 192 | return (uiFontDescriptor *) pkguiAlloc(sizeof (uiFontDescriptor)); 193 | } 194 | 195 | void pkguiFreeFontDescriptor(uiFontDescriptor *fd) 196 | { 197 | free(fd); 198 | } 199 | 200 | uiDrawTextLayoutParams *pkguiNewDrawTextLayoutParams(void) 201 | { 202 | return (uiDrawTextLayoutParams *) pkguiAlloc(sizeof (uiDrawTextLayoutParams)); 203 | } 204 | 205 | void pkguiFreeDrawTextLayoutParams(uiDrawTextLayoutParams *p) 206 | { 207 | free(p); 208 | } 209 | 210 | uiAreaHandler *pkguiAllocAreaHandler(void) 211 | { 212 | uiAreaHandler *ah; 213 | 214 | ah = (uiAreaHandler *) pkguiAlloc(sizeof (uiAreaHandler)); 215 | ah->Draw = pkguiDoAreaHandlerDraw; 216 | ah->MouseEvent = pkguiDoAreaHandlerMouseEvent; 217 | ah->MouseCrossed = pkguiDoAreaHandlerMouseCrossed; 218 | ah->DragBroken = pkguiDoAreaHandlerDragBroken; 219 | ah->KeyEvent = pkguiDoAreaHandlerKeyEvent; 220 | return ah; 221 | } 222 | 223 | void pkguiFreeAreaHandler(uiAreaHandler *ah) 224 | { 225 | free(ah); 226 | } 227 | 228 | // cgo can't generate const, so we need this trampoline 229 | static void realDoTableModelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int column, const uiTableValue *value) 230 | { 231 | pkguiDoTableModelSetCellValue(mh, m, row, column, (uiTableValue *) value); 232 | } 233 | 234 | const uiTableModelHandler pkguiTableModelHandler = { 235 | .NumColumns = pkguiDoTableModelNumColumns, 236 | .ColumnType = pkguiDoTableModelColumnType, 237 | .NumRows = pkguiDoTableModelNumRows, 238 | .CellValue = pkguiDoTableModelCellValue, 239 | .SetCellValue = realDoTableModelSetCellValue, 240 | }; 241 | 242 | uiTableTextColumnOptionalParams *pkguiAllocTableTextColumnOptionalParams(void) 243 | { 244 | return (uiTableTextColumnOptionalParams *) pkguiAlloc(sizeof (uiTableTextColumnOptionalParams)); 245 | } 246 | 247 | void pkguiFreeTableTextColumnOptionalParams(uiTableTextColumnOptionalParams *p) 248 | { 249 | free(p); 250 | } 251 | 252 | uiTableParams *pkguiAllocTableParams(void) 253 | { 254 | return (uiTableParams *) pkguiAlloc(sizeof (uiTableParams)); 255 | } 256 | 257 | void pkguiFreeTableParams(uiTableParams *p) 258 | { 259 | free(p); 260 | } 261 | -------------------------------------------------------------------------------- /pkgui.h: -------------------------------------------------------------------------------- 1 | // 12 august 2018 2 | #ifndef pkguiHFileIncluded 3 | #define pkguiHFileIncluded 4 | 5 | #include 6 | #include 7 | #include 8 | #include "ui.h" 9 | 10 | // main.go 11 | extern uiInitOptions *pkguiAllocInitOptions(void); 12 | extern void pkguiFreeInitOptions(uiInitOptions *o); 13 | extern void pkguiQueueMain(uintptr_t n); 14 | extern void pkguiOnShouldQuit(void); 15 | 16 | // window.go 17 | extern void pkguiWindowOnClosing(uiWindow *w); 18 | 19 | // button.go 20 | extern void pkguiButtonOnClicked(uiButton *b); 21 | 22 | // checkbox.go 23 | extern void pkguiCheckboxOnToggled(uiCheckbox *c); 24 | 25 | // colorbutton.go 26 | extern void pkguiColorButtonOnChanged(uiColorButton *c); 27 | typedef struct pkguiColorDoubles pkguiColorDoubles; 28 | struct pkguiColorDoubles { 29 | double *r; 30 | double *g; 31 | double *b; 32 | double *a; 33 | }; 34 | extern pkguiColorDoubles pkguiAllocColorDoubles(void); 35 | extern void pkguiFreeColorDoubles(pkguiColorDoubles c); 36 | 37 | // combobox.go 38 | extern void pkguiComboboxOnSelected(uiCombobox *c); 39 | 40 | // datetimepicker.go 41 | extern void pkguiDateTimePickerOnChanged(uiDateTimePicker *d); 42 | extern struct tm *pkguiAllocTime(void); 43 | extern void pkguiFreeTime(struct tm *t); 44 | 45 | // editablecombobox.go 46 | extern void pkguiEditableComboboxOnChanged(uiEditableCombobox *c); 47 | 48 | // entry.go 49 | extern void pkguiEntryOnChanged(uiEntry *e); 50 | 51 | // fontbutton.go 52 | extern void pkguiFontButtonOnChanged(uiFontButton *b); 53 | 54 | // multilineentry.go 55 | extern void pkguiMultilineEntryOnChanged(uiMultilineEntry *e); 56 | 57 | // radiobuttons.go 58 | extern void pkguiRadioButtonsOnSelected(uiRadioButtons *r); 59 | 60 | // slider.go 61 | extern void pkguiSliderOnChanged(uiSlider *s); 62 | 63 | // spinbox.go 64 | extern void pkguiSpinboxOnChanged(uiSpinbox *s); 65 | 66 | // draw.go 67 | extern uiDrawBrush *pkguiAllocBrush(void); 68 | extern void pkguiFreeBrush(uiDrawBrush *b); 69 | extern uiDrawBrushGradientStop *pkguiAllocGradientStops(size_t n); 70 | extern void pkguiFreeGradientStops(uiDrawBrushGradientStop *stops); 71 | extern void pkguiSetGradientStop(uiDrawBrushGradientStop *stops, size_t i, double pos, double r, double g, double b, double a); 72 | extern uiDrawStrokeParams *pkguiAllocStrokeParams(void); 73 | extern void pkguiFreeStrokeParams(uiDrawStrokeParams *p); 74 | extern double *pkguiAllocDashes(size_t n); 75 | extern void pkguiFreeDashes(double *dashes); 76 | extern void pkguiSetDash(double *dashes, size_t i, double dash); 77 | extern uiDrawMatrix *pkguiAllocMatrix(void); 78 | extern void pkguiFreeMatrix(uiDrawMatrix *m); 79 | 80 | // drawtext.go 81 | extern uiUnderlineColor *pkguiNewUnderlineColor(void); 82 | extern void pkguiFreeUnderlineColor(uiUnderlineColor *c); 83 | extern uiFontDescriptor *pkguiNewFontDescriptor(void); 84 | extern void pkguiFreeFontDescriptor(uiFontDescriptor *fd); 85 | extern uiDrawTextLayoutParams *pkguiNewDrawTextLayoutParams(void); 86 | extern void pkguiFreeDrawTextLayoutParams(uiDrawTextLayoutParams *p); 87 | 88 | // areahandler.go 89 | extern uiAreaHandler *pkguiAllocAreaHandler(void); 90 | extern void pkguiFreeAreaHandler(uiAreaHandler *ah); 91 | 92 | // tablemodel.go 93 | extern const uiTableModelHandler pkguiTableModelHandler; 94 | 95 | // table.go 96 | extern uiTableTextColumnOptionalParams *pkguiAllocTableTextColumnOptionalParams(void); 97 | extern void pkguiFreeTableTextColumnOptionalParams(uiTableTextColumnOptionalParams *p); 98 | extern uiTableParams *pkguiAllocTableParams(void); 99 | extern void pkguiFreeTableParams(uiTableParams *p); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /progressbar.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // ProgressBar is a Control that represents a horizontal bar that 13 | // is filled in progressively over time as a process completes. 14 | type ProgressBar struct { 15 | ControlBase 16 | p *C.uiProgressBar 17 | } 18 | 19 | // NewProgressBar creates a new ProgressBar. 20 | func NewProgressBar() *ProgressBar { 21 | p := new(ProgressBar) 22 | 23 | p.p = C.uiNewProgressBar() 24 | 25 | p.ControlBase = NewControlBase(p, uintptr(unsafe.Pointer(p.p))) 26 | return p 27 | } 28 | 29 | // Value returns the value currently shown in the ProgressBar. 30 | func (p *ProgressBar) Value() int { 31 | return int(C.uiProgressBarValue(p.p)) 32 | } 33 | 34 | // SetValue sets the ProgressBar's currently displayed percentage 35 | // to value. value must be between 0 and 100 inclusive, or -1 for 36 | // an indeterminate progressbar. 37 | func (p *ProgressBar) SetValue(value int) { 38 | C.uiProgressBarSetValue(p.p, C.int(value)) 39 | } 40 | -------------------------------------------------------------------------------- /radiobuttons.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // RadioButtons is a Control that represents a set of checkable 13 | // buttons from which exactly one may be chosen by the user. 14 | type RadioButtons struct { 15 | ControlBase 16 | r *C.uiRadioButtons 17 | onSelected func(*RadioButtons) 18 | } 19 | 20 | // NewRadioButtons creates a new RadioButtons. 21 | func NewRadioButtons() *RadioButtons { 22 | r := new(RadioButtons) 23 | 24 | r.r = C.uiNewRadioButtons() 25 | 26 | C.pkguiRadioButtonsOnSelected(r.r) 27 | 28 | r.ControlBase = NewControlBase(r, uintptr(unsafe.Pointer(r.r))) 29 | return r 30 | } 31 | 32 | // Append adds the named button to the end of the RadioButtons. 33 | func (r *RadioButtons) Append(text string) { 34 | ctext := C.CString(text) 35 | C.uiRadioButtonsAppend(r.r, ctext) 36 | freestr(ctext) 37 | } 38 | 39 | // Selected returns the index of the currently selected option in the 40 | // RadioButtons, or -1 if no item is selected. 41 | func (r *RadioButtons) Selected() int { 42 | return int(C.uiRadioButtonsSelected(r.r)) 43 | } 44 | 45 | // SetSelected sets the currently selected option in the RadioButtons 46 | // to index. 47 | func (r *RadioButtons) SetSelected(index int) { 48 | C.uiRadioButtonsSetSelected(r.r, C.int(index)) 49 | } 50 | 51 | // OnSelected registers f to be run when the user selects an option in 52 | // the RadioButtons. Only one function can be registered at a time. 53 | func (r *RadioButtons) OnSelected(f func(*RadioButtons)) { 54 | r.onSelected = f 55 | } 56 | 57 | //export pkguiDoRadioButtonsOnSelected 58 | func pkguiDoRadioButtonsOnSelected(rr *C.uiRadioButtons, data unsafe.Pointer) { 59 | r := ControlFromLibui(uintptr(unsafe.Pointer(rr))).(*RadioButtons) 60 | if r.onSelected != nil { 61 | r.onSelected(r) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /separator.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Separator is a Control that represents a horizontal line that 13 | // visually separates controls. 14 | type Separator struct { 15 | ControlBase 16 | s *C.uiSeparator 17 | } 18 | 19 | // NewHorizontalSeparator creates a new horizontal Separator. 20 | func NewHorizontalSeparator() *Separator { 21 | s := new(Separator) 22 | 23 | s.s = C.uiNewHorizontalSeparator() 24 | 25 | s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s))) 26 | return s 27 | } 28 | 29 | // NewVerticalSeparator creates a new vertical Separator. 30 | func NewVerticalSeparator() *Separator { 31 | s := new(Separator) 32 | 33 | s.s = C.uiNewVerticalSeparator() 34 | 35 | s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s))) 36 | return s 37 | } 38 | -------------------------------------------------------------------------------- /slider.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Slider is a Control that represents a horizontal bar that represents 13 | // a range of integers. The user can drag a pointer on the bar to 14 | // select an integer. 15 | type Slider struct { 16 | ControlBase 17 | s *C.uiSlider 18 | onChanged func(*Slider) 19 | } 20 | 21 | // NewSlider creates a new Slider. If min >= max, they are swapped. 22 | func NewSlider(min int, max int) *Slider { 23 | s := new(Slider) 24 | 25 | s.s = C.uiNewSlider(C.int(min), C.int(max)) 26 | 27 | C.pkguiSliderOnChanged(s.s) 28 | 29 | s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s))) 30 | return s 31 | } 32 | 33 | // Value returns the Slider's current value. 34 | func (s *Slider) Value() int { 35 | return int(C.uiSliderValue(s.s)) 36 | } 37 | 38 | // SetValue sets the Slider's current value to value. 39 | func (s *Slider) SetValue(value int) { 40 | C.uiSliderSetValue(s.s, C.int(value)) 41 | } 42 | 43 | // OnChanged registers f to be run when the user changes the value 44 | // of the Slider. Only one function can be registered at a time. 45 | func (s *Slider) OnChanged(f func(*Slider)) { 46 | s.onChanged = f 47 | } 48 | 49 | //export pkguiDoSliderOnChanged 50 | func pkguiDoSliderOnChanged(ss *C.uiSlider, data unsafe.Pointer) { 51 | s := ControlFromLibui(uintptr(unsafe.Pointer(ss))).(*Slider) 52 | if s.onChanged != nil { 53 | s.onChanged(s) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spinbox.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Spinbox is a Control that represents a space where the user can 13 | // enter integers. The space also comes with buttons to add or 14 | // subtract 1 from the integer. 15 | type Spinbox struct { 16 | ControlBase 17 | s *C.uiSpinbox 18 | onChanged func(*Spinbox) 19 | } 20 | 21 | // NewSpinbox creates a new Spinbox. If min >= max, they are swapped. 22 | func NewSpinbox(min int, max int) *Spinbox { 23 | s := new(Spinbox) 24 | 25 | s.s = C.uiNewSpinbox(C.int(min), C.int(max)) 26 | 27 | C.pkguiSpinboxOnChanged(s.s) 28 | 29 | s.ControlBase = NewControlBase(s, uintptr(unsafe.Pointer(s.s))) 30 | return s 31 | } 32 | 33 | // Value returns the Spinbox's current value. 34 | func (s *Spinbox) Value() int { 35 | return int(C.uiSpinboxValue(s.s)) 36 | } 37 | 38 | // SetValue sets the Spinbox's current value to value. 39 | func (s *Spinbox) SetValue(value int) { 40 | C.uiSpinboxSetValue(s.s, C.int(value)) 41 | } 42 | 43 | // OnChanged registers f to be run when the user changes the value 44 | // of the Spinbox. Only one function can be registered at a time. 45 | func (s *Spinbox) OnChanged(f func(*Spinbox)) { 46 | s.onChanged = f 47 | } 48 | 49 | //export pkguiDoSpinboxOnChanged 50 | func pkguiDoSpinboxOnChanged(ss *C.uiSpinbox, data unsafe.Pointer) { 51 | s := ControlFromLibui(uintptr(unsafe.Pointer(ss))).(*Spinbox) 52 | if s.onChanged != nil { 53 | s.onChanged(s) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stddialogs.go: -------------------------------------------------------------------------------- 1 | // 20 december 2015 2 | 3 | package ui 4 | 5 | // #include "pkgui.h" 6 | import "C" 7 | 8 | // TODO 9 | func MsgBoxError(w *Window, title string, description string) { 10 | ctitle := C.CString(title) 11 | defer freestr(ctitle) 12 | cdescription := C.CString(description) 13 | defer freestr(cdescription) 14 | C.uiMsgBoxError(w.w, ctitle, cdescription) 15 | } 16 | 17 | func OpenFile(w *Window) string { 18 | cname := C.uiOpenFile(w.w) 19 | if cname == nil { 20 | return "" 21 | } 22 | defer C.uiFreeText(cname) 23 | return C.GoString(cname) 24 | } 25 | 26 | func SaveFile(w *Window) string { 27 | cname := C.uiSaveFile(w.w) 28 | if cname == nil { 29 | return "" 30 | } 31 | defer C.uiFreeText(cname) 32 | return C.GoString(cname) 33 | } 34 | 35 | func MsgBox(w *Window, title string, description string) { 36 | ctitle := C.CString(title) 37 | defer freestr(ctitle) 38 | cdescription := C.CString(description) 39 | defer freestr(cdescription) 40 | C.uiMsgBox(w.w, ctitle, cdescription) 41 | } 42 | -------------------------------------------------------------------------------- /tab.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Tab is a Control that holds tabbed pages of Controls. Each tab 13 | // has a label. The user can click on the tabs themselves to switch 14 | // pages. Individual pages can also have margins. 15 | type Tab struct { 16 | ControlBase 17 | t *C.uiTab 18 | children []Control 19 | } 20 | 21 | // NewTab creates a new Tab. 22 | func NewTab() *Tab { 23 | t := new(Tab) 24 | 25 | t.t = C.uiNewTab() 26 | 27 | t.ControlBase = NewControlBase(t, uintptr(unsafe.Pointer(t.t))) 28 | return t 29 | } 30 | 31 | // Destroy destroys the Tab. If the Tab has pages, 32 | // Destroy calls Destroy on the pages's Controls as well. 33 | func (t *Tab) Destroy() { 34 | for len(t.children) != 0 { 35 | c := t.children[0] 36 | t.Delete(0) 37 | c.Destroy() 38 | } 39 | t.ControlBase.Destroy() 40 | } 41 | 42 | // Append adds the given page to the end of the Tab. 43 | func (t *Tab) Append(name string, child Control) { 44 | t.InsertAt(name, len(t.children), child) 45 | } 46 | 47 | // InsertAt adds the given page to the Tab such that it is the 48 | // nth page of the Tab (starting at 0). 49 | func (t *Tab) InsertAt(name string, n int, child Control) { 50 | c := (*C.uiControl)(nil) 51 | if child != nil { 52 | c = touiControl(child.LibuiControl()) 53 | } 54 | cname := C.CString(name) 55 | C.uiTabInsertAt(t.t, cname, C.int(n), c) 56 | freestr(cname) 57 | ch := make([]Control, len(t.children) + 1) 58 | // and insert into t.children at the right place 59 | copy(ch[:n], t.children[:n]) 60 | ch[n] = child 61 | copy(ch[n + 1:], t.children[n:]) 62 | t.children = ch 63 | } 64 | 65 | // Delete deletes the nth page of the Tab. 66 | func (t *Tab) Delete(n int) { 67 | t.children = append(t.children[:n], t.children[n + 1:]...) 68 | C.uiTabDelete(t.t, C.int(n)) 69 | } 70 | 71 | // NumPages returns the number of pages in the Tab. 72 | func (t *Tab) NumPages() int { 73 | return len(t.children) 74 | } 75 | 76 | // Margined returns whether page n (starting at 0) of the Tab 77 | // has margins around its child. 78 | func (t *Tab) Margined(n int) bool { 79 | return tobool(C.uiTabMargined(t.t, C.int(n))) 80 | } 81 | 82 | // SetMargined controls whether page n (starting at 0) of the Tab 83 | // has margins around its child. The size of the margins are 84 | // determined by the OS and its best practices. 85 | func (t *Tab) SetMargined(n int, margined bool) { 86 | C.uiTabSetMargined(t.t, C.int(n), frombool(margined)) 87 | } 88 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | // 26 august 2018 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // TableModelColumnNeverEditable and 13 | // TableModelColumnAlwaysEditable are the value of an editable 14 | // model column parameter to one of the Table create column 15 | // functions; if used, that jparticular Table colum is not editable 16 | // by the user and always editable by the user, respectively. 17 | const ( 18 | TableModelColumnNeverEditable = -1 19 | TableModelColumnAlwaysEditable = -2 20 | ) 21 | 22 | // TableTextColumnOptionalParams are the optional parameters 23 | // that control the appearance of the text column of a Table. 24 | type TableTextColumnOptionalParams struct { 25 | // ColorModelColumn is the model column containing the 26 | // text color of this Table column's text, or -1 to use the 27 | // default color. 28 | // 29 | // If CellValue for this column for any cell returns nil, that 30 | // cell will also use the default text color. 31 | ColorModelColumn int 32 | } 33 | 34 | func (p *TableTextColumnOptionalParams) toLibui() *C.uiTableTextColumnOptionalParams { 35 | if p == nil { 36 | return nil 37 | } 38 | cp := C.pkguiAllocTableTextColumnOptionalParams() 39 | cp.ColorModelColumn = C.int(p.ColorModelColumn) 40 | return cp 41 | } 42 | 43 | // TableParams defines the parameters passed to NewTable. 44 | type TableParams struct { 45 | // Model is the TableModel to use for this uiTable. 46 | // This parameter cannot be nil. 47 | Model *TableModel 48 | 49 | // RowBackgroundColorModelColumn is a model column 50 | // number that defines the background color used for the 51 | // entire row in the Table, or -1 to use the default color for 52 | // all rows. 53 | // 54 | // If CellValue for this column for any row returns NULL, that 55 | // row will also use the default background color. 56 | RowBackgroundColorModelColumn int 57 | } 58 | 59 | func (p *TableParams) toLibui() *C.uiTableParams { 60 | cp := C.pkguiAllocTableParams() 61 | cp.Model = p.Model.m 62 | cp.RowBackgroundColorModelColumn = C.int(p.RowBackgroundColorModelColumn) 63 | return cp 64 | } 65 | 66 | // Table is a Control that shows tabular data, allowing users to 67 | // manipulate rows of such data at a time. 68 | type Table struct { 69 | ControlBase 70 | t *C.uiTable 71 | } 72 | 73 | // NewTable creates a new Table with the specified parameters. 74 | func NewTable(p *TableParams) *Table { 75 | t := new(Table) 76 | 77 | cp := p.toLibui() 78 | t.t = C.uiNewTable(cp) 79 | C.pkguiFreeTableParams(cp) 80 | 81 | t.ControlBase = NewControlBase(t, uintptr(unsafe.Pointer(t.t))) 82 | return t 83 | } 84 | 85 | // AppendTextColumn appends a text column to t. name is 86 | // displayed in the table header. textModelColumn is where the text 87 | // comes from. If a row is editable according to 88 | // textEditableModelColumn, SetCellValue is called with 89 | // textModelColumn as the column. 90 | func (t *Table) AppendTextColumn(name string, textModelColumn int, textEditableModelColumn int, textParams *TableTextColumnOptionalParams) { 91 | cname := C.CString(name) 92 | defer freestr(cname) 93 | cp := textParams.toLibui() 94 | defer C.pkguiFreeTableTextColumnOptionalParams(cp) 95 | C.uiTableAppendTextColumn(t.t, cname, C.int(textModelColumn), C.int(textEditableModelColumn), cp) 96 | } 97 | 98 | // AppendImageColumn appends an image column to t. 99 | // Images are drawn at icon size, appropriate to the pixel density 100 | // of the screen showing the Table. 101 | func (t *Table) AppendImageColumn(name string, imageModelColumn int) { 102 | cname := C.CString(name) 103 | defer freestr(cname) 104 | C.uiTableAppendImageColumn(t.t, cname, C.int(imageModelColumn)) 105 | } 106 | 107 | // AppendImageTextColumn appends a column to t that 108 | // shows both an image and text. 109 | func (t *Table) AppendImageTextColumn(name string, imageModelColumn int, textModelColumn int, textEditableModelColumn int, textParams *TableTextColumnOptionalParams) { 110 | cname := C.CString(name) 111 | defer freestr(cname) 112 | cp := textParams.toLibui() 113 | defer C.pkguiFreeTableTextColumnOptionalParams(cp) 114 | C.uiTableAppendImageTextColumn(t.t, cname, C.int(imageModelColumn), C.int(textModelColumn), C.int(textEditableModelColumn), cp) 115 | } 116 | 117 | // AppendCheckboxColumn appends a column to t that 118 | // contains a checkbox that the user can interact with (assuming the 119 | // checkbox is editable). SetCellValue will be called with 120 | // checkboxModelColumn as the column in this case. 121 | func (t *Table) AppendCheckboxColumn(name string, checkboxModelColumn int, checkboxEditableModelColumn int) { 122 | cname := C.CString(name) 123 | defer freestr(cname) 124 | C.uiTableAppendCheckboxColumn(t.t, cname, C.int(checkboxModelColumn), C.int(checkboxEditableModelColumn)) 125 | } 126 | 127 | // AppendCheckboxTextColumn appends a column to t 128 | // that contains both a checkbox and text. 129 | func (t *Table) AppendCheckboxTextColumn(name string, checkboxModelColumn int, checkboxEditableModelColumn int, textModelColumn int, textEditableModelColumn int, textParams *TableTextColumnOptionalParams) { 130 | cname := C.CString(name) 131 | defer freestr(cname) 132 | cp := textParams.toLibui() 133 | defer C.pkguiFreeTableTextColumnOptionalParams(cp) 134 | C.uiTableAppendCheckboxTextColumn(t.t, cname, C.int(checkboxModelColumn), C.int(checkboxEditableModelColumn), C.int(textModelColumn), C.int(textEditableModelColumn), cp) 135 | } 136 | 137 | // AppendProgressBarColumn appends a column to t 138 | // that displays a progress bar. These columns work like 139 | // ProgressBar: a cell value of 0..100 displays that percentage, and 140 | // a cell value of -1 displays an indeterminate progress bar. 141 | func (t *Table) AppendProgressBarColumn(name string, progressModelColumn int) { 142 | cname := C.CString(name) 143 | defer freestr(cname) 144 | C.uiTableAppendProgressBarColumn(t.t, cname, C.int(progressModelColumn)) 145 | } 146 | 147 | // AppendButtonColumn appends a column to t 148 | // that shows a button that the user can click on. When the user 149 | // does click on the button, SetCellValue is called with a nil 150 | // value and buttonModelColumn as the column. 151 | // CellValue on buttonModelColumn should return the text to show 152 | // in the button. 153 | func (t *Table) AppendButtonColumn(name string, buttonModelColumn int, buttonClickableModelColumn int) { 154 | cname := C.CString(name) 155 | defer freestr(cname) 156 | C.uiTableAppendButtonColumn(t.t, cname, C.int(buttonModelColumn), C.int(buttonClickableModelColumn)) 157 | } 158 | -------------------------------------------------------------------------------- /tablemodel.go: -------------------------------------------------------------------------------- 1 | // 24 august 2018 2 | 3 | package ui 4 | 5 | // #include "pkgui.h" 6 | import "C" 7 | 8 | // TableValue is a type that represents a piece of data that can come 9 | // out of a TableModel. 10 | type TableValue interface { 11 | toLibui() *C.uiTableValue 12 | } 13 | 14 | // TableString is a TableValue that stores a string. TableString is 15 | // used for displaying text in a Table. 16 | type TableString string 17 | 18 | func (s TableString) toLibui() *C.uiTableValue { 19 | cs := C.CString(string(s)) 20 | defer freestr(cs) 21 | return C.uiNewTableValueString(cs) 22 | } 23 | 24 | // TableImage is a TableValue that represents an Image. Ownership 25 | // of the Image is not copied; you must keep it alive alongside the 26 | // TableImage. 27 | type TableImage struct { 28 | I *Image 29 | } 30 | 31 | func (i TableImage) toLibui() *C.uiTableValue { 32 | return C.uiNewTableValueImage(i.I.i) 33 | } 34 | 35 | // TableInt is a TableValue that stores integers. These are used for 36 | // progressbars. Due to current limitations of libui, they also 37 | // represent checkbox states, via TableFalse and TableTrue. 38 | type TableInt int 39 | 40 | // TableFalse and TableTrue are the Boolean constants for TableInt. 41 | const ( 42 | TableFalse TableInt = 0 43 | TableTrue TableInt = 1 44 | ) 45 | 46 | func (i TableInt) toLibui() *C.uiTableValue { 47 | return C.uiNewTableValueInt(C.int(i)) 48 | } 49 | 50 | // TableColor is a TableValue that represents a color. 51 | type TableColor struct { 52 | R float64 53 | G float64 54 | B float64 55 | A float64 56 | } 57 | 58 | func (c TableColor) toLibui() *C.uiTableValue { 59 | return C.uiNewTableValueColor(C.double(c.R), C.double(c.G), C.double(c.B), C.double(c.A)) 60 | } 61 | 62 | func tableValueFromLibui(value *C.uiTableValue) TableValue { 63 | if value == nil { 64 | return nil 65 | } 66 | switch C.uiTableValueGetType(value) { 67 | case C.uiTableValueTypeString: 68 | cs := C.uiTableValueString(value) 69 | return TableString(C.GoString(cs)) 70 | case C.uiTableValueTypeImage: 71 | panic("TODO") 72 | case C.uiTableValueTypeInt: 73 | return TableInt(C.uiTableValueInt(value)) 74 | case C.uiTableValueTypeColor: 75 | panic("TODO") 76 | } 77 | panic("unreachable") 78 | } 79 | 80 | // no need to lock these; only the GUI thread can access them 81 | var modelhandlers = make(map[*C.uiTableModel]TableModelHandler) 82 | var models = make(map[*C.uiTableModel]*TableModel) 83 | 84 | // TableModel is an object that provides the data for a Table. 85 | // This data is returned via methods you provide in the 86 | // TableModelHandler interface. 87 | // 88 | // TableModel represents data using a table, but this table does 89 | // not map directly to Table itself. Instead, you can have data 90 | // columns which provide instructions for how to render a given 91 | // Table's column — for instance, one model column can be used 92 | // to give certain rows of a Table a different background color. 93 | // Row numbers DO match with uiTable row numbers. 94 | // 95 | // Once created, the number and data types of columns of a 96 | // TableModel cannot change. 97 | // 98 | // Row and column numbers start at 0. A TableModel can be 99 | // associated with more than one Table at a time. 100 | type TableModel struct { 101 | m *C.uiTableModel 102 | } 103 | 104 | // TableModelHandler defines the methods that TableModel 105 | // calls when it needs data. 106 | type TableModelHandler interface { 107 | // ColumnTypes returns a slice of value types of the data 108 | // stored in the model columns of the TableModel. 109 | // Each entry in the slice should ideally be a zero value for 110 | // the TableValue type of the column in question; the number 111 | // of elements in the slice determines the number of model 112 | // columns in the TableModel. The returned slice must remain 113 | // constant through the lifetime of the TableModel. This 114 | // method is not guaranteed to be called depending on the 115 | // system. 116 | ColumnTypes(m *TableModel) []TableValue 117 | 118 | // NumRows returns the number or rows in the TableModel. 119 | // This value must be non-negative. 120 | NumRows(m *TableModel) int 121 | 122 | // CellValue returns a TableValue corresponding to the model 123 | // cell at (row, column). The type of the returned TableValue 124 | // must match column's value type. Under some circumstances, 125 | // nil may be returned; refer to the various methods that add 126 | // columns to Table for details. 127 | CellValue(m *TableModel, row, column int) TableValue 128 | 129 | // SetCellValue changes the model cell value at (row, column) 130 | // in the TableModel. Within this function, either do nothing 131 | // to keep the current cell value or save the new cell value as 132 | // appropriate. After SetCellValue is called, the Table will 133 | // itself reload the table cell. Under certain conditions, the 134 | // TableValue passed in can be nil; refer to the various 135 | // methods that add columns to Table for details. 136 | SetCellValue(m *TableModel, row, column int, value TableValue) 137 | } 138 | 139 | //export pkguiDoTableModelNumColumns 140 | func pkguiDoTableModelNumColumns(umh *C.uiTableModelHandler, um *C.uiTableModel) C.int { 141 | mh := modelhandlers[um] 142 | return C.int(len(mh.ColumnTypes(models[um]))) 143 | } 144 | 145 | //export pkguiDoTableModelColumnType 146 | func pkguiDoTableModelColumnType(umh *C.uiTableModelHandler, um *C.uiTableModel, n C.int) C.uiTableValueType { 147 | mh := modelhandlers[um] 148 | c := mh.ColumnTypes(models[um]) 149 | switch c[n].(type) { 150 | case TableString: 151 | return C.uiTableValueTypeString 152 | case TableImage: 153 | return C.uiTableValueTypeImage 154 | case TableInt: 155 | return C.uiTableValueTypeInt 156 | case TableColor: 157 | return C.uiTableValueTypeColor 158 | } 159 | panic("unreachable") 160 | } 161 | 162 | //export pkguiDoTableModelNumRows 163 | func pkguiDoTableModelNumRows(umh *C.uiTableModelHandler, um *C.uiTableModel) C.int { 164 | mh := modelhandlers[um] 165 | return C.int(mh.NumRows(models[um])) 166 | } 167 | 168 | //export pkguiDoTableModelCellValue 169 | func pkguiDoTableModelCellValue(umh *C.uiTableModelHandler, um *C.uiTableModel, row, column C.int) *C.uiTableValue { 170 | mh := modelhandlers[um] 171 | v := mh.CellValue(models[um], int(row), int(column)) 172 | if v == nil { 173 | return nil 174 | } 175 | return v.toLibui() 176 | } 177 | 178 | //export pkguiDoTableModelSetCellValue 179 | func pkguiDoTableModelSetCellValue(umh *C.uiTableModelHandler, um *C.uiTableModel, row, column C.int, value *C.uiTableValue) { 180 | mh := modelhandlers[um] 181 | v := tableValueFromLibui(value) 182 | mh.SetCellValue(models[um], int(row), int(column), v) 183 | } 184 | 185 | // NewTableModel creates a new TableModel. 186 | func NewTableModel(handler TableModelHandler) *TableModel { 187 | m := &TableModel{ 188 | m: C.uiNewTableModel(&C.pkguiTableModelHandler), 189 | } 190 | modelhandlers[m.m] = handler 191 | models[m.m] = m 192 | return m 193 | } 194 | 195 | // Free frees m. It is an error to Free any models associated with a 196 | // Table. 197 | func (m *TableModel) Free() { 198 | delete(models, m.m) 199 | delete(modelhandlers, m.m) 200 | C.uiFreeTableModel(m.m) 201 | } 202 | 203 | // RowInserted tells any Tables associated with m that a new row 204 | // has been added to m at index index. You call this method when 205 | // the number of rows in your model has changed; after calling it, 206 | // NumRows should returm the new row count. 207 | func (m *TableModel) RowInserted(index int) { 208 | C.uiTableModelRowInserted(m.m, C.int(index)) 209 | } 210 | 211 | // RowChanged tells any Tables associated with m that the data in 212 | // the row at index has changed. You do not need to call this in 213 | // your SetCellValue handlers, but you do need to call this if your 214 | // data changes at some other point. 215 | func (m *TableModel) RowChanged(index int) { 216 | C.uiTableModelRowChanged(m.m, C.int(index)) 217 | } 218 | 219 | // RowDeleted tells any Tables associated with m that the row at 220 | // index index has been deleted. You call this function when the 221 | // number of rows in your model has changed; after calling it, 222 | // NumRows should returm the new row count. 223 | func (m *TableModel) RowDeleted(index int) { 224 | C.uiTableModelRowDeleted(m.m, C.int(index)) 225 | } 226 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | //export pkguiAlloc 13 | func pkguiAlloc(n C.size_t) unsafe.Pointer { 14 | // cgo turns C.malloc() into a panic-on-OOM version; use it 15 | ret := C.malloc(n) 16 | // and this won't zero-initialize; do it ourselves 17 | C.memset(ret, 0, n) 18 | return ret 19 | } 20 | 21 | func freestr(str *C.char) { 22 | C.free(unsafe.Pointer(str)) 23 | } 24 | 25 | func tobool(b C.int) bool { 26 | return b != 0 27 | } 28 | 29 | func frombool(b bool) C.int { 30 | if b { 31 | return 1 32 | } 33 | return 0 34 | } 35 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | // 12 december 2015 2 | 3 | package ui 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // #include "pkgui.h" 10 | import "C" 11 | 12 | // Window is a Control that represents a top-level window. 13 | // A Window contains one child Control that occupies the 14 | // entirety of the window. Though a Window is a Control, 15 | // a Window cannot be the child of another Control. 16 | type Window struct { 17 | ControlBase 18 | w *C.uiWindow 19 | child Control 20 | onClosing func(w *Window) bool 21 | } 22 | 23 | // NewWindow creates a new Window. 24 | func NewWindow(title string, width int, height int, hasMenubar bool) *Window { 25 | w := new(Window) 26 | 27 | ctitle := C.CString(title) 28 | w.w = C.uiNewWindow(ctitle, C.int(width), C.int(height), frombool(hasMenubar)) 29 | freestr(ctitle) 30 | 31 | C.pkguiWindowOnClosing(w.w) 32 | 33 | w.ControlBase = NewControlBase(w, uintptr(unsafe.Pointer(w.w))) 34 | return w 35 | } 36 | 37 | // Destroy destroys the Window. If the Window has a child, 38 | // Destroy calls Destroy on that as well. 39 | func (w *Window) Destroy() { 40 | w.Hide() // first hide the window, in case anything in the below if statement forces an immediate redraw 41 | if w.child != nil { 42 | c := w.child 43 | w.SetChild(nil) 44 | c.Destroy() 45 | } 46 | w.ControlBase.Destroy() 47 | } 48 | 49 | // Title returns the Window's title. 50 | func (w *Window) Title() string { 51 | ctitle := C.uiWindowTitle(w.w) 52 | title := C.GoString(ctitle) 53 | C.uiFreeText(ctitle) 54 | return title 55 | } 56 | 57 | // SetTitle sets the Window's title to title. 58 | func (w *Window) SetTitle(title string) { 59 | ctitle := C.CString(title) 60 | C.uiWindowSetTitle(w.w, ctitle) 61 | freestr(ctitle) 62 | } 63 | 64 | // TODO ContentSize 65 | // TODO SetContentSize 66 | // TODO Fullscreen 67 | // TODO SetFullscreen 68 | // TODO OnContentSizeChanged 69 | 70 | // OnClosing registers f to be run when the user clicks the Window's 71 | // close button. Only one function can be registered at a time. 72 | // If f returns true, the window is destroyed with the Destroy method. 73 | // If f returns false, or if OnClosing is never called, the window is not 74 | // destroyed and is kept visible. 75 | func (w *Window) OnClosing(f func(*Window) bool) { 76 | w.onClosing = f 77 | } 78 | 79 | //export pkguiDoWindowOnClosing 80 | func pkguiDoWindowOnClosing(ww *C.uiWindow, data unsafe.Pointer) C.int { 81 | w := ControlFromLibui(uintptr(unsafe.Pointer(ww))).(*Window) 82 | if w.onClosing == nil { 83 | return 0 84 | } 85 | if w.onClosing(w) { 86 | w.Destroy() 87 | } 88 | return 0 89 | } 90 | 91 | // Borderless returns whether the Window is borderless. 92 | func (w *Window) Borderless() bool { 93 | return tobool(C.uiWindowBorderless(w.w)) 94 | } 95 | 96 | // SetBorderless sets the Window to be borderless or not. 97 | func (w *Window) SetBorderless(borderless bool) { 98 | C.uiWindowSetBorderless(w.w, frombool(borderless)) 99 | } 100 | 101 | // SetChild sets the Window's child to child. If child is nil, the Window 102 | // will not have a child. 103 | func (w *Window) SetChild(child Control) { 104 | w.child = child 105 | c := (*C.uiControl)(nil) 106 | if w.child != nil { 107 | c = touiControl(w.child.LibuiControl()) 108 | } 109 | C.uiWindowSetChild(w.w, c) 110 | } 111 | 112 | // Margined returns whether the Window has margins around its child. 113 | func (w *Window) Margined() bool { 114 | return tobool(C.uiWindowMargined(w.w)) 115 | } 116 | 117 | // SetMargined controls whether the Window has margins around its 118 | // child. The size of the margins are determined by the OS and its 119 | // best practices. 120 | func (w *Window) SetMargined(margined bool) { 121 | C.uiWindowSetMargined(w.w, frombool(margined)) 122 | } 123 | -------------------------------------------------------------------------------- /winmanifest/doc.go: -------------------------------------------------------------------------------- 1 | // 2 september 2018 2 | 3 | // Package winmanifest provides a basic manifest for use with 4 | // package ui. You import it for its side effects only, as 5 | // 6 | // import _ "github.com/andlabs/ui/winmanifest" 7 | // 8 | // On non-Windows platforms this package does nothing. 9 | // 10 | // If you intend on using a custom manifest instead of the generic 11 | // one in this package, be sure to read package ui's README so your 12 | // manifest can have the directives necessary for package ui to work. 13 | package winmanifest 14 | -------------------------------------------------------------------------------- /winmanifest/resources.rc: -------------------------------------------------------------------------------- 1 | // 30 may 2015 2 | 3 | // this is a UTF-8 file 4 | #pragma code_page(65001) 5 | 6 | // this is the Common Controls 6 manifest 7 | // TODO set up the string values here 8 | // 1 is the value of CREATEPROCESS_MANIFEST_RESOURCE_ID and 24 is the value of RT_MANIFEST; we use it directly to avoid needing to share winapi.h with the tests and examples 9 | 1 24 "ui.manifest" 10 | -------------------------------------------------------------------------------- /winmanifest/ui.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | Your application description here. 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /winmanifest/winmanifest_windows_386.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/winmanifest/winmanifest_windows_386.syso -------------------------------------------------------------------------------- /winmanifest/winmanifest_windows_amd64.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andlabs/ui/70a69d6ae31ed9d8bb0619a191179c63d846ab8e/winmanifest/winmanifest_windows_amd64.syso --------------------------------------------------------------------------------