├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cmdlist.go ├── embeddedfonts └── oswaldfont.go ├── eweygewey.go ├── examples ├── assets │ ├── HammersmithOne SIL OFL Font License.txt │ ├── HammersmithOne.ttf │ ├── Oswald-Heavy.ttf │ ├── Oswald-Regular.ttf │ ├── SIL Open Font License.txt │ ├── potions-credits.txt │ ├── potions-license.htm │ └── potions.png ├── basicGLFW │ └── main.go └── screenshots │ └── basic_ss_0.jpg ├── font.go ├── glfwinput └── glfwinput.go ├── keys.go ├── manager.go └── window.go /.gitignore: -------------------------------------------------------------------------------- 1 | examples/basicGLFW/*.exe 2 | examples/basicGLFW/basicGLFW 3 | examples/basicGLFW/debug 4 | *.exe 5 | *.swp 6 | *.dll 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version v0.3.2 2 | ============== 3 | 4 | * NEW: embeddedfonts package that embeds the Oswald-Heavy font so that the client 5 | executables can be installed with `go install` and then run without having to locate 6 | the font file. This was generate with `go-bindata`. 7 | 8 | * New: Added Manager.NewFontBytes() to load a font by byte slice so that 9 | clients can load embedded fonts. 10 | 11 | Version v0.3.1 12 | ============== 13 | 14 | * BUG: Fixed issue #3 where the VBO data was getting corrupted by attempting 15 | to add zero faces. 16 | 17 | Version v0.3.0 18 | ============== 19 | 20 | * MISC: Changes required for v0.3.0 of github.com/tbogdala/fizzle inclusing using 21 | the new Material type and the new built-in shaders. 22 | 23 | Version v0.2.0 24 | ============== 25 | 26 | * BUG: Fixed Manager.RemoveWindow() bug with indexing a slice incorrectly. 27 | * BUG: Fixed editboxes with too long of text overflowing the widget. 28 | 29 | * NEW: Manager.GetWindowsByFilter() to get UI Windows using a function 30 | to filter the list. 31 | 32 | * NEW: Font.CreateTextAdv() for advanced control of text creation -- useful for 33 | the editbox widget -- to create text of a maximum width starting at a custom 34 | spot in the string. 35 | 36 | * NEW: Font.OffsetForIndexAdv() for advance control while getting the offset 37 | in pixels for a location in a string based on a custom starting spot in the string. 38 | 39 | * MISC: Added an editbox with too long of a string to display at once in 40 | the main example application. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Timothy Bogdala 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EweyGewey v0.3.2 2 | ================ 3 | 4 | EweyGewey is an OpenGL immediate-mode GUI library written in the [Go][golang] programming 5 | language that is designed to be integrated easily into an OpenGL application. 6 | 7 | The design of the library is heavily inspired by [imgui][imgui]. 8 | 9 | UNDER CONSTRUCTION 10 | ================== 11 | 12 | At present, it is very much in an alpha stage with new development adding in 13 | features, widgets and possibly API breaks. Any API break should increment the 14 | minor version number and any patch release tags should remain compatible even 15 | in development 0.x versions. 16 | 17 | Screenshots 18 | ----------- 19 | 20 | Here's some of what's available right now in the [basic example][basic_example]: 21 | 22 | ![basic_ss][basic_ss] 23 | 24 | 25 | Requirements 26 | ------------ 27 | 28 | * [Mathgl][mgl] - for 3d math 29 | * [Freetype][ftgo] - for dynamic font texture generation 30 | * [Fizzle][fizzle] - provides an OpenGL 3/es2/es3 abstraction 31 | * [GLFW][glfw-go] (v3.1) - currently GLFW is the only 'host' support for input 32 | 33 | Additionally, a backend graphics provider needs to be used. At present, fizzle 34 | supports the following: 35 | 36 | * [Go GL][go-gl] - pre-generated OpenGL bindings using their glow project 37 | * [opengles2][opengles2] - Go bindings to the OpenGL ES 2.0 library 38 | 39 | These are included when the `graphicsprovider` subpackage is used and direct 40 | importing is not required. 41 | 42 | Installation 43 | ------------ 44 | 45 | The dependency Go libraries can be installed with the following commands. 46 | 47 | ```bash 48 | go get github.com/go-gl/glfw/v3.1/glfw 49 | go get github.com/go-gl/mathgl/mgl32 50 | go get github.com/golang/freetype 51 | go get github.com/tbogdala/fizzle 52 | ``` 53 | 54 | An OpenGL library will also be required for desktop applications; install 55 | the OpenGL 3.3 library with the following command: 56 | 57 | ```bash 58 | go get github.com/go-gl/gl/v3.3-core/gl 59 | ``` 60 | 61 | If you're compiling for Android/iOS, then you will need an OpenGL ES library, 62 | and that can be installed with the following command instead: 63 | 64 | ```bash 65 | go get github.com/remogatto/opengles2 66 | ``` 67 | 68 | This does assume that you have the native GLFW 3.1 library installed already 69 | accessible to Go tools. This should be the only native library needed. 70 | 71 | Current Features 72 | ---------------- 73 | 74 | * Basic windowing system 75 | * Basic theming support 76 | * Basic input support that detects mouse clicks and double-clicks 77 | * Basic scaling for larger resolutions 78 | * Widgets: 79 | * Text 80 | * Buttons 81 | * Sliders for integers and floats with ranges and without 82 | * Scroll bars 83 | * Images 84 | * Editbox 85 | * Checkbox 86 | * Separator 87 | * Custom drawn 3d widgets 88 | 89 | TODO 90 | ---- 91 | 92 | The following need to be addressed in order to start releases: 93 | 94 | * more widgets: 95 | * text wrapping 96 | * multi-line text editors 97 | * combobox 98 | * image buttons 99 | * detailed theming (e.g. custom drawing of slider cursor) 100 | * texture atlas creation 101 | * z-ordering for windows 102 | * scroll bars don't scroll on mouse drag 103 | * editbox cursor doesn't start where mouse was clicked 104 | * text overflow on editboxes isn't handled well 105 | * better OpenGL flag management 106 | * documentation 107 | * samples 108 | 109 | 110 | LICENSE 111 | ======= 112 | 113 | EweyGewey is released under the BSD license. See the [LICENSE][license-link] file for more details. 114 | 115 | Fonts in the `examples/assets` directory are licensed under the [SIL OFL][sil_ofl] open font license. 116 | 117 | [golang]: https://golang.org/ 118 | [fizzle]: https://github.com/tbogdala/fizzle 119 | [glfw-go]: https://github.com/go-gl/glfw 120 | [mgl]: https://github.com/go-gl/mathgl 121 | [ftgo]: https://github.com/golang/freetype 122 | [go-gl]: https://github.com/go-gl/glow 123 | [opengles2]: https://github.com/remogatto/opengles2 124 | [imgui]: https://github.com/ocornut/imgui 125 | [sil_ofl]: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 126 | [license-link]: https://raw.githubusercontent.com/tbogdala/eweygewey/master/LICENSE 127 | [basic_ss]: https://github.com/tbogdala/eweygewey/blob/master/examples/screenshots/basic_ss_0.jpg 128 | [basic_example]: https://github.com/tbogdala/eweygewey/blob/master/examples/basicGLFW/main.go 129 | -------------------------------------------------------------------------------- /cmdlist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | import ( 7 | mgl "github.com/go-gl/mathgl/mgl32" 8 | graphics "github.com/tbogdala/fizzle/graphicsprovider" 9 | ) 10 | 11 | // cmdList will hold all of the information required for one draw call in the 12 | // user interface. 13 | type cmdList struct { 14 | comboBuffer []float32 // vbo combo floats 15 | indexBuffer []uint32 // vbo elements 16 | faceCount uint32 // face count 17 | indexTracker uint32 // the offset for the next set of indexes when adding new faces 18 | clipRect mgl.Vec4 // clip rect [x1,y1,x2,y2] top-left to bottom-right 19 | textureID graphics.Texture // texture to bind 20 | 21 | isCustom bool // is this a custom render command? 22 | onCustomDraw func() // called during Manager.Draw() 23 | } 24 | 25 | // NewCmdList creates a new command list for rendering. 26 | func newCmdList() *cmdList { 27 | const defaultBufferSize = 1024 28 | cmds := new(cmdList) 29 | cmds.comboBuffer = make([]float32, 0, defaultBufferSize) 30 | cmds.indexBuffer = make([]uint32, 0, defaultBufferSize) 31 | return cmds 32 | } 33 | 34 | // AddFaces takes the raw vertex attribute data in a float slice as well as the 35 | // element indexes and adds it to the internal buffers for rendering. 36 | func (cmds *cmdList) AddFaces(comboFloats []float32, indexInts []uint32, faceCount uint32) { 37 | // sanity check the input 38 | if faceCount < 1 || len(comboFloats) == 0 || len(indexInts) == 0 { 39 | return 40 | } 41 | 42 | cmds.comboBuffer = append(cmds.comboBuffer, comboFloats...) 43 | 44 | // manually adjust each index so that they don't collide with 45 | // existing element indexes 46 | var highestIndex uint32 47 | startIndex := cmds.indexTracker 48 | for _, idx := range indexInts { 49 | if idx > highestIndex { 50 | highestIndex = idx 51 | } 52 | cmds.indexBuffer = append(cmds.indexBuffer, startIndex+idx) 53 | } 54 | 55 | cmds.faceCount += faceCount 56 | cmds.indexTracker += highestIndex + 1 57 | } 58 | 59 | // PrefixFaces takes the raw vertex attribute data in a float slice as well as the 60 | // element indexes and adds it to the internal buffers for rendering at the begining. 61 | func (cmds *cmdList) PrefixFaces(comboFloats []float32, indexInts []uint32, faceCount uint32) { 62 | cmds.comboBuffer = append(cmds.comboBuffer, comboFloats...) 63 | 64 | // manually adjust each index so that they don't collide with 65 | // existing element indexes 66 | var temp []uint32 67 | var highestIndex uint32 68 | startIndex := cmds.indexTracker 69 | for _, idx := range indexInts { 70 | if idx > highestIndex { 71 | highestIndex = idx 72 | } 73 | temp = append(temp, startIndex+idx) 74 | } 75 | cmds.indexBuffer = append(temp, cmds.indexBuffer...) 76 | 77 | cmds.faceCount += faceCount 78 | cmds.indexTracker += highestIndex + 1 79 | } 80 | 81 | // DrawRectFilledDC draws a rectangle in the user interface using a solid background. 82 | // Coordinate parameters should be passed in display coordinates. 83 | // Returns the combo vertex data, element indexes and face count for the rect. 84 | func (cmds *cmdList) DrawRectFilledDC(tlx, tly, brx, bry float32, color mgl.Vec4, textureIndex uint32, whitePixelUv mgl.Vec4) ([]float32, []uint32, uint32) { 85 | uv := whitePixelUv 86 | 87 | verts := [8]float32{ 88 | tlx, bry, 89 | brx, bry, 90 | tlx, tly, 91 | brx, tly, 92 | } 93 | indexes := [6]uint32{ 94 | 0, 1, 2, 95 | 1, 3, 2, 96 | } 97 | 98 | uvs := [8]float32{ 99 | uv[0], uv[1], 100 | uv[2], uv[1], 101 | uv[0], uv[3], 102 | uv[2], uv[3], 103 | } 104 | 105 | comboBuffer := []float32{} 106 | indexBuffer := []uint32{} 107 | 108 | // add the four vertices 109 | for i := 0; i < 4; i++ { 110 | // add the vertex 111 | comboBuffer = append(comboBuffer, verts[i*2]) 112 | comboBuffer = append(comboBuffer, verts[i*2+1]) 113 | 114 | // add the uv 115 | comboBuffer = append(comboBuffer, uvs[i*2]) 116 | comboBuffer = append(comboBuffer, uvs[i*2+1]) 117 | 118 | // add the texture index to use in UV lookup 119 | comboBuffer = append(comboBuffer, float32(textureIndex)) 120 | 121 | // add the color 122 | comboBuffer = append(comboBuffer, color[:]...) 123 | } 124 | 125 | // define the polys with 2 faces (6 indexes) 126 | for i := 0; i < 6; i++ { 127 | indexBuffer = append(indexBuffer, indexes[i]) 128 | } 129 | 130 | // return the vertex data 131 | return comboBuffer, indexBuffer, 2 132 | } 133 | 134 | func (cmds *cmdList) drawTreeNodeIcon(isOpen bool, tlx, tly, brx, bry float32, color mgl.Vec4, textureIndex uint32, whitePixelUv mgl.Vec4) ([]float32, []uint32, uint32) { 135 | comboBuffer := []float32{} 136 | indexBuffer := []uint32{} 137 | 138 | iconW2 := (brx - tlx) * 0.5 139 | iconH2 := (tly - bry) * 0.5 140 | centerX := tlx + (brx-tlx)*0.5 141 | centerY := bry + (tly-bry)*0.5 142 | 143 | if isOpen { 144 | // Vert #1 TL (vert, uv, texture index, color) 145 | comboBuffer = append(comboBuffer, centerX-iconW2) 146 | comboBuffer = append(comboBuffer, centerY+iconH2) 147 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 148 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 149 | comboBuffer = append(comboBuffer, float32(textureIndex)) 150 | comboBuffer = append(comboBuffer, color[:]...) 151 | 152 | // Vert #2 BM (vert, uv, texture index, color) 153 | comboBuffer = append(comboBuffer, centerX) 154 | comboBuffer = append(comboBuffer, centerY-iconH2) 155 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 156 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 157 | comboBuffer = append(comboBuffer, float32(textureIndex)) 158 | comboBuffer = append(comboBuffer, color[:]...) 159 | 160 | // Vert #3 TR (vert, uv, texture index, color) 161 | comboBuffer = append(comboBuffer, centerX+iconW2) 162 | comboBuffer = append(comboBuffer, centerY+iconH2) 163 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 164 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 165 | comboBuffer = append(comboBuffer, float32(textureIndex)) 166 | comboBuffer = append(comboBuffer, color[:]...) 167 | } else { 168 | // Vert #1 TL (vert, uv, texture index, color) 169 | comboBuffer = append(comboBuffer, centerX-iconW2) 170 | comboBuffer = append(comboBuffer, centerY+iconH2) 171 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 172 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 173 | comboBuffer = append(comboBuffer, float32(textureIndex)) 174 | comboBuffer = append(comboBuffer, color[:]...) 175 | 176 | // Vert #2 BL (vert, uv, texture index, color) 177 | comboBuffer = append(comboBuffer, centerX-iconW2) 178 | comboBuffer = append(comboBuffer, centerY-iconH2) 179 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 180 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 181 | comboBuffer = append(comboBuffer, float32(textureIndex)) 182 | comboBuffer = append(comboBuffer, color[:]...) 183 | 184 | // Vert #3 RM (vert, uv, texture index, color) 185 | comboBuffer = append(comboBuffer, centerX+iconW2) 186 | comboBuffer = append(comboBuffer, centerY) 187 | comboBuffer = append(comboBuffer, whitePixelUv[0]) 188 | comboBuffer = append(comboBuffer, whitePixelUv[1]) 189 | comboBuffer = append(comboBuffer, float32(textureIndex)) 190 | comboBuffer = append(comboBuffer, color[:]...) 191 | } 192 | 193 | indexBuffer = append(indexBuffer, 0) 194 | indexBuffer = append(indexBuffer, 1) 195 | indexBuffer = append(indexBuffer, 2) 196 | 197 | // return the vertex data 198 | return comboBuffer, indexBuffer, 1 199 | } 200 | -------------------------------------------------------------------------------- /eweygewey.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | import ( 7 | mgl "github.com/go-gl/mathgl/mgl32" 8 | ) 9 | 10 | // constants used for polling the state of a mouse button 11 | const ( 12 | MouseDown = 0 13 | MouseUp = 1 14 | MouseClick = 2 15 | MouseDoubleClick = 4 16 | ) 17 | 18 | // Style defines parameters to the drawing functions that control the way 19 | // the widgets are organized and drawn. 20 | type Style struct { 21 | ButtonColor mgl.Vec4 // button background color 22 | ButtonHoverColor mgl.Vec4 // button background color with mouse hovering 23 | ButtonActiveColor mgl.Vec4 // button background color when clicked 24 | ButtonTextColor mgl.Vec4 // button text color 25 | ButtonMargin mgl.Vec4 // [left,right,top,bottom] margin values for buttons 26 | ButtonPadding mgl.Vec4 // [left,right,top,bottom] padding values for buttons 27 | CheckboxColor mgl.Vec4 // checkbox background color 28 | CheckboxCheckColor mgl.Vec4 // checkbox cursor color when clicked 29 | CheckboxCursorWidth float32 // checkbox inner check cursor size 30 | CheckboxMargin mgl.Vec4 // [left,right,top,bottom] margin values for checkbox 31 | CheckboxPadding mgl.Vec4 // [left,right,top,bottom] padding values for checkbox 32 | EditboxBgColor mgl.Vec4 // Editbox background color 33 | EditboxActiveColor mgl.Vec4 // Editbox background color when clicked 34 | EditboxCursorColor mgl.Vec4 // color for the editbox cursor 35 | EditboxCursorWidth float32 // width of the editbox cursor in pixels 36 | EditboxBlinkDuration float32 // how long the cursor is visible during a blink (in seconds) 37 | EditboxBlinkInterval float32 // how many seconds between the start of the cursor blink (in seconds) 38 | EditboxTextColor mgl.Vec4 // Editbox text color 39 | EditboxMargin mgl.Vec4 // [left,right,top,bottom] margin values for Editbox 40 | EditboxPadding mgl.Vec4 // [left,right,top,bottom] padding values for Editbox 41 | FontName string // font name to use by default 42 | ImageMargin mgl.Vec4 // margin for the image widgets 43 | IndentSpacing float32 // the amount of pixels to indent 44 | ScrollBarCursorColor mgl.Vec4 // the color of the cursor of the scroll bar 45 | ScrollBarBgColor mgl.Vec4 // the color of the background of the scroll bar 46 | ScrollBarWidth float32 // the width of the scroll bar 47 | ScrollBarCursorWidth float32 // the width of the scroll bar cursor 48 | SeparatorColor mgl.Vec4 // the color of the separator bar 49 | SeparatorHeight float32 // the height of the separator rectangle 50 | SeparatorMargin mgl.Vec4 // the margin for the separator rectangle 51 | SliderBgColor mgl.Vec4 // slider background color 52 | SliderCursorColor mgl.Vec4 // slider cursor color 53 | SliderFloatFormat string // formatting string for the float value in a slider 54 | SliderIntFormat string // formatting string for the int value in a slider 55 | SliderMargin mgl.Vec4 // margin for the slider text strings 56 | SliderPadding mgl.Vec4 // padding for the slider text strings 57 | SliderTextColor mgl.Vec4 // slider text color 58 | SliderCursorWidth float32 // slider cursor width 59 | TextColor mgl.Vec4 // text color 60 | TextMargin mgl.Vec4 // margin for text widgets 61 | TitleBarPadding mgl.Vec4 // padding for the title bar of the window 62 | TitleBarTextColor mgl.Vec4 // text color 63 | TitleBarBgColor mgl.Vec4 // window background color 64 | TreeNodeTextColor mgl.Vec4 // text color for tree nodes 65 | TreeNodeMargin mgl.Vec4 // [left,right,top,bottom] margin values for tree nodes 66 | TreeNodePadding mgl.Vec4 // [left,right,top,bottom] padding values for tree nodes 67 | WindowBgColor mgl.Vec4 // window background color 68 | WindowPadding mgl.Vec4 // [left,right,top,bottom] padding values for windows 69 | } 70 | 71 | var ( 72 | // VertShader330 is the GLSL vertex shader program for the user interface. 73 | VertShader330 = `#version 330 74 | uniform mat4 VIEW; 75 | in vec2 VERTEX_POSITION; 76 | in vec2 VERTEX_UV; 77 | in float VERTEX_TEXTURE_INDEX; 78 | in vec4 VERTEX_COLOR; 79 | out vec2 vs_uv; 80 | out vec4 vs_color; 81 | out float vs_tex_index; 82 | void main() 83 | { 84 | vs_uv = VERTEX_UV; 85 | vs_color = VERTEX_COLOR; 86 | vs_tex_index = VERTEX_TEXTURE_INDEX; 87 | gl_Position = VIEW * vec4(VERTEX_POSITION, 0.0, 1.0); 88 | }` 89 | 90 | // FragShader330 is the GLSL fragment shader program for the user interface. 91 | // NOTE: 4 samplers is a hardcoded value now, but there's no reason it has to be that specifically. 92 | FragShader330 = `#version 330 93 | uniform sampler2D TEX[4]; 94 | in vec2 vs_uv; 95 | in vec4 vs_color; 96 | in float vs_tex_index; 97 | out vec4 frag_color; 98 | void main() 99 | { 100 | int i = int(vs_tex_index); 101 | switch(int(vs_tex_index)) 102 | { 103 | case 0: frag_color = vs_color * texture(TEX[0], vs_uv).rgba; break; 104 | case 1: frag_color = vs_color * texture(TEX[1], vs_uv).rgba; break; 105 | case 2: frag_color = vs_color * texture(TEX[2], vs_uv).rgba; break; 106 | case 3: frag_color = vs_color * texture(TEX[3], vs_uv).rgba; break; 107 | } 108 | 109 | }` 110 | 111 | // DefaultStyle is the default style to use for drawing widgets 112 | DefaultStyle = Style{ 113 | ButtonColor: ColorIToV(171, 102, 102, 153), 114 | ButtonActiveColor: ColorIToV(204, 128, 120, 255), 115 | ButtonHoverColor: ColorIToV(171, 102, 102, 255), 116 | ButtonTextColor: ColorIToV(230, 230, 230, 255), 117 | ButtonMargin: mgl.Vec4{2, 2, 2, 2}, 118 | ButtonPadding: mgl.Vec4{2, 2, 4, 4}, 119 | CheckboxColor: ColorIToV(128, 128, 128, 179), 120 | CheckboxCheckColor: ColorIToV(204, 128, 120, 255), 121 | CheckboxCursorWidth: 15.0, 122 | CheckboxMargin: mgl.Vec4{2, 2, 2, 2}, 123 | CheckboxPadding: mgl.Vec4{4, 4, 4, 4}, 124 | EditboxBgColor: ColorIToV(128, 128, 128, 179), 125 | EditboxActiveColor: ColorIToV(204, 128, 120, 255), 126 | EditboxCursorColor: ColorIToV(230, 230, 230, 255), 127 | EditboxCursorWidth: 3.0, 128 | EditboxBlinkDuration: 0.25, 129 | EditboxBlinkInterval: 1.0, 130 | EditboxTextColor: ColorIToV(230, 230, 230, 255), 131 | EditboxMargin: mgl.Vec4{2, 2, 2, 2}, 132 | EditboxPadding: mgl.Vec4{2, 2, 4, 4}, 133 | FontName: "Default", 134 | ImageMargin: mgl.Vec4{0, 0, 0, 0}, 135 | IndentSpacing: 26.0, 136 | ScrollBarCursorColor: ColorIToV(102, 102, 204, 77), 137 | ScrollBarBgColor: ColorIToV(51, 64, 77, 153), 138 | ScrollBarWidth: 16.0, 139 | ScrollBarCursorWidth: 10.0, 140 | SeparatorColor: ColorIToV(230, 230, 230, 255), 141 | SeparatorHeight: 1.0, 142 | SeparatorMargin: mgl.Vec4{4, 4, 8, 8}, 143 | SliderBgColor: ColorIToV(128, 128, 128, 179), 144 | SliderCursorColor: ColorIToV(179, 179, 179, 179), 145 | SliderFloatFormat: "%0.3f", 146 | SliderIntFormat: "%d", 147 | SliderMargin: mgl.Vec4{2, 2, 2, 2}, 148 | SliderPadding: mgl.Vec4{2, 2, 4, 4}, 149 | SliderTextColor: ColorIToV(230, 230, 230, 255), 150 | SliderCursorWidth: 15.0, 151 | TextMargin: mgl.Vec4{4, 4, 6, 6}, 152 | TextColor: ColorIToV(230, 230, 230, 255), 153 | TitleBarPadding: mgl.Vec4{2, 2, 6, 6}, 154 | TitleBarTextColor: ColorIToV(230, 230, 230, 255), 155 | TitleBarBgColor: ColorIToV(69, 69, 138, 255), 156 | TreeNodeMargin: mgl.Vec4{2, 2, 2, 2}, 157 | TreeNodePadding: mgl.Vec4{2, 2, 4, 4}, 158 | TreeNodeTextColor: ColorIToV(230, 230, 230, 255), 159 | WindowBgColor: ColorIToV(0, 0, 0, 179), 160 | WindowPadding: mgl.Vec4{4, 4, 4, 4}, 161 | } 162 | ) 163 | 164 | // ColorIToV takes the color parameters as integers and returns them 165 | // as a float vector. 166 | func ColorIToV(r, g, b, a int) mgl.Vec4 { 167 | return mgl.Vec4{float32(r) / 255.0, float32(g) / 255.0, float32(b) / 255.0, float32(a) / 255.0} 168 | } 169 | 170 | // ClipF32 returns a value that is between the closed interval of [min .. max]. 171 | func ClipF32(min, max, value float32) float32 { 172 | if value < min { 173 | return min 174 | } 175 | if value > max { 176 | return max 177 | } 178 | return value 179 | } 180 | -------------------------------------------------------------------------------- /examples/assets/HammersmithOne SIL OFL Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 by Sorkin Type Co (www.sorkintype.com), 2 | with Reserved Font Name "Hammersmith", "Hammersmith One". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /examples/assets/HammersmithOne.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/eweygewey/3a487316a7c34a2377a865cb2c313162fb21e715/examples/assets/HammersmithOne.ttf -------------------------------------------------------------------------------- /examples/assets/Oswald-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/eweygewey/3a487316a7c34a2377a865cb2c313162fb21e715/examples/assets/Oswald-Heavy.ttf -------------------------------------------------------------------------------- /examples/assets/Oswald-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/eweygewey/3a487316a7c34a2377a865cb2c313162fb21e715/examples/assets/Oswald-Regular.ttf -------------------------------------------------------------------------------- /examples/assets/SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Vernon Adams (vern@newtypography.co.uk), 2 | with Reserved Font Name Oswald 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 13 | 14 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 18 | 19 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 20 | 21 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 22 | 23 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 24 | 25 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 26 | 27 | PERMISSION & CONDITIONS 28 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 29 | 30 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 31 | 32 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 33 | 34 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 35 | 36 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 37 | 38 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 39 | 40 | TERMINATION 41 | This license becomes null and void if any of the above conditions are not met. 42 | 43 | DISCLAIMER 44 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /examples/assets/potions-credits.txt: -------------------------------------------------------------------------------- 1 | This work is released under a Creative Commons (CC) BY-SA 3.0 license. 2 | 3 | For further information, please check: http://creativecommons.org/licenses/by-sa/3.0/ 4 | 5 | How to credit me (for example): Bonsaiheldin | http://bonsaiheld.org 6 | 7 | ---------- 8 | 9 | Although it's not really needed, it makes me happy to know what you do with my work. 10 | It took me time to create it, so, why not drop me a line somewhere if you happen to use it? :) 11 | -------------------------------------------------------------------------------- /examples/assets/potions-license.htm: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Creative Commons Legal Code 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 |
17 |
18 | 21 |

Creative Commons Legal Code

22 |
23 |

Attribution-ShareAlike 3.0 Unported

24 |
25 |
26 |
27 |
28 | 29 |
30 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES 31 | NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE 32 | DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE 33 | COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. 34 | CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE 35 | INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES 36 | RESULTING FROM ITS USE. 37 |
38 |

License

39 |

THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS 40 | OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR 41 | "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER 42 | APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS 43 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS 44 | PROHIBITED.

45 |

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU 46 | ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. 47 | TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A 48 | CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE 49 | IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND 50 | CONDITIONS.

51 |

1. Definitions

52 |
    53 |
  1. "Adaptation" means a work based upon 54 | the Work, or upon the Work and other pre-existing works, 55 | such as a translation, adaptation, derivative work, 56 | arrangement of music or other alterations of a literary 57 | or artistic work, or phonogram or performance and 58 | includes cinematographic adaptations or any other form in 59 | which the Work may be recast, transformed, or adapted 60 | including in any form recognizably derived from the 61 | original, except that a work that constitutes a 62 | Collection will not be considered an Adaptation for the 63 | purpose of this License. For the avoidance of doubt, 64 | where the Work is a musical work, performance or 65 | phonogram, the synchronization of the Work in 66 | timed-relation with a moving image ("synching") will be 67 | considered an Adaptation for the purpose of this 68 | License.
  2. 69 |
  3. "Collection" means a collection of 70 | literary or artistic works, such as encyclopedias and 71 | anthologies, or performances, phonograms or broadcasts, 72 | or other works or subject matter other than works listed 73 | in Section 1(f) below, which, by reason of the selection 74 | and arrangement of their contents, constitute 75 | intellectual creations, in which the Work is included in 76 | its entirety in unmodified form along with one or more 77 | other contributions, each constituting separate and 78 | independent works in themselves, which together are 79 | assembled into a collective whole. A work that 80 | constitutes a Collection will not be considered an 81 | Adaptation (as defined below) for the purposes of this 82 | License.
  4. 83 |
  5. "Creative Commons Compatible 84 | License" means a license that is listed at 85 | https://creativecommons.org/compatiblelicenses that has 86 | been approved by Creative Commons as being essentially 87 | equivalent to this License, including, at a minimum, 88 | because that license: (i) contains terms that have the 89 | same purpose, meaning and effect as the License Elements 90 | of this License; and, (ii) explicitly permits the 91 | relicensing of adaptations of works made available under 92 | that license under this License or a Creative Commons 93 | jurisdiction license with the same License Elements as 94 | this License.
  6. 95 |
  7. "Distribute" means to make available 96 | to the public the original and copies of the Work or 97 | Adaptation, as appropriate, through sale or other 98 | transfer of ownership.
  8. 99 |
  9. "License Elements" means the 100 | following high-level license attributes as selected by 101 | Licensor and indicated in the title of this License: 102 | Attribution, ShareAlike.
  10. 103 |
  11. "Licensor" means the individual, 104 | individuals, entity or entities that offer(s) the Work 105 | under the terms of this License.
  12. 106 |
  13. "Original Author" means, in the case 107 | of a literary or artistic work, the individual, 108 | individuals, entity or entities who created the Work or 109 | if no individual or entity can be identified, the 110 | publisher; and in addition (i) in the case of a 111 | performance the actors, singers, musicians, dancers, and 112 | other persons who act, sing, deliver, declaim, play in, 113 | interpret or otherwise perform literary or artistic works 114 | or expressions of folklore; (ii) in the case of a 115 | phonogram the producer being the person or legal entity 116 | who first fixes the sounds of a performance or other 117 | sounds; and, (iii) in the case of broadcasts, the 118 | organization that transmits the broadcast.
  14. 119 |
  15. "Work" means the literary and/or 120 | artistic work offered under the terms of this License 121 | including without limitation any production in the 122 | literary, scientific and artistic domain, whatever may be 123 | the mode or form of its expression including digital 124 | form, such as a book, pamphlet and other writing; a 125 | lecture, address, sermon or other work of the same 126 | nature; a dramatic or dramatico-musical work; a 127 | choreographic work or entertainment in dumb show; a 128 | musical composition with or without words; a 129 | cinematographic work to which are assimilated works 130 | expressed by a process analogous to cinematography; a 131 | work of drawing, painting, architecture, sculpture, 132 | engraving or lithography; a photographic work to which 133 | are assimilated works expressed by a process analogous to 134 | photography; a work of applied art; an illustration, map, 135 | plan, sketch or three-dimensional work relative to 136 | geography, topography, architecture or science; a 137 | performance; a broadcast; a phonogram; a compilation of 138 | data to the extent it is protected as a copyrightable 139 | work; or a work performed by a variety or circus 140 | performer to the extent it is not otherwise considered a 141 | literary or artistic work.
  16. 142 |
  17. "You" means an individual or entity 143 | exercising rights under this License who has not 144 | previously violated the terms of this License with 145 | respect to the Work, or who has received express 146 | permission from the Licensor to exercise rights under 147 | this License despite a previous violation.
  18. 148 |
  19. "Publicly Perform" means to perform 149 | public recitations of the Work and to communicate to the 150 | public those public recitations, by any means or process, 151 | including by wire or wireless means or public digital 152 | performances; to make available to the public Works in 153 | such a way that members of the public may access these 154 | Works from a place and at a place individually chosen by 155 | them; to perform the Work to the public by any means or 156 | process and the communication to the public of the 157 | performances of the Work, including by public digital 158 | performance; to broadcast and rebroadcast the Work by any 159 | means including signs, sounds or images.
  20. 160 |
  21. "Reproduce" means to make copies of 161 | the Work by any means including without limitation by 162 | sound or visual recordings and the right of fixation and 163 | reproducing fixations of the Work, including storage of a 164 | protected performance or phonogram in digital form or 165 | other electronic medium.
  22. 166 |
167 |

2. Fair Dealing Rights. Nothing in this 168 | License is intended to reduce, limit, or restrict any uses 169 | free from copyright or rights arising from limitations or 170 | exceptions that are provided for in connection with the 171 | copyright protection under copyright law or other 172 | applicable laws.

173 |

3. License Grant. Subject to the terms 174 | and conditions of this License, Licensor hereby grants You 175 | a worldwide, royalty-free, non-exclusive, perpetual (for 176 | the duration of the applicable copyright) license to 177 | exercise the rights in the Work as stated below:

178 |
    179 |
  1. to Reproduce the Work, to incorporate the Work into 180 | one or more Collections, and to Reproduce the Work as 181 | incorporated in the Collections;
  2. 182 |
  3. to create and Reproduce Adaptations provided that any 183 | such Adaptation, including any translation in any medium, 184 | takes reasonable steps to clearly label, demarcate or 185 | otherwise identify that changes were made to the original 186 | Work. For example, a translation could be marked "The 187 | original work was translated from English to Spanish," or 188 | a modification could indicate "The original work has been 189 | modified.";
  4. 190 |
  5. to Distribute and Publicly Perform the Work including 191 | as incorporated in Collections; and,
  6. 192 |
  7. to Distribute and Publicly Perform Adaptations.
  8. 193 |
  9. 194 |

    For the avoidance of doubt:

    195 |
      196 |
    1. Non-waivable Compulsory License 197 | Schemes. In those jurisdictions in which the 198 | right to collect royalties through any statutory or 199 | compulsory licensing scheme cannot be waived, the 200 | Licensor reserves the exclusive right to collect such 201 | royalties for any exercise by You of the rights 202 | granted under this License;
    2. 203 |
    3. Waivable Compulsory License 204 | Schemes. In those jurisdictions in which the 205 | right to collect royalties through any statutory or 206 | compulsory licensing scheme can be waived, the 207 | Licensor waives the exclusive right to collect such 208 | royalties for any exercise by You of the rights 209 | granted under this License; and,
    4. 210 |
    5. Voluntary License Schemes. The 211 | Licensor waives the right to collect royalties, 212 | whether individually or, in the event that the 213 | Licensor is a member of a collecting society that 214 | administers voluntary licensing schemes, via that 215 | society, from any exercise by You of the rights 216 | granted under this License.
    6. 217 |
    218 |
  10. 219 |
220 |

The above rights may be exercised in all media and 221 | formats whether now known or hereafter devised. The above 222 | rights include the right to make such modifications as are 223 | technically necessary to exercise the rights in other media 224 | and formats. Subject to Section 8(f), all rights not 225 | expressly granted by Licensor are hereby reserved.

226 |

4. Restrictions. The license granted in 227 | Section 3 above is expressly made subject to and limited by 228 | the following restrictions:

229 |
    230 |
  1. You may Distribute or Publicly Perform the Work only 231 | under the terms of this License. You must include a copy 232 | of, or the Uniform Resource Identifier (URI) for, this 233 | License with every copy of the Work You Distribute or 234 | Publicly Perform. You may not offer or impose any terms 235 | on the Work that restrict the terms of this License or 236 | the ability of the recipient of the Work to exercise the 237 | rights granted to that recipient under the terms of the 238 | License. You may not sublicense the Work. You must keep 239 | intact all notices that refer to this License and to the 240 | disclaimer of warranties with every copy of the Work You 241 | Distribute or Publicly Perform. When You Distribute or 242 | Publicly Perform the Work, You may not impose any 243 | effective technological measures on the Work that 244 | restrict the ability of a recipient of the Work from You 245 | to exercise the rights granted to that recipient under 246 | the terms of the License. This Section 4(a) applies to 247 | the Work as incorporated in a Collection, but this does 248 | not require the Collection apart from the Work itself to 249 | be made subject to the terms of this License. If You 250 | create a Collection, upon notice from any Licensor You 251 | must, to the extent practicable, remove from the 252 | Collection any credit as required by Section 4(c), as 253 | requested. If You create an Adaptation, upon notice from 254 | any Licensor You must, to the extent practicable, remove 255 | from the Adaptation any credit as required by Section 256 | 4(c), as requested.
  2. 257 |
  3. You may Distribute or Publicly Perform an Adaptation 258 | only under the terms of: (i) this License; (ii) a later 259 | version of this License with the same License Elements as 260 | this License; (iii) a Creative Commons jurisdiction 261 | license (either this or a later license version) that 262 | contains the same License Elements as this License (e.g., 263 | Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons 264 | Compatible License. If you license the Adaptation under 265 | one of the licenses mentioned in (iv), you must comply 266 | with the terms of that license. If you license the 267 | Adaptation under the terms of any of the licenses 268 | mentioned in (i), (ii) or (iii) (the "Applicable 269 | License"), you must comply with the terms of the 270 | Applicable License generally and the following 271 | provisions: (I) You must include a copy of, or the URI 272 | for, the Applicable License with every copy of each 273 | Adaptation You Distribute or Publicly Perform; (II) You 274 | may not offer or impose any terms on the Adaptation that 275 | restrict the terms of the Applicable License or the 276 | ability of the recipient of the Adaptation to exercise 277 | the rights granted to that recipient under the terms of 278 | the Applicable License; (III) You must keep intact all 279 | notices that refer to the Applicable License and to the 280 | disclaimer of warranties with every copy of the Work as 281 | included in the Adaptation You Distribute or Publicly 282 | Perform; (IV) when You Distribute or Publicly Perform the 283 | Adaptation, You may not impose any effective 284 | technological measures on the Adaptation that restrict 285 | the ability of a recipient of the Adaptation from You to 286 | exercise the rights granted to that recipient under the 287 | terms of the Applicable License. This Section 4(b) 288 | applies to the Adaptation as incorporated in a 289 | Collection, but this does not require the Collection 290 | apart from the Adaptation itself to be made subject to 291 | the terms of the Applicable License.
  4. 292 |
  5. If You Distribute, or Publicly Perform the Work or 293 | any Adaptations or Collections, You must, unless a 294 | request has been made pursuant to Section 4(a), keep 295 | intact all copyright notices for the Work and provide, 296 | reasonable to the medium or means You are utilizing: (i) 297 | the name of the Original Author (or pseudonym, if 298 | applicable) if supplied, and/or if the Original Author 299 | and/or Licensor designate another party or parties (e.g., 300 | a sponsor institute, publishing entity, journal) for 301 | attribution ("Attribution Parties") in Licensor's 302 | copyright notice, terms of service or by other reasonable 303 | means, the name of such party or parties; (ii) the title 304 | of the Work if supplied; (iii) to the extent reasonably 305 | practicable, the URI, if any, that Licensor specifies to 306 | be associated with the Work, unless such URI does not 307 | refer to the copyright notice or licensing information 308 | for the Work; and (iv) , consistent with Ssection 3(b), 309 | in the case of an Adaptation, a credit identifying the 310 | use of the Work in the Adaptation (e.g., "French 311 | translation of the Work by Original Author," or 312 | "Screenplay based on original Work by Original Author"). 313 | The credit required by this Section 4(c) may be 314 | implemented in any reasonable manner; provided, however, 315 | that in the case of a Adaptation or Collection, at a 316 | minimum such credit will appear, if a credit for all 317 | contributing authors of the Adaptation or Collection 318 | appears, then as part of these credits and in a manner at 319 | least as prominent as the credits for the other 320 | contributing authors. For the avoidance of doubt, You may 321 | only use the credit required by this Section for the 322 | purpose of attribution in the manner set out above and, 323 | by exercising Your rights under this License, You may not 324 | implicitly or explicitly assert or imply any connection 325 | with, sponsorship or endorsement by the Original Author, 326 | Licensor and/or Attribution Parties, as appropriate, of 327 | You or Your use of the Work, without the separate, 328 | express prior written permission of the Original Author, 329 | Licensor and/or Attribution Parties.
  6. 330 |
  7. Except as otherwise agreed in writing by the Licensor 331 | or as may be otherwise permitted by applicable law, if 332 | You Reproduce, Distribute or Publicly Perform the Work 333 | either by itself or as part of any Adaptations or 334 | Collections, You must not distort, mutilate, modify or 335 | take other derogatory action in relation to the Work 336 | which would be prejudicial to the Original Author's honor 337 | or reputation. Licensor agrees that in those 338 | jurisdictions (e.g. Japan), in which any exercise of the 339 | right granted in Section 3(b) of this License (the right 340 | to make Adaptations) would be deemed to be a distortion, 341 | mutilation, modification or other derogatory action 342 | prejudicial to the Original Author's honor and 343 | reputation, the Licensor will waive or not assert, as 344 | appropriate, this Section, to the fullest extent 345 | permitted by the applicable national law, to enable You 346 | to reasonably exercise Your right under Section 3(b) of 347 | this License (right to make Adaptations) but not 348 | otherwise.
  8. 349 |
350 |

5. Representations, Warranties and 351 | Disclaimer

352 |

UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN 353 | WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO 354 | REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE 355 | WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, 356 | WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, 357 | FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE 358 | ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE 359 | PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. 360 | SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED 361 | WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

362 |

6. Limitation on Liability. EXCEPT TO 363 | THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL 364 | LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY 365 | SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY 366 | DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, 367 | EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF 368 | SUCH DAMAGES.

369 |

7. Termination

370 |
    371 |
  1. This License and the rights granted hereunder will 372 | terminate automatically upon any breach by You of the 373 | terms of this License. Individuals or entities who have 374 | received Adaptations or Collections from You under this 375 | License, however, will not have their licenses terminated 376 | provided such individuals or entities remain in full 377 | compliance with those licenses. Sections 1, 2, 5, 6, 7, 378 | and 8 will survive any termination of this License.
  2. 379 |
  3. Subject to the above terms and conditions, the 380 | license granted here is perpetual (for the duration of 381 | the applicable copyright in the Work). Notwithstanding 382 | the above, Licensor reserves the right to release the 383 | Work under different license terms or to stop 384 | distributing the Work at any time; provided, however that 385 | any such election will not serve to withdraw this License 386 | (or any other license that has been, or is required to 387 | be, granted under the terms of this License), and this 388 | License will continue in full force and effect unless 389 | terminated as stated above.
  4. 390 |
391 |

8. Miscellaneous

392 |
    393 |
  1. Each time You Distribute or Publicly Perform the Work 394 | or a Collection, the Licensor offers to the recipient a 395 | license to the Work on the same terms and conditions as 396 | the license granted to You under this License.
  2. 397 |
  3. Each time You Distribute or Publicly Perform an 398 | Adaptation, Licensor offers to the recipient a license to 399 | the original Work on the same terms and conditions as the 400 | license granted to You under this License.
  4. 401 |
  5. If any provision of this License is invalid or 402 | unenforceable under applicable law, it shall not affect 403 | the validity or enforceability of the remainder of the 404 | terms of this License, and without further action by the 405 | parties to this agreement, such provision shall be 406 | reformed to the minimum extent necessary to make such 407 | provision valid and enforceable.
  6. 408 |
  7. No term or provision of this License shall be deemed 409 | waived and no breach consented to unless such waiver or 410 | consent shall be in writing and signed by the party to be 411 | charged with such waiver or consent.
  8. 412 |
  9. This License constitutes the entire agreement between 413 | the parties with respect to the Work licensed here. There 414 | are no understandings, agreements or representations with 415 | respect to the Work not specified here. Licensor shall 416 | not be bound by any additional provisions that may appear 417 | in any communication from You. This License may not be 418 | modified without the mutual written agreement of the 419 | Licensor and You.
  10. 420 |
  11. The rights granted under, and the subject matter 421 | referenced, in this License were drafted utilizing the 422 | terminology of the Berne Convention for the Protection of 423 | Literary and Artistic Works (as amended on September 28, 424 | 1979), the Rome Convention of 1961, the WIPO Copyright 425 | Treaty of 1996, the WIPO Performances and Phonograms 426 | Treaty of 1996 and the Universal Copyright Convention (as 427 | revised on July 24, 1971). These rights and subject 428 | matter take effect in the relevant jurisdiction in which 429 | the License terms are sought to be enforced according to 430 | the corresponding provisions of the implementation of 431 | those treaty provisions in the applicable national law. 432 | If the standard suite of rights granted under applicable 433 | copyright law includes additional rights not granted 434 | under this License, such additional rights are deemed to 435 | be included in the License; this License is not intended 436 | to restrict the license of any rights under applicable 437 | law.
  12. 438 |
439 | 440 |
441 |

Creative Commons Notice

442 |

Creative Commons is not a party to this License, and 443 | makes no warranty whatsoever in connection with the Work. 444 | Creative Commons will not be liable to You or any party 445 | on any legal theory for any damages whatsoever, including 446 | without limitation any general, special, incidental or 447 | consequential damages arising in connection to this 448 | license. Notwithstanding the foregoing two (2) sentences, 449 | if Creative Commons has expressly identified itself as 450 | the Licensor hereunder, it shall have all rights and 451 | obligations of Licensor.

452 |

Except for the limited purpose of indicating to the 453 | public that the Work is licensed under the CCPL, Creative 454 | Commons does not authorize the use by either party of the 455 | trademark "Creative Commons" or any related trademark or 456 | logo of Creative Commons without the prior written 457 | consent of Creative Commons. Any permitted use will be in 458 | compliance with Creative Commons' then-current trademark 459 | usage guidelines, as may be published on its website or 460 | otherwise made available upon request from time to time. 461 | For the avoidance of doubt, this trademark restriction 462 | does not form part of the License.

463 |

Creative Commons may be contacted at https://creativecommons.org/.

464 |
465 |
466 |
467 | 470 |
471 | 472 | 473 | -------------------------------------------------------------------------------- /examples/assets/potions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/eweygewey/3a487316a7c34a2377a865cb2c313162fb21e715/examples/assets/potions.png -------------------------------------------------------------------------------- /examples/basicGLFW/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "math" 9 | "runtime" 10 | "time" 11 | 12 | glfw "github.com/go-gl/glfw/v3.1/glfw" 13 | mgl "github.com/go-gl/mathgl/mgl32" 14 | 15 | gui "github.com/tbogdala/eweygewey" 16 | glfwinput "github.com/tbogdala/eweygewey/glfwinput" 17 | fizzle "github.com/tbogdala/fizzle" 18 | graphics "github.com/tbogdala/fizzle/graphicsprovider" 19 | gl "github.com/tbogdala/fizzle/graphicsprovider/opengl" 20 | forward "github.com/tbogdala/fizzle/renderer/forward" 21 | ) 22 | 23 | const ( 24 | fontScale = 14 25 | fontFilepath = "../assets/Oswald-Heavy.ttf" 26 | fontGlyphs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890., :[]{}\\|<>;\"'~`?/-+_=()*&^%$#@!" 27 | testImage = "../assets/potions.png" 28 | ) 29 | 30 | var ( 31 | glfwWindow *glfw.Window 32 | gfx graphics.GraphicsProvider 33 | uiman *gui.Manager 34 | 35 | thisFrame time.Time 36 | lastFrame time.Time 37 | frameCounterTime time.Time 38 | frameCounter int 39 | lastCalcFPS int 40 | frameDelta float64 41 | ) 42 | 43 | // GLFW event handling must run on the main OS thread 44 | func init() { 45 | runtime.LockOSThread() 46 | } 47 | 48 | func keyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 49 | if key == glfw.KeyEscape && action == glfw.Press { 50 | w.SetShouldClose(true) 51 | } 52 | } 53 | 54 | func renderFrame(frameDelta float64) { 55 | // calculate the frame timing and FPS 56 | if thisFrame.Sub(frameCounterTime).Seconds() > 1.0 { 57 | lastCalcFPS = frameCounter 58 | frameCounterTime = thisFrame 59 | frameCounter = 0 60 | } 61 | frameCounter++ 62 | lastFrame = thisFrame 63 | 64 | // clear the screen 65 | width, height := uiman.GetResolution() 66 | clearColor := gui.ColorIToV(114, 144, 154, 255) 67 | gfx.Viewport(0, 0, width, height) 68 | gfx.ClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]) 69 | gfx.Clear(graphics.COLOR_BUFFER_BIT | graphics.DEPTH_BUFFER_BIT) 70 | 71 | // draw the user interface 72 | uiman.Construct(frameDelta) 73 | uiman.Draw() 74 | } 75 | 76 | func main() { 77 | const w = 1280 78 | const h = 720 79 | glfwWindow, gfx = initGraphics("gui basic", w, h) 80 | glfwWindow.SetKeyCallback(keyCallback) 81 | lastFrame = time.Now() 82 | frameCounterTime = lastFrame 83 | lastCalcFPS = -1 84 | 85 | // setup the OpenGL graphics provider 86 | var err error 87 | gfx, err = gl.InitOpenGL() 88 | if err != nil { 89 | panic("Failed to initialize OpenGL! " + err.Error()) 90 | } 91 | 92 | // create and initialize the gui Manager 93 | uiman = gui.NewManager(gfx) 94 | err = uiman.Initialize(gui.VertShader330, gui.FragShader330, w, h, h) 95 | if err != nil { 96 | panic("Failed to initialize the user interface! " + err.Error()) 97 | } 98 | glfwinput.SetInputHandlers(uiman, glfwWindow) 99 | 100 | // load a font 101 | _, err = uiman.NewFont("Default", fontFilepath, fontScale, fontGlyphs) 102 | if err != nil { 103 | panic("Failed to load the font file! " + err.Error()) 104 | } 105 | 106 | // load a test image 107 | potionsTex, err := fizzle.LoadImageToTexture(testImage) 108 | if err != nil { 109 | panic("Failed to load the texture: " + testImage + " " + err.Error()) 110 | } 111 | 112 | // delcare the windows so that we can use them in the closures below 113 | var testInt, testInt2 int 114 | var testFloat, testFloat2 float32 115 | var mouseTestWindow, imageTestWindow, mainWindow *gui.Window 116 | 117 | // create a small overlay window in the corner 118 | mouseTestWindow = uiman.NewWindow("MouseTest", 0.01, 0.99, 0.2, 0.35, func(wnd *gui.Window) { 119 | // display the mouse coordinate 120 | mouseX, mouseY := uiman.GetMousePosition() 121 | wnd.Text(fmt.Sprintf("Mouse position = %.2f,%.2f", mouseX, mouseY)) 122 | 123 | // display the LMB button status 124 | wnd.StartRow() 125 | lmbAction := uiman.GetMouseButtonAction(0) 126 | if lmbAction == gui.MouseUp { 127 | wnd.Text("LMB = UP") 128 | } else if lmbAction == gui.MouseDown { 129 | wnd.Text("LMB = DOWN") 130 | } 131 | 132 | // display the RMB button status 133 | wnd.StartRow() 134 | rmbAction := uiman.GetMouseButtonAction(1) 135 | if rmbAction == gui.MouseUp { 136 | wnd.Text("RMB = UP") 137 | } else if rmbAction == gui.MouseDown { 138 | wnd.Text("RMB = DOWN") 139 | } 140 | }) 141 | mouseTestWindow.ShowTitleBar = false 142 | mouseTestWindow.IsMoveable = false 143 | mouseTestWindow.AutoAdjustHeight = true 144 | 145 | var color [4]int 146 | var truth bool 147 | var longString = "This is a longer text string and the editor probably can't show all of it." 148 | 149 | // create a window that looks a bit like a property editor 150 | propertyTestWindow := uiman.NewWindow("PropertyTest", 0.01, 0.85, 0.2, 0.25, func(wnd *gui.Window) { 151 | // throw a few test buttons into the mix 152 | wnd.RequestItemWidthMin(.5) 153 | wnd.Button("TestBtn0", "Button0") 154 | wnd.RequestItemWidthMin(.5) 155 | wnd.Button("TestBtn1", "Button1") 156 | 157 | wnd.Separator() 158 | wnd.Editbox("TestLongEdit", &longString) 159 | 160 | const colWidth = 0.33 161 | wnd.Separator() 162 | wnd.RequestItemWidthMin(colWidth) 163 | wnd.Text("Int Slider") 164 | wnd.SliderInt("IntSlider", &testInt, 0, 255) 165 | 166 | wnd.StartRow() 167 | wnd.RequestItemWidthMin(colWidth) 168 | wnd.Text("Slider Big Label") 169 | wnd.SliderFloat("FloatSlider", &testFloat, 0, 1.0) 170 | 171 | wnd.StartRow() 172 | wnd.RequestItemWidthMin(colWidth) 173 | wnd.Text("Int Drag") 174 | wnd.DragSliderInt("DragInt", 0.5, &testInt2) 175 | 176 | wnd.StartRow() 177 | wnd.RequestItemWidthMin(colWidth) 178 | wnd.Text("Float Drag") 179 | wnd.DragSliderFloat("DragFloat", 0.1, &testFloat2) 180 | 181 | wnd.StartRow() 182 | wnd.RequestItemWidthMin(colWidth) 183 | wnd.Text("Color") 184 | wnd.RequestItemWidthMax(0.165) 185 | wnd.SliderInt("Color1", &color[0], 0, 255) 186 | wnd.RequestItemWidthMax(0.165) 187 | wnd.SliderInt("Color2", &color[1], 0, 255) 188 | wnd.RequestItemWidthMax(0.165) 189 | wnd.SliderInt("Color3", &color[2], 0, 255) 190 | wnd.RequestItemWidthMax(0.165) 191 | wnd.SliderInt("Color4", &color[3], 0, 255) 192 | 193 | wnd.StartRow() 194 | wnd.Checkbox("checkTrue", &truth) 195 | wnd.Text("Active") 196 | 197 | wnd.Separator() 198 | var nodeOpen bool 199 | if nodeOpen, _ = wnd.TreeNode("TN_Hello1", "Tree Node #1"); nodeOpen { 200 | wnd.Indent() 201 | wnd.StartRow() 202 | if nodeOpen, _ = wnd.TreeNode("TN_Hello2", "Tree Node #2"); nodeOpen { 203 | wnd.Indent() 204 | wnd.StartRow() 205 | if nodeOpen, _ = wnd.TreeNode("TN_Hello3", "Tree Node #3"); nodeOpen { 206 | wnd.Indent() 207 | wnd.StartRow() 208 | wnd.Text("Leaf #1") 209 | wnd.Unindent() 210 | } 211 | wnd.Unindent() 212 | } 213 | wnd.Unindent() 214 | } 215 | wnd.StartRow() 216 | if nodeOpen, _ = wnd.TreeNode("TN_Hello4", "Tree Node #4"); nodeOpen { 217 | wnd.Indent() 218 | wnd.StartRow() 219 | wnd.Text("Leaf #2") 220 | wnd.Unindent() 221 | } 222 | }) 223 | propertyTestWindow.Title = "Property Test" 224 | propertyTestWindow.ShowTitleBar = true 225 | propertyTestWindow.IsMoveable = true 226 | propertyTestWindow.AutoAdjustHeight = false 227 | propertyTestWindow.ShowScrollBar = true 228 | propertyTestWindow.IsScrollable = true 229 | 230 | // create a simple window to house an editbox and a button 231 | editString := "/c/gocode/src" 232 | editboxWindow := uiman.NewWindow("EditboxWnd", 0.3, 0.99, 0.6, 0.0, func(wnd *gui.Window) { 233 | wnd.Button("EditboxButton", "Press Me") 234 | wnd.Editbox("Editbox1", &editString) 235 | }) 236 | editboxWindow.Title = "Editbox Test" 237 | editboxWindow.ShowTitleBar = false 238 | editboxWindow.AutoAdjustHeight = true 239 | 240 | // create a log window 241 | mainWindow = uiman.NewWindow("MainWnd", 0.5, 0.7, 0.4, 0.4, func(wnd *gui.Window) { 242 | wnd.Text(fmt.Sprintf("Current FPS = %d ; frame delta = %0.06g ms", lastCalcFPS, frameDelta/1000.0)) 243 | }) 244 | mainWindow.Title = "Widget Test" 245 | mainWindow.Style.WindowBgColor[3] = 1.0 // turn off transparent bg 246 | 247 | // make a toolbar style window at the bottom center of the screen showing 248 | // five test images. 249 | imgWS, imgHS := uiman.DisplayToScreen(16, 16) 250 | imageTestWindow = uiman.NewWindow("ImageTest", 0.5-imgWS*4*2.5, imgHS*4, imgWS*4*5, imgHS*4, func(wnd *gui.Window) { 251 | imageTexIndex := uiman.AddTextureToStack(potionsTex) 252 | const offset = 0.1 / 16.0 / 2.0 253 | for i := 0; i < 5; i++ { 254 | wnd.Image("FontTexture", imgWS*4, imgHS*4, mgl.Vec4{1, 1, 1, 1}, imageTexIndex, mgl.Vec4{0.4 - offset, 0.5 + float32(i)*0.1 - offset, 0.5 - offset, 0.6 + float32(i)*0.1 - offset}) 255 | } 256 | }) 257 | imageTestWindow.Title = "Image Test" 258 | imageTestWindow.ShowTitleBar = false 259 | imageTestWindow.IsMoveable = false 260 | imageTestWindow.Style.WindowBgColor[3] = 0.0 // transparent 261 | imageTestWindow.Style.WindowPadding = mgl.Vec4{0, 0, 0, 0} // no padding 262 | 263 | // make a test window that will just have a custom 3d rendering view 264 | const windowSize = 256 265 | customMargin := mgl.Vec4{0, 0, 0, 0} 266 | customWS, customHS := uiman.DisplayToScreen(windowSize, windowSize) 267 | 268 | renderer := forward.NewForwardRenderer(gfx) 269 | // load data for custom rendering 270 | renderer.ChangeResolution(windowSize, windowSize) 271 | defer renderer.Destroy() 272 | 273 | // put a light in there 274 | light := renderer.NewDirectionalLight(mgl.Vec3{1.0, -0.5, -1.0}) 275 | light.AmbientIntensity = 0.20 276 | light.DiffuseIntensity = 0.70 277 | light.SpecularIntensity = 0.10 278 | renderer.ActiveLights[0] = light 279 | 280 | // load the diffuse shader 281 | diffuseShader, err := forward.CreateBasicShader() 282 | if err != nil { 283 | panic("Failed to compile and link the diffuse shader program!\n" + err.Error()) 284 | } 285 | defer diffuseShader.Destroy() 286 | 287 | // create a 2x2x2 cube to render 288 | const cubeRadsPerSec = math.Pi / 4.0 289 | cube := fizzle.CreateCube(-1, -1, -1, 1, 1, 1) 290 | cube.Material = fizzle.NewMaterial() 291 | cube.Material.Shader = diffuseShader 292 | cube.Material.DiffuseColor = mgl.Vec4{0.9, 0.05, 0.05, 1.0} 293 | cube.Material.SpecularColor = mgl.Vec4{1.0, 1.0, 1.0, 1.0} 294 | cube.Material.Shininess = 4.8 295 | 296 | // setup the camera to look at the cube 297 | camera := fizzle.NewOrbitCamera(mgl.Vec3{0, 0, 0}, math.Pi/2.0, 5.0, math.Pi/2.0) 298 | 299 | // now create the window itself 300 | customWindow := uiman.NewWindow("CustomTest", 0.01, 0.5, customWS, customHS, func(wnd *gui.Window) { 301 | wnd.Custom(customWS, customHS, customMargin, func() { 302 | // rotate the cube and sphere around the Y axis at a speed of radsPerSec 303 | rotDelta := mgl.QuatRotate(cubeRadsPerSec*float32(wnd.Owner.FrameDelta), mgl.Vec3{0.0, 1.0, 0.0}) 304 | cube.LocalRotation = cube.LocalRotation.Mul(rotDelta) 305 | 306 | gfx.ClearColor(0.0, 0.0, 0.0, 1.0) 307 | gfx.Clear(graphics.COLOR_BUFFER_BIT | graphics.DEPTH_BUFFER_BIT) 308 | 309 | perspective := mgl.Perspective(mgl.DegToRad(60.0), float32(windowSize)/float32(windowSize), 1.0, 100.0) 310 | view := camera.GetViewMatrix() 311 | renderer.DrawRenderable(cube, nil, perspective, view, camera) 312 | }) 313 | }) 314 | customWindow.Title = "Custom Widget" 315 | customWindow.ShowTitleBar = true 316 | customWindow.Style.WindowPadding = mgl.Vec4{0, 0, 0, 0} 317 | 318 | // set some additional OpenGL flags 319 | gfx.BlendEquation(graphics.FUNC_ADD) 320 | gfx.BlendFunc(graphics.SRC_ALPHA, graphics.ONE_MINUS_SRC_ALPHA) 321 | gfx.Enable(graphics.BLEND) 322 | gfx.Enable(graphics.TEXTURE_2D) 323 | gfx.Enable(graphics.CULL_FACE) 324 | 325 | // enter the renderloop 326 | thisFrame = time.Now() 327 | for !glfwWindow.ShouldClose() { 328 | // draw the sample 329 | thisFrame = time.Now() 330 | frameDelta = thisFrame.Sub(lastFrame).Seconds() 331 | renderFrame(frameDelta) 332 | 333 | // draw the screen and get any input 334 | glfwWindow.SwapBuffers() 335 | glfw.PollEvents() 336 | 337 | // update the last render time 338 | lastFrame = thisFrame 339 | } 340 | } 341 | 342 | // onWindowResize is called when the window changes size 343 | func onWindowResize(w *glfw.Window, width int, height int) { 344 | uiman.AdviseResolution(int32(width), int32(height)) 345 | } 346 | 347 | // initGraphics creates an OpenGL window and initializes the required graphics libraries. 348 | // It will either succeed or panic. 349 | func initGraphics(title string, w int, h int) (*glfw.Window, graphics.GraphicsProvider) { 350 | // GLFW must be initialized before it's called 351 | err := glfw.Init() 352 | if err != nil { 353 | panic("Can't init glfw! " + err.Error()) 354 | } 355 | 356 | // request a OpenGL 3.3 core context 357 | glfw.WindowHint(glfw.Samples, 0) 358 | glfw.WindowHint(glfw.ContextVersionMajor, 3) 359 | glfw.WindowHint(glfw.ContextVersionMinor, 3) 360 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 361 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 362 | 363 | // do the actual window creation 364 | mainWindow, err := glfw.CreateWindow(w, h, title, nil, nil) 365 | if err != nil { 366 | panic("Failed to create the main window! " + err.Error()) 367 | } 368 | mainWindow.SetSizeCallback(onWindowResize) 369 | mainWindow.MakeContextCurrent() 370 | 371 | // disable v-sync for max draw rate 372 | glfw.SwapInterval(0) 373 | 374 | // initialize OpenGL 375 | gfx, err := gl.InitOpenGL() 376 | if err != nil { 377 | panic("Failed to initialize OpenGL! " + err.Error()) 378 | } 379 | fizzle.SetGraphics(gfx) 380 | 381 | return mainWindow, gfx 382 | } 383 | -------------------------------------------------------------------------------- /examples/screenshots/basic_ss_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbogdala/eweygewey/3a487316a7c34a2377a865cb2c313162fb21e715/examples/screenshots/basic_ss_0.jpg -------------------------------------------------------------------------------- /font.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | /* 7 | Based primarily on gltext found at https://github.com/go-gl/gltext 8 | But also based on examples from the freetype-go project: 9 | 10 | https://github.com/golang/freetype 11 | 12 | This implementation differs in the way the images are rendered and then 13 | copied into an OpenGL texture. In addition to that, this module can 14 | create a renderable 'string' node which is a bunch of polygons with uv's 15 | mapped to the appropriate glyphs. 16 | */ 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "io/ioutil" 24 | "math" 25 | "os" 26 | 27 | mgl "github.com/go-gl/mathgl/mgl32" 28 | ft "github.com/golang/freetype" 29 | "github.com/golang/freetype/truetype" 30 | graphics "github.com/tbogdala/fizzle/graphicsprovider" 31 | imgfont "golang.org/x/image/font" 32 | "golang.org/x/image/math/fixed" 33 | ) 34 | 35 | // runeData stores information pulled from the freetype parsing of glyphs. 36 | type runeData struct { 37 | imgX, imgY int // offset into the image texture for the top left position of rune 38 | advanceWidth, leftSideBearing float32 // HMetric data from glyph 39 | advanceHeight, topSideBearing float32 // VMetric data from glyph 40 | uvMinX, uvMinY float32 41 | uvMaxX, uvMaxY float32 42 | } 43 | 44 | // Font contains data regarding a font and the texture that was created 45 | // with the specified set of glyphs. It can then be used to create 46 | // renderable string objects. 47 | type Font struct { 48 | Texture graphics.Texture 49 | TextureSize int 50 | Glyphs string 51 | GlyphHeight float32 52 | GlyphWidth float32 53 | Owner *Manager 54 | locations map[rune]runeData 55 | opts truetype.Options 56 | face imgfont.Face 57 | } 58 | 59 | // newFont takes a fontFilepath and uses the Go freetype library to parse it 60 | // and render the specified glyphs to a texture that is then buffered into OpenGL. 61 | func newFont(owner *Manager, fontFilepath string, scaleInt int, glyphs string) (f *Font, e error) { 62 | // Load the font used for UI interaction 63 | fontFile, err := os.Open(fontFilepath) 64 | if err != nil { 65 | return f, fmt.Errorf("Failed to open the font file.\n%v", err) 66 | } 67 | defer fontFile.Close() 68 | 69 | // load in the font 70 | fontBytes, err := ioutil.ReadAll(fontFile) 71 | if err != nil { 72 | return f, fmt.Errorf("Failed to load font data from stream.\n%v", err) 73 | } 74 | 75 | return newFontBytes(owner, fontBytes, scaleInt, glyphs) 76 | } 77 | 78 | // newFontBytes takes a byte slice representing the font and uses the Go freetype library to parse it 79 | // and render the specified glyphs to a texture that is then buffered into OpenGL. 80 | func newFontBytes(owner *Manager, fontBytes []byte, scaleInt int, glyphs string) (f *Font, e error) { 81 | f = new(Font) 82 | scale := fixed.I(scaleInt) 83 | 84 | // allocate the location map 85 | f.locations = make(map[rune]runeData) 86 | 87 | // parse the truetype font data 88 | ttfData, err := ft.ParseFont(fontBytes) 89 | if err != nil { 90 | return f, fmt.Errorf("Failed to prase the truetype font data.\n%v", err) 91 | } 92 | 93 | f.opts.Size = float64(scaleInt) 94 | f.face = truetype.NewFace(ttfData, &f.opts) 95 | 96 | // this may have negative components, but get the bounds for the font 97 | glyphBounds := ttfData.Bounds(scale) 98 | 99 | // width and height are getting +2 here since the glyph will be buffered by a 100 | // pixel in the texture 101 | glyphDimensions := glyphBounds.Max.Sub(glyphBounds.Min) 102 | glyphWidth := fixedInt26ToFloat(glyphDimensions.X) 103 | glyphHeight := fixedInt26ToFloat(glyphDimensions.Y) 104 | glyphCeilWidth := int(math.Ceil(float64(glyphWidth))) 105 | glyphCeilHeight := int(math.Ceil(float64(glyphHeight))) 106 | 107 | // create the buffer image used to draw the glyphs 108 | glyphRect := image.Rect(0, 0, glyphCeilWidth, glyphCeilHeight) 109 | glyphImg := image.NewRGBA(glyphRect) 110 | 111 | // calculate the area needed for the font texture 112 | var fontTexSize = 2 113 | minAreaNeeded := (glyphCeilWidth) * (glyphCeilHeight) * len(glyphs) 114 | for (fontTexSize * fontTexSize) < minAreaNeeded { 115 | fontTexSize *= 2 116 | if fontTexSize > 2048 { 117 | return f, fmt.Errorf("Font texture was going to exceed 2048x2048 and that's currently not supported.") 118 | } 119 | } 120 | 121 | // create the font image 122 | fontImgRect := image.Rect(0, 0, fontTexSize, fontTexSize) 123 | fontImg := image.NewRGBA(fontImgRect) 124 | 125 | // the number of glyphs 126 | fontRowSize := fontTexSize / glyphCeilWidth 127 | 128 | // create the freetype context 129 | c := ft.NewContext() 130 | c.SetDPI(72) 131 | c.SetFont(ttfData) 132 | c.SetFontSize(float64(scaleInt)) 133 | c.SetClip(glyphImg.Bounds()) 134 | c.SetDst(glyphImg) 135 | c.SetSrc(image.White) 136 | 137 | // NOTE: always disabled for now since it causes a stack overflow error 138 | //c.SetHinting(imgfont.HintingFull) 139 | 140 | var fx, fy int 141 | for _, ch := range glyphs { 142 | index := ttfData.Index(ch) 143 | metricH := ttfData.HMetric(scale, index) 144 | metricV := ttfData.VMetric(scale, index) 145 | 146 | fxGW := fx * glyphCeilWidth 147 | fyGH := fy * glyphCeilHeight 148 | 149 | f.locations[ch] = runeData{ 150 | fxGW, fyGH, 151 | fixedInt26ToFloat(metricH.AdvanceWidth), fixedInt26ToFloat(metricH.LeftSideBearing), 152 | fixedInt26ToFloat(metricV.AdvanceHeight), fixedInt26ToFloat(metricV.TopSideBearing), 153 | float32(fxGW) / float32(fontTexSize), (float32(fyGH) + glyphHeight) / float32(fontTexSize), 154 | (float32(fxGW) + glyphWidth) / float32(fontTexSize), float32(fyGH) / float32(fontTexSize), 155 | } 156 | 157 | pt := ft.Pt(1, 1+int(c.PointToFixed(float64(scaleInt))>>6)) 158 | _, err := c.DrawString(string(ch), pt) 159 | if err != nil { 160 | return f, fmt.Errorf("Freetype returned an error while drawing a glyph: %v.", err) 161 | } 162 | 163 | // copy the glyph image into the font image 164 | for subY := 0; subY < glyphCeilHeight; subY++ { 165 | for subX := 0; subX < glyphCeilWidth; subX++ { 166 | glyphRGBA := glyphImg.RGBAAt(subX, subY) 167 | fontImg.SetRGBA((fxGW)+subX, (fyGH)+subY, glyphRGBA) 168 | } 169 | } 170 | 171 | // erase the glyph image buffer 172 | draw.Draw(glyphImg, glyphImg.Bounds(), image.Transparent, image.ZP, draw.Src) 173 | 174 | // adjust the pointers into the font image 175 | fx++ 176 | if fx > fontRowSize { 177 | fx = 0 178 | fy++ 179 | } 180 | 181 | } 182 | 183 | // set the white point 184 | fontImg.SetRGBA(fontTexSize-1, fontTexSize-1, color.RGBA{R: 255, G: 255, B: 255, A: 255}) 185 | 186 | // buffer the font image into an OpenGL texture 187 | f.Glyphs = glyphs 188 | f.TextureSize = fontTexSize 189 | f.GlyphWidth = glyphWidth 190 | f.GlyphHeight = glyphHeight 191 | f.Owner = owner 192 | f.Texture = f.loadRGBAToTexture(fontImg.Pix, int32(fontImg.Rect.Max.X)) 193 | 194 | return 195 | } 196 | 197 | // Destroy releases the OpenGL texture for the font. 198 | func (f *Font) Destroy() { 199 | f.Owner.gfx.DeleteTexture(f.Texture) 200 | } 201 | 202 | // GetCurrentScale returns the scale value for the font based on the current 203 | // Manager's resolution vs the resolution the UI was designed for. 204 | func (f *Font) GetCurrentScale() float32 { 205 | _, uiHeight := f.Owner.GetResolution() 206 | designHeight := f.Owner.GetDesignHeight() 207 | return float32(uiHeight) / float32(designHeight) 208 | } 209 | 210 | // GetRenderSize returns the width and height necessary in pixels for the 211 | // font to display a string. The third return value is the advance height the string. 212 | func (f *Font) GetRenderSize(msg string) (float32, float32, float32) { 213 | var w, h float32 214 | 215 | // see how much to scale the size based on current resolution vs desgin resolution 216 | fontScale := f.GetCurrentScale() 217 | 218 | for _, ch := range msg { 219 | bounds, _, _ := f.face.GlyphBounds(ch) 220 | glyphDimensions := bounds.Max.Sub(bounds.Min) 221 | 222 | adv, _ := f.face.GlyphAdvance(ch) 223 | w += fixedInt26ToFloat(adv) 224 | 225 | glyphDYf := fixedInt26ToFloat(glyphDimensions.Y) 226 | if h < glyphDYf { 227 | h = glyphDYf 228 | } 229 | } 230 | 231 | metrics := f.face.Metrics() 232 | advH := fixedInt26ToFloat(metrics.Ascent) 233 | 234 | return w * fontScale, h * fontScale, advH * fontScale 235 | } 236 | 237 | // OffsetFloor returns the maximum width offset that will fit between characters that 238 | // is still smaller than the offset passed in. 239 | func (f *Font) OffsetFloor(msg string, offset float32) float32 { 240 | var w float32 241 | 242 | // see how much to scale the size based on current resolution vs desgin resolution 243 | fontScale := f.GetCurrentScale() 244 | 245 | for _, ch := range msg { 246 | adv, ok := f.face.GlyphAdvance(ch) 247 | if !ok { 248 | fmt.Printf("ERROR on glyphadvance for %c!\n", ch) 249 | } 250 | advf := fixedInt26ToFloat(adv) 251 | 252 | // break if we go over the distance 253 | if w+advf > offset { 254 | break 255 | } 256 | w += advf 257 | } 258 | 259 | return w * fontScale 260 | } 261 | 262 | // OffsetForIndex returns the width offset that will fit just before the `stopIndex` 263 | // number character in the msg. 264 | func (f *Font) OffsetForIndex(msg string, stopIndex int) float32 { 265 | return f.OffsetForIndexAdv(msg, 0, stopIndex) 266 | } 267 | 268 | // OffsetForIndexAdv returns the width offset that will fit just before the `stopIndex` 269 | // number character in the msg, starting at charStartIndex. 270 | func (f *Font) OffsetForIndexAdv(msg string, charStartIndex int, stopIndex int) float32 { 271 | var w float32 272 | 273 | // sanity test the input 274 | if len(msg) < 1 { 275 | return 0.0 276 | } 277 | if charStartIndex > stopIndex { 278 | return 0.0 279 | } 280 | 281 | // see how much to scale the size based on current resolution vs desgin resolution 282 | fontScale := f.GetCurrentScale() 283 | for i, ch := range msg[charStartIndex:] { 284 | // calculate up to the stopIndex but do not include it 285 | if i+charStartIndex >= stopIndex { 286 | break 287 | } 288 | adv, _ := f.face.GlyphAdvance(ch) 289 | w += fixedInt26ToFloat(adv) 290 | } 291 | 292 | return w * fontScale 293 | } 294 | 295 | // fixedInt26ToFloat converts a fixed int 26:6 precision to a float32. 296 | func fixedInt26ToFloat(fixedInt fixed.Int26_6) float32 { 297 | var result float32 298 | i := int32(fixedInt) 299 | result += float32(i >> 6) 300 | result += float32(i&0x003F) / float32(64.0) 301 | return result 302 | } 303 | 304 | // TextRenderData is a structure containing the raw OpenGL VBO data needed 305 | // to render a text string for a given texture. 306 | type TextRenderData struct { 307 | ComboBuffer []float32 // the combo VBO data (vert/uv/color) 308 | IndexBuffer []uint32 // the element index VBO data 309 | Faces uint32 // the number of faces in the text string 310 | Width float32 // the width in pixels of the text string 311 | Height float32 // the height in pixels of the text string 312 | AdvanceHeight float32 // the amount of pixels to move the pen in the verticle direction 313 | CursorOverflowRight bool // whether or not the cursor was too far to the right for string width 314 | } 315 | 316 | // CreateText makes a new renderable object from the supplied string 317 | // using the data in the font. The data is returned as a TextRenderData object. 318 | func (f *Font) CreateText(pos mgl.Vec3, color mgl.Vec4, msg string) TextRenderData { 319 | return f.CreateTextAdv(pos, color, -1.0, -1, -1, msg) 320 | } 321 | 322 | // CreateTextAdv makes a new renderable object from the supplied string 323 | // using the data in the font. The string returned will be the maximum amount of the msg that fits 324 | // the specified maxWidth (if greater than 0.0) starting at the charOffset specified. 325 | // The data is returned as a TextRenderData object. 326 | func (f *Font) CreateTextAdv(pos mgl.Vec3, color mgl.Vec4, maxWidth float32, charOffset int, cursorPosition int, msg string) TextRenderData { 327 | // this is the texture ID of the font to use in the shader; by default 328 | // the library always binds the font to the first texture sampler. 329 | const floatTexturePosition = 0.0 330 | 331 | // sanity checks 332 | originalLen := len(msg) 333 | trimmedMsg := msg 334 | if originalLen == 0 { 335 | return TextRenderData{ 336 | ComboBuffer: nil, 337 | IndexBuffer: nil, 338 | Faces: 0, 339 | Width: 0.0, 340 | Height: 0.0, 341 | AdvanceHeight: 0.0, 342 | CursorOverflowRight: false, 343 | } 344 | } 345 | if charOffset > 0 && charOffset < originalLen { 346 | // trim the string based on incoming character offset 347 | trimmedMsg = trimmedMsg[charOffset:] 348 | } 349 | 350 | // get the length of our message 351 | msgLength := len(trimmedMsg) 352 | 353 | // create the arrays to hold the data to buffer to OpenGL 354 | comboBuffer := make([]float32, 0, msgLength*(2+2+4)*4) // pos, uv, color4 355 | indexBuffer := make([]uint32, 0, msgLength*6) // two faces * three indexes 356 | 357 | // do a preliminary test to see how much room the message will take up 358 | dimX, dimY, advH := f.GetRenderSize(trimmedMsg) 359 | 360 | // see how much to scale the size based on current resolution vs desgin resolution 361 | fontScale := f.GetCurrentScale() 362 | 363 | // loop through the message 364 | var totalChars = 0 365 | var scaledSize float32 = 0.0 366 | var cursorOverflowRight bool 367 | var penX = pos[0] 368 | var penY = pos[1] - float32(advH) 369 | for chi, ch := range trimmedMsg { 370 | // get the rune data 371 | chData := f.locations[ch] 372 | 373 | /* 374 | bounds, _, _ := f.face.GlyphBounds(ch) 375 | glyphD := bounds.Max.Sub(bounds.Min) 376 | glyphAdvW, _ := f.face.GlyphAdvance(ch) 377 | metrics := f.face.Metrics() 378 | glyphAdvH := float32(metrics.Ascent.Round()) 379 | 380 | glyphH := float32(glyphD.Y.Round()) 381 | glyphW := float32(glyphD.X.Round()) 382 | advHeight := glyphAdvH 383 | advWidth := float32(glyphAdvW.Round()) 384 | */ 385 | 386 | glyphH := f.GlyphHeight 387 | glyphW := f.GlyphWidth 388 | advHeight := chData.advanceHeight 389 | advWidth := chData.advanceWidth 390 | 391 | // possibly stop here if we're going to overflow the max width 392 | if maxWidth > 0.0 && scaledSize+(advWidth*fontScale) > maxWidth { 393 | // we overflowed the size of the string, now check to see if 394 | // the cursor position is covered within this string or if that hasn't 395 | // been reached yet. 396 | if cursorPosition >= 0 && cursorPosition-charOffset > chi { 397 | cursorOverflowRight = true 398 | } 399 | 400 | // adjust the dimX here since we shortened the string 401 | dimX = scaledSize 402 | break 403 | } 404 | scaledSize += advWidth * fontScale 405 | 406 | // setup the coordinates for ther vetexes 407 | x0 := penX 408 | y0 := penY - (glyphH-advHeight)*fontScale 409 | x1 := x0 + glyphW*fontScale 410 | y1 := y0 + glyphH*fontScale 411 | s0 := chData.uvMinX 412 | t0 := chData.uvMinY 413 | s1 := chData.uvMaxX 414 | t1 := chData.uvMaxY 415 | 416 | // set the vertex data 417 | comboBuffer = append(comboBuffer, x1) 418 | comboBuffer = append(comboBuffer, y0) 419 | comboBuffer = append(comboBuffer, s1) 420 | comboBuffer = append(comboBuffer, t0) 421 | comboBuffer = append(comboBuffer, floatTexturePosition) 422 | comboBuffer = append(comboBuffer, color[:]...) 423 | 424 | comboBuffer = append(comboBuffer, x1) 425 | comboBuffer = append(comboBuffer, y1) 426 | comboBuffer = append(comboBuffer, s1) 427 | comboBuffer = append(comboBuffer, t1) 428 | comboBuffer = append(comboBuffer, floatTexturePosition) 429 | comboBuffer = append(comboBuffer, color[:]...) 430 | 431 | comboBuffer = append(comboBuffer, x0) 432 | comboBuffer = append(comboBuffer, y1) 433 | comboBuffer = append(comboBuffer, s0) 434 | comboBuffer = append(comboBuffer, t1) 435 | comboBuffer = append(comboBuffer, floatTexturePosition) 436 | comboBuffer = append(comboBuffer, color[:]...) 437 | 438 | comboBuffer = append(comboBuffer, x0) 439 | comboBuffer = append(comboBuffer, y0) 440 | comboBuffer = append(comboBuffer, s0) 441 | comboBuffer = append(comboBuffer, t0) 442 | comboBuffer = append(comboBuffer, floatTexturePosition) 443 | comboBuffer = append(comboBuffer, color[:]...) 444 | 445 | startIndex := uint32(chi) * 4 446 | indexBuffer = append(indexBuffer, startIndex) 447 | indexBuffer = append(indexBuffer, startIndex+1) 448 | indexBuffer = append(indexBuffer, startIndex+2) 449 | 450 | indexBuffer = append(indexBuffer, startIndex+2) 451 | indexBuffer = append(indexBuffer, startIndex+3) 452 | indexBuffer = append(indexBuffer, startIndex) 453 | 454 | // advance the pen 455 | penX += advWidth * fontScale 456 | totalChars++ 457 | } 458 | 459 | return TextRenderData{ 460 | ComboBuffer: comboBuffer, 461 | IndexBuffer: indexBuffer, 462 | Faces: uint32(totalChars * 2), 463 | Width: float32(dimX), 464 | Height: float32(dimY), 465 | AdvanceHeight: float32(advH), 466 | CursorOverflowRight: cursorOverflowRight, 467 | } 468 | } 469 | 470 | // loadRGBAToTexture takes a byte slice and throws it into an OpenGL texture. 471 | func (f *Font) loadRGBAToTexture(rgba []byte, imageSize int32) graphics.Texture { 472 | return f.loadRGBAToTextureExt(rgba, imageSize, graphics.LINEAR, graphics.LINEAR, graphics.CLAMP_TO_EDGE, graphics.CLAMP_TO_EDGE) 473 | } 474 | 475 | // loadRGBAToTextureExt takes a byte slice and throws it into an OpenGL texture. 476 | func (f *Font) loadRGBAToTextureExt(rgba []byte, imageSize, magFilter, minFilter, wrapS, wrapT int32) graphics.Texture { 477 | tex := f.Owner.gfx.GenTexture() 478 | f.Owner.gfx.ActiveTexture(graphics.TEXTURE0) 479 | f.Owner.gfx.BindTexture(graphics.TEXTURE_2D, tex) 480 | f.Owner.gfx.TexParameteri(graphics.TEXTURE_2D, graphics.TEXTURE_MAG_FILTER, magFilter) 481 | f.Owner.gfx.TexParameteri(graphics.TEXTURE_2D, graphics.TEXTURE_MIN_FILTER, minFilter) 482 | f.Owner.gfx.TexParameteri(graphics.TEXTURE_2D, graphics.TEXTURE_WRAP_S, wrapS) 483 | f.Owner.gfx.TexParameteri(graphics.TEXTURE_2D, graphics.TEXTURE_WRAP_T, wrapT) 484 | f.Owner.gfx.TexImage2D(graphics.TEXTURE_2D, 0, graphics.RGBA, imageSize, imageSize, 0, graphics.RGBA, graphics.UNSIGNED_BYTE, f.Owner.gfx.Ptr(rgba), len(rgba)) 485 | return tex 486 | } 487 | -------------------------------------------------------------------------------- /glfwinput/glfwinput.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package glfw 5 | 6 | import ( 7 | "time" 8 | 9 | glfw "github.com/go-gl/glfw/v3.1/glfw" 10 | mgl "github.com/go-gl/mathgl/mgl32" 11 | gui "github.com/tbogdala/eweygewey" 12 | ) 13 | 14 | // mouseButtonData is used to track button presses between frames. 15 | type mouseButtonData struct { 16 | // lastPress is the time the last UP->DOWN transition took place 17 | lastPress time.Time 18 | 19 | // lastPressLocation is the position of the mouse when the last UP->DOWN 20 | // transition took place 21 | lastPressLocation mgl.Vec2 22 | 23 | // lastAction was the last detected action for the button 24 | lastAction int 25 | 26 | // doubleClickDetected should be set to true if the last UP->DOWN->UP 27 | // sequence was fast enough to be a double click. 28 | doubleClickDetected bool 29 | 30 | // lastCheckedAt should be set to the time the functions last checked 31 | // for action. This way, input can be polled only once per frame. 32 | lastCheckedAt time.Time 33 | } 34 | 35 | // SetInputHandlers sets the input callbacks for the GUI Manager to work with 36 | // GLFW. This function takes advantage of closures to track input across 37 | // multiple calls. 38 | func SetInputHandlers(uiman *gui.Manager, window *glfw.Window) { 39 | lastMouseX := -1.0 40 | lastMouseY := -1.0 41 | lastDeltaX := -1.0 42 | lastDeltaY := -1.0 43 | 44 | needsMousePosCheck := true 45 | 46 | // at the start of a new frame, reset some flags 47 | uiman.AddConstructionStartCallback(func(startTime time.Time) { 48 | needsMousePosCheck = true 49 | }) 50 | 51 | uiman.GetMousePosition = func() (float32, float32) { 52 | // if we've already checked the position this frame, then just return 53 | // the old coordinates using math 54 | if needsMousePosCheck == false { 55 | return float32(lastMouseX), float32(lastMouseY) 56 | } 57 | 58 | x, y := window.GetCursorPos() 59 | 60 | // in this package, we reverse the Y location so that the origin 61 | // is in the lower left corner and not the top left corner. 62 | _, resY := uiman.GetResolution() 63 | y = float64(resY) - y 64 | 65 | lastDeltaX = x - lastMouseX 66 | lastDeltaY = y - lastMouseY 67 | lastMouseX = x 68 | lastMouseY = y 69 | needsMousePosCheck = false 70 | return float32(x), float32(y) 71 | } 72 | 73 | uiman.GetMousePositionDelta = func() (float32, float32) { 74 | // test to see if we polled the delta this frame 75 | if needsMousePosCheck { 76 | // if not, then update the location data 77 | uiman.GetMousePosition() 78 | } 79 | return float32(lastDeltaX), float32(lastDeltaY) 80 | } 81 | 82 | const doubleClickThreshold = 0.5 // seconds 83 | mouseButtonTracker := make(map[int]mouseButtonData) 84 | 85 | uiman.GetMouseButtonAction = func(button int) int { 86 | var action int 87 | var mbData mouseButtonData 88 | var tracked bool 89 | 90 | // get the mouse button data and return the stale result if we're 91 | // in the same frame. 92 | mbData, tracked = mouseButtonTracker[button] 93 | if tracked == true && mbData.lastCheckedAt == uiman.FrameStart { 94 | return mbData.lastAction 95 | } 96 | 97 | // poll the button action 98 | glfwAction := window.GetMouseButton(glfw.MouseButton(int(glfw.MouseButton1) + button)) 99 | if glfwAction == glfw.Release { 100 | action = gui.MouseUp 101 | } else if glfwAction == glfw.Press { 102 | action = gui.MouseDown 103 | } else if glfwAction == glfw.Repeat { 104 | action = gui.MouseDown 105 | } 106 | 107 | // see if we're tracking this button yet 108 | if tracked == false { 109 | // create a new mouse button tracker data object 110 | if action == gui.MouseDown { 111 | mx, my := uiman.GetMousePosition() 112 | mbData.lastPressLocation = mgl.Vec2{mx, my} 113 | mbData.lastPress = uiman.FrameStart 114 | } else { 115 | mbData.lastPress = time.Unix(0, 0) 116 | } 117 | } else { 118 | if action == gui.MouseDown { 119 | // check to see if there was a transition from UP to DOWN 120 | if mbData.lastAction == gui.MouseUp { 121 | // check to see the time between the last UP->DOWN transition 122 | // and this one. If it's less than the double click threshold 123 | // then change the doubleClickDetected member so that the 124 | // next DOWN->UP will return a double click instead. 125 | if uiman.FrameStart.Sub(mbData.lastPress).Seconds() < doubleClickThreshold { 126 | mbData.doubleClickDetected = true 127 | } 128 | 129 | // count this as a press and log the time 130 | mx, my := uiman.GetMousePosition() 131 | mbData.lastPressLocation = mgl.Vec2{mx, my} 132 | mbData.lastPress = uiman.FrameStart 133 | } 134 | } else { 135 | // check to see if there was a transition from DOWN to UP 136 | if mbData.lastAction == gui.MouseDown { 137 | if mbData.doubleClickDetected { 138 | // return the double click 139 | action = gui.MouseDoubleClick 140 | 141 | // reset the tracker 142 | mbData.doubleClickDetected = false 143 | } else { 144 | // return the single click 145 | action = gui.MouseClick 146 | } 147 | } 148 | } 149 | } 150 | 151 | // put the updated data back into the map and return the action 152 | mbData.lastAction = action 153 | mbData.lastCheckedAt = uiman.FrameStart 154 | mouseButtonTracker[button] = mbData 155 | return action 156 | } 157 | 158 | uiman.ClearMouseButtonAction = func(buttonNumber int) { 159 | // get the mouse button data and return the stale result if we're 160 | // in the same frame. 161 | mbData, tracked := mouseButtonTracker[buttonNumber] 162 | if tracked == true { 163 | mbData.lastAction = gui.MouseUp 164 | mbData.doubleClickDetected = false 165 | mouseButtonTracker[buttonNumber] = mbData 166 | } 167 | } 168 | 169 | uiman.GetMouseDownPosition = func(button int) (float32, float32) { 170 | // test to see if we polled the delta this frame 171 | if needsMousePosCheck { 172 | // if not, then update the location data 173 | uiman.GetMousePosition() 174 | } 175 | 176 | // is the mouse button down? 177 | if uiman.GetMouseButtonAction(button) != gui.MouseUp { 178 | var tracked bool 179 | var mbData mouseButtonData 180 | 181 | // get the mouse button data and return the stale result if we're 182 | // in the same frame. 183 | mbData, tracked = mouseButtonTracker[button] 184 | if tracked == true { 185 | return mbData.lastPressLocation[0], mbData.lastPressLocation[1] 186 | } 187 | } 188 | 189 | // mouse not down or not tracked. 190 | return -1.0, -1.0 191 | } 192 | 193 | scrollWheelDelta := float32(0.0) 194 | scrollWheelCache := float32(0.0) 195 | uiman.GetScrollWheelDelta = func(useCached bool) float32 { 196 | if useCached { 197 | return scrollWheelCache 198 | } 199 | scrollWheelCache = scrollWheelDelta 200 | scrollWheelDelta = 0.0 201 | return scrollWheelCache 202 | } 203 | 204 | // create our own handler for the scroll wheel which then passes the 205 | // correct data to our own scroll wheel handler function 206 | window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 207 | scrollWheelDelta += float32(yoff) * uiman.ScrollSpeed 208 | }) 209 | 210 | // stores all of the key press events 211 | keyBuffer := []gui.KeyPressEvent{} 212 | 213 | // make a translation table from GLFW->EweyGewey key codes 214 | keyTranslation := make(map[glfw.Key]int) 215 | keyTranslation[glfw.KeyWorld1] = gui.EweyKeyWorld1 216 | keyTranslation[glfw.KeyWorld2] = gui.EweyKeyWorld2 217 | keyTranslation[glfw.KeyEscape] = gui.EweyKeyEscape 218 | keyTranslation[glfw.KeyEnter] = gui.EweyKeyEnter 219 | keyTranslation[glfw.KeyTab] = gui.EweyKeyTab 220 | keyTranslation[glfw.KeyBackspace] = gui.EweyKeyBackspace 221 | keyTranslation[glfw.KeyInsert] = gui.EweyKeyInsert 222 | keyTranslation[glfw.KeyDelete] = gui.EweyKeyDelete 223 | keyTranslation[glfw.KeyRight] = gui.EweyKeyRight 224 | keyTranslation[glfw.KeyLeft] = gui.EweyKeyLeft 225 | keyTranslation[glfw.KeyDown] = gui.EweyKeyDown 226 | keyTranslation[glfw.KeyUp] = gui.EweyKeyUp 227 | keyTranslation[glfw.KeyPageUp] = gui.EweyKeyPageUp 228 | keyTranslation[glfw.KeyPageDown] = gui.EweyKeyPageDown 229 | keyTranslation[glfw.KeyHome] = gui.EweyKeyHome 230 | keyTranslation[glfw.KeyEnd] = gui.EweyKeyEnd 231 | keyTranslation[glfw.KeyCapsLock] = gui.EweyKeyCapsLock 232 | keyTranslation[glfw.KeyNumLock] = gui.EweyKeyNumLock 233 | keyTranslation[glfw.KeyPrintScreen] = gui.EweyKeyPrintScreen 234 | keyTranslation[glfw.KeyPause] = gui.EweyKeyPause 235 | keyTranslation[glfw.KeyF1] = gui.EweyKeyF1 236 | keyTranslation[glfw.KeyF2] = gui.EweyKeyF2 237 | keyTranslation[glfw.KeyF3] = gui.EweyKeyF3 238 | keyTranslation[glfw.KeyF4] = gui.EweyKeyF4 239 | keyTranslation[glfw.KeyF5] = gui.EweyKeyF5 240 | keyTranslation[glfw.KeyF6] = gui.EweyKeyF6 241 | keyTranslation[glfw.KeyF7] = gui.EweyKeyF7 242 | keyTranslation[glfw.KeyF8] = gui.EweyKeyF8 243 | keyTranslation[glfw.KeyF9] = gui.EweyKeyF9 244 | keyTranslation[glfw.KeyF10] = gui.EweyKeyF10 245 | keyTranslation[glfw.KeyF11] = gui.EweyKeyF11 246 | keyTranslation[glfw.KeyF12] = gui.EweyKeyF12 247 | keyTranslation[glfw.KeyF13] = gui.EweyKeyF13 248 | keyTranslation[glfw.KeyF14] = gui.EweyKeyF14 249 | keyTranslation[glfw.KeyF15] = gui.EweyKeyF15 250 | keyTranslation[glfw.KeyF16] = gui.EweyKeyF16 251 | keyTranslation[glfw.KeyF17] = gui.EweyKeyF17 252 | keyTranslation[glfw.KeyF18] = gui.EweyKeyF18 253 | keyTranslation[glfw.KeyF19] = gui.EweyKeyF19 254 | keyTranslation[glfw.KeyF20] = gui.EweyKeyF20 255 | keyTranslation[glfw.KeyF21] = gui.EweyKeyF21 256 | keyTranslation[glfw.KeyF22] = gui.EweyKeyF22 257 | keyTranslation[glfw.KeyF23] = gui.EweyKeyF23 258 | keyTranslation[glfw.KeyF24] = gui.EweyKeyF24 259 | keyTranslation[glfw.KeyF25] = gui.EweyKeyF25 260 | keyTranslation[glfw.KeyLeftShift] = gui.EweyKeyLeftShift 261 | keyTranslation[glfw.KeyLeftAlt] = gui.EweyKeyLeftAlt 262 | keyTranslation[glfw.KeyLeftControl] = gui.EweyKeyLeftControl 263 | keyTranslation[glfw.KeyLeftSuper] = gui.EweyKeyLeftSuper 264 | keyTranslation[glfw.KeyRightShift] = gui.EweyKeyRightShift 265 | keyTranslation[glfw.KeyRightAlt] = gui.EweyKeyRightAlt 266 | keyTranslation[glfw.KeyRightControl] = gui.EweyKeyRightControl 267 | keyTranslation[glfw.KeyRightSuper] = gui.EweyKeyRightSuper 268 | 269 | //keyTranslation[glfw.Key] = gui.EweyKey 270 | 271 | // create our own handler for key input so that it can buffer the keys 272 | // and then consume them in an edit box or whatever widget has focus. 273 | var prevKeyCallback glfw.KeyCallback 274 | var prevCharModsCallback glfw.CharModsCallback 275 | prevKeyCallback = window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 276 | if action != glfw.Press && action != glfw.Repeat { 277 | return 278 | } 279 | 280 | // we have a new event, so init the structure 281 | var kpe gui.KeyPressEvent 282 | 283 | // try to look it up in the translation table; if it exists, then we log 284 | // the event; if it doesn't exist, then we assume it will be caught by 285 | // the CharMods callback. 286 | code, okay := keyTranslation[key] 287 | if okay == false { 288 | // there are some exceptions to this that will get implemented here. 289 | // when ctrl is held down, it doesn't appear that runes get sent 290 | // through the CharModsCallback function, so we must handle the 291 | // ones we want here. 292 | if (key == glfw.KeyV) && (mods&glfw.ModControl == glfw.ModControl) { 293 | kpe.Rune = 'V' 294 | kpe.IsRune = true 295 | kpe.CtrlDown = true 296 | } else { 297 | return 298 | } 299 | } else { 300 | kpe.KeyCode = code 301 | 302 | // set the modifier flags 303 | if mods&glfw.ModShift == glfw.ModShift { 304 | kpe.ShiftDown = true 305 | } 306 | if mods&glfw.ModAlt == glfw.ModAlt { 307 | kpe.AltDown = true 308 | } 309 | if mods&glfw.ModControl == glfw.ModControl { 310 | kpe.CtrlDown = true 311 | } 312 | if mods&glfw.ModSuper == glfw.ModSuper { 313 | kpe.SuperDown = true 314 | } 315 | } 316 | 317 | // add it to the keys that have been buffered 318 | keyBuffer = append(keyBuffer, kpe) 319 | 320 | // if there was a pre-existing callback, we'll chain it here 321 | if prevKeyCallback != nil { 322 | prevKeyCallback(w, key, scancode, action, mods) 323 | } 324 | }) 325 | 326 | window.SetCharModsCallback(func(w *glfw.Window, char rune, mods glfw.ModifierKey) { 327 | var kpe gui.KeyPressEvent 328 | //fmt.Printf("SetCharModsCallback Rune: %v | mods:%v | ctrl: %v\n", char, mods, mods&glfw.ModControl) 329 | 330 | // set the character 331 | kpe.Rune = char 332 | kpe.IsRune = true 333 | 334 | // set the modifier flags 335 | if mods&glfw.ModShift == glfw.ModShift { 336 | kpe.ShiftDown = true 337 | } 338 | if mods&glfw.ModAlt == glfw.ModAlt { 339 | kpe.AltDown = true 340 | } 341 | if mods&glfw.ModControl == glfw.ModControl { 342 | kpe.CtrlDown = true 343 | } 344 | if mods&glfw.ModSuper == glfw.ModSuper { 345 | kpe.SuperDown = true 346 | } 347 | 348 | // add it to the keys that have been buffered 349 | keyBuffer = append(keyBuffer, kpe) 350 | 351 | // if there was a pre-existing callback, we'll chain it here 352 | if prevCharModsCallback != nil { 353 | prevCharModsCallback(w, char, mods) 354 | } 355 | }) 356 | 357 | uiman.GetKeyEvents = func() []gui.KeyPressEvent { 358 | returnVal := keyBuffer 359 | keyBuffer = keyBuffer[:0] 360 | return returnVal 361 | } 362 | 363 | uiman.ClearKeyEvents = func() { 364 | keyBuffer = keyBuffer[:0] 365 | } 366 | 367 | uiman.GetClipboardString = func() (string, error) { 368 | return window.GetClipboardString() 369 | } 370 | 371 | uiman.SetClipboardString = func(clippy string) { 372 | window.SetClipboardString(clippy) 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | const ( 7 | EweyKeyUnknown = iota 8 | EweyKeyWorld1 9 | EweyKeyWorld2 10 | EweyKeyEscape 11 | EweyKeyEnter 12 | EweyKeyTab 13 | EweyKeyBackspace 14 | EweyKeyInsert 15 | EweyKeyDelete 16 | EweyKeyRight 17 | EweyKeyLeft 18 | EweyKeyDown 19 | EweyKeyUp 20 | EweyKeyPageUp 21 | EweyKeyPageDown 22 | EweyKeyHome 23 | EweyKeyEnd 24 | EweyKeyCapsLock 25 | EweyKeyScrollLock 26 | EweyKeyNumLock 27 | EweyKeyPrintScreen 28 | EweyKeyPause 29 | EweyKeyF1 30 | EweyKeyF2 31 | EweyKeyF3 32 | EweyKeyF4 33 | EweyKeyF5 34 | EweyKeyF6 35 | EweyKeyF7 36 | EweyKeyF8 37 | EweyKeyF9 38 | EweyKeyF10 39 | EweyKeyF11 40 | EweyKeyF12 41 | EweyKeyF13 42 | EweyKeyF14 43 | EweyKeyF15 44 | EweyKeyF16 45 | EweyKeyF17 46 | EweyKeyF18 47 | EweyKeyF19 48 | EweyKeyF20 49 | EweyKeyF21 50 | EweyKeyF22 51 | EweyKeyF23 52 | EweyKeyF24 53 | EweyKeyF25 54 | EweyKeyLeftShift 55 | EweyKeyLeftControl 56 | EweyKeyLeftAlt 57 | EweyKeyLeftSuper 58 | EweyKeyRightShift 59 | EweyKeyRightControl 60 | EweyKeyRightAlt 61 | EweyKeyRightSuper 62 | ) 63 | 64 | // KeyPressEvent represents the data associated with a single key-press event 65 | // from whatever input library is used in conjunction with this package. 66 | type KeyPressEvent struct { 67 | // the key that was hit if it is alpha-numeric or otherwise able to be 68 | // stored as a character 69 | Rune rune 70 | 71 | // if the key was not something that can be stored as a rune, then 72 | // use the corresponding key enum value here (e.g. eweyKeyF1) 73 | KeyCode int 74 | 75 | // IsRune indicates if the event for a rune or non-rune key 76 | IsRune bool 77 | 78 | // ShiftDown indicates if the shift key was down at time of key press 79 | ShiftDown bool 80 | 81 | // CtrlDown indicates if the ctrl key was down at time of key press 82 | CtrlDown bool 83 | 84 | // AltDown indicates if the alt key was down at time of key press 85 | AltDown bool 86 | 87 | // SuperDown indicates if the super key was down at time of key press 88 | SuperDown bool 89 | } 90 | -------------------------------------------------------------------------------- /manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | mgl "github.com/go-gl/mathgl/mgl32" 11 | graphics "github.com/tbogdala/fizzle/graphicsprovider" 12 | ) 13 | 14 | // FrameStartFunc is the type of function to be called when the manager is starting 15 | // a new frame to construct and draw. 16 | type FrameStartFunc func(startTime time.Time) 17 | 18 | // textEditState containst state information for the last widget that edits text 19 | // and can be used to store cursor position and other useful info. 20 | type textEditState struct { 21 | // ID is the ID of the text widget claiming text edit state 22 | ID string 23 | 24 | // The number of runes in the buffer string to place the cursor after 25 | CursorOffset int 26 | 27 | // CursorTimer tracks the amount of time since the start of the last blink 28 | // episode of the cursor. The cursor should be shown during the interval 29 | // [0 .. Style.EditboxBlinkDuration]. 30 | CursorTimer float32 31 | 32 | // CharacterShift is the amount of characters to shift the dispayed text. 33 | CharacterShift int 34 | } 35 | 36 | // Manager holds all of the widgets and knows how to draw the UI. 37 | type Manager struct { 38 | // GetMousePosition should be a function that returns the current mouse 39 | // position for the application. 40 | GetMousePosition func() (float32, float32) 41 | 42 | // GetMouseDownPosition should be a function that returns the mouse position 43 | // stored for when the button made the transition from UP->DOWN. 44 | GetMouseDownPosition func(buttonNumber int) (float32, float32) 45 | 46 | // GetMousePositionDelta should be a function that returns the amount 47 | // of change in the mouse position. 48 | GetMousePositionDelta func() (float32, float32) 49 | 50 | // GetMouseButtonAction should be a function that returns the state 51 | // of a mouse button: MouseUp | MouseDown | MouseRepeat. 52 | GetMouseButtonAction func(buttonNumber int) int 53 | 54 | // ClearMouseButtonAction should be a function that clears any tracked 55 | // action data for a mouse button 56 | ClearMouseButtonAction func(buttonNumber int) 57 | 58 | // GetScrollWheelDelta should be a function that returns the amount of 59 | // change to the scroll wheel position that has happened since last check. 60 | GetScrollWheelDelta func(bool) float32 61 | 62 | // GetKeyEvents is the function to be called to get the slice of 63 | // currently buffered key press events 64 | GetKeyEvents func() []KeyPressEvent 65 | 66 | // ClearKeyEvents is the function to be called to clear out the key press event buffer 67 | ClearKeyEvents func() 68 | 69 | // GetClipboardString returns a possible string from the clipboarnd and 70 | // possibly an error. 71 | GetClipboardString func() (string, error) 72 | 73 | // SetClipboardString sets a string in the system clipboard. 74 | SetClipboardString func(string) 75 | 76 | // FrameStart is the time the UI manager's Construct() was called. 77 | FrameStart time.Time 78 | 79 | // FrameDelta is the time between frames as given to Construct(). 80 | FrameDelta float64 81 | 82 | // ScrollSpeed is how much each move of the scroll wheel should be magnified 83 | ScrollSpeed float32 84 | 85 | // width is used to construct the ortho projection matrix and is probably 86 | // best set to the width of the window. 87 | width int32 88 | 89 | // height is used to construct the ortho projection matrix and is probably 90 | // best set to the height of the window. 91 | height int32 92 | 93 | // designHeight is the height the UI was designed at. Practically, this 94 | // means that text should scale to adjust for resolution changes so that it 95 | // has the same height relative to the different resolutions. 96 | // E.g. 800x600 and the font glyphs have a height of 30, then adjusting 97 | // to 1600x1200 will instruct the package to create text with a height of 60. 98 | designHeight int32 99 | 100 | // windows is the slice of known windows to render. 101 | windows []*Window 102 | 103 | // activeInputID is the ID string of the widget that claimed input on mouse down. 104 | activeInputID string 105 | 106 | // activeTextEdit is the active text editing widget state; if set til nil 107 | // then there are no text editing widgets with active input focus. 108 | activeTextEdit *textEditState 109 | 110 | // gfx is the underlying graphics implementation to be used for rendering. 111 | gfx graphics.GraphicsProvider 112 | 113 | // shader is the shader program used to draw the user interface. 114 | shader graphics.Program 115 | 116 | // fonts is a map of loaded fonts keyed by a client specified name. 117 | fonts map[string]*Font 118 | 119 | // whitePixelUv is a vec4 of the UV coordinate to use for the white pixel 120 | // with (s1,t1,s2,t2) where (s1,t1) is bottom-left and (s2,t2) is top-right. 121 | whitePixelUv mgl.Vec4 122 | 123 | // frameStartCallbacks is a slice of functions that should be called when 124 | // the manager is constructing a new frame to draw. 125 | frameStartCallbacks []FrameStartFunc 126 | 127 | comboBuffer []float32 128 | indexBuffer []uint32 129 | comboVBO graphics.Buffer 130 | indexVBO graphics.Buffer 131 | vao uint32 132 | faceCount uint32 133 | textureStack []graphics.Texture // cleared each frame 134 | } 135 | 136 | // NewManager is the constructor for the Manager type that will create 137 | // a new object and sets sane defaults. 138 | func NewManager(gfx graphics.GraphicsProvider) *Manager { 139 | m := new(Manager) 140 | m.windows = make([]*Window, 0) 141 | m.fonts = make(map[string]*Font) 142 | m.gfx = gfx 143 | m.whitePixelUv = mgl.Vec4{1.0, 1.0, 1.0, 1.0} 144 | m.FrameStart = time.Now() 145 | m.ScrollSpeed = 10.0 146 | 147 | m.vao = gfx.GenVertexArray() 148 | 149 | m.GetMousePosition = func() (float32, float32) { return 0, 0 } 150 | m.GetMousePositionDelta = func() (float32, float32) { return 0, 0 } 151 | m.GetMouseButtonAction = func(buttonNumber int) int { return MouseUp } 152 | m.frameStartCallbacks = []FrameStartFunc{} 153 | m.textureStack = []graphics.Texture{} 154 | 155 | return m 156 | } 157 | 158 | // Initialize does the setup required for the user interface to draw. This 159 | // includes heavier operations like compiling shaders. 160 | func (ui *Manager) Initialize(vertShader, fragShader string, w, h, designH int32) error { 161 | // compile the shader program from the source provided 162 | var err error 163 | ui.shader, err = ui.compileShader(vertShader, fragShader) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | // generate the VBOs 169 | ui.comboVBO = ui.gfx.GenBuffer() 170 | ui.indexVBO = ui.gfx.GenBuffer() 171 | ui.vao = ui.gfx.GenVertexArray() 172 | 173 | // set the resolution for the user interface 174 | ui.AdviseResolution(w, h) 175 | ui.designHeight = designH 176 | 177 | return nil 178 | } 179 | 180 | // AddTextureToStack adds a texture ID to the stack of textures the manager maintains 181 | // and returns it's index in the stack +1. In other words, this is a one-based 182 | // number scheme because 0 is reserved for the font. 183 | func (ui *Manager) AddTextureToStack(texID graphics.Texture) uint32 { 184 | ui.textureStack = append(ui.textureStack, texID) 185 | return uint32(len(ui.textureStack)) 186 | } 187 | 188 | // AdviseResolution will change the resolution the Manager uses to draw widgets. 189 | func (ui *Manager) AdviseResolution(w int32, h int32) { 190 | ui.width = w 191 | ui.height = h 192 | } 193 | 194 | // GetDesignHeight returns the normalized height for the UI. 195 | func (ui *Manager) GetDesignHeight() int32 { 196 | return ui.designHeight 197 | } 198 | 199 | // GetResolution returns the width and height of the user interface. 200 | func (ui *Manager) GetResolution() (int32, int32) { 201 | return ui.width, ui.height 202 | } 203 | 204 | // NewWindow creates a new window and adds it to the collection of windows to draw. 205 | func (ui *Manager) NewWindow(id string, x, y, w, h float32, constructor BuildCallback) *Window { 206 | wnd := newWindow(id, x, y, w, h, constructor) 207 | wnd.Owner = ui 208 | ui.windows = append(ui.windows, wnd) 209 | return wnd 210 | } 211 | 212 | // GetWindow returns a window based on the id string passed in 213 | func (ui *Manager) GetWindow(id string) *Window { 214 | for _, wnd := range ui.windows { 215 | if wnd.ID == id { 216 | return wnd 217 | } 218 | } 219 | 220 | return nil 221 | } 222 | 223 | // GetWindowsByFilter returns a slice of *Window which is populated by 224 | // filtering the internal window list with the function provided. 225 | // If the function returns true the window will get included in the results. 226 | func (ui *Manager) GetWindowsByFilter(filter func(w *Window) bool) []*Window { 227 | results := []*Window{} 228 | for _, wnd := range ui.windows { 229 | if filter(wnd) { 230 | results = append(results, wnd) 231 | } 232 | } 233 | 234 | return results 235 | } 236 | 237 | // RemoveWindow will remove the window from the user interface. 238 | func (ui *Manager) RemoveWindow(wndToRemove *Window) { 239 | filtered := ui.windows[:0] 240 | for _, wnd := range ui.windows { 241 | if wnd.ID != wndToRemove.ID { 242 | filtered = append(filtered, wnd) 243 | } 244 | } 245 | ui.windows = filtered 246 | } 247 | 248 | // NewFont loads the font from a file and 'registers' it with the UI manager. 249 | func (ui *Manager) NewFont(name string, fontFilepath string, scaleInt int, glyphs string) (*Font, error) { 250 | f, err := newFont(ui, fontFilepath, scaleInt, glyphs) 251 | 252 | // if we succeeded, store the font with the name specified 253 | if err == nil { 254 | ui.fonts[name] = f 255 | } 256 | 257 | return f, err 258 | } 259 | 260 | // NewFontBytes loads the font from a byte slice and 'registers' it with the UI manager. 261 | func (ui *Manager) NewFontBytes(name string, fontBytes []byte, scaleInt int, glyphs string) (*Font, error) { 262 | f, err := newFontBytes(ui, fontBytes, scaleInt, glyphs) 263 | 264 | // if we succeeded, store the font with the name specified 265 | if err == nil { 266 | ui.fonts[name] = f 267 | } 268 | 269 | return f, err 270 | } 271 | 272 | // GetFont attempts to get the font by name from the Manager's collection. 273 | // It returns the font on success or nil on failure. 274 | func (ui *Manager) GetFont(name string) *Font { 275 | return ui.fonts[name] 276 | } 277 | 278 | // AddConstructionStartCallback adds a new callback to the slice of callbacks that 279 | // will be called when the manager is starting construction of a new frame to draw. 280 | func (ui *Manager) AddConstructionStartCallback(cb FrameStartFunc) { 281 | ui.frameStartCallbacks = append(ui.frameStartCallbacks, cb) 282 | } 283 | 284 | // SetActiveInputID sets the active input id which tells the user interface 285 | // which widget is currently claiming 'focus' for input. Returns a bool indicating 286 | // if the focus claim was successful because the input can be claimed only once 287 | // per UP->DOWN mouse transition. 288 | func (ui *Manager) SetActiveInputID(id string) bool { 289 | if ui.activeInputID == "" || ui.GetMouseButtonAction(0) != MouseDown { 290 | ui.activeInputID = id 291 | 292 | // clear out the editor state if we select a different widget 293 | if ui.activeTextEdit != nil && ui.activeInputID != ui.activeTextEdit.ID { 294 | ui.activeTextEdit = nil 295 | } 296 | 297 | return true 298 | } 299 | 300 | return false 301 | } 302 | 303 | // GetActiveInputID returns the active input id which claimed input focus. 304 | func (ui *Manager) GetActiveInputID() string { 305 | return ui.activeInputID 306 | } 307 | 308 | // ClearActiveInputID clears any focus claims. 309 | func (ui *Manager) ClearActiveInputID() { 310 | ui.activeInputID = "" 311 | } 312 | 313 | // setActiveTextEditor sets the active widget id which gets text editing input. 314 | // Returns a bool indicating if the claim for active text editor successed. 315 | func (ui *Manager) setActiveTextEditor(id string, cursorPos int) bool { 316 | // already claimed, so sorry 317 | if ui.activeTextEdit != nil { 318 | return false 319 | } 320 | 321 | // claim the fresh focus 322 | var ate textEditState 323 | ate.ID = id 324 | ate.CursorOffset = cursorPos 325 | ui.activeTextEdit = &ate 326 | 327 | // clear out the old key events 328 | ui.ClearKeyEvents() 329 | 330 | return true 331 | } 332 | 333 | // getActiveTextEditor returns the active text editor state if one is set. 334 | func (ui *Manager) getActiveTextEditor() *textEditState { 335 | return ui.activeTextEdit 336 | } 337 | 338 | // clearActiveTextEditor will remove the active text editor from tracking. 339 | func (ui *Manager) clearActiveTextEditor() { 340 | ui.activeTextEdit = nil 341 | } 342 | 343 | // Construct loops through all of the Windows in the Manager and creates 344 | // all of the widgets and their data. This function does not buffer the 345 | // result to VBO or do the actual rendering -- call Draw() for that. 346 | func (ui *Manager) Construct(frameDelta float64) { 347 | // reset the display data 348 | ui.comboBuffer = ui.comboBuffer[:0] 349 | ui.indexBuffer = ui.indexBuffer[:0] 350 | ui.faceCount = 0 351 | ui.FrameStart = time.Now() 352 | ui.textureStack = ui.textureStack[:0] 353 | ui.FrameDelta = frameDelta 354 | 355 | // call all of the frame start callbacks 356 | for _, frameStartCB := range ui.frameStartCallbacks { 357 | frameStartCB(ui.FrameStart) 358 | } 359 | 360 | // trigger a mouse position check each frame 361 | ui.GetMousePosition() 362 | ui.GetScrollWheelDelta(false) 363 | 364 | // see if we need to clear the active widget id 365 | if ui.GetMouseButtonAction(0) != MouseDown { 366 | ui.ClearActiveInputID() 367 | } 368 | 369 | // loop through all of the windows and tell them to self-construct. 370 | for _, w := range ui.windows { 371 | w.construct() 372 | } 373 | } 374 | 375 | // bindOpenGLData sets the program, VAO, uniforms and attributes required for the 376 | // controls to be drawn from the command buffers 377 | func (ui *Manager) bindOpenGLData(style *Style, view mgl.Mat4) { 378 | const floatSize = 4 379 | const uintSize = 4 380 | const posOffset = 0 381 | const uvOffset = floatSize * 2 382 | const texIdxOffset = floatSize * 4 383 | const colorOffset = floatSize * 5 384 | const VBOStride = floatSize * (2 + 2 + 1 + 4) // vert / uv / texIndex / color 385 | 386 | gfx := ui.gfx 387 | 388 | gfx.UseProgram(ui.shader) 389 | gfx.BindVertexArray(ui.vao) 390 | 391 | // bind the uniforms and attributes 392 | shaderViewMatrix := gfx.GetUniformLocation(ui.shader, "VIEW") 393 | gfx.UniformMatrix4fv(shaderViewMatrix, 1, false, view) 394 | 395 | font := ui.GetFont(style.FontName) 396 | shaderTex0 := gfx.GetUniformLocation(ui.shader, "TEX[0]") 397 | if shaderTex0 >= 0 { 398 | if font != nil { 399 | gfx.ActiveTexture(graphics.TEXTURE0) 400 | gfx.BindTexture(graphics.TEXTURE_2D, font.Texture) 401 | gfx.Uniform1i(shaderTex0, 0) 402 | } 403 | } 404 | if len(ui.textureStack) > 0 { 405 | for stackIdx, texID := range ui.textureStack { 406 | uniStr := fmt.Sprintf("TEX[%d]", stackIdx+1) 407 | texUniLoc := gfx.GetUniformLocation(ui.shader, uniStr) 408 | if texUniLoc >= 0 { 409 | gfx.ActiveTexture(graphics.TEXTURE0 + graphics.Texture(stackIdx+1)) 410 | gfx.BindTexture(graphics.TEXTURE_2D, texID) 411 | gfx.Uniform1i(texUniLoc, int32(stackIdx+1)) 412 | } 413 | } 414 | } 415 | 416 | shaderPosition := gfx.GetAttribLocation(ui.shader, "VERTEX_POSITION") 417 | gfx.BindBuffer(graphics.ARRAY_BUFFER, ui.comboVBO) 418 | gfx.EnableVertexAttribArray(uint32(shaderPosition)) 419 | gfx.VertexAttribPointer(uint32(shaderPosition), 2, graphics.FLOAT, false, VBOStride, gfx.PtrOffset(posOffset)) 420 | 421 | uvPosition := gfx.GetAttribLocation(ui.shader, "VERTEX_UV") 422 | gfx.EnableVertexAttribArray(uint32(uvPosition)) 423 | gfx.VertexAttribPointer(uint32(uvPosition), 2, graphics.FLOAT, false, VBOStride, gfx.PtrOffset(uvOffset)) 424 | 425 | colorPosition := gfx.GetAttribLocation(ui.shader, "VERTEX_COLOR") 426 | gfx.EnableVertexAttribArray(uint32(colorPosition)) 427 | gfx.VertexAttribPointer(uint32(colorPosition), 4, graphics.FLOAT, false, VBOStride, gfx.PtrOffset(colorOffset)) 428 | 429 | texIdxPosition := gfx.GetAttribLocation(ui.shader, "VERTEX_TEXTURE_INDEX") 430 | gfx.EnableVertexAttribArray(uint32(texIdxPosition)) 431 | gfx.VertexAttribPointer(uint32(texIdxPosition), 1, graphics.FLOAT, false, VBOStride, gfx.PtrOffset(texIdxOffset)) 432 | 433 | gfx.BindBuffer(graphics.ELEMENT_ARRAY_BUFFER, ui.indexVBO) 434 | } 435 | 436 | // Draw buffers the UI vertex data into the rendering pipeline and does 437 | // the actual draw call. 438 | func (ui *Manager) Draw() { 439 | // short circuit the rendering if there are no windows to draw 440 | if len(ui.windows) < 1 { 441 | return 442 | } 443 | 444 | const floatSize = 4 445 | const uintSize = 4 446 | const posOffset = 0 447 | const uvOffset = floatSize * 2 448 | const texIdxOffset = floatSize * 4 449 | const colorOffset = floatSize * 5 450 | const VBOStride = floatSize * (2 + 2 + 1 + 4) // vert / uv / texIndex / color 451 | gfx := ui.gfx 452 | 453 | // FIXME: move the zdepth definitions elsewhere 454 | const minZDepth = -100.0 455 | const maxZDepth = 100.0 456 | 457 | gfx.Disable(graphics.DEPTH_TEST) 458 | gfx.Enable(graphics.SCISSOR_TEST) 459 | 460 | // for now, loop through all of the windows and copy all of the data into the manager's buffer 461 | // FIXME: this could be buffered straight from the cmdList 462 | var startIndex uint32 463 | for _, w := range ui.windows { 464 | for _, cmd := range w.cmds { 465 | if cmd.isCustom { 466 | continue 467 | } 468 | 469 | ui.comboBuffer = append(ui.comboBuffer, cmd.comboBuffer...) 470 | 471 | // reindex the index buffer to reference the correct vertex data 472 | highestIndex := uint32(0) 473 | for _, i := range cmd.indexBuffer { 474 | if i > highestIndex { 475 | highestIndex = i 476 | } 477 | ui.indexBuffer = append(ui.indexBuffer, i+startIndex) 478 | } 479 | ui.faceCount += cmd.faceCount 480 | startIndex += highestIndex + 1 481 | } 482 | } 483 | 484 | // make sure that we're going to draw something 485 | if startIndex == 0 { 486 | return 487 | } 488 | 489 | gfx.BindVertexArray(ui.vao) 490 | view := mgl.Ortho(0.5, float32(ui.width)+0.5, 0.5, float32(ui.height)+0.5, minZDepth, maxZDepth) 491 | 492 | // buffer the data 493 | gfx.BindBuffer(graphics.ARRAY_BUFFER, ui.comboVBO) 494 | gfx.BufferData(graphics.ARRAY_BUFFER, floatSize*len(ui.comboBuffer), gfx.Ptr(&ui.comboBuffer[0]), graphics.STREAM_DRAW) 495 | gfx.BindBuffer(graphics.ELEMENT_ARRAY_BUFFER, ui.indexVBO) 496 | gfx.BufferData(graphics.ELEMENT_ARRAY_BUFFER, uintSize*len(ui.indexBuffer), gfx.Ptr(&ui.indexBuffer[0]), graphics.STREAM_DRAW) 497 | 498 | // this should be set to true when the uniforms and attributes, etc... need to be rebound 499 | needRebinding := true 500 | 501 | // loop through the windows and each window's draw cmd list 502 | indexOffset := uint32(0) 503 | for _, w := range ui.windows { 504 | for _, cmd := range w.cmds { 505 | gfx.Scissor(int32(cmd.clipRect[0]), int32(cmd.clipRect[1]-cmd.clipRect[3]), int32(cmd.clipRect[2]), int32(cmd.clipRect[3])) 506 | 507 | // for most widgets, isCustom will be false, so we just draw things how we have them bound and then 508 | // update the index offset into the master combo and index buffers stored in Manager. 509 | if cmd.isCustom == false { 510 | if needRebinding { 511 | // bind all of the uniforms and attributes 512 | ui.bindOpenGLData(&DefaultStyle, view) 513 | gfx.Viewport(0, 0, ui.width, ui.height) 514 | needRebinding = false 515 | } 516 | gfx.DrawElements(graphics.TRIANGLES, int32(cmd.faceCount*3), graphics.UNSIGNED_INT, gfx.PtrOffset(int(indexOffset)*uintSize)) 517 | indexOffset += cmd.faceCount * 3 518 | } else { 519 | gfx.Viewport(int32(cmd.clipRect[0]), int32(cmd.clipRect[1]-cmd.clipRect[3]), int32(cmd.clipRect[2]), int32(cmd.clipRect[3])) 520 | cmd.onCustomDraw() 521 | needRebinding = true 522 | } 523 | } 524 | } 525 | 526 | gfx.BindVertexArray(0) 527 | gfx.Disable(graphics.SCISSOR_TEST) 528 | gfx.Enable(graphics.DEPTH_TEST) 529 | } 530 | 531 | func (ui *Manager) compileShader(vertShader, fragShader string) (graphics.Program, error) { 532 | gfx := ui.gfx 533 | 534 | // create the program 535 | prog := gfx.CreateProgram() 536 | 537 | // create the vertex shader 538 | var status int32 539 | vs := gfx.CreateShader(graphics.VERTEX_SHADER) 540 | gfx.ShaderSource(vs, vertShader) 541 | gfx.CompileShader(vs) 542 | gfx.GetShaderiv(vs, graphics.COMPILE_STATUS, &status) 543 | if status == graphics.FALSE { 544 | log := gfx.GetShaderInfoLog(vs) 545 | return 0, fmt.Errorf("Failed to compile the vertex shader:\n%s", log) 546 | } 547 | defer gfx.DeleteShader(vs) 548 | 549 | // create the fragment shader 550 | fs := gfx.CreateShader(graphics.FRAGMENT_SHADER) 551 | gfx.ShaderSource(fs, fragShader) 552 | gfx.CompileShader(fs) 553 | gfx.GetShaderiv(fs, graphics.COMPILE_STATUS, &status) 554 | if status == graphics.FALSE { 555 | log := gfx.GetShaderInfoLog(fs) 556 | return 0, fmt.Errorf("Failed to compile the fragment shader:\n%s", log) 557 | } 558 | defer gfx.DeleteShader(fs) 559 | 560 | // attach the shaders to the program and link 561 | gfx.AttachShader(prog, vs) 562 | gfx.AttachShader(prog, fs) 563 | gfx.LinkProgram(prog) 564 | gfx.GetProgramiv(prog, graphics.LINK_STATUS, &status) 565 | if status == graphics.FALSE { 566 | log := gfx.GetProgramInfoLog(prog) 567 | return 0, fmt.Errorf("Failed to link the program!\n%s", log) 568 | } 569 | 570 | return prog, nil 571 | } 572 | 573 | // ScreenToDisplay converts screen-normalized point to resolution-specific 574 | // coordinates with the origin in the lower left corner. 575 | // E.g. if the UI is 800x600, calling with (0.5, 0.5) returns (400, 300) 576 | func (ui *Manager) ScreenToDisplay(xS, yS float32) (float32, float32) { 577 | return xS * float32(ui.width), yS * float32(ui.height) 578 | } 579 | 580 | // DisplayToScreen converts a resolution-specific coordinate to screen-normalized 581 | // space with the origin in the lower left corner. 582 | // E.g. if the UI is 800x600, coalling with (400,300) returns (0.5, 0.5) 583 | func (ui *Manager) DisplayToScreen(xD, yD float32) (float32, float32) { 584 | return xD / float32(ui.width), yD / float32(ui.height) 585 | } 586 | 587 | // DrawRectFilled draws a rectangle in the user interface using a solid background. 588 | // Coordinate parameters should be passed in screen-normalized space. This gets 589 | // appended to the command list passed in. 590 | func (ui *Manager) DrawRectFilled(cmd *cmdList, xS, yS, wS, hS float32, color mgl.Vec4, textureIndex uint32) { 591 | x, y := ui.ScreenToDisplay(xS, yS) 592 | w, h := ui.ScreenToDisplay(wS, hS) 593 | combos, indexes, fc := cmd.DrawRectFilledDC(x, y, x+w, y-h, color, textureIndex, ui.whitePixelUv) 594 | cmd.AddFaces(combos, indexes, fc) 595 | } 596 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Timothy Bogdala 2 | // See the LICENSE file for more details. 3 | 4 | package eweygewey 5 | 6 | import ( 7 | "fmt" 8 | 9 | mgl "github.com/go-gl/mathgl/mgl32" 10 | ) 11 | 12 | const ( 13 | defaultTextureSampler = uint32(0) 14 | ) 15 | 16 | // BuildCallback is a type for the function that builds the widgets for the window. 17 | type BuildCallback func(window *Window) 18 | 19 | // Window represents a collection of widgets in the user interface. 20 | type Window struct { 21 | // ID is the widget id string for the window for claiming focus. 22 | ID string 23 | 24 | // Location is the location of the upper left hand corner of the window. 25 | // The X and Y axis should be specified screen-normalized coordinates. 26 | Location mgl.Vec3 27 | 28 | // Width is how wide the window is in screen-normalized space. 29 | Width float32 30 | 31 | // Height is how tall the window is in screen-normalized space. 32 | Height float32 33 | 34 | // ShowScrollBar indicates if the scroll bar should be attached to the side 35 | // of the window 36 | ShowScrollBar bool 37 | 38 | // ShowTitleBar indicates if the title bar should be drawn or not 39 | ShowTitleBar bool 40 | 41 | // IsMoveable indicates if the window should be moveable by LMB drags 42 | IsMoveable bool 43 | 44 | // IsScrollable indicates if the window should scroll the contents based 45 | // on mouse scroll wheel input. 46 | IsScrollable bool 47 | 48 | // AutoAdjustHeight indicates if the window's height should be automatically 49 | // adjusted to accommodate all of the widgets. 50 | AutoAdjustHeight bool 51 | 52 | // Title is the string to display in the title bar if it is visible 53 | Title string 54 | 55 | // OnBuild gets called by the UI Manager when the UI is getting built. 56 | // This should be a function that makes all of the calls necessary 57 | // to build the window's widgets. 58 | OnBuild BuildCallback 59 | 60 | // Owner is the owning UI Manager object. 61 | Owner *Manager 62 | 63 | // ScrollOffset is essentially the scrollbar *position* which tells the 64 | // window hot to offset the controlls to give the scrolling effect. 65 | ScrollOffset float32 66 | 67 | // Style is the set of visual parameters to use when drawing this window. 68 | Style 69 | 70 | // widgetCursorDC is the current location to insert widgets and should 71 | // be updated after adding new widgets. This is specified in display 72 | // coordinates. 73 | widgetCursorDC mgl.Vec3 74 | 75 | // nextRowCursorOffsetDC is the value the widgetCursorDC's y component 76 | // should change for the next widget that starts a new row in the window. 77 | nextRowCursorOffsetDC float32 78 | 79 | // requestedItemWidthMinDC is set by the client code to adjust the width of the 80 | // next control to be at least a specific size. 81 | requestedItemWidthMinDC float32 82 | 83 | // requestedItemWidthMaxDC is set by the client code to adjust the width of the 84 | // next control to be at most a specific size. 85 | requestedItemWidthMaxDC float32 86 | 87 | // indentLevel is the number of indents that each row should start off with. 88 | // this means the new row should have a widgetCursorDC that is offset the 89 | // amount of (Style.IndentSpacing * indentLevel). 90 | indentLevel int 91 | 92 | // cmds is the slice of cmdLists used to to render the window 93 | cmds []*cmdList 94 | 95 | // intStorage is a map that allows an int to be stored by string -- typically 96 | // an ID from a widget as a key. 97 | intStorage map[string]int 98 | } 99 | 100 | // newWindow creates a new window with a top-left coordinate of (x,y) and 101 | // dimensions of (w,h). 102 | func newWindow(id string, x, y, w, h float32, constructor BuildCallback) *Window { 103 | wnd := new(Window) 104 | wnd.cmds = []*cmdList{} 105 | wnd.intStorage = make(map[string]int) 106 | wnd.ID = id 107 | wnd.Location[0] = x 108 | wnd.Location[1] = y 109 | wnd.Width = w 110 | wnd.Height = h 111 | wnd.OnBuild = constructor 112 | wnd.ShowTitleBar = true 113 | wnd.IsMoveable = true 114 | //wnd.IsScrollable = false 115 | wnd.Style = DefaultStyle 116 | return wnd 117 | } 118 | 119 | // construct builds the frame (if one is to be made) for the window and then 120 | // calls the OnBuild function specified for the window to create the widgets. 121 | func (wnd *Window) construct() { 122 | // empty out the cmd list and start a new command 123 | wnd.cmds = wnd.cmds[:0] 124 | 125 | mouseX, mouseY := wnd.Owner.GetMousePosition() 126 | mouseDeltaX, mouseDeltaY := wnd.Owner.GetMousePositionDelta() 127 | lmbDown := wnd.Owner.GetMouseButtonAction(0) == MouseDown 128 | 129 | // if the mouse is in the window, then let's scroll if the scroll input 130 | // was received. 131 | if wnd.IsScrollable && wnd.ContainsPosition(mouseX, mouseY) { 132 | wnd.ScrollOffset -= wnd.Owner.GetScrollWheelDelta(true) 133 | if wnd.ScrollOffset < 0.0 { 134 | wnd.ScrollOffset = 0.0 135 | } 136 | } 137 | 138 | // reset the cursor for the window 139 | wnd.widgetCursorDC = mgl.Vec3{wnd.Style.WindowPadding[0], wnd.ScrollOffset, 0} 140 | wnd.nextRowCursorOffsetDC = 0 141 | 142 | // advance the cursor to account for the title bar 143 | _, _, _, frameHeight := wnd.GetFrameSize() 144 | _, _, _, displayHeight := wnd.GetDisplaySize() 145 | wnd.widgetCursorDC[1] = wnd.widgetCursorDC[1] - (frameHeight - displayHeight) - wnd.WindowPadding[2] 146 | 147 | // invoke the callback to build the widgets for the window 148 | if wnd.OnBuild != nil { 149 | wnd.OnBuild(wnd) 150 | } 151 | 152 | // calculate the height all of the controls would need to draw. this can be 153 | // used to automatically resize the window and will be used to draw a correctly 154 | // proportioned scroll bar cursor. 155 | totalControlHeightDC := -wnd.widgetCursorDC[1] + wnd.nextRowCursorOffsetDC + wnd.ScrollOffset + wnd.WindowPadding[3] 156 | _, totalControlHeightS := wnd.Owner.DisplayToScreen(0.0, totalControlHeightDC) 157 | 158 | // are we going to fit the height of the window to the height of the controls? 159 | if wnd.AutoAdjustHeight { 160 | wnd.Height = totalControlHeightS 161 | } 162 | 163 | // do we need to roll back the scroll bar change? has it overextended the 164 | // bounds and need to be pulled back in? make sure that the total control 165 | // height is actually greter than display height and requires scrolling first. 166 | controlHeightOverflow := totalControlHeightDC - displayHeight 167 | if wnd.IsScrollable && controlHeightOverflow > 0 && wnd.ScrollOffset > controlHeightOverflow { 168 | wnd.ScrollOffset = controlHeightOverflow 169 | } else if controlHeightOverflow < 0 { 170 | // more space then needed so reset the scroll bar 171 | wnd.ScrollOffset = 0 172 | } 173 | 174 | // build the frame background for the window including title bar and scroll bar. 175 | wnd.buildFrame(totalControlHeightDC) 176 | 177 | // next frame we potientially will have a different window location 178 | // do we need to move the window? (LMB down in a window and mouse dragged) 179 | if wnd.IsMoveable && lmbDown && wnd.ContainsPosition(mouseX, mouseY) { 180 | claimed := wnd.Owner.SetActiveInputID(wnd.ID) 181 | if claimed || wnd.Owner.GetActiveInputID() == wnd.ID { 182 | // mouse down in the window, lets move the thing before we make the vertices 183 | deltaXS, deltaYS := wnd.Owner.DisplayToScreen(mouseDeltaX, mouseDeltaY) 184 | wnd.Location[0] += deltaXS 185 | wnd.Location[1] += deltaYS 186 | } 187 | } 188 | } 189 | 190 | // GetDisplaySize returns four values: the x and y positions of the window 191 | // on the screen in display-space and then the width and height of the window 192 | // in display-space values. This does not include space for the scroll bars. 193 | func (wnd *Window) GetDisplaySize() (float32, float32, float32, float32) { 194 | winxDC, winyDC := wnd.Owner.ScreenToDisplay(wnd.Location[0], wnd.Location[1]) 195 | winwDC, winhDC := wnd.Owner.ScreenToDisplay(wnd.Width, wnd.Height) 196 | 197 | return winxDC, winyDC, winwDC, winhDC 198 | } 199 | 200 | // GetAspectRatio returns the aspect ratio of the window (width / height) 201 | func (wnd *Window) GetAspectRatio() float32 { 202 | return wnd.Width / wnd.Height 203 | } 204 | 205 | // GetFrameSize returns the (x,y) top-left corner of the window in display-space 206 | // coordinates and the width and height of the total window frame as well, including 207 | // the space window decorations take up like titlebar and scrollbar. 208 | func (wnd *Window) GetFrameSize() (float32, float32, float32, float32) { 209 | winxDC, winyDC, winwDC, winhDC := wnd.GetDisplaySize() 210 | 211 | // add in the size of the scroll bar if we're going to show it 212 | if wnd.ShowScrollBar { 213 | winwDC += wnd.Style.ScrollBarWidth 214 | } 215 | 216 | // add the size of the title bar if it's visible 217 | if wnd.ShowTitleBar { 218 | font := wnd.Owner.GetFont(wnd.Style.FontName) 219 | if font != nil { 220 | _, dimY, _ := font.GetRenderSize(wnd.GetTitleString()) 221 | // TODO: for now just add 1 pixel on each side of the string for padding 222 | winhDC += float32(dimY) + wnd.Style.TitleBarPadding[2] + wnd.Style.TitleBarPadding[3] 223 | } 224 | } 225 | return winxDC, winyDC, winwDC, winhDC 226 | } 227 | 228 | // GetTitleString will return a string with one space in it or the Title property 229 | // if the Title is not an empty string. 230 | func (wnd *Window) GetTitleString() string { 231 | titleString := " " 232 | if len(wnd.Title) > 0 { 233 | titleString = wnd.Title 234 | } 235 | return titleString 236 | } 237 | 238 | func (wnd *Window) makeCmdList() *cmdList { 239 | // clip to the frame size which includes space for title bar and scroll bar 240 | wx, wy, ww, wh := wnd.GetFrameSize() 241 | cmdList := newCmdList() 242 | cmdList.clipRect[0] = wx 243 | cmdList.clipRect[1] = wy 244 | cmdList.clipRect[2] = ww 245 | cmdList.clipRect[3] = wh 246 | return cmdList 247 | } 248 | 249 | // getFirstCmd will return the first non-custom cmdList; if the first cmdList 250 | // is custom, it makes a new one. 251 | func (wnd *Window) getFirstCmd() *cmdList { 252 | // empty list 253 | if len(wnd.cmds) == 0 { 254 | wnd.cmds = []*cmdList{wnd.makeCmdList()} 255 | } 256 | 257 | // if the first cmd is custom, then insert a new one 258 | if wnd.cmds[0].isCustom { 259 | newCmd := wnd.makeCmdList() 260 | newSlice := []*cmdList{} 261 | newSlice = append(newSlice, newCmd) 262 | newSlice = append(newSlice, wnd.cmds...) 263 | wnd.cmds = newSlice 264 | } 265 | 266 | return wnd.cmds[0] 267 | } 268 | 269 | // getLastCmd will return the last non-custom cmdList 270 | func (wnd *Window) getLastCmd() *cmdList { 271 | // empty list 272 | if len(wnd.cmds) == 0 { 273 | wnd.cmds = []*cmdList{wnd.makeCmdList()} 274 | } 275 | 276 | // we don't want to add to the custom draw command 277 | if wnd.cmds[len(wnd.cmds)-1].isCustom { 278 | return wnd.addNewCmd() 279 | } 280 | 281 | // just return the last cmdList 282 | return wnd.cmds[len(wnd.cmds)-1] 283 | } 284 | 285 | // addNewCmd creates a new cmdList and adds it to the window's slice of cmlLists. 286 | func (wnd *Window) addNewCmd() *cmdList { 287 | if len(wnd.cmds) == 0 { 288 | return wnd.getFirstCmd() 289 | } 290 | newCmd := wnd.makeCmdList() 291 | wnd.cmds = append(wnd.cmds, newCmd) 292 | return newCmd 293 | } 294 | 295 | // buildFrame builds the background for the window 296 | func (wnd *Window) buildFrame(totalControlHeightDC float32) { 297 | var combos []float32 298 | var indexes []uint32 299 | var fc uint32 300 | 301 | // get the first cmdList and insert the frame data into it 302 | firstCmd := wnd.getFirstCmd() 303 | 304 | // get the dimensions for the window frame 305 | x, y, w, h := wnd.GetFrameSize() 306 | titleBarHeight := float32(0.0) 307 | 308 | // if we don't have a title bar, then simply render the background frame 309 | if wnd.ShowTitleBar { 310 | // how big should the title bar be? 311 | titleString := " " 312 | if len(wnd.Title) > 0 { 313 | titleString = wnd.Title 314 | } 315 | font := wnd.Owner.GetFont(wnd.Style.FontName) 316 | _, dimY, _ := font.GetRenderSize(titleString) 317 | 318 | titleBarHeight = float32(dimY) + wnd.Style.TitleBarPadding[2] + wnd.Style.TitleBarPadding[3] 319 | titleBarTextPos := mgl.Vec3{ 320 | x + wnd.Style.TitleBarPadding[0], 321 | y - wnd.Style.TitleBarPadding[2], 322 | 0} 323 | 324 | // render the title bar background 325 | combos, indexes, fc = firstCmd.DrawRectFilledDC(x, y, x+w, y-titleBarHeight, wnd.Style.TitleBarBgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 326 | firstCmd.AddFaces(combos, indexes, fc) 327 | 328 | // render the title bar text 329 | if len(wnd.Title) > 0 { 330 | renderData := font.CreateText(titleBarTextPos, wnd.Style.TitleBarTextColor, wnd.Title) 331 | firstCmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 332 | } 333 | 334 | // render the rest of the window background 335 | combos, indexes, fc = firstCmd.DrawRectFilledDC(x, y-titleBarHeight, x+w, y-h-titleBarHeight, wnd.Style.WindowBgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 336 | firstCmd.PrefixFaces(combos, indexes, fc) 337 | } else { 338 | // build the background of the window 339 | combos, indexes, fc = firstCmd.DrawRectFilledDC(x, y, x+w, y-h, wnd.Style.WindowBgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 340 | firstCmd.PrefixFaces(combos, indexes, fc) 341 | } 342 | 343 | if wnd.ShowScrollBar { 344 | // now add in the scroll bar at the end to overlay everything 345 | sbX := x + w - wnd.Style.ScrollBarWidth 346 | sbY := y - titleBarHeight 347 | combos, indexes, fc = firstCmd.DrawRectFilledDC(sbX, sbY, x+w, y-h, wnd.Style.ScrollBarBgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 348 | firstCmd.AddFaces(combos, indexes, fc) 349 | 350 | // figure out the positioning 351 | sbCursorWidth := wnd.Style.ScrollBarCursorWidth 352 | if sbCursorWidth > wnd.Style.ScrollBarWidth { 353 | sbCursorWidth = wnd.Style.ScrollBarWidth 354 | } 355 | sbCursorOffX := (wnd.Style.ScrollBarWidth - sbCursorWidth) / 2.0 356 | 357 | // calculate the height required for the scrollbar 358 | sbUsableHeight := h - titleBarHeight 359 | sbRatio := sbUsableHeight / totalControlHeightDC 360 | 361 | // if we have more usable height than controls, just make the scrollbar 362 | // take up the whole space. 363 | if sbRatio >= 1.0 { 364 | sbRatio = 1.0 365 | } 366 | 367 | sbCursorHeight := sbUsableHeight * sbRatio 368 | 369 | // move the scroll bar down based on the scroll position 370 | sbOffY := wnd.ScrollOffset * sbRatio 371 | 372 | // draw the scroll bar cursor 373 | combos, indexes, fc = firstCmd.DrawRectFilledDC(sbX+sbCursorOffX, sbY-sbOffY, x+w-sbCursorOffX, sbY-sbOffY-sbCursorHeight, wnd.Style.ScrollBarCursorColor, 374 | defaultTextureSampler, wnd.Owner.whitePixelUv) 375 | firstCmd.AddFaces(combos, indexes, fc) 376 | 377 | } 378 | } 379 | 380 | // ContainsPosition returns true if the position passed in is contained within 381 | // the window's space. 382 | func (wnd *Window) ContainsPosition(x, y float32) bool { 383 | locXDC, locYDC, wndWDC, wndHDC := wnd.GetFrameSize() 384 | if x > locXDC && x < locXDC+wndWDC && y < locYDC && y > locYDC-wndHDC { 385 | return true 386 | } 387 | return false 388 | } 389 | 390 | // StartRow starts a new row of widgets in the window. 391 | func (wnd *Window) StartRow() { 392 | // adjust the widgetCursor if necessary to start a new row. 393 | wnd.widgetCursorDC[0] = wnd.Style.WindowPadding[0] + wnd.Style.IndentSpacing*float32(wnd.indentLevel) 394 | wnd.widgetCursorDC[1] = wnd.widgetCursorDC[1] - wnd.nextRowCursorOffsetDC 395 | 396 | // clear out the next row height offset 397 | wnd.nextRowCursorOffsetDC = 0.0 398 | } 399 | 400 | // getCursorDC returns the current cursor offset as an absolute location 401 | // in the user interface. 402 | func (wnd *Window) getCursorDC() mgl.Vec3 { 403 | // start with the widget DC offet 404 | pos := wnd.widgetCursorDC 405 | 406 | // add in the position of the window in pixels 407 | windowDx, windowDy := wnd.Owner.ScreenToDisplay(wnd.Location[0], wnd.Location[1]) 408 | pos[0] += windowDx 409 | pos[1] += windowDy 410 | 411 | return pos 412 | } 413 | 414 | // RequestItemWidthMin will request the window to draw the next widget with the 415 | // specified window-normalized size (e.g. if Window's width is 500 px, then passing 416 | // 0.25 here translates to 125 px). 417 | func (wnd *Window) RequestItemWidthMin(nextMinWS float32) { 418 | // clip the incoming value 419 | reqMin := ClipF32(0.0, 1.0, nextMinWS) 420 | 421 | // calc the amount of window width we're requesting 422 | _, _, wndW, _ := wnd.GetDisplaySize() 423 | unpaddedWndW := wndW - wnd.Style.WindowPadding[0] - wnd.Style.WindowPadding[1] 424 | 425 | // convert this to display space 426 | wnd.requestedItemWidthMinDC = reqMin * unpaddedWndW 427 | 428 | // clip the request to window size left 429 | if wnd.widgetCursorDC[0]+wnd.requestedItemWidthMinDC > unpaddedWndW { 430 | wnd.requestedItemWidthMinDC = unpaddedWndW - wnd.widgetCursorDC[0] 431 | } 432 | } 433 | 434 | // RequestItemWidthMax will request the window to draw the next widget with at most the 435 | // specified window-normalized size (e.g. if Window's width is 500 px, then passing 436 | // 0.25 here translates to 125 px). 437 | func (wnd *Window) RequestItemWidthMax(nextMaxWS float32) { 438 | // clip the incoming value 439 | reqMax := ClipF32(0.0, 1.0, nextMaxWS) 440 | 441 | // calc the amount of window width we're requesting 442 | _, _, wndW, _ := wnd.GetDisplaySize() 443 | unpaddedWndW := wndW - wnd.Style.WindowPadding[0] - wnd.Style.WindowPadding[1] 444 | 445 | // convert this to display space 446 | wnd.requestedItemWidthMaxDC = reqMax * unpaddedWndW 447 | 448 | // clip the request to window size left 449 | if wnd.widgetCursorDC[0]+wnd.requestedItemWidthMaxDC > unpaddedWndW { 450 | wnd.requestedItemWidthMaxDC = unpaddedWndW - wnd.widgetCursorDC[0] 451 | } 452 | } 453 | 454 | // addCursorHorizontalDelta sets the amount the cursor will to change 455 | // laterally based on wether or not the client code requested a minimum size. 456 | func (wnd *Window) addCursorHorizontalDelta(hWidth float32) { 457 | // we have request, so expand the width if necessary 458 | if wnd.requestedItemWidthMinDC > 0.0 { 459 | if wnd.requestedItemWidthMinDC > hWidth { 460 | hWidth = wnd.requestedItemWidthMinDC 461 | } 462 | 463 | // reset the request to make it a one-off operation 464 | wnd.requestedItemWidthMinDC = 0.0 465 | } 466 | if wnd.requestedItemWidthMaxDC > 0.0 { 467 | if wnd.requestedItemWidthMaxDC < hWidth { 468 | hWidth = wnd.requestedItemWidthMaxDC 469 | } 470 | 471 | // reset the request to make it a one-off operation 472 | wnd.requestedItemWidthMaxDC = 0.0 473 | } 474 | 475 | wnd.widgetCursorDC[0] += hWidth 476 | } 477 | 478 | // setNextRowCursorOffset specifies how much to change the cursor position 479 | // when a new row is started. 480 | func (wnd *Window) setNextRowCursorOffset(offset float32) { 481 | // only set the next row offset if the one being passed in is greater 482 | // than the offset recorded by other widgets 483 | if offset > wnd.nextRowCursorOffsetDC { 484 | wnd.nextRowCursorOffsetDC = offset 485 | } 486 | } 487 | 488 | // clampWidgetWidthToReqW clamps the incoming width of a widget to the requested 489 | // min and max values if they are set. 490 | func (wnd *Window) clampWidgetWidthToReqW(widthDC float32) float32 { 491 | result := widthDC 492 | 493 | if wnd.requestedItemWidthMinDC > 0.0 && wnd.requestedItemWidthMinDC > widthDC { 494 | result = wnd.requestedItemWidthMinDC 495 | } 496 | if wnd.requestedItemWidthMaxDC > 0.0 && wnd.requestedItemWidthMaxDC < widthDC { 497 | result = wnd.requestedItemWidthMaxDC 498 | } 499 | 500 | return result 501 | } 502 | 503 | // Indent increases the indent level in the window, which also immediately changes 504 | // the widgetCursorDC value. 505 | func (wnd *Window) Indent() { 506 | wnd.indentLevel++ 507 | wnd.widgetCursorDC[0] += wnd.Style.IndentSpacing 508 | } 509 | 510 | // Unindent decreases the indent level in the window, which also immediately changes 511 | // the widgetCursorDC value. 512 | func (wnd *Window) Unindent() { 513 | wnd.indentLevel-- 514 | if wnd.indentLevel < 0 { 515 | wnd.indentLevel = 0 516 | } 517 | wnd.widgetCursorDC[0] -= wnd.Style.IndentSpacing 518 | } 519 | 520 | // getStoredInt will return an int and a bool indicating if key was present. 521 | func (wnd *Window) getStoredInt(key string) (int, bool) { 522 | val, okay := wnd.intStorage[key] 523 | return val, okay 524 | } 525 | 526 | // setStoredInt stores an int value for a given key; returns the previous value 527 | // and a bool indicating if a value was set previously. 528 | func (wnd *Window) setStoredInt(key string, value int) (int, bool) { 529 | oldValue, present := wnd.intStorage[key] 530 | wnd.intStorage[key] = value 531 | return oldValue, present 532 | } 533 | 534 | /* ***************************************************************************************************************************************************** 535 | _ _ _____ ______ _____ _____ _____ _____ 536 | | | | ||_ _|| _ \| __ \| ___||_ _|/ ___| 537 | | | | | | | | | | || | \/| |__ | | \ `--. 538 | | |/\| | | | | | | || | __ | __| | | `--. \ 539 | \ /\ / _| |_ | |/ / | |_\ \| |___ | | /\__/ / 540 | \/ \/ \___/ |___/ \____/\____/ \_/ \____/ 541 | 542 | ***************************************************************************************************************************************************** */ 543 | 544 | // Text renders a text widget 545 | func (wnd *Window) Text(msg string) error { 546 | cmd := wnd.getLastCmd() 547 | 548 | // get the font for the text 549 | font := wnd.Owner.GetFont(wnd.Style.FontName) 550 | if font == nil { 551 | return fmt.Errorf("couldn't access font %s from the Manager", wnd.Style.FontName) 552 | } 553 | 554 | // calculate the location for the widget 555 | pos := wnd.getCursorDC() 556 | pos[0] += wnd.Style.TextMargin[0] 557 | pos[1] -= wnd.Style.TextMargin[2] 558 | 559 | // create the text widget itself 560 | renderData := font.CreateText(pos, wnd.Style.TextColor, msg) 561 | cmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 562 | 563 | // advance the cursor for the width of the text widget 564 | wnd.addCursorHorizontalDelta(renderData.Width + wnd.Style.TextMargin[0] + wnd.Style.TextMargin[1]) 565 | wnd.setNextRowCursorOffset(renderData.Height + wnd.Style.TextMargin[2] + wnd.Style.TextMargin[3]) 566 | 567 | return nil 568 | } 569 | 570 | // Checkbox draws the checkbox widget on screen. 571 | func (wnd *Window) Checkbox(id string, value *bool) (bool, error) { 572 | cmd := wnd.getLastCmd() 573 | 574 | // calculate the location for the widget 575 | pos := wnd.getCursorDC() 576 | pos[0] += wnd.Style.CheckboxMargin[0] 577 | pos[1] -= wnd.Style.CheckboxMargin[2] 578 | 579 | // calculate the size necessary for the widget 580 | //screenW, screenH := wnd.Owner.GetResolution() 581 | checkW := wnd.Style.CheckboxPadding[0] + wnd.Style.CheckboxPadding[1] + wnd.Style.CheckboxCursorWidth 582 | checkH := wnd.Style.CheckboxPadding[2] + wnd.Style.CheckboxPadding[3] + wnd.Style.CheckboxCursorWidth 583 | 584 | // clamp the width of the widget to respect any requests to size 585 | checkW = wnd.clampWidgetWidthToReqW(checkW) 586 | 587 | // set a default color for the button 588 | bgColor := wnd.Style.CheckboxColor 589 | pressed := false 590 | 591 | // test to see if the mouse is inside the widget 592 | buttonTest := wnd.buttonBehavior(id, pos[0], pos[1], checkW, checkH) 593 | if buttonTest == buttonPressed { 594 | pressed = true 595 | *value = !(*value) 596 | } 597 | 598 | // render the widget background 599 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+checkW, pos[1]-checkH, bgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 600 | cmd.AddFaces(combos, indexes, fc) 601 | 602 | // do we show the check in the checkbox 603 | if *value { 604 | // render the checkbox cursor 605 | combos, indexes, fc = cmd.DrawRectFilledDC( 606 | pos[0]+wnd.Style.CheckboxPadding[0], 607 | pos[1]-wnd.Style.CheckboxPadding[2], 608 | pos[0]+checkW-wnd.Style.CheckboxPadding[1], //-wnd.Style.CheckboxPadding[1], 609 | pos[1]-checkH+wnd.Style.CheckboxPadding[3], //+wnd.Style.CheckboxPadding[3], 610 | wnd.Style.CheckboxCheckColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 611 | cmd.AddFaces(combos, indexes, fc) 612 | } 613 | 614 | // advance the cursor for the width of the text widget 615 | wnd.addCursorHorizontalDelta(checkW + wnd.Style.CheckboxMargin[0] + wnd.Style.CheckboxMargin[1]) 616 | wnd.setNextRowCursorOffset(checkH + wnd.Style.CheckboxMargin[2] + wnd.Style.CheckboxMargin[3]) 617 | 618 | // if we've captured the mouse click event and registered a button press, clear 619 | // the tracking data for the mouse button so that we don't get duplicate matches. 620 | if pressed { 621 | wnd.Owner.ClearMouseButtonAction(0) 622 | } 623 | 624 | return pressed, nil 625 | } 626 | 627 | // Button draws the button widget on screen with the given text. 628 | func (wnd *Window) Button(id string, text string) (bool, error) { 629 | cmd := wnd.getLastCmd() 630 | 631 | // get the font for the text 632 | font := wnd.Owner.GetFont(wnd.Style.FontName) 633 | if font == nil { 634 | return false, fmt.Errorf("Couldn't access font %s from the Manager.", wnd.Style.FontName) 635 | } 636 | 637 | // calculate the location for the widget 638 | pos := wnd.getCursorDC() 639 | pos[0] += wnd.Style.ButtonMargin[0] 640 | pos[1] -= wnd.Style.ButtonMargin[2] 641 | 642 | // calculate the size necessary for the widget 643 | dimX, dimY, _ := font.GetRenderSize(text) 644 | buttonW := dimX + wnd.Style.ButtonPadding[0] + wnd.Style.ButtonPadding[1] 645 | buttonH := dimY + wnd.Style.ButtonPadding[2] + wnd.Style.ButtonPadding[3] 646 | 647 | // clamp the width of the widget to respect any requests to size 648 | buttonW = wnd.clampWidgetWidthToReqW(buttonW + wnd.Style.ButtonMargin[0] + wnd.Style.ButtonMargin[1]) 649 | buttonW = buttonW - wnd.Style.ButtonMargin[0] - wnd.Style.ButtonMargin[1] 650 | 651 | // set a default color for the button 652 | bgColor := wnd.Style.ButtonColor 653 | pressed := false 654 | 655 | // test to see if the mouse is inside the widget 656 | buttonTest := wnd.buttonBehavior(id, pos[0], pos[1], buttonW, buttonH) 657 | if buttonTest == buttonPressed { 658 | pressed = true 659 | } else if buttonTest == buttonHover { 660 | bgColor = wnd.Style.ButtonHoverColor 661 | } 662 | 663 | // render the button background 664 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+buttonW, pos[1]-buttonH, bgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 665 | cmd.AddFaces(combos, indexes, fc) 666 | 667 | // create the text for the button 668 | centerTextX := (buttonW - dimX) / 2.0 669 | textPos := pos 670 | textPos[0] = textPos[0] + centerTextX 671 | textPos[1] = textPos[1] - wnd.Style.ButtonPadding[2] 672 | 673 | renderData := font.CreateText(textPos, wnd.Style.ButtonTextColor, text) 674 | cmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 675 | 676 | // advance the cursor for the width of the text widget 677 | wnd.addCursorHorizontalDelta(buttonW + wnd.Style.ButtonMargin[0] + wnd.Style.ButtonMargin[1]) 678 | wnd.setNextRowCursorOffset(buttonH + wnd.Style.ButtonMargin[2] + wnd.Style.ButtonMargin[3]) 679 | 680 | // if we've captured the mouse click event and registered a button press, clear 681 | // the tracking data for the mouse button so that we don't get duplicate matches. 682 | if pressed { 683 | wnd.Owner.ClearMouseButtonAction(0) 684 | } 685 | 686 | return pressed, nil 687 | } 688 | 689 | // SliderFloat creates a slider widget that alters a value based on the min/max 690 | // values provided. 691 | func (wnd *Window) SliderFloat(id string, value *float32, min, max float32) error { 692 | var valueString string 693 | sliderPressed, sliderW, _ := wnd.sliderHitTest(id) 694 | 695 | // we have a mouse down in the widget, so check to see how much the mouse has 696 | // moved and slide the control cursor and edit the value accordingly. 697 | if sliderPressed { 698 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 699 | moveRatio := mouseDeltaX / sliderW 700 | delta := moveRatio * max 701 | tmp := *value + delta 702 | if tmp > max { 703 | tmp = max 704 | } else if tmp < min { 705 | tmp = min 706 | } 707 | *value = tmp 708 | } 709 | 710 | // get the position / size for the slider 711 | cursorRel := *value 712 | cursorRel = (cursorRel - min) / (max - min) 713 | 714 | valueString = fmt.Sprintf(wnd.Style.SliderFloatFormat, *value) 715 | return wnd.sliderBehavior(valueString, cursorRel, true) 716 | } 717 | 718 | // SliderInt creates a slider widget that alters a value based on the min/max 719 | // values provided. 720 | func (wnd *Window) SliderInt(id string, value *int, min, max int) error { 721 | var valueString string 722 | sliderPressed, sliderW, _ := wnd.sliderHitTest(id) 723 | 724 | // we have a mouse down in the widget, so check to see how much the mouse has 725 | // moved and slide the control cursor and edit the value accordingly. 726 | if sliderPressed { 727 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 728 | moveRatio := mouseDeltaX / sliderW 729 | delta := moveRatio * float32(max) 730 | tmp := int(float32(*value) + delta) 731 | if tmp > max { 732 | tmp = max 733 | } else if tmp < min { 734 | tmp = min 735 | } 736 | *value = tmp 737 | } 738 | 739 | // get the position / size for the slider 740 | cursorRel := float32(*value-min) / float32(max-min) 741 | 742 | valueString = fmt.Sprintf(wnd.Style.SliderIntFormat, *value) 743 | return wnd.sliderBehavior(valueString, cursorRel, true) 744 | } 745 | 746 | // DragSliderInt creates a slider widget that alters a value based on mouse 747 | // movement only. 748 | func (wnd *Window) DragSliderInt(id string, speed float32, value *int) error { 749 | var valueString string 750 | sliderPressed, _, _ := wnd.sliderHitTest(id) 751 | 752 | // we have a mouse down in the widget, so check to see how much the mouse has 753 | // moved and slide the control cursor and edit the value accordingly. 754 | if sliderPressed { 755 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 756 | *value += int(mouseDeltaX * speed) 757 | } 758 | 759 | valueString = fmt.Sprintf(wnd.Style.SliderIntFormat, *value) 760 | return wnd.sliderBehavior(valueString, 0.0, false) 761 | } 762 | 763 | // DragSliderUInt creates a slider widget that alters a value based on mouse 764 | // movement only. 765 | func (wnd *Window) DragSliderUInt(id string, speed float32, value *uint) error { 766 | var valueString string 767 | sliderPressed, _, _ := wnd.sliderHitTest(id) 768 | 769 | // we have a mouse down in the widget, so check to see how much the mouse has 770 | // moved and slide the control cursor and edit the value accordingly. 771 | if sliderPressed { 772 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 773 | if int(mouseDeltaX*speed)+int(*value) >= 0 { 774 | *value += uint(mouseDeltaX * speed) 775 | } 776 | } 777 | 778 | valueString = fmt.Sprintf(wnd.Style.SliderIntFormat, *value) 779 | return wnd.sliderBehavior(valueString, 0.0, false) 780 | } 781 | 782 | // DragSliderFloat creates a slider widget that alters a value based on mouse 783 | // movement only. 784 | func (wnd *Window) DragSliderFloat(id string, speed float32, value *float32) error { 785 | var valueString string 786 | sliderPressed, _, _ := wnd.sliderHitTest(id) 787 | 788 | // we have a mouse down in the widget, so check to see how much the mouse has 789 | // moved and slide the control cursor and edit the value accordingly. 790 | if sliderPressed { 791 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 792 | *value += mouseDeltaX * speed 793 | } 794 | 795 | valueString = fmt.Sprintf(wnd.Style.SliderFloatFormat, *value) 796 | return wnd.sliderBehavior(valueString, 0.0, false) 797 | } 798 | 799 | // DragSliderUFloat creates a slider widget that alters a value based on mouse 800 | // movement only. 801 | func (wnd *Window) DragSliderUFloat(id string, speed float32, value *float32) error { 802 | var valueString string 803 | sliderPressed, _, _ := wnd.sliderHitTest(id) 804 | 805 | // we have a mouse down in the widget, so check to see how much the mouse has 806 | // moved and slide the control cursor and edit the value accordingly. 807 | if sliderPressed { 808 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 809 | *value += mouseDeltaX * speed 810 | if *value < 0.0 { 811 | *value = 0.0 812 | } 813 | } 814 | 815 | valueString = fmt.Sprintf(wnd.Style.SliderFloatFormat, *value) 816 | return wnd.sliderBehavior(valueString, 0.0, false) 817 | } 818 | 819 | // DragSliderFloat64 creates a slider widget that alters a value based on mouse 820 | // movement only. 821 | func (wnd *Window) DragSliderFloat64(id string, speed float64, value *float64) error { 822 | var valueString string 823 | sliderPressed, _, _ := wnd.sliderHitTest(id) 824 | 825 | // we have a mouse down in the widget, so check to see how much the mouse has 826 | // moved and slide the control cursor and edit the value accordingly. 827 | if sliderPressed { 828 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 829 | *value += float64(mouseDeltaX) * speed 830 | } 831 | 832 | valueString = fmt.Sprintf(wnd.Style.SliderFloatFormat, *value) 833 | return wnd.sliderBehavior(valueString, 0.0, false) 834 | } 835 | 836 | // DragSliderUFloat64 creates a slider widget that alters a value based on mouse 837 | // movement only. 838 | func (wnd *Window) DragSliderUFloat64(id string, speed float64, value *float64) error { 839 | var valueString string 840 | sliderPressed, _, _ := wnd.sliderHitTest(id) 841 | 842 | // we have a mouse down in the widget, so check to see how much the mouse has 843 | // moved and slide the control cursor and edit the value accordingly. 844 | if sliderPressed { 845 | mouseDeltaX, _ := wnd.Owner.GetMousePositionDelta() 846 | *value += float64(mouseDeltaX) * speed 847 | if *value < 0.0 { 848 | *value = 0.0 849 | } 850 | } 851 | 852 | valueString = fmt.Sprintf(wnd.Style.SliderFloatFormat, *value) 853 | return wnd.sliderBehavior(valueString, 0.0, false) 854 | } 855 | 856 | // sliderHitTest calculates the size of the widget and then 857 | // returns true if mouse is within the bounding box of this widget; 858 | // as a convenience it also returns the width and height of the control 859 | // as the second and third results respectively. 860 | func (wnd *Window) sliderHitTest(id string) (bool, float32, float32) { 861 | // get the font for the text 862 | font := wnd.Owner.GetFont(wnd.Style.FontName) 863 | if font == nil { 864 | return false, 0, 0 865 | } 866 | 867 | // calculate the location for the widget 868 | pos := wnd.getCursorDC() 869 | pos[0] += wnd.Style.SliderMargin[0] 870 | pos[1] -= wnd.Style.SliderMargin[2] 871 | 872 | // calculate the size necessary for the widget 873 | _, _, wndWidth, _ := wnd.GetDisplaySize() 874 | _, dimY, _ := font.GetRenderSize("0.0") 875 | sliderW := wndWidth - wnd.Style.WindowPadding[0] - wnd.Style.WindowPadding[1] - wnd.Style.SliderMargin[0] - wnd.Style.SliderMargin[1] 876 | sliderH := dimY + wnd.Style.SliderPadding[2] + wnd.Style.SliderPadding[3] 877 | 878 | // calculate how much of the slider control is available to the cursor for 879 | // movement, which affects the scale of the value to edit. 880 | sliderW = sliderW - wnd.Style.SliderCursorWidth - wnd.Style.SliderPadding[0] - wnd.Style.SliderPadding[1] 881 | sliderW = sliderW - wnd.Style.SliderMargin[0] - wnd.Style.SliderMargin[1] 882 | 883 | // clamp the widget to the requested width 884 | sliderW = wnd.clampWidgetWidthToReqW(sliderW) 885 | 886 | // test to see if the mouse is inside the widget 887 | lmbStatus := wnd.Owner.GetMouseButtonAction(0) 888 | if lmbStatus != MouseUp { 889 | // are we already the active widget? 890 | if wnd.Owner.GetActiveInputID() == id { 891 | return true, sliderW, sliderH 892 | } 893 | 894 | // try to claim focus -- wont work if something already claimed it this mouse press 895 | mx, my := wnd.Owner.GetMouseDownPosition(0) 896 | if mx > pos[0] && my > pos[1]-sliderH && mx < pos[0]+sliderW && my < pos[1] { 897 | claimed := wnd.Owner.SetActiveInputID(id) 898 | if claimed { 899 | return true, sliderW, sliderH 900 | } 901 | } 902 | } 903 | 904 | return false, sliderW, sliderH 905 | } 906 | 907 | // sliderBehavior is the actual action of drawing the slider widget. 908 | func (wnd *Window) sliderBehavior(valueString string, valueRatio float32, drawCursor bool) error { 909 | cmd := wnd.getLastCmd() 910 | 911 | // get the font for the text 912 | font := wnd.Owner.GetFont(wnd.Style.FontName) 913 | if font == nil { 914 | return fmt.Errorf("Couldn't access font %s from the Manager.", wnd.Style.FontName) 915 | } 916 | 917 | // calculate the location for the widget 918 | pos := wnd.getCursorDC() 919 | pos[0] += wnd.Style.SliderMargin[0] 920 | pos[1] -= wnd.Style.SliderMargin[2] 921 | 922 | // calculate the size necessary for the widget 923 | _, _, wndWidth, _ := wnd.GetDisplaySize() 924 | dimX, dimY, _ := font.GetRenderSize(valueString) 925 | sliderW := wndWidth - wnd.widgetCursorDC[0] - wnd.Style.WindowPadding[1] - wnd.Style.SliderMargin[1] 926 | sliderH := dimY + wnd.Style.SliderPadding[2] + wnd.Style.SliderPadding[3] 927 | 928 | // clamp the widget to the requested width 929 | sliderW = wnd.clampWidgetWidthToReqW(sliderW) 930 | sliderW = sliderW - wnd.Style.SliderMargin[0] - wnd.Style.SliderMargin[1] 931 | 932 | // set a default color for the background 933 | bgColor := wnd.Style.SliderBgColor 934 | 935 | // render the widget background 936 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+sliderW, pos[1]-sliderH, bgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 937 | cmd.AddFaces(combos, indexes, fc) 938 | 939 | if drawCursor { 940 | // calculate how much of the slider control is available to the cursor for 941 | // movement, which affects the scale of the value to edit. 942 | sliderRangeW := sliderW - wnd.Style.SliderCursorWidth - wnd.Style.SliderPadding[0] - wnd.Style.SliderPadding[1] 943 | cursorH := sliderH - wnd.Style.SliderPadding[2] - wnd.Style.SliderPadding[3] 944 | 945 | // get the position / size for the slider 946 | cursorPosX := valueRatio*sliderRangeW + wnd.Style.SliderPadding[0] 947 | 948 | // render the slider cursor 949 | combos, indexes, fc = cmd.DrawRectFilledDC(pos[0]+cursorPosX, pos[1]-wnd.Style.SliderPadding[2], 950 | pos[0]+cursorPosX+wnd.Style.SliderCursorWidth, pos[1]-cursorH-wnd.Style.SliderPadding[3], 951 | wnd.Style.SliderCursorColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 952 | cmd.AddFaces(combos, indexes, fc) 953 | } 954 | 955 | // create the text for the slider 956 | textPos := pos 957 | textPos[0] += wnd.Style.SliderPadding[0] + (0.5 * sliderW) - (0.5 * dimX) 958 | textPos[1] -= wnd.Style.SliderPadding[2] 959 | renderData := font.CreateText(textPos, wnd.Style.SliderTextColor, valueString) 960 | cmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 961 | 962 | // advance the cursor for the width of the text widget 963 | wnd.addCursorHorizontalDelta(sliderW + wnd.Style.SliderMargin[0] + wnd.Style.SliderMargin[1]) 964 | wnd.setNextRowCursorOffset(sliderH + wnd.Style.SliderMargin[2] + wnd.Style.SliderMargin[3]) 965 | 966 | return nil 967 | } 968 | 969 | // Image draws the image widget on screen. 970 | func (wnd *Window) Image(id string, widthS, heightS float32, color mgl.Vec4, textureIndex uint32, uvPair mgl.Vec4) error { 971 | cmd := wnd.getLastCmd() 972 | 973 | // get the font for the text 974 | font := wnd.Owner.GetFont(wnd.Style.FontName) 975 | if font == nil { 976 | return fmt.Errorf("Couldn't access font %s from the Manager.", wnd.Style.FontName) 977 | } 978 | 979 | // calculate the location for the widget 980 | pos := wnd.getCursorDC() 981 | pos[0] += wnd.Style.ImageMargin[0] 982 | pos[1] += wnd.Style.ImageMargin[2] 983 | widthDC, heightDC := wnd.Owner.ScreenToDisplay(widthS, heightS) 984 | 985 | // clamp the width to the requsted size 986 | widthDC = wnd.clampWidgetWidthToReqW(widthDC) 987 | 988 | // render the button background 989 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+widthDC, pos[1]-heightDC, color, textureIndex, uvPair) 990 | cmd.AddFaces(combos, indexes, fc) 991 | 992 | // advance the cursor for the width of the text widget 993 | wnd.addCursorHorizontalDelta(widthDC + wnd.Style.ImageMargin[0] + wnd.Style.ImageMargin[1]) 994 | wnd.setNextRowCursorOffset(heightDC + wnd.Style.ImageMargin[2] + wnd.Style.ImageMargin[3]) 995 | 996 | return nil 997 | } 998 | 999 | // Separator draws a separator rectangle and advances the cursor to a new row automatically. 1000 | func (wnd *Window) Separator() { 1001 | wnd.StartRow() 1002 | cmd := wnd.getLastCmd() 1003 | 1004 | // calculate the location for the widget 1005 | pos := wnd.getCursorDC() 1006 | pos[0] += wnd.Style.SeparatorMargin[0] 1007 | pos[1] -= wnd.Style.SeparatorMargin[2] 1008 | 1009 | _, _, widthDC, _ := wnd.GetDisplaySize() 1010 | widthDC += -wnd.Style.SeparatorMargin[0] - wnd.Style.SeparatorMargin[1] - wnd.Style.WindowPadding[0] - wnd.Style.WindowPadding[1] 1011 | 1012 | // draw the separator 1013 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+widthDC, pos[1]-wnd.Style.SeparatorHeight, wnd.Style.SeparatorColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 1014 | cmd.AddFaces(combos, indexes, fc) 1015 | 1016 | // start a new row 1017 | wnd.setNextRowCursorOffset(wnd.Style.SeparatorHeight + wnd.Style.SeparatorMargin[2] + wnd.Style.SeparatorMargin[3]) 1018 | wnd.StartRow() 1019 | } 1020 | 1021 | // Space adds some horizontal space based on the relative width of the window. 1022 | // For example: a window width of 800, passing 0.1 adds a space of 80 1023 | func (wnd *Window) Space(spaceS float32) { 1024 | _, _, widthDC, _ := wnd.GetDisplaySize() 1025 | wnd.addCursorHorizontalDelta(widthDC * spaceS) 1026 | } 1027 | 1028 | // Custom inserts a new cmdList and sets it up for custom rendering. 1029 | func (wnd *Window) Custom(widthS, heightS float32, margin mgl.Vec4, customDraw func()) { 1030 | // get the location and size of this widget 1031 | pos := wnd.getCursorDC() 1032 | pos[0] += margin[0] 1033 | pos[1] -= margin[2] 1034 | widthDC, heightDC := wnd.Owner.ScreenToDisplay(widthS, heightS) 1035 | 1036 | // clamp the width to the requsted size 1037 | widthDC = wnd.clampWidgetWidthToReqW(widthDC) 1038 | 1039 | // create a new command for this one 1040 | cmd := wnd.addNewCmd() 1041 | cmd.isCustom = true 1042 | cmd.onCustomDraw = customDraw 1043 | cmd.clipRect[0] = pos[0] + 1.0 1044 | cmd.clipRect[1] = pos[1] 1045 | cmd.clipRect[2] = widthDC - 1.0 1046 | cmd.clipRect[3] = heightDC 1047 | 1048 | // advance the cursor 1049 | wnd.addCursorHorizontalDelta(widthDC + margin[0] + margin[1]) 1050 | wnd.setNextRowCursorOffset(heightDC + margin[2] + margin[3]) 1051 | } 1052 | 1053 | // Editbox creates an editbox control that changes the value string. 1054 | func (wnd *Window) Editbox(id string, value *string) (bool, error) { 1055 | cmd := wnd.getLastCmd() 1056 | 1057 | // get the font for the text 1058 | font := wnd.Owner.GetFont(wnd.Style.FontName) 1059 | if font == nil { 1060 | return false, fmt.Errorf("Couldn't access font %s from the Manager.", wnd.Style.FontName) 1061 | } 1062 | 1063 | // calculate the location for the widget 1064 | pos := wnd.getCursorDC() 1065 | pos[0] += wnd.Style.EditboxMargin[0] 1066 | pos[1] -= wnd.Style.EditboxMargin[2] 1067 | 1068 | // calculate the size necessary for the widget; if the text is empty use 1069 | // a const string to calculate the height. 1070 | _, _, wndWidth, _ := wnd.GetDisplaySize() 1071 | textToSize := *value 1072 | if len(textToSize) == 0 { 1073 | textToSize = "FIXEDSIZE" 1074 | } 1075 | _, dimY, _ := font.GetRenderSize(textToSize) 1076 | editboxW := wndWidth - wnd.widgetCursorDC[0] - wnd.Style.WindowPadding[1] - wnd.Style.EditboxMargin[1] 1077 | editboxH := dimY + wnd.Style.EditboxPadding[2] + wnd.Style.EditboxPadding[3] 1078 | 1079 | // clamp the width to the requsted size 1080 | editboxW = wnd.clampWidgetWidthToReqW(editboxW) 1081 | editboxW = editboxW - wnd.Style.EditboxMargin[0] - wnd.Style.EditboxMargin[1] 1082 | 1083 | // set a default color for the button 1084 | bgColor := wnd.Style.EditboxBgColor 1085 | 1086 | // test to see if the mouse is inside the widget 1087 | lmbStatus := wnd.Owner.GetMouseButtonAction(0) 1088 | if lmbStatus != MouseUp { 1089 | // are we already the active widget? 1090 | if wnd.Owner.GetActiveInputID() != id { 1091 | // try to claim focus -- wont work if something already claimed it this mouse press 1092 | mx, my := wnd.Owner.GetMouseDownPosition(0) 1093 | if mx > pos[0] && my > pos[1]-editboxH && mx < pos[0]+editboxW && my < pos[1] { 1094 | wnd.Owner.SetActiveInputID(id) 1095 | wnd.Owner.setActiveTextEditor(id, 0) 1096 | } 1097 | } 1098 | } 1099 | 1100 | // see if we're the active editor. if so, then we can consume the key events; 1101 | // otherwise we leave them be. 1102 | editorState := wnd.Owner.getActiveTextEditor() 1103 | if editorState != nil && editorState.ID == id { 1104 | // we're the active editor so set the background color accordingly 1105 | bgColor = wnd.Style.EditboxActiveColor 1106 | 1107 | // grab the key events 1108 | keyEvents := wnd.Owner.GetKeyEvents() 1109 | for _, event := range keyEvents { 1110 | if event.IsRune == false { 1111 | // all of these keys reset the timer if it doesn't lose focus, so 1112 | // just reset it here for convenience 1113 | editorState.CursorTimer = 0.0 1114 | 1115 | // handle the key events specially in their own way 1116 | switch event.KeyCode { 1117 | case EweyKeyRight: 1118 | if editorState.CursorOffset < len(*value) { 1119 | editorState.CursorOffset++ 1120 | } 1121 | case EweyKeyLeft: 1122 | if editorState.CursorOffset > 0 { 1123 | editorState.CursorOffset-- 1124 | } 1125 | if editorState.CharacterShift > 0 { 1126 | editorState.CharacterShift-- 1127 | } 1128 | case EweyKeyBackspace: 1129 | // erase the rune previous to the cursor 1130 | if editorState.CursorOffset > 0 { 1131 | newString := (*value)[:editorState.CursorOffset-1] + (*value)[editorState.CursorOffset:] 1132 | *value = newString 1133 | editorState.CursorOffset-- 1134 | } 1135 | if editorState.CharacterShift > 0 { 1136 | editorState.CharacterShift-- 1137 | } 1138 | case EweyKeyDelete: 1139 | // erase the rune just after the cursor 1140 | if editorState.CursorOffset < len(*value) { 1141 | newString := (*value)[:editorState.CursorOffset] + (*value)[editorState.CursorOffset+1:] 1142 | *value = newString 1143 | } 1144 | case EweyKeyEnter, EweyKeyEscape: 1145 | // give up the focus voluntarily here 1146 | wnd.Owner.clearActiveTextEditor() 1147 | wnd.Owner.ClearActiveInputID() 1148 | editorState = nil 1149 | case EweyKeyEnd: 1150 | editorState.CursorOffset = len(*value) 1151 | case EweyKeyHome: 1152 | editorState.CursorOffset = 0 1153 | editorState.CharacterShift = 0 1154 | case EweyKeyInsert: 1155 | if event.ShiftDown { 1156 | clippy, _ := wnd.Owner.GetClipboardString() 1157 | newString := (*value)[:editorState.CursorOffset] + clippy + (*value)[editorState.CursorOffset:] 1158 | *value = newString 1159 | } 1160 | } 1161 | } else { 1162 | // do some special testing for clipboard commands 1163 | if event.Rune == 'V' && event.CtrlDown { 1164 | clippy, _ := wnd.Owner.GetClipboardString() 1165 | newString := (*value)[:editorState.CursorOffset] + clippy + (*value)[editorState.CursorOffset:] 1166 | *value = newString 1167 | } else { 1168 | // insert the rune into the value string 1169 | newString := (*value)[:editorState.CursorOffset] + string(event.Rune) + (*value)[editorState.CursorOffset:] 1170 | *value = newString 1171 | editorState.CursorOffset++ 1172 | } 1173 | } 1174 | } 1175 | 1176 | } 1177 | 1178 | // render the button background 1179 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0], pos[1], pos[0]+editboxW, pos[1]-editboxH, bgColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 1180 | cmd.AddFaces(combos, indexes, fc) 1181 | 1182 | // create the text for the button if the string is not empty 1183 | if len(*value) > 0 { 1184 | textPos := pos 1185 | textPos[0] += wnd.Style.EditboxPadding[0] 1186 | textPos[1] -= wnd.Style.EditboxPadding[2] 1187 | cursorPos := -1 1188 | textOffset := -1 1189 | if editorState != nil && editorState.ID == id { 1190 | cursorPos = editorState.CursorOffset 1191 | textOffset = editorState.CharacterShift 1192 | } 1193 | renderData := font.CreateTextAdv(textPos, wnd.Style.EditboxTextColor, editboxW-wnd.Style.EditboxCursorWidth, textOffset, cursorPos, *value) 1194 | cmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 1195 | 1196 | // if we overflowed the cursor, start shifting the text over one frame at a time until 1197 | // we don't overflow anymore. 1198 | if renderData.CursorOverflowRight { 1199 | editorState.CharacterShift++ 1200 | } 1201 | } 1202 | 1203 | // if we're the active editor, deal with drawing the cursor here 1204 | if editorState != nil && editorState.ID == id { 1205 | // add the current delta to the timer 1206 | editorState.CursorTimer += float32(wnd.Owner.FrameDelta) 1207 | 1208 | // did we overflow the blink interval? if so, reset the timer 1209 | if editorState.CursorTimer > wnd.Style.EditboxBlinkInterval { 1210 | editorState.CursorTimer -= wnd.Style.EditboxBlinkInterval 1211 | } 1212 | 1213 | // draw the cursor if we're within the blink duration 1214 | if editorState.CursorTimer < wnd.Style.EditboxBlinkDuration { 1215 | cursorOffsetDC := font.OffsetForIndexAdv(*value, editorState.CharacterShift, editorState.CursorOffset) 1216 | cursorOffsetDC += wnd.Style.EditboxPadding[0] 1217 | 1218 | // render the editbox cursor 1219 | combos, indexes, fc := cmd.DrawRectFilledDC(pos[0]+cursorOffsetDC, pos[1], pos[0]+cursorOffsetDC+wnd.Style.EditboxCursorWidth, pos[1]-editboxH, 1220 | wnd.Style.EditboxCursorColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 1221 | cmd.AddFaces(combos, indexes, fc) 1222 | } 1223 | } 1224 | 1225 | // advance the cursor for the width of the text widget 1226 | wnd.addCursorHorizontalDelta(editboxW + wnd.Style.EditboxMargin[0] + wnd.Style.EditboxMargin[1]) 1227 | wnd.setNextRowCursorOffset(editboxH + wnd.Style.EditboxMargin[2] + wnd.Style.EditboxMargin[3]) 1228 | 1229 | return true, nil 1230 | } 1231 | 1232 | // TreeNode draws the tree node widget on screen with the given text. Returns a 1233 | // bool indicating if the tree node is considered to be 'open'. 1234 | func (wnd *Window) TreeNode(id string, text string) (bool, error) { 1235 | cmd := wnd.getLastCmd() 1236 | 1237 | // get the font for the text 1238 | font := wnd.Owner.GetFont(wnd.Style.FontName) 1239 | if font == nil { 1240 | return false, fmt.Errorf("Couldn't access font %s from the Manager.", wnd.Style.FontName) 1241 | } 1242 | 1243 | // calculate the location for the widget 1244 | pos := wnd.getCursorDC() 1245 | pos[0] += wnd.Style.TreeNodeMargin[0] 1246 | pos[1] -= wnd.Style.TreeNodeMargin[2] 1247 | 1248 | // calculate the size necessary for the widget 1249 | dimX, dimY, _ := font.GetRenderSize(text) 1250 | nodeW := dimX + wnd.Style.TreeNodePadding[0] + wnd.Style.TreeNodePadding[1] 1251 | nodeH := dimY + wnd.Style.TreeNodePadding[2] + wnd.Style.TreeNodePadding[3] 1252 | 1253 | // clamp the width of the widget to respect any requests to size 1254 | nodeW = wnd.clampWidgetWidthToReqW(nodeW) 1255 | nodeW = nodeW - wnd.Style.TreeNodeMargin[0] - wnd.Style.TreeNodeMargin[1] 1256 | 1257 | // check to see if the window has a stored value for this node's ID and 1258 | // whether or not that value indicates if the node is considered open. 1259 | var openState bool 1260 | storedOpenState, statePresent := wnd.getStoredInt(id) 1261 | if statePresent && storedOpenState > 0 { 1262 | openState = true 1263 | } 1264 | 1265 | // test to see if the mouse is inside the widget 1266 | pressed := false 1267 | buttonTest := wnd.buttonBehavior(id, pos[0], pos[1], nodeW, nodeH) 1268 | if buttonTest == buttonPressed { 1269 | pressed = true 1270 | } 1271 | 1272 | // if it's pressed, we invert the state and store the updated value 1273 | if pressed { 1274 | openState = !openState 1275 | if openState == false { 1276 | wnd.setStoredInt(id, 0) 1277 | } else { 1278 | wnd.setStoredInt(id, 1) 1279 | } 1280 | } 1281 | 1282 | // render the node icons in a square 1283 | iconX1 := pos[0] 1284 | iconY1 := pos[1] - nodeH*0.375 1285 | iconX2 := pos[0] + nodeH*0.25 1286 | iconY2 := pos[1] - nodeH*0.625 1287 | combos, indexes, fc := cmd.drawTreeNodeIcon(openState, iconX1, iconY1, iconX2, iconY2, wnd.Style.TreeNodeTextColor, defaultTextureSampler, wnd.Owner.whitePixelUv) 1288 | cmd.AddFaces(combos, indexes, fc) 1289 | iconXOffset := nodeH*0.25 + 4 1290 | pos[0] += iconXOffset // adjust the position to acocund for this 1291 | 1292 | // create the text for the button 1293 | internalW := nodeW - wnd.Style.TreeNodePadding[0] - wnd.Style.TreeNodePadding[0] 1294 | centerTextX := (internalW / 2.0) - (dimX / 2.0) 1295 | textPos := pos 1296 | textPos[0] += centerTextX 1297 | textPos[1] -= wnd.Style.TreeNodePadding[2] 1298 | renderData := font.CreateText(textPos, wnd.Style.TreeNodeTextColor, text) 1299 | cmd.AddFaces(renderData.ComboBuffer, renderData.IndexBuffer, renderData.Faces) 1300 | 1301 | // advance the cursor for the width of the text + iconXOffset 1302 | wnd.addCursorHorizontalDelta(nodeW + iconXOffset + wnd.Style.TreeNodeMargin[0] + wnd.Style.TreeNodeMargin[1]) 1303 | wnd.setNextRowCursorOffset(nodeH + wnd.Style.TreeNodeMargin[2] + wnd.Style.TreeNodeMargin[3]) 1304 | 1305 | // if we've captured the mouse click event and registered a button press, clear 1306 | // the tracking data for the mouse button so that we don't get duplicate matches. 1307 | if pressed { 1308 | wnd.Owner.ClearMouseButtonAction(0) 1309 | } 1310 | 1311 | return openState, nil 1312 | } 1313 | 1314 | const ( 1315 | buttonNoAction = 0 1316 | buttonPressed = 1 1317 | buttonHover = 2 1318 | ) 1319 | 1320 | // buttonBehavior returns a enumerated value of the consts above indicating a 1321 | // buttonNoAction, buttonPressed or buttonHover for the mouse interaction with 1322 | // the 'button'. 1323 | // the function also will set the active input id if mouse is down and was 1324 | // originally pressed inside the 'button' space. 1325 | func (wnd *Window) buttonBehavior(id string, minX, minY, width, height float32) int { 1326 | result := buttonNoAction 1327 | 1328 | // test to see if the mouse is inside the widget 1329 | mx, my := wnd.Owner.GetMousePosition() 1330 | if mx > minX && my > minY-height && mx < minX+width && my < minY { 1331 | lmbStatus := wnd.Owner.GetMouseButtonAction(0) 1332 | 1333 | if lmbStatus == MouseClick { 1334 | result = buttonPressed 1335 | } else if lmbStatus == MouseUp { 1336 | result = buttonHover 1337 | } else { 1338 | // mouse is down, but was it pressed inside the button? 1339 | mdx, mdy := wnd.Owner.GetMouseDownPosition(0) 1340 | if mdx > minX && mdy > minY-height && mdx < minX+width && mdy < minY { 1341 | result = buttonHover 1342 | wnd.Owner.SetActiveInputID(id) 1343 | } 1344 | } 1345 | } 1346 | 1347 | return result 1348 | } 1349 | --------------------------------------------------------------------------------