├── .gitignore ├── .gitmodules ├── README.rst ├── build.sh ├── button.go ├── checkbox.go ├── colorpicker.go ├── colorwheel.go ├── combobox.go ├── constant.go ├── doc ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── entypoicons.go ├── fonts.go ├── fonts ├── Roboto-Bold.ttf ├── Roboto-Regular.ttf └── entypo.ttf ├── global.go ├── glshader.go ├── graph.go ├── icons └── materialicons │ ├── build.sh │ ├── font.go │ ├── font │ └── MaterialIcons-Regular.ttf │ ├── load.go │ ├── materialicons.go │ └── util │ ├── codepoints │ └── gen_icon_symbols.py ├── imagepanel.go ├── imageview.go ├── keyboard.go ├── label.go ├── layout.go ├── nanoguiext ├── layout.go └── spinner.go ├── popup.go ├── popupbutton.go ├── progressbar.go ├── sample1 ├── bindata.go ├── demo │ └── demo.go ├── font │ └── GenShinGothic-P-Regular.ttf ├── icons │ ├── icon1.png │ ├── icon2.png │ ├── icon3.png │ ├── icon4.png │ ├── icon5.png │ ├── icon6.png │ ├── icon7.png │ └── icon8.png ├── sample.go └── web.go ├── screen.go ├── slider.go ├── textbox.go ├── theme.go ├── util.go ├── util ├── entypo.yml └── gen_icon_symbols.py ├── vscrollpanel.go ├── widget.go └── window.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | doc/_build 4 | *.js 5 | *.js.map 6 | sample1/sample1 7 | sample1/sample1.exe 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/.gitmodules -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | NanoGUI.go 2 | ================ 3 | 4 | This is a golang port of `NanoGUI `_. NanoGUI is a a minimalistic cross-platform widget library for OpenGL. 5 | 6 | Default Font Customize Guide 7 | -------------------------------- 8 | 9 | TBD 10 | 11 | Copyrights 12 | ---------------- 13 | 14 | It contains the following fonts: 15 | 16 | * Roboto Fonts 17 | 18 | * Genshin Bold 19 | * Genshin Regular 20 | 21 | * Material Design Icon: https://www.google.com/design/icons/ 22 | 23 | * Material Design Icon is developed by Google and it is released under Creative Commons 4.0 CC-BY. 24 | 25 | License 26 | ---------- 27 | 28 | zlib license 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pushd util 3 | python gen_icon_symbols.py 4 | popd 5 | go-bindata -pkg nanogui -o fonts.go fonts 6 | -------------------------------------------------------------------------------- /button.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/glfw" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type ButtonFlags int 9 | 10 | const ( 11 | NormalButtonType ButtonFlags = 1 12 | RadioButtonType ButtonFlags = 2 13 | ToggleButtonType ButtonFlags = 4 14 | PopupButtonType ButtonFlags = 8 15 | ) 16 | 17 | type ButtonIconPosition int 18 | 19 | const ( 20 | ButtonIconLeft ButtonIconPosition = iota 21 | ButtonIconLeftCentered 22 | ButtonIconRight 23 | ButtonIconRightCentered 24 | ) 25 | 26 | type Button struct { 27 | WidgetImplement 28 | 29 | caption string 30 | icon Icon 31 | imageIcon int 32 | iconPosition ButtonIconPosition 33 | pushed bool 34 | flags ButtonFlags 35 | backgroundColor nanovgo.Color 36 | textColor nanovgo.Color 37 | callback func() 38 | changeCallback func(bool) 39 | buttonGroup []*Button 40 | } 41 | 42 | func NewButton(parent Widget, captions ...string) *Button { 43 | var caption string 44 | switch len(captions) { 45 | case 0: 46 | caption = "Untitled" 47 | case 1: 48 | caption = captions[0] 49 | default: 50 | panic("NewButton can accept only one extra parameter (label)") 51 | } 52 | 53 | button := &Button{ 54 | caption: caption, 55 | iconPosition: ButtonIconLeftCentered, 56 | flags: NormalButtonType, 57 | } 58 | InitWidget(button, parent) 59 | return button 60 | } 61 | 62 | func NewToolButton(parent Widget, icon Icon) *Button { 63 | button := NewButton(parent, "") 64 | button.SetCaption("") 65 | button.SetIcon(icon) 66 | button.SetFlags(RadioButtonType | ToggleButtonType) 67 | //button.SetFixedSize(25, 25) 68 | return button 69 | } 70 | 71 | func NewToolButtonByImage(parent Widget, img int) *Button { 72 | button := NewButton(parent, "") 73 | button.SetCaption("") 74 | button.SetImageIcon(img) 75 | button.SetFlags(RadioButtonType | ToggleButtonType) 76 | //button.SetFixedSize(25, 25) 77 | return button 78 | } 79 | 80 | func (b *Button) Caption() string { 81 | return b.caption 82 | } 83 | 84 | func (b *Button) SetCaption(caption string) { 85 | b.caption = caption 86 | } 87 | 88 | func (b *Button) BackgroundColor() nanovgo.Color { 89 | return b.backgroundColor 90 | } 91 | 92 | func (b *Button) SetBackgroundColor(c nanovgo.Color) { 93 | b.backgroundColor = c 94 | } 95 | 96 | func (b *Button) TextColor() nanovgo.Color { 97 | if !b.enabled { 98 | return b.theme.DisabledTextColor 99 | } else if b.textColor.A == 0.0 { 100 | return b.theme.TextColor 101 | } 102 | return b.textColor 103 | } 104 | 105 | func (b *Button) SetTextColor(c nanovgo.Color) { 106 | b.textColor = c 107 | } 108 | 109 | func (b *Button) Icon() Icon { 110 | return b.icon 111 | } 112 | 113 | func (b *Button) SetIcon(i Icon) { 114 | b.icon = i 115 | b.imageIcon = 0 116 | } 117 | 118 | func (b *Button) ImageIcon() int { 119 | return b.imageIcon 120 | } 121 | 122 | func (b *Button) SetImageIcon(i int) { 123 | b.imageIcon = i 124 | b.icon = 0 125 | } 126 | func (b *Button) Flags() ButtonFlags { 127 | return b.flags 128 | } 129 | 130 | func (b *Button) SetFlags(f ButtonFlags) { 131 | b.flags = f 132 | } 133 | 134 | func (b *Button) IconPosition() ButtonIconPosition { 135 | return b.iconPosition 136 | } 137 | 138 | func (b *Button) SetIconPosition(p ButtonIconPosition) { 139 | b.iconPosition = p 140 | } 141 | 142 | func (b *Button) Pushed() bool { 143 | return b.pushed 144 | } 145 | 146 | func (b *Button) SetPushed(p bool) { 147 | b.pushed = p 148 | } 149 | 150 | // SetCallback set the push callback (for any type of button) 151 | func (b *Button) SetCallback(callback func()) { 152 | b.callback = callback 153 | } 154 | 155 | // SetChangeCallback set the change callback (for toggle buttons) 156 | func (b *Button) SetChangeCallback(callback func(bool)) { 157 | b.changeCallback = callback 158 | } 159 | 160 | // SetButtonGroup set the button group (for radio buttons) 161 | func (b *Button) SetButtonGroup(group []*Button) { 162 | b.buttonGroup = group 163 | } 164 | 165 | // ButtonGroup returns the button group 166 | func (b *Button) ButtonGroup() []*Button { 167 | return b.buttonGroup 168 | } 169 | 170 | func (b *Button) FontSize() int { 171 | if b.fontSize > 0 { 172 | return b.fontSize 173 | } 174 | return b.theme.ButtonFontSize 175 | } 176 | 177 | func (b *Button) SetFontSize(size int) { 178 | b.fontSize = size 179 | } 180 | 181 | func (b *Button) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 182 | b.WidgetImplement.MouseButtonEvent(b, x, y, button, down, modifier) 183 | 184 | if button == glfw.MouseButton1 && b.enabled { 185 | pushedBackup := b.pushed 186 | if down { 187 | if b.flags&RadioButtonType != 0 { 188 | if len(b.buttonGroup) == 0 { 189 | for _, child := range self.Parent().Children() { 190 | button, ok := child.(*Button) 191 | if ok && button != b && button.Flags()&RadioButtonType != 0 && button.Pushed() { 192 | button.SetPushed(false) 193 | if button.changeCallback != nil { 194 | button.changeCallback(false) 195 | } 196 | } 197 | } 198 | } else { 199 | for _, button := range b.buttonGroup { 200 | if button != b && button.Flags()&RadioButtonType != 0 && button.Pushed() { 201 | button.SetPushed(false) 202 | if button.changeCallback != nil { 203 | button.changeCallback(false) 204 | } 205 | } 206 | } 207 | } 208 | } else if b.flags&PopupButtonType != 0 { 209 | for _, widget := range b.Parent().Children() { 210 | button, ok := widget.(*Button) 211 | if ok && button != b && button.Flags()&PopupButtonType != 0 && button.Pushed() { 212 | button.SetPushed(false) 213 | if button.changeCallback != nil { 214 | button.changeCallback(false) 215 | } 216 | } 217 | } 218 | } 219 | if b.flags&ToggleButtonType != 0 { 220 | b.pushed = !b.pushed 221 | } else { 222 | b.pushed = true 223 | } 224 | } else if b.pushed { 225 | if b.Contains(x, y) && b.callback != nil { 226 | b.callback() 227 | } 228 | if b.flags&NormalButtonType != 0 { 229 | b.pushed = false 230 | } 231 | } 232 | if pushedBackup != b.pushed && b.changeCallback != nil { 233 | b.changeCallback(b.pushed) 234 | } 235 | return true 236 | } 237 | return false 238 | } 239 | 240 | func (b *Button) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 241 | fontSize := float32(b.FontSize()) 242 | 243 | ctx.SetFontSize(fontSize) 244 | ctx.SetFontFace(b.theme.FontBold) 245 | tw, _ := ctx.TextBounds(0, 0, b.caption) 246 | var iw float32 247 | ih := fontSize 248 | 249 | if b.icon > 0 { 250 | ih *= 1.5 / 2 251 | ctx.SetFontFace(b.theme.FontIcons) 252 | ctx.SetFontSize(ih) 253 | iw, _ = ctx.TextBounds(0, 0, string([]rune{rune(b.icon)})) 254 | iw += float32(b.y) * 0.15 255 | } else if b.imageIcon > 0 { 256 | ih *= 0.9 257 | w, h, _ := ctx.ImageSize(b.imageIcon) 258 | iw = float32(w) * ih / float32(h) 259 | } 260 | return int(tw + iw + 20), int(fontSize) + 10 261 | } 262 | 263 | func (b *Button) Draw(self Widget, ctx *nanovgo.Context) { 264 | b.WidgetImplement.Draw(self, ctx) 265 | 266 | bx := float32(b.x) 267 | by := float32(b.y) 268 | bw := float32(b.w) 269 | bh := float32(b.h) 270 | 271 | var gradTop nanovgo.Color 272 | var gradBot nanovgo.Color 273 | 274 | if b.pushed { 275 | gradTop = b.theme.ButtonGradientTopPushed 276 | gradBot = b.theme.ButtonGradientBotPushed 277 | } else if b.mouseFocus && b.enabled { 278 | gradTop = b.theme.ButtonGradientTopFocused 279 | gradBot = b.theme.ButtonGradientBotFocused 280 | } else { 281 | gradTop = b.theme.ButtonGradientTopUnfocused 282 | gradBot = b.theme.ButtonGradientBotUnfocused 283 | } 284 | ctx.BeginPath() 285 | ctx.RoundedRect(bx+1.0, by+1.0, bw-2.0, bh-2.0, float32(b.theme.ButtonCornerRadius-1)) 286 | 287 | if b.backgroundColor.A != 0.0 { 288 | bgColor := b.backgroundColor 289 | bgColor.A = 1.0 290 | ctx.SetFillColor(bgColor) 291 | ctx.Fill() 292 | if b.pushed { 293 | gradTop.A = 0.8 294 | gradBot.A = 0.8 295 | } else { 296 | a := 1 - b.backgroundColor.A 297 | if !b.enabled { 298 | a = a*0.5 + 0.5 299 | } 300 | gradTop.A = a 301 | gradBot.A = a 302 | } 303 | } 304 | 305 | bg := nanovgo.LinearGradient(bx, by, bx, by+bh, gradTop, gradBot) 306 | ctx.SetFillPaint(bg) 307 | ctx.Fill() 308 | 309 | ctx.BeginPath() 310 | var pOff float32 = 0.0 311 | if b.pushed { 312 | pOff = 1.0 313 | } 314 | ctx.RoundedRect(bx+0.5, by+1.5-pOff, bw-1.0, bh-2+pOff, float32(b.theme.ButtonCornerRadius)) 315 | ctx.SetStrokeColor(b.theme.BorderLight) 316 | ctx.Stroke() 317 | 318 | ctx.BeginPath() 319 | ctx.RoundedRect(bx+0.5, by+0.5, bw-1.0, bh-2, float32(b.theme.ButtonCornerRadius)) 320 | ctx.SetStrokeColor(b.theme.BorderDark) 321 | ctx.Stroke() 322 | 323 | fontSize := float32(b.FontSize()) 324 | ctx.SetFontSize(fontSize) 325 | ctx.SetFontFace(b.theme.FontBold) 326 | caption := b.caption 327 | tw, _ := ctx.TextBounds(0, 0, caption) 328 | 329 | centerX := bx + bw*0.5 330 | centerY := by + bh*0.5 331 | textPosX := centerX - tw*0.5 332 | textPosY := centerY - 1.0 333 | 334 | textColor := b.TextColor() 335 | if b.icon > 0 || b.imageIcon > 0 { 336 | var iw, ih float32 337 | if b.icon > 0 { 338 | ih = fontSize * 1.5 / 2 339 | ctx.SetFontSize(ih) 340 | ctx.SetFontFace(b.theme.FontIcons) 341 | iw, _ = ctx.TextBounds(0, 0, string([]rune{rune(b.icon)})) 342 | } else if b.imageIcon > 0 { 343 | ih = fontSize * 0.9 344 | w, h, _ := ctx.ImageSize(b.imageIcon) 345 | iw = float32(w) * ih / float32(h) 346 | } 347 | if b.caption != "" { 348 | iw += float32(b.h) * 0.15 349 | } 350 | ctx.SetFillColor(textColor) 351 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 352 | iconPosX := centerX 353 | iconPosY := centerY - 1 354 | 355 | switch b.iconPosition { 356 | case ButtonIconLeftCentered: 357 | iconPosX -= (tw + iw) * 0.5 358 | textPosX += iw * 0.5 359 | case ButtonIconRightCentered: 360 | iconPosX -= iw * 0.5 361 | textPosX += tw * 0.5 362 | case ButtonIconLeft: 363 | iconPosX = bx + 8.0 364 | case ButtonIconRight: 365 | iconPosX = bx + bw - iw - 8 366 | } 367 | if b.icon > 0 { 368 | ctx.TextRune(iconPosX, iconPosY, []rune{rune(b.icon)}) 369 | } else { 370 | var eOff float32 = 0.25 371 | if b.enabled { 372 | eOff = 0.5 373 | } 374 | imgPaint := nanovgo.ImagePattern(iconPosX, iconPosY-ih*0.5, iw, ih, 0, b.imageIcon, eOff) 375 | ctx.SetFillPaint(imgPaint) 376 | ctx.Fill() 377 | } 378 | } 379 | ctx.SetFontSize(fontSize) 380 | ctx.SetFontFace(b.theme.FontBold) 381 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 382 | ctx.SetFillColor(b.theme.TextColorShadow) 383 | ctx.Text(textPosX, textPosY, caption) 384 | ctx.SetFillColor(textColor) 385 | ctx.Text(textPosX, textPosY+1.0, caption) 386 | } 387 | 388 | func (b *Button) String() string { 389 | return b.StringHelper("Button", b.caption) 390 | } 391 | -------------------------------------------------------------------------------- /checkbox.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/glfw" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type CheckBox struct { 9 | WidgetImplement 10 | caption string 11 | pushed bool 12 | checked bool 13 | callback func(bool) 14 | } 15 | 16 | func NewCheckBox(parent Widget, caption string) *CheckBox { 17 | if caption == "" { 18 | caption = "Untitled" 19 | } 20 | checkBox := &CheckBox{ 21 | caption: caption, 22 | } 23 | InitWidget(checkBox, parent) 24 | return checkBox 25 | } 26 | 27 | func (c *CheckBox) Caption() string { 28 | return c.caption 29 | } 30 | 31 | func (c *CheckBox) SetCaption(caption string) { 32 | c.caption = caption 33 | } 34 | 35 | func (c *CheckBox) Checked() bool { 36 | return c.checked 37 | } 38 | 39 | func (c *CheckBox) SetChecked(checked bool) { 40 | c.checked = checked 41 | } 42 | 43 | func (c *CheckBox) Pushed() bool { 44 | return c.pushed 45 | } 46 | 47 | func (c *CheckBox) SetPushed(pushed bool) { 48 | c.pushed = pushed 49 | } 50 | 51 | func (c *CheckBox) SetCallback(callback func(bool)) { 52 | c.callback = callback 53 | } 54 | 55 | func (c *CheckBox) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 56 | c.WidgetImplement.MouseButtonEvent(self, x, y, button, down, modifier) 57 | if !c.enabled { 58 | return false 59 | } 60 | if button == glfw.MouseButton1 { 61 | if down { 62 | c.pushed = true 63 | } else if c.pushed { 64 | if c.Contains(x, y) { 65 | c.checked = !c.checked 66 | if c.callback != nil { 67 | c.callback(c.checked) 68 | } 69 | } 70 | c.pushed = false 71 | } 72 | return true 73 | } 74 | return false 75 | } 76 | 77 | func (c *CheckBox) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 78 | fw, fh := c.FixedSize() 79 | if fw > 0 || fh > 0 { 80 | return fw, fh 81 | } 82 | fontSize := float32(c.FontSize()) 83 | ctx.SetFontSize(fontSize) 84 | ctx.SetFontFace(c.theme.FontNormal) 85 | w, _ := ctx.TextBounds(0, 0, c.caption) 86 | return int(w + 1.7*fontSize), int(fontSize * 1.3) 87 | } 88 | 89 | func (c *CheckBox) Draw(self Widget, ctx *nanovgo.Context) { 90 | cx := float32(c.x) 91 | cy := float32(c.y) 92 | ch := float32(c.h) 93 | c.WidgetImplement.Draw(self, ctx) 94 | fontSize := float32(c.FontSize()) 95 | ctx.SetFontSize(fontSize) 96 | ctx.SetFontFace(c.theme.FontNormal) 97 | if c.enabled { 98 | ctx.SetFillColor(c.theme.TextColor) 99 | } else { 100 | ctx.SetFillColor(c.theme.DisabledTextColor) 101 | } 102 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 103 | ctx.Text(cx+1.2*ch+5, cy+ch*0.5, c.caption) 104 | var bgAlpha uint8 105 | if c.pushed { 106 | bgAlpha = 100 107 | } else { 108 | bgAlpha = 32 109 | } 110 | bgPaint := nanovgo.BoxGradient(cx+1.5, cy+1.5, ch-2.0, ch-2.0, 3, 3, nanovgo.MONO(0, bgAlpha), nanovgo.MONO(0, 180)) 111 | ctx.BeginPath() 112 | ctx.RoundedRect(cx+1.0, cy+1.0, ch-2.0, ch-2.0, 3) 113 | ctx.SetFillPaint(bgPaint) 114 | ctx.Fill() 115 | 116 | if c.checked { 117 | ctx.SetFontSize(ch) 118 | ctx.SetFontFace(c.theme.FontIcons) 119 | if c.enabled { 120 | ctx.SetFillColor(c.theme.IconColor) 121 | } else { 122 | ctx.SetFillColor(c.theme.DisabledTextColor) 123 | } 124 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 125 | ctx.Text(cx+ch*0.5+1.0, cy+ch*0.5, string([]rune{rune(IconCheck)})) 126 | } 127 | } 128 | 129 | func (c *CheckBox) String() string { 130 | return c.StringHelper("CheckBox", c.caption) 131 | } 132 | -------------------------------------------------------------------------------- /colorpicker.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type ColorPicker struct { 9 | PopupButton 10 | 11 | callback func(color nanovgo.Color) 12 | colorWheel *ColorWheel 13 | pickButton *Button 14 | } 15 | 16 | func NewColorPicker(parent Widget, colors ...nanovgo.Color) *ColorPicker { 17 | var color nanovgo.Color 18 | switch len(colors) { 19 | case 0: 20 | color = nanovgo.RGBAf(1.0, 0.0, 0.0, 1.0) 21 | case 1: 22 | color = colors[0] 23 | default: 24 | panic("NewColorPicker can accept only one extra parameter (color)") 25 | } 26 | 27 | colorPicker := &ColorPicker{} 28 | 29 | // init PopupButton member 30 | colorPicker.chevronIcon = IconRightOpen 31 | colorPicker.SetIconPosition(ButtonIconLeftCentered) 32 | colorPicker.SetFlags(ToggleButtonType | PopupButtonType) 33 | parentWindow := parent.FindWindow() 34 | 35 | colorPicker.popup = NewPopup(parentWindow.Parent(), parentWindow) 36 | colorPicker.popup.panel.SetLayout(NewGroupLayout()) 37 | 38 | colorPicker.colorWheel = NewColorWheel(colorPicker.popup.panel) 39 | 40 | colorPicker.pickButton = NewButton(colorPicker.popup.panel, "Pick") 41 | colorPicker.pickButton.SetFixedSize(100, 25) 42 | 43 | InitWidget(colorPicker, parent) 44 | 45 | colorPicker.SetColor(color) 46 | 47 | colorPicker.PopupButton.SetChangeCallback(func(flag bool) { 48 | colorPicker.SetColor(colorPicker.BackgroundColor()) 49 | if colorPicker.callback != nil { 50 | colorPicker.callback(colorPicker.BackgroundColor()) 51 | } 52 | }) 53 | 54 | colorPicker.colorWheel.SetCallback(func(color nanovgo.Color) { 55 | colorPicker.pickButton.SetBackgroundColor(color) 56 | colorPicker.pickButton.SetTextColor(color.ContrastingColor()) 57 | }) 58 | 59 | colorPicker.pickButton.SetCallback(func() { 60 | color := colorPicker.colorWheel.Color() 61 | colorPicker.SetPushed(false) 62 | colorPicker.SetColor(color) 63 | if colorPicker.callback != nil { 64 | colorPicker.callback(colorPicker.BackgroundColor()) 65 | } 66 | }) 67 | 68 | return colorPicker 69 | } 70 | 71 | func (c *ColorPicker) SetCallback(callback func(color nanovgo.Color)) { 72 | c.callback = callback 73 | } 74 | 75 | func (c *ColorPicker) Color() nanovgo.Color { 76 | return c.BackgroundColor() 77 | } 78 | 79 | func (c *ColorPicker) SetColor(color nanovgo.Color) { 80 | if !c.pushed { 81 | fgColor := color.ContrastingColor() 82 | c.SetBackgroundColor(color) 83 | c.SetTextColor(fgColor) 84 | c.colorWheel.SetColor(color) 85 | c.pickButton.SetBackgroundColor(color) 86 | c.pickButton.SetTextColor(fgColor) 87 | } 88 | } 89 | 90 | func (c *ColorPicker) String() string { 91 | cw := c.colorWheel 92 | return c.StringHelper("ColorPicker", fmt.Sprintf("h:%f s:%f l:%f", cw.hue, cw.saturation, cw.lightness)) 93 | } 94 | -------------------------------------------------------------------------------- /colorwheel.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/glfw" 6 | "github.com/shibukawa/nanovgo" 7 | "math" 8 | ) 9 | 10 | var sqrt3 float32 = float32(math.Sqrt(3)) 11 | 12 | type ColorWheelRegion int 13 | 14 | const ( 15 | RegionNone ColorWheelRegion = 0 16 | RegionInnerTriangle ColorWheelRegion = 1 17 | RegionOuterCircle ColorWheelRegion = 2 18 | RegionBoth ColorWheelRegion = 3 19 | ) 20 | 21 | type ColorWheel struct { 22 | WidgetImplement 23 | dragRegion ColorWheelRegion 24 | hue, saturation, lightness float32 25 | callback func(color nanovgo.Color) 26 | } 27 | 28 | func NewColorWheel(parent Widget, colors ...nanovgo.Color) *ColorWheel { 29 | var color nanovgo.Color 30 | switch len(colors) { 31 | case 0: 32 | color = nanovgo.RGBAf(1.0, 0.0, 0.0, 1.0) 33 | case 1: 34 | color = colors[0] 35 | default: 36 | panic("NewColorWheel can accept only one extra parameter (color)") 37 | } 38 | colorWheel := &ColorWheel{ 39 | dragRegion: RegionNone, 40 | } 41 | InitWidget(colorWheel, parent) 42 | colorWheel.SetColor(color) 43 | return colorWheel 44 | } 45 | 46 | func (c *ColorWheel) SetCallback(callback func(color nanovgo.Color)) { 47 | c.callback = callback 48 | } 49 | 50 | func (c *ColorWheel) Color() nanovgo.Color { 51 | return nanovgo.HSL(c.hue, c.saturation, c.lightness) 52 | } 53 | 54 | func (c *ColorWheel) SetColor(color nanovgo.Color) { 55 | c.hue, c.saturation, c.lightness, _ = color.HSLA() 56 | c.calculatePosition() 57 | } 58 | 59 | func (c *ColorWheel) MouseDragEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 60 | c.adjustPosition(x, y) 61 | return true 62 | } 63 | 64 | func (c *ColorWheel) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 65 | c.WidgetImplement.MouseButtonEvent(self, x, y, button, down, modifier) 66 | 67 | if !c.enabled || button != glfw.MouseButton1 { 68 | return false 69 | } 70 | if down { 71 | c.adjustRegion(x, y) 72 | return c.dragRegion != RegionNone 73 | } 74 | c.dragRegion = RegionNone 75 | return true 76 | } 77 | 78 | func (c *ColorWheel) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 79 | return 100, 100 80 | } 81 | 82 | func (c *ColorWheel) Draw(self Widget, ctx *nanovgo.Context) { 83 | c.WidgetImplement.Draw(self, ctx) 84 | 85 | if !c.visible { 86 | return 87 | } 88 | x := float32(c.x) 89 | y := float32(c.y) 90 | w := float32(c.w) 91 | h := float32(c.h) 92 | 93 | ctx.Save() 94 | defer ctx.Restore() 95 | 96 | cx := x + w*0.5 97 | cy := y + h*0.5 98 | r1 := toF(w < h, w, h)*0.5 - 5.0 99 | r0 := r1 * 0.75 100 | 101 | aeps := 0.7 / r1 // half a pixel arc length in radians (2pi cancels out). 102 | for i := 0; i < 6; i++ { 103 | a0 := float32(i)/6.0*nanovgo.PI*2.0 - aeps 104 | a1 := float32(i+1)/6.0*nanovgo.PI*2.0 + aeps 105 | ctx.BeginPath() 106 | ctx.Arc(cx, cy, r0, a0, a1, nanovgo.Clockwise) 107 | ctx.Arc(cx, cy, r1, a1, a0, nanovgo.CounterClockwise) 108 | ctx.ClosePath() 109 | 110 | sin1, cos1 := sinCosF(a0) 111 | sin2, cos2 := sinCosF(a1) 112 | ax := cx + cos1*(r0+r1)*0.5 113 | ay := cy + sin1*(r0+r1)*0.5 114 | bx := cx + cos2*(r0+r1)*0.5 115 | by := cy + sin2*(r0+r1)*0.5 116 | color1 := nanovgo.HSLA(a0/(nanovgo.PI*2), 1.0, 0.55, 255) 117 | color2 := nanovgo.HSLA(a1/(nanovgo.PI*2), 1.0, 0.55, 255) 118 | paint := nanovgo.LinearGradient(ax, ay, bx, by, color1, color2) 119 | ctx.SetFillPaint(paint) 120 | ctx.Fill() 121 | } 122 | 123 | ctx.BeginPath() 124 | ctx.Circle(cx, cy, r0-0.5) 125 | ctx.Circle(cx, cy, r1+0.5) 126 | ctx.SetStrokeColor(nanovgo.MONO(0, 64)) 127 | ctx.Stroke() 128 | 129 | // Selector 130 | ctx.Save() 131 | defer ctx.Restore() 132 | ctx.Translate(cx, cy) 133 | ctx.Rotate(c.hue * nanovgo.PI * 2) 134 | 135 | // Marker on 136 | u := clampF(r1/50, 1.5, 4.0) 137 | ctx.SetStrokeWidth(u) 138 | ctx.BeginPath() 139 | ctx.Rect(r0-1, -2*u, r1-r0+2, 4*u) 140 | ctx.SetStrokeColor(nanovgo.MONO(255, 192)) 141 | ctx.Stroke() 142 | 143 | paint := nanovgo.BoxGradient(r0-3, -5, r1-r0+6, 10, 2, 4, nanovgo.MONO(0, 128), nanovgo.MONO(0, 0)) 144 | ctx.BeginPath() 145 | ctx.Rect(r0-2-10, -4-10, r1-r0+4+20, 8+20) 146 | ctx.Rect(r0-2, -4, r1-r0+4, 8) 147 | ctx.PathWinding(nanovgo.Hole) 148 | ctx.SetFillPaint(paint) 149 | ctx.Fill() 150 | 151 | // Center triangle 152 | r := r0 - 6 153 | sin1, cos1 := sinCosF(120.0 / 180.0 * nanovgo.PI) 154 | sin2, cos2 := sinCosF(-120.0 / 180.0 * nanovgo.PI) 155 | ax := cos1 * r 156 | ay := sin1 * r 157 | bx := cos2 * r 158 | by := sin2 * r 159 | ctx.BeginPath() 160 | ctx.MoveTo(r, 0) 161 | ctx.LineTo(ax, ay) 162 | ctx.LineTo(bx, by) 163 | ctx.ClosePath() 164 | triPaint1 := nanovgo.LinearGradient(r, 0, ax, ay, nanovgo.HSL(c.hue, 1.0, 0.5), nanovgo.MONO(255, 255)) 165 | ctx.SetFillPaint(triPaint1) 166 | ctx.Fill() 167 | triPaint2 := nanovgo.LinearGradient((r+ax)*0.5, ay*0.5, bx, by, nanovgo.MONO(0, 0), nanovgo.MONO(0, 255)) 168 | ctx.SetFillPaint(triPaint2) 169 | ctx.Fill() 170 | 171 | // selector circle on triangle 172 | px, py := c.calculatePosition() 173 | ctx.SetStrokeWidth(u) 174 | ctx.BeginPath() 175 | ctx.Circle(px, py, 2*u) 176 | ctx.SetStrokeColor(nanovgo.MONO(255, 192)) 177 | ctx.Stroke() 178 | } 179 | 180 | func (c *ColorWheel) String() string { 181 | return c.StringHelper("ColorWheel", fmt.Sprintf("h:%f s:%f l:%f", c.hue, c.saturation, c.lightness)) 182 | } 183 | 184 | var sinOneThird float32 = float32(math.Sin(math.Pi * 2.0 / 3.0)) 185 | var cosOneThird float32 = float32(math.Cos(math.Pi * 2.0 / 3.0)) 186 | var sinTwoThird float32 = float32(math.Sin(-math.Pi * 2.0 / 3.0)) 187 | var cosTwoThird float32 = float32(math.Cos(-math.Pi * 2.0 / 3.0)) 188 | 189 | func (c *ColorWheel) calculatePosition() (float32, float32) { 190 | w := float32(c.w) 191 | h := float32(c.h) 192 | hw := w * 0.5 193 | hh := h * 0.5 194 | r1 := toF(w < h, hw, hh) - 5.0 195 | radius := r1*0.75 - 6 196 | 197 | // Colored point 198 | hx := radius 199 | // Black point 200 | sx := cosTwoThird * radius 201 | sy := -sinTwoThird * radius 202 | // White point 203 | vx := cosOneThird * radius 204 | vy := -sinOneThird * radius 205 | // Current point 206 | mx := (sx + vx) / 2.0 207 | my := (sy + vy) / 2.0 208 | a := (1.0 - 2.0*absF(c.lightness-0.5)) * c.saturation 209 | var px, py float32 210 | px = sx + (hx-mx)*a 211 | py = -(sy + (vy-sy)*c.lightness + (-my)*a) 212 | return px, py 213 | } 214 | 215 | func (c *ColorWheel) adjustRegion(px, py int) { 216 | x := float32(px - c.x) 217 | y := float32(py - c.y) 218 | w := float32(c.w) 219 | h := float32(c.h) 220 | hw := w * 0.5 221 | hh := h * 0.5 222 | r1 := toF(w < h, hw, hh) - 5.0 223 | radius := r1*0.75 - 6 224 | x -= hw 225 | y -= hh 226 | 227 | mr := sqrtF(x*x + y*y) 228 | 229 | if mr >= radius && mr <= r1 { 230 | c.dragRegion = RegionOuterCircle 231 | c.adjustPosition(px-c.x, py-c.y) 232 | } else if mr < radius { 233 | c.dragRegion = RegionInnerTriangle 234 | c.adjustPosition(px-c.x, py-c.y) 235 | } else { 236 | c.dragRegion = RegionNone 237 | } 238 | } 239 | 240 | func (c *ColorWheel) adjustPosition(px, py int) { 241 | if c.dragRegion == RegionNone { 242 | return 243 | } 244 | x := float32(px) 245 | y := float32(py) 246 | w := float32(c.w) 247 | h := float32(c.h) 248 | hw := w * 0.5 249 | hh := h * 0.5 250 | r1 := toF(w < h, hw, hh) - 5.0 251 | r0 := r1 * 0.75 252 | x -= hw 253 | y -= hh 254 | radius := r0 - 6 255 | 256 | rad := math.Atan2(float64(y), float64(x)) 257 | if rad < 0 { 258 | rad += 2 * math.Pi 259 | } 260 | 261 | if c.dragRegion == RegionOuterCircle { 262 | c.hue = float32(rad / (2 * math.Pi)) 263 | if c.callback != nil { 264 | c.callback(c.Color()) 265 | } 266 | } else if c.dragRegion == RegionInnerTriangle { 267 | rad0 := math.Mod(rad+2*math.Pi*float64(1-c.hue), 2*math.Pi) 268 | rad1 := math.Mod(rad0, 2/3*math.Pi) - math.Pi/3 269 | a := 0.5 * radius 270 | b := float32(math.Tan(rad1)) * a 271 | r := sqrtF(x*x + y*y) 272 | maxR := sqrtF(a*a + b*b) 273 | 274 | if r > maxR { 275 | dx := float32(math.Tan(rad1)) * r 276 | rad2 := math.Atan(float64(dx / maxR)) 277 | if rad2 > math.Pi/3 { 278 | rad2 = math.Pi / 3 279 | } else if rad2 < -math.Pi/3 { 280 | rad2 = -math.Pi / 3 281 | } 282 | rad += rad2 - rad1 283 | 284 | rad0 = math.Mod(rad+2*math.Pi-float64(c.hue)*2*math.Pi, 2*math.Pi) 285 | rad1 = math.Mod(rad0, (2/3)*math.Pi) - (math.Pi / 3) 286 | b = float32(math.Tan(rad1)) * a 287 | maxR = sqrtF(a*a + b*b) // Pythagoras 288 | r = maxR 289 | } 290 | sin, cos := math.Sincos(rad0) 291 | 292 | c.lightness = clampF(((float32(sin)*r)/radius/sqrt3)+0.5, 0.0, 1.0) 293 | 294 | widthShare := 1 - (absF(c.lightness-0.5) * 2) 295 | s := (((float32(cos) * r) + (radius / 2)) / (1.5 * radius)) / widthShare 296 | c.saturation = clampF(s, 0.0, 1.0) 297 | 298 | if c.callback != nil { 299 | c.callback(c.Color()) 300 | } 301 | } 302 | } 303 | 304 | // https://github.com/timjb/colortriangle/blob/master/colortriangle.js 305 | -------------------------------------------------------------------------------- /combobox.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | type ComboBox struct { 4 | PopupButton 5 | callback func(int) 6 | items []string 7 | shortItems []string 8 | selectedIndex int 9 | } 10 | 11 | func NewComboBox(parent Widget, items ...[]string) *ComboBox { 12 | var itemsParam []string 13 | var shortItemsParam []string 14 | switch len(items) { 15 | case 0: 16 | case 1: 17 | itemsParam = items[0] 18 | case 2: 19 | itemsParam = items[0] 20 | shortItemsParam = items[1] 21 | default: 22 | panic("NewComboBox can accept upto 2 extra parameters (items, shortItems)") 23 | } 24 | var index int 25 | if len(items) == 0 { 26 | index = -1 27 | } 28 | combobox := &ComboBox{ 29 | selectedIndex: index, 30 | } 31 | // init PopupButton member 32 | combobox.chevronIcon = IconRightOpen 33 | combobox.SetIconPosition(ButtonIconLeftCentered) 34 | combobox.SetFlags(ToggleButtonType | PopupButtonType) 35 | parentWindow := parent.FindWindow() 36 | combobox.popup = NewPopup(parentWindow.Parent(), parentWindow) 37 | combobox.popup.SetSize(320, 250) 38 | InitWidget(combobox, parent) 39 | combobox.SetItems(itemsParam, shortItemsParam) 40 | return combobox 41 | } 42 | 43 | func (c *ComboBox) SelectedIndex() int { 44 | return c.selectedIndex 45 | } 46 | 47 | func (c *ComboBox) SetSelectedIndex(i int) { 48 | if len(c.shortItems) == 0 { 49 | return 50 | } 51 | children := c.PopupButton.Popup().Children() 52 | if c.selectedIndex > -1 { 53 | children[c.selectedIndex].(*Button).SetPushed(false) 54 | } 55 | if i < 0 || i >= len(c.items) { 56 | c.selectedIndex = -1 57 | c.SetCaption("") 58 | } else { 59 | children[i].(*Button).SetPushed(true) 60 | c.selectedIndex = i 61 | c.SetCaption(c.shortItems[i]) 62 | } 63 | } 64 | 65 | func generateCallback(c *ComboBox, popup Widget, i int) func() { 66 | return func() { 67 | c.selectedIndex = i 68 | c.SetCaption(c.shortItems[i]) 69 | c.SetPushed(false) 70 | popup.SetVisible(false) 71 | if c.callback != nil { 72 | c.callback(i) 73 | } 74 | } 75 | } 76 | 77 | func (c *ComboBox) SetItems(items []string, shortItems ...[]string) { 78 | var shortItemsParam []string 79 | switch len(shortItems) { 80 | case 0: 81 | case 1: 82 | shortItemsParam = shortItems[0] 83 | default: 84 | panic("ComboBox.SetItems can accept only one extra parameter (shortItems)") 85 | } 86 | if len(shortItemsParam) == 0 { 87 | shortItemsParam = items 88 | } 89 | if len(items) != len(shortItemsParam) { 90 | panic("ComboBox.SetItems can accept only same length string lists as items and shortItems.") 91 | } 92 | c.items = items 93 | c.shortItems = shortItemsParam 94 | if c.selectedIndex < 0 || c.selectedIndex >= len(c.items) { 95 | c.selectedIndex = -1 96 | } 97 | popup := c.Popup() 98 | for popup.ChildCount() > 0 { 99 | popup.RemoveChildByIndex(popup.ChildCount() - 1) 100 | } 101 | popup.SetLayout(NewGroupLayout(10)) 102 | for i, item := range items { 103 | button := NewButton(popup, item) 104 | button.SetFlags(RadioButtonType) 105 | button.SetCallback(generateCallback(c, popup, i)) 106 | } 107 | c.SetSelectedIndex(c.selectedIndex) 108 | } 109 | 110 | func (c *ComboBox) Items() []string { 111 | return c.items 112 | } 113 | 114 | func (c *ComboBox) ShortItems() []string { 115 | return c.shortItems 116 | } 117 | 118 | func (c *ComboBox) SetCallback(callback func(int)) { 119 | c.callback = callback 120 | } 121 | 122 | func (c *ComboBox) String() string { 123 | return c.StringHelper("ComboBox", c.caption) 124 | } 125 | -------------------------------------------------------------------------------- /constant.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | type Cursor int 4 | 5 | const ( 6 | Arrow Cursor = iota 7 | IBeam 8 | Crosshair 9 | Hand 10 | HResize 11 | VResize 12 | CursorCount 13 | ) 14 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) ../gh-pages/ 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/NanoVGo.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/NanoVGo.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/NanoVGo" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/NanoVGo" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # NanoGUI.go documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Dec 5 23:10:08 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'NanoGUI.go' 50 | copyright = u'2015, NanoGUI.go' 51 | author = u'NanoGUI.go' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '1.0' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '1.0' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'alabaster' 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | #html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'NanoGUIgodoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'NanoGUIgo.tex', u'NanoGUI.go Documentation', 226 | u'NanoGUI.go', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'nanoguigo', u'NanoGUI.go Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'NanoGUIgo', u'NanoGUI.go Documentation', 270 | author, 'NanoGUIgo', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. NanoGUI.go documentation master file, created by 2 | sphinx-quickstart on Sat Dec 5 23:10:08 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | NanoGUI.go 7 | ====================================== 8 | 9 | .. image:: https://godoc.org/github.com/shibukawa/nanogui.go?status.svg 10 | :target: https://godoc.org/github.com/shibukawa/nanogui.go 11 | 12 | .. raw:: html 13 | 14 | 15 | 16 | `Full Screen `_ 17 | 18 | NanoGUI.go is apure golang implementation of `NanoGUI `_. NanoGUI is a minimalistic GUI library for OpenGL. 19 | 20 | NanoGUI.go can run on desktops and browsers (I didn't test on other environemnt). 21 | 22 | It uses the following components: 23 | 24 | * `OpenGL/WebGL library `_ 25 | * `Cross platform glfw wrapper `_ 26 | * `NanoVGo `_ 27 | 28 | Install 29 | ------------- 30 | 31 | .. code-block:: bash 32 | 33 | $ go get github.com/shibukawa/nanogui.go 34 | 35 | .. warning:: 36 | 37 | Current code depends on unreleased version of GLFW to support IME (now sending PR for 3.2). 38 | 39 | Use the following packages before v3.2 would be released. If v3.2 will be released, **github.com/shibukawa/glfw\*** packages will be removed. 40 | 41 | - **github.com/go-gl/glfw/v3.1/glfw** → **gihtub.com/shibukawa/glfw-2/v3.2/glfw** 42 | - **github.com/goxjs/glfw** → **github.com/shibukawa/glfw** 43 | 44 | API Reference 45 | --------------- 46 | 47 | See `GoDoc `_ 48 | 49 | Porting Status 50 | ------------------ 51 | 52 | .. list-table:: 53 | :widths: 15 15 10 10 54 | :header-rows: 1 55 | 56 | - * Category 57 | * Classes 58 | * Finished 59 | * Status 60 | - * Non-Visual types 61 | * Screen 62 | * ☑ 63 | * except cursor feature 64 | - * 65 | * Widget 66 | * ☑ 67 | * 68 | - * 69 | * GroupLayout, BoxLayout, GridLayout, Advancedgridlayout 70 | * ☑ 71 | * 72 | - * 73 | * Theme 74 | * ☑ 75 | * Entype icons, Roboto fonts included 76 | - * Widgets 77 | * Window, Popup 78 | * ☑ 79 | * 80 | - * 81 | * Label 82 | * ☑ 83 | * 84 | - * 85 | * Button, ToolButton 86 | * ☑ 87 | * 88 | - * 89 | * PopupButton, ComboBox, ImagePanel, ColorPicker 90 | * ☑ 91 | * 92 | - * 93 | * CheckBox 94 | * ☑ 95 | * 96 | - * 97 | * Slider 98 | * ☑ 99 | * 100 | - * 101 | * ProgressBar 102 | * ☑ 103 | * 104 | - * 105 | * TextBox, IntBox, FloatBox 106 | * ☑ 107 | * It supports Emacs key bind like MacOS X, IME not supported. 108 | - * 109 | * ImageView, Graph 110 | * ☑ 111 | * 112 | - * 113 | * VScrollPanel 114 | * ☑ 115 | * 116 | - * 117 | * ColorWheel 118 | * ☑ 119 | * 120 | - * 121 | * MessageDialog 122 | * ☐ 123 | * 124 | - * Utitility 125 | * FormHelper 126 | * ☐ 127 | * 128 | - * OpenGL Helper 129 | * GLShader, GLFramebuffer, Arcball 130 | * ☐ 131 | * 132 | 133 | Author 134 | --------------- 135 | 136 | * `Yoshiki Shibukawa `_ 137 | 138 | License 139 | ---------- 140 | 141 | NanoGUI.go is released under zlib license. But the following contents are under other licenses: 142 | 143 | * Roboto fonts: Apatch 2 license 144 | * Entypo icon: Creative Commons 4.0 BY-SA 145 | 146 | Indices and tables 147 | ================== 148 | 149 | * :ref:`genindex` 150 | * :ref:`modindex` 151 | * :ref:`search` 152 | 153 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\NanoGUIgo.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\NanoGUIgo.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /entypoicons.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | // generated by util/gen_icon_symbols.py 4 | 5 | type Icon rune 6 | 7 | const ( 8 | IconNote Icon = 0x266a 9 | IconNoteBeamed Icon = 0x266b 10 | IconMusic Icon = 0x1f3b5 11 | IconSearch Icon = 0x1f50d 12 | IconFlashlight Icon = 0x1f526 13 | IconMail Icon = 0x2709 14 | IconHeart Icon = 0x2665 15 | IconHeartEmpty Icon = 0x2661 16 | IconStar Icon = 0x2605 17 | IconStarEmpty Icon = 0x2606 18 | IconUser Icon = 0x1f464 19 | IconUsers Icon = 0x1f465 20 | IconUserAdd Icon = 0xe700 21 | IconVideo Icon = 0x1f3ac 22 | IconPicture Icon = 0x1f304 23 | IconCamera Icon = 0x1f4f7 24 | IconLayout Icon = 0x268f 25 | IconMenu Icon = 0x2630 26 | IconCheck Icon = 0x2713 27 | IconCancel Icon = 0x2715 28 | IconCancelCircled Icon = 0x2716 29 | IconCancelSquared Icon = 0x274e 30 | IconPlus Icon = 0x2b 31 | IconPlusCircled Icon = 0x2795 32 | IconPlusSquared Icon = 0x229e 33 | IconMinus Icon = 0x2d 34 | IconMinusCircled Icon = 0x2796 35 | IconMinusSquared Icon = 0x229f 36 | IconHelp Icon = 0x2753 37 | IconHelpCircled Icon = 0xe704 38 | IconInfo Icon = 0x2139 39 | IconInfoCircled Icon = 0xe705 40 | IconBack Icon = 0x1f519 41 | IconHome Icon = 0x2302 42 | IconLink Icon = 0x1f517 43 | IconAttach Icon = 0x1f4ce 44 | IconLock Icon = 0x1f512 45 | IconLockOpen Icon = 0x1f513 46 | IconTag Icon = 0xe70c 47 | IconBookmark Icon = 0x1f516 48 | IconBookmarks Icon = 0x1f4d1 49 | IconFlag Icon = 0x2691 50 | IconThumbsUp Icon = 0x1f44d 51 | IconThumbsDown Icon = 0x1f44e 52 | IconDownload Icon = 0x1f4e5 53 | IconUpload Icon = 0x1f4e4 54 | IconUploadCloud Icon = 0xe711 55 | IconReply Icon = 0xe712 56 | IconReplyAll Icon = 0xe713 57 | IconForward Icon = 0x27a6 58 | IconQuote Icon = 0x275e 59 | IconCode Icon = 0xe714 60 | IconExport Icon = 0xe715 61 | IconFeather Icon = 0x2712 62 | IconPrint Icon = 0xe716 63 | IconRetweet Icon = 0xe717 64 | IconKeyboard Icon = 0x2328 65 | IconComment Icon = 0xe718 66 | IconChat Icon = 0xe720 67 | IconBell Icon = 0x1f514 68 | IconAttention Icon = 0x26a0 69 | IconAlert Icon = 0x1f4a5 70 | IconVcard Icon = 0xe722 71 | IconAddress Icon = 0xe723 72 | IconLocation Icon = 0xe724 73 | IconMap Icon = 0xe727 74 | IconDirection Icon = 0x27a2 75 | IconCompass Icon = 0xe728 76 | IconCup Icon = 0x2615 77 | IconTrash Icon = 0xe729 78 | IconDoc Icon = 0xe730 79 | IconDocs Icon = 0xe736 80 | IconDocLandscape Icon = 0xe737 81 | IconDocText Icon = 0x1f4c4 82 | IconDocTextInv Icon = 0xe731 83 | IconNewspaper Icon = 0x1f4f0 84 | IconBookOpen Icon = 0x1f4d6 85 | IconBook Icon = 0x1f4d5 86 | IconFolder Icon = 0x1f4c1 87 | IconArchive Icon = 0xe738 88 | IconBox Icon = 0x1f4e6 89 | IconRss Icon = 0xe73a 90 | IconPhone Icon = 0x1f4de 91 | IconCog Icon = 0x2699 92 | IconTools Icon = 0x2692 93 | IconShare Icon = 0xe73c 94 | IconShareable Icon = 0xe73e 95 | IconBasket Icon = 0xe73d 96 | IconBag Icon = 0x1f45c 97 | IconCalendar Icon = 0x1f4c5 98 | IconLogin Icon = 0xe740 99 | IconLogout Icon = 0xe741 100 | IconMic Icon = 0x1f3a4 101 | IconMute Icon = 0x1f507 102 | IconSound Icon = 0x1f50a 103 | IconVolume Icon = 0xe742 104 | IconClock Icon = 0x1f554 105 | IconHourglass Icon = 0x23f3 106 | IconLamp Icon = 0x1f4a1 107 | IconLightDown Icon = 0x1f505 108 | IconLightUp Icon = 0x1f506 109 | IconAdjust Icon = 0x25d1 110 | IconBlock Icon = 0x1f6ab 111 | IconResizeFull Icon = 0xe744 112 | IconResizeSmall Icon = 0xe746 113 | IconPopup Icon = 0xe74c 114 | IconPublish Icon = 0xe74d 115 | IconWindow Icon = 0xe74e 116 | IconArrowCombo Icon = 0xe74f 117 | IconDownCircled Icon = 0xe758 118 | IconLeftCircled Icon = 0xe759 119 | IconRightCircled Icon = 0xe75a 120 | IconUpCircled Icon = 0xe75b 121 | IconDownOpen Icon = 0xe75c 122 | IconLeftOpen Icon = 0xe75d 123 | IconRightOpen Icon = 0xe75e 124 | IconUpOpen Icon = 0xe75f 125 | IconDownOpenMini Icon = 0xe760 126 | IconLeftOpenMini Icon = 0xe761 127 | IconRightOpenMini Icon = 0xe762 128 | IconUpOpenMini Icon = 0xe763 129 | IconDownOpenBig Icon = 0xe764 130 | IconLeftOpenBig Icon = 0xe765 131 | IconRightOpenBig Icon = 0xe766 132 | IconUpOpenBig Icon = 0xe767 133 | IconDown Icon = 0x2b07 134 | IconLeft Icon = 0x2b05 135 | IconRight Icon = 0x27a1 136 | IconUp Icon = 0x2b06 137 | IconDownDir Icon = 0x25be 138 | IconLeftDir Icon = 0x25c2 139 | IconRightDir Icon = 0x25b8 140 | IconUpDir Icon = 0x25b4 141 | IconDownBold Icon = 0xe4b0 142 | IconLeftBold Icon = 0xe4ad 143 | IconRightBold Icon = 0xe4ae 144 | IconUpBold Icon = 0xe4af 145 | IconDownThin Icon = 0x2193 146 | IconLeftThin Icon = 0x2190 147 | IconRightThin Icon = 0x2192 148 | IconUpThin Icon = 0x2191 149 | IconCcw Icon = 0x27f2 150 | IconCw Icon = 0x27f3 151 | IconArrowsCcw Icon = 0x1f504 152 | IconLevelDown Icon = 0x21b3 153 | IconLevelUp Icon = 0x21b0 154 | IconShuffle Icon = 0x1f500 155 | IconLoop Icon = 0x1f501 156 | IconSwitch Icon = 0x21c6 157 | IconPlay Icon = 0x25b6 158 | IconStop Icon = 0x25a0 159 | IconPause Icon = 0x2389 160 | IconRecord Icon = 0x26ab 161 | IconToEnd Icon = 0x23ed 162 | IconToStart Icon = 0x23ee 163 | IconFastForward Icon = 0x23e9 164 | IconFastBackward Icon = 0x23ea 165 | IconProgress0 Icon = 0xe768 166 | IconProgress1 Icon = 0xe769 167 | IconProgress2 Icon = 0xe76a 168 | IconProgress3 Icon = 0xe76b 169 | IconTarget Icon = 0x1f3af 170 | IconPalette Icon = 0x1f3a8 171 | IconList Icon = 0xe005 172 | IconListAdd Icon = 0xe003 173 | IconSignal Icon = 0x1f4f6 174 | IconTrophy Icon = 0x1f3c6 175 | IconBattery Icon = 0x1f50b 176 | IconBackInTime Icon = 0xe771 177 | IconMonitor Icon = 0x1f4bb 178 | IconMobile Icon = 0x1f4f1 179 | IconNetwork Icon = 0xe776 180 | IconInbox Icon = 0xe777 181 | IconInstall Icon = 0xe778 182 | IconGlobe Icon = 0x1f30e 183 | IconCloud Icon = 0x2601 184 | IconCloudThunder Icon = 0x26c8 185 | IconFlash Icon = 0x26a1 186 | IconMoon Icon = 0x263d 187 | IconFlight Icon = 0x2708 188 | IconPaperPlane Icon = 0xe79b 189 | IconLeaf Icon = 0x1f342 190 | IconLifebuoy Icon = 0xe788 191 | IconMouse Icon = 0xe789 192 | IconBriefcase Icon = 0x1f4bc 193 | IconSuitcase Icon = 0xe78e 194 | IconDot Icon = 0xe78b 195 | IconDot2 Icon = 0xe78c 196 | IconDot3 Icon = 0xe78d 197 | IconBrush Icon = 0xe79a 198 | IconMagnet Icon = 0xe7a1 199 | IconInfinity Icon = 0x221e 200 | IconErase Icon = 0x232b 201 | IconChartPie Icon = 0xe751 202 | IconChartLine Icon = 0x1f4c8 203 | IconChartBar Icon = 0x1f4ca 204 | IconChartArea Icon = 0x1f53e 205 | IconTape Icon = 0x2707 206 | IconGraduationCap Icon = 0x1f393 207 | IconLanguage Icon = 0xe752 208 | IconTicket Icon = 0x1f3ab 209 | IconWater Icon = 0x1f4a6 210 | IconDroplet Icon = 0x1f4a7 211 | IconAir Icon = 0xe753 212 | IconCreditCard Icon = 0x1f4b3 213 | IconFloppy Icon = 0x1f4be 214 | IconClipboard Icon = 0x1f4cb 215 | IconMegaphone Icon = 0x1f4e3 216 | IconDatabase Icon = 0xe754 217 | IconDrive Icon = 0xe755 218 | IconBucket Icon = 0xe756 219 | IconThermometer Icon = 0xe757 220 | IconKey Icon = 0x1f511 221 | IconFlowCascade Icon = 0xe790 222 | IconFlowBranch Icon = 0xe791 223 | IconFlowTree Icon = 0xe792 224 | IconFlowLine Icon = 0xe793 225 | IconFlowParallel Icon = 0xe794 226 | IconRocket Icon = 0x1f680 227 | IconGauge Icon = 0xe7a2 228 | IconTrafficCone Icon = 0xe7a3 229 | IconCc Icon = 0xe7a5 230 | IconCcBy Icon = 0xe7a6 231 | IconCcNc Icon = 0xe7a7 232 | IconCcNcEu Icon = 0xe7a8 233 | IconCcNcJp Icon = 0xe7a9 234 | IconCcSa Icon = 0xe7aa 235 | IconCcNd Icon = 0xe7ab 236 | IconCcPd Icon = 0xe7ac 237 | IconCcZero Icon = 0xe7ad 238 | IconCcShare Icon = 0xe7ae 239 | IconCcRemix Icon = 0xe7af 240 | IconGithub Icon = 0xf300 241 | IconGithubCircled Icon = 0xf301 242 | IconFlickr Icon = 0xf303 243 | IconFlickrCircled Icon = 0xf304 244 | IconVimeo Icon = 0xf306 245 | IconVimeoCircled Icon = 0xf307 246 | IconTwitter Icon = 0xf309 247 | IconTwitterCircled Icon = 0xf30a 248 | IconFacebook Icon = 0xf30c 249 | IconFacebookCircled Icon = 0xf30d 250 | IconFacebookSquared Icon = 0xf30e 251 | IconGplus Icon = 0xf30f 252 | IconGplusCircled Icon = 0xf310 253 | IconPinterest Icon = 0xf312 254 | IconPinterestCircled Icon = 0xf313 255 | IconTumblr Icon = 0xf315 256 | IconTumblrCircled Icon = 0xf316 257 | IconLinkedin Icon = 0xf318 258 | IconLinkedinCircled Icon = 0xf319 259 | IconDribbble Icon = 0xf31b 260 | IconDribbbleCircled Icon = 0xf31c 261 | IconStumbleupon Icon = 0xf31e 262 | IconStumbleuponCircled Icon = 0xf31f 263 | IconLastfm Icon = 0xf321 264 | IconLastfmCircled Icon = 0xf322 265 | IconRdio Icon = 0xf324 266 | IconRdioCircled Icon = 0xf325 267 | IconSpotify Icon = 0xf327 268 | IconSpotifyCircled Icon = 0xf328 269 | IconQq Icon = 0xf32a 270 | IconInstagrem Icon = 0xf32d 271 | IconDropbox Icon = 0xf330 272 | IconEvernote Icon = 0xf333 273 | IconFlattr Icon = 0xf336 274 | IconSkype Icon = 0xf339 275 | IconSkypeCircled Icon = 0xf33a 276 | IconRenren Icon = 0xf33c 277 | IconSinaWeibo Icon = 0xf33f 278 | IconPaypal Icon = 0xf342 279 | IconPicasa Icon = 0xf345 280 | IconSoundcloud Icon = 0xf348 281 | IconMixi Icon = 0xf34b 282 | IconBehance Icon = 0xf34e 283 | IconGoogleCircles Icon = 0xf351 284 | IconVkontakte Icon = 0xf354 285 | IconSmashing Icon = 0xf357 286 | IconSweden Icon = 0xf601 287 | IconDbShape Icon = 0xf600 288 | IconLogoDb Icon = 0xf603 289 | ) 290 | -------------------------------------------------------------------------------- /fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /fonts/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/fonts/entypo.ttf -------------------------------------------------------------------------------- /global.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/goxjs/gl" 5 | "github.com/shibukawa/glfw" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | var mainloopActive bool = false 11 | var startTime time.Time 12 | var debugFlag bool 13 | 14 | func Init() { 15 | err := glfw.Init(gl.ContextWatcher) 16 | if err != nil { 17 | panic(err) 18 | } 19 | startTime = time.Now() 20 | } 21 | 22 | func GetTime() float32 { 23 | return float32(time.Now().Sub(startTime)/time.Millisecond) * 0.001 24 | } 25 | 26 | func MainLoop() { 27 | mainloopActive = true 28 | 29 | var wg sync.WaitGroup 30 | 31 | /* If there are no mouse/keyboard events, try to refresh the 32 | view roughly every 50 ms; this is to support animations 33 | such as progress bars while keeping the system load 34 | reasonably low */ 35 | wg.Add(1) 36 | go func() { 37 | for mainloopActive { 38 | time.Sleep(50 * time.Millisecond) 39 | glfw.PostEmptyEvent() 40 | } 41 | wg.Done() 42 | }() 43 | for mainloopActive { 44 | haveActiveScreen := false 45 | for _, screen := range nanoguiScreens { 46 | if !screen.Visible() { 47 | continue 48 | } else if screen.GLFWWindow().ShouldClose() { 49 | screen.SetVisible(false) 50 | continue 51 | } 52 | //screen.DebugPrint() 53 | screen.DrawAll() 54 | haveActiveScreen = true 55 | } 56 | if !haveActiveScreen { 57 | mainloopActive = false 58 | break 59 | } 60 | glfw.WaitEvents() 61 | } 62 | 63 | wg.Wait() 64 | } 65 | 66 | func SetDebug(d bool) { 67 | debugFlag = d 68 | } 69 | 70 | func InitWidget(child, parent Widget) { 71 | //w.cursor = Arrow 72 | if parent != nil { 73 | parent.AddChild(parent, child) 74 | child.SetTheme(parent.Theme()) 75 | } 76 | child.SetVisible(true) 77 | child.SetEnabled(true) 78 | child.SetFontSize(-1) 79 | } 80 | -------------------------------------------------------------------------------- /glshader.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | type GLShader struct { 4 | } 5 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | ) 6 | 7 | type Graph struct { 8 | WidgetImplement 9 | 10 | caption, header, footer string 11 | backgroundColor, foregroundColor, textColor nanovgo.Color 12 | values []float32 13 | } 14 | 15 | func NewGraph(parent Widget, captions ...string) *Graph { 16 | var caption string 17 | switch len(captions) { 18 | case 0: 19 | caption = "Untitled" 20 | case 1: 21 | caption = captions[0] 22 | default: 23 | panic("NewGraph can accept only one extra parameter (label)") 24 | } 25 | graph := &Graph{ 26 | caption: caption, 27 | backgroundColor: nanovgo.MONO(20, 128), 28 | foregroundColor: nanovgo.RGBA(255, 192, 0, 128), 29 | textColor: nanovgo.MONO(240, 192), 30 | } 31 | InitWidget(graph, parent) 32 | return graph 33 | } 34 | 35 | func (g *Graph) Caption() string { 36 | return g.caption 37 | } 38 | 39 | func (g *Graph) SetCaption(caption string) { 40 | g.caption = caption 41 | } 42 | 43 | func (g *Graph) Header() string { 44 | return g.header 45 | } 46 | 47 | func (g *Graph) SetHeader(header string) { 48 | g.header = header 49 | } 50 | 51 | func (g *Graph) Footer() string { 52 | return g.footer 53 | } 54 | 55 | func (g *Graph) SetFooter(footer string) { 56 | g.footer = footer 57 | } 58 | 59 | func (g *Graph) BackgroundColor() nanovgo.Color { 60 | return g.backgroundColor 61 | } 62 | 63 | func (g *Graph) SetBackgroundColor(color nanovgo.Color) { 64 | g.backgroundColor = color 65 | } 66 | 67 | func (g *Graph) ForegroundColor() nanovgo.Color { 68 | return g.foregroundColor 69 | } 70 | 71 | func (g *Graph) SetForegroundColor(color nanovgo.Color) { 72 | g.foregroundColor = color 73 | } 74 | 75 | func (g *Graph) TextColor() nanovgo.Color { 76 | return g.textColor 77 | } 78 | 79 | func (g *Graph) SetTextColor(color nanovgo.Color) { 80 | g.textColor = color 81 | } 82 | 83 | func (g *Graph) Values() []float32 { 84 | return g.values 85 | } 86 | 87 | func (g *Graph) SetValues(values []float32) { 88 | g.values = values 89 | } 90 | 91 | func (g *Graph) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 92 | return 180, 45 93 | 94 | } 95 | 96 | func (g *Graph) Draw(self Widget, ctx *nanovgo.Context) { 97 | g.WidgetImplement.Draw(self, ctx) 98 | 99 | x := float32(g.x) 100 | y := float32(g.y) 101 | w := float32(g.w) 102 | h := float32(g.h) 103 | 104 | ctx.BeginPath() 105 | ctx.Rect(x, y, w, h) 106 | ctx.SetFillColor(g.backgroundColor) 107 | ctx.Fill() 108 | 109 | if len(g.values) < 2 { 110 | return 111 | } 112 | 113 | ctx.BeginPath() 114 | ctx.MoveTo(x, y+h) 115 | dx := float32(len(g.values) - 1) 116 | for i, v := range g.values { 117 | vx := x + float32(i)*w/dx 118 | vy := y + (1.0-v)*h 119 | ctx.LineTo(vx, vy) 120 | } 121 | 122 | ctx.LineTo(x+w, y+h) 123 | ctx.SetStrokeColor(nanovgo.MONO(100, 255)) 124 | ctx.Stroke() 125 | ctx.SetFillColor(g.foregroundColor) 126 | ctx.Fill() 127 | 128 | ctx.SetFontFace(g.theme.FontNormal) 129 | ctx.SetFillColor(g.textColor) 130 | if g.caption != "" { 131 | ctx.SetFontSize(14) 132 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 133 | ctx.Text(x+3, y+1, g.caption) 134 | } 135 | 136 | if g.header != "" { 137 | ctx.SetFontSize(18) 138 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignTop) 139 | ctx.Text(x+w-3, y+1, g.header) 140 | } 141 | 142 | if g.footer != "" { 143 | ctx.SetFontSize(15) 144 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignBottom) 145 | ctx.Text(x+w-3, y+h-1, g.footer) 146 | } 147 | 148 | ctx.BeginPath() 149 | ctx.Rect(x, y, w, h) 150 | ctx.SetStrokeColor(nanovgo.MONO(100, 255)) 151 | ctx.Stroke() 152 | } 153 | 154 | func (g *Graph) String() string { 155 | return g.StringHelper("Graph", g.caption) 156 | } 157 | -------------------------------------------------------------------------------- /icons/materialicons/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pushd util 3 | python gen_icon_symbols.py 4 | popd 5 | 6 | go-bindata -o font.go -pkg materialicons font 7 | -------------------------------------------------------------------------------- /icons/materialicons/font/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/icons/materialicons/font/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /icons/materialicons/load.go: -------------------------------------------------------------------------------- 1 | package materialicons 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | ) 6 | 7 | func LoadFont(ctx *nanovgo.Context) { 8 | ctx.CreateFontFromMemory("materialicons", MustAsset("font/MaterialIcons-Regular.ttf"), 0) 9 | } 10 | 11 | func LoadFontAs(ctx *nanovgo.Context, name string) { 12 | ctx.CreateFontFromMemory(name, MustAsset("font/MaterialIcons-Regular.ttf"), 0) 13 | } -------------------------------------------------------------------------------- /icons/materialicons/util/codepoints: -------------------------------------------------------------------------------- 1 | 3d_rotation e84d 2 | ac_unit eb3b 3 | access_alarm e190 4 | access_alarms e191 5 | access_time e192 6 | accessibility e84e 7 | accessible e914 8 | account_balance e84f 9 | account_balance_wallet e850 10 | account_box e851 11 | account_circle e853 12 | adb e60e 13 | add e145 14 | add_a_photo e439 15 | add_alarm e193 16 | add_alert e003 17 | add_box e146 18 | add_circle e147 19 | add_circle_outline e148 20 | add_location e567 21 | add_shopping_cart e854 22 | add_to_photos e39d 23 | add_to_queue e05c 24 | adjust e39e 25 | airline_seat_flat e630 26 | airline_seat_flat_angled e631 27 | airline_seat_individual_suite e632 28 | airline_seat_legroom_extra e633 29 | airline_seat_legroom_normal e634 30 | airline_seat_legroom_reduced e635 31 | airline_seat_recline_extra e636 32 | airline_seat_recline_normal e637 33 | airplanemode_active e195 34 | airplanemode_inactive e194 35 | airplay e055 36 | airport_shuttle eb3c 37 | alarm e855 38 | alarm_add e856 39 | alarm_off e857 40 | alarm_on e858 41 | album e019 42 | all_inclusive eb3d 43 | all_out e90b 44 | android e859 45 | announcement e85a 46 | apps e5c3 47 | archive e149 48 | arrow_back e5c4 49 | arrow_downward e5db 50 | arrow_drop_down e5c5 51 | arrow_drop_down_circle e5c6 52 | arrow_drop_up e5c7 53 | arrow_forward e5c8 54 | arrow_upward e5d8 55 | art_track e060 56 | aspect_ratio e85b 57 | assessment e85c 58 | assignment e85d 59 | assignment_ind e85e 60 | assignment_late e85f 61 | assignment_return e860 62 | assignment_returned e861 63 | assignment_turned_in e862 64 | assistant e39f 65 | assistant_photo e3a0 66 | attach_file e226 67 | attach_money e227 68 | attachment e2bc 69 | audiotrack e3a1 70 | autorenew e863 71 | av_timer e01b 72 | backspace e14a 73 | backup e864 74 | battery_alert e19c 75 | battery_charging_full e1a3 76 | battery_full e1a4 77 | battery_std e1a5 78 | battery_unknown e1a6 79 | beach_access eb3e 80 | beenhere e52d 81 | block e14b 82 | bluetooth e1a7 83 | bluetooth_audio e60f 84 | bluetooth_connected e1a8 85 | bluetooth_disabled e1a9 86 | bluetooth_searching e1aa 87 | blur_circular e3a2 88 | blur_linear e3a3 89 | blur_off e3a4 90 | blur_on e3a5 91 | book e865 92 | bookmark e866 93 | bookmark_border e867 94 | border_all e228 95 | border_bottom e229 96 | border_clear e22a 97 | border_color e22b 98 | border_horizontal e22c 99 | border_inner e22d 100 | border_left e22e 101 | border_outer e22f 102 | border_right e230 103 | border_style e231 104 | border_top e232 105 | border_vertical e233 106 | brightness_1 e3a6 107 | brightness_2 e3a7 108 | brightness_3 e3a8 109 | brightness_4 e3a9 110 | brightness_5 e3aa 111 | brightness_6 e3ab 112 | brightness_7 e3ac 113 | brightness_auto e1ab 114 | brightness_high e1ac 115 | brightness_low e1ad 116 | brightness_medium e1ae 117 | broken_image e3ad 118 | brush e3ae 119 | bug_report e868 120 | build e869 121 | business e0af 122 | business_center eb3f 123 | cached e86a 124 | cake e7e9 125 | call e0b0 126 | call_end e0b1 127 | call_made e0b2 128 | call_merge e0b3 129 | call_missed e0b4 130 | call_missed_outgoing e0e4 131 | call_received e0b5 132 | call_split e0b6 133 | camera e3af 134 | camera_alt e3b0 135 | camera_enhance e8fc 136 | camera_front e3b1 137 | camera_rear e3b2 138 | camera_roll e3b3 139 | cancel e5c9 140 | card_giftcard e8f6 141 | card_membership e8f7 142 | card_travel e8f8 143 | casino eb40 144 | cast e307 145 | cast_connected e308 146 | center_focus_strong e3b4 147 | center_focus_weak e3b5 148 | change_history e86b 149 | chat e0b7 150 | chat_bubble e0ca 151 | chat_bubble_outline e0cb 152 | check e5ca 153 | check_box e834 154 | check_box_outline_blank e835 155 | check_circle e86c 156 | chevron_left e5cb 157 | chevron_right e5cc 158 | child_care eb41 159 | child_friendly eb42 160 | chrome_reader_mode e86d 161 | class e86e 162 | clear e14c 163 | clear_all e0b8 164 | close e5cd 165 | closed_caption e01c 166 | cloud e2bd 167 | cloud_circle e2be 168 | cloud_done e2bf 169 | cloud_download e2c0 170 | cloud_off e2c1 171 | cloud_queue e2c2 172 | cloud_upload e2c3 173 | code e86f 174 | collections e3b6 175 | collections_bookmark e431 176 | color_lens e3b7 177 | colorize e3b8 178 | comment e0b9 179 | compare e3b9 180 | compare_arrows e915 181 | computer e30a 182 | confirmation_number e638 183 | contact_mail e0d0 184 | contact_phone e0cf 185 | contacts e0ba 186 | content_copy e14d 187 | content_cut e14e 188 | content_paste e14f 189 | control_point e3ba 190 | control_point_duplicate e3bb 191 | copyright e90c 192 | create e150 193 | create_new_folder e2cc 194 | credit_card e870 195 | crop e3be 196 | crop_16_9 e3bc 197 | crop_3_2 e3bd 198 | crop_5_4 e3bf 199 | crop_7_5 e3c0 200 | crop_din e3c1 201 | crop_free e3c2 202 | crop_landscape e3c3 203 | crop_original e3c4 204 | crop_portrait e3c5 205 | crop_rotate e437 206 | crop_square e3c6 207 | dashboard e871 208 | data_usage e1af 209 | date_range e916 210 | dehaze e3c7 211 | delete e872 212 | description e873 213 | desktop_mac e30b 214 | desktop_windows e30c 215 | details e3c8 216 | developer_board e30d 217 | developer_mode e1b0 218 | device_hub e335 219 | devices e1b1 220 | devices_other e337 221 | dialer_sip e0bb 222 | dialpad e0bc 223 | directions e52e 224 | directions_bike e52f 225 | directions_boat e532 226 | directions_bus e530 227 | directions_car e531 228 | directions_railway e534 229 | directions_run e566 230 | directions_subway e533 231 | directions_transit e535 232 | directions_walk e536 233 | disc_full e610 234 | dns e875 235 | do_not_disturb e612 236 | do_not_disturb_alt e611 237 | dock e30e 238 | domain e7ee 239 | done e876 240 | done_all e877 241 | donut_large e917 242 | donut_small e918 243 | drafts e151 244 | drag_handle e25d 245 | drive_eta e613 246 | dvr e1b2 247 | edit e3c9 248 | edit_location e568 249 | eject e8fb 250 | email e0be 251 | enhanced_encryption e63f 252 | equalizer e01d 253 | error e000 254 | error_outline e001 255 | event e878 256 | event_available e614 257 | event_busy e615 258 | event_note e616 259 | event_seat e903 260 | exit_to_app e879 261 | expand_less e5ce 262 | expand_more e5cf 263 | explicit e01e 264 | explore e87a 265 | exposure e3ca 266 | exposure_neg_1 e3cb 267 | exposure_neg_2 e3cc 268 | exposure_plus_1 e3cd 269 | exposure_plus_2 e3ce 270 | exposure_zero e3cf 271 | extension e87b 272 | face e87c 273 | fast_forward e01f 274 | fast_rewind e020 275 | favorite e87d 276 | favorite_border e87e 277 | feedback e87f 278 | fiber_dvr e05d 279 | fiber_manual_record e061 280 | fiber_new e05e 281 | fiber_pin e06a 282 | fiber_smart_record e062 283 | file_download e2c4 284 | file_upload e2c6 285 | filter e3d3 286 | filter_1 e3d0 287 | filter_2 e3d1 288 | filter_3 e3d2 289 | filter_4 e3d4 290 | filter_5 e3d5 291 | filter_6 e3d6 292 | filter_7 e3d7 293 | filter_8 e3d8 294 | filter_9 e3d9 295 | filter_9_plus e3da 296 | filter_b_and_w e3db 297 | filter_center_focus e3dc 298 | filter_drama e3dd 299 | filter_frames e3de 300 | filter_hdr e3df 301 | filter_list e152 302 | filter_none e3e0 303 | filter_tilt_shift e3e2 304 | filter_vintage e3e3 305 | find_in_page e880 306 | find_replace e881 307 | fingerprint e90d 308 | fitness_center eb43 309 | flag e153 310 | flare e3e4 311 | flash_auto e3e5 312 | flash_off e3e6 313 | flash_on e3e7 314 | flight e539 315 | flight_land e904 316 | flight_takeoff e905 317 | flip e3e8 318 | flip_to_back e882 319 | flip_to_front e883 320 | folder e2c7 321 | folder_open e2c8 322 | folder_shared e2c9 323 | folder_special e617 324 | font_download e167 325 | format_align_center e234 326 | format_align_justify e235 327 | format_align_left e236 328 | format_align_right e237 329 | format_bold e238 330 | format_clear e239 331 | format_color_fill e23a 332 | format_color_reset e23b 333 | format_color_text e23c 334 | format_indent_decrease e23d 335 | format_indent_increase e23e 336 | format_italic e23f 337 | format_line_spacing e240 338 | format_list_bulleted e241 339 | format_list_numbered e242 340 | format_paint e243 341 | format_quote e244 342 | format_shapes e25e 343 | format_size e245 344 | format_strikethrough e246 345 | format_textdirection_l_to_r e247 346 | format_textdirection_r_to_l e248 347 | format_underlined e249 348 | forum e0bf 349 | forward e154 350 | forward_10 e056 351 | forward_30 e057 352 | forward_5 e058 353 | free_breakfast eb44 354 | fullscreen e5d0 355 | fullscreen_exit e5d1 356 | functions e24a 357 | gamepad e30f 358 | games e021 359 | gavel e90e 360 | gesture e155 361 | get_app e884 362 | gif e908 363 | golf_course eb45 364 | gps_fixed e1b3 365 | gps_not_fixed e1b4 366 | gps_off e1b5 367 | grade e885 368 | gradient e3e9 369 | grain e3ea 370 | graphic_eq e1b8 371 | grid_off e3eb 372 | grid_on e3ec 373 | group e7ef 374 | group_add e7f0 375 | group_work e886 376 | hd e052 377 | hdr_off e3ed 378 | hdr_on e3ee 379 | hdr_strong e3f1 380 | hdr_weak e3f2 381 | headset e310 382 | headset_mic e311 383 | healing e3f3 384 | hearing e023 385 | help e887 386 | help_outline e8fd 387 | high_quality e024 388 | highlight e25f 389 | highlight_off e888 390 | history e889 391 | home e88a 392 | hot_tub eb46 393 | hotel e53a 394 | hourglass_empty e88b 395 | hourglass_full e88c 396 | http e902 397 | https e88d 398 | image e3f4 399 | image_aspect_ratio e3f5 400 | import_contacts e0e0 401 | import_export e0c3 402 | important_devices e912 403 | inbox e156 404 | indeterminate_check_box e909 405 | info e88e 406 | info_outline e88f 407 | input e890 408 | insert_chart e24b 409 | insert_comment e24c 410 | insert_drive_file e24d 411 | insert_emoticon e24e 412 | insert_invitation e24f 413 | insert_link e250 414 | insert_photo e251 415 | invert_colors e891 416 | invert_colors_off e0c4 417 | iso e3f6 418 | keyboard e312 419 | keyboard_arrow_down e313 420 | keyboard_arrow_left e314 421 | keyboard_arrow_right e315 422 | keyboard_arrow_up e316 423 | keyboard_backspace e317 424 | keyboard_capslock e318 425 | keyboard_hide e31a 426 | keyboard_return e31b 427 | keyboard_tab e31c 428 | keyboard_voice e31d 429 | kitchen eb47 430 | label e892 431 | label_outline e893 432 | landscape e3f7 433 | language e894 434 | laptop e31e 435 | laptop_chromebook e31f 436 | laptop_mac e320 437 | laptop_windows e321 438 | launch e895 439 | layers e53b 440 | layers_clear e53c 441 | leak_add e3f8 442 | leak_remove e3f9 443 | lens e3fa 444 | library_add e02e 445 | library_books e02f 446 | library_music e030 447 | lightbulb_outline e90f 448 | line_style e919 449 | line_weight e91a 450 | linear_scale e260 451 | link e157 452 | linked_camera e438 453 | list e896 454 | live_help e0c6 455 | live_tv e639 456 | local_activity e53f 457 | local_airport e53d 458 | local_atm e53e 459 | local_bar e540 460 | local_cafe e541 461 | local_car_wash e542 462 | local_convenience_store e543 463 | local_dining e556 464 | local_drink e544 465 | local_florist e545 466 | local_gas_station e546 467 | local_grocery_store e547 468 | local_hospital e548 469 | local_hotel e549 470 | local_laundry_service e54a 471 | local_library e54b 472 | local_mall e54c 473 | local_movies e54d 474 | local_offer e54e 475 | local_parking e54f 476 | local_pharmacy e550 477 | local_phone e551 478 | local_pizza e552 479 | local_play e553 480 | local_post_office e554 481 | local_printshop e555 482 | local_see e557 483 | local_shipping e558 484 | local_taxi e559 485 | location_city e7f1 486 | location_disabled e1b6 487 | location_off e0c7 488 | location_on e0c8 489 | location_searching e1b7 490 | lock e897 491 | lock_open e898 492 | lock_outline e899 493 | looks e3fc 494 | looks_3 e3fb 495 | looks_4 e3fd 496 | looks_5 e3fe 497 | looks_6 e3ff 498 | looks_one e400 499 | looks_two e401 500 | loop e028 501 | loupe e402 502 | loyalty e89a 503 | mail e158 504 | mail_outline e0e1 505 | map e55b 506 | markunread e159 507 | markunread_mailbox e89b 508 | memory e322 509 | menu e5d2 510 | merge_type e252 511 | message e0c9 512 | mic e029 513 | mic_none e02a 514 | mic_off e02b 515 | mms e618 516 | mode_comment e253 517 | mode_edit e254 518 | money_off e25c 519 | monochrome_photos e403 520 | mood e7f2 521 | mood_bad e7f3 522 | more e619 523 | more_horiz e5d3 524 | more_vert e5d4 525 | motorcycle e91b 526 | mouse e323 527 | move_to_inbox e168 528 | movie e02c 529 | movie_creation e404 530 | movie_filter e43a 531 | music_note e405 532 | music_video e063 533 | my_location e55c 534 | nature e406 535 | nature_people e407 536 | navigate_before e408 537 | navigate_next e409 538 | navigation e55d 539 | near_me e569 540 | network_cell e1b9 541 | network_check e640 542 | network_locked e61a 543 | network_wifi e1ba 544 | new_releases e031 545 | next_week e16a 546 | nfc e1bb 547 | no_encryption e641 548 | no_sim e0cc 549 | not_interested e033 550 | note_add e89c 551 | notifications e7f4 552 | notifications_active e7f7 553 | notifications_none e7f5 554 | notifications_off e7f6 555 | notifications_paused e7f8 556 | offline_pin e90a 557 | ondemand_video e63a 558 | opacity e91c 559 | open_in_browser e89d 560 | open_in_new e89e 561 | open_with e89f 562 | pages e7f9 563 | pageview e8a0 564 | palette e40a 565 | pan_tool e925 566 | panorama e40b 567 | panorama_fish_eye e40c 568 | panorama_horizontal e40d 569 | panorama_vertical e40e 570 | panorama_wide_angle e40f 571 | party_mode e7fa 572 | pause e034 573 | pause_circle_filled e035 574 | pause_circle_outline e036 575 | payment e8a1 576 | people e7fb 577 | people_outline e7fc 578 | perm_camera_mic e8a2 579 | perm_contact_calendar e8a3 580 | perm_data_setting e8a4 581 | perm_device_information e8a5 582 | perm_identity e8a6 583 | perm_media e8a7 584 | perm_phone_msg e8a8 585 | perm_scan_wifi e8a9 586 | person e7fd 587 | person_add e7fe 588 | person_outline e7ff 589 | person_pin e55a 590 | person_pin_circle e56a 591 | personal_video e63b 592 | pets e91d 593 | phone e0cd 594 | phone_android e324 595 | phone_bluetooth_speaker e61b 596 | phone_forwarded e61c 597 | phone_in_talk e61d 598 | phone_iphone e325 599 | phone_locked e61e 600 | phone_missed e61f 601 | phone_paused e620 602 | phonelink e326 603 | phonelink_erase e0db 604 | phonelink_lock e0dc 605 | phonelink_off e327 606 | phonelink_ring e0dd 607 | phonelink_setup e0de 608 | photo e410 609 | photo_album e411 610 | photo_camera e412 611 | photo_filter e43b 612 | photo_library e413 613 | photo_size_select_actual e432 614 | photo_size_select_large e433 615 | photo_size_select_small e434 616 | picture_as_pdf e415 617 | picture_in_picture e8aa 618 | picture_in_picture_alt e911 619 | pin_drop e55e 620 | place e55f 621 | play_arrow e037 622 | play_circle_filled e038 623 | play_circle_outline e039 624 | play_for_work e906 625 | playlist_add e03b 626 | playlist_add_check e065 627 | playlist_play e05f 628 | plus_one e800 629 | poll e801 630 | polymer e8ab 631 | pool eb48 632 | portable_wifi_off e0ce 633 | portrait e416 634 | power e63c 635 | power_input e336 636 | power_settings_new e8ac 637 | pregnant_woman e91e 638 | present_to_all e0df 639 | print e8ad 640 | public e80b 641 | publish e255 642 | query_builder e8ae 643 | question_answer e8af 644 | queue e03c 645 | queue_music e03d 646 | queue_play_next e066 647 | radio e03e 648 | radio_button_checked e837 649 | radio_button_unchecked e836 650 | rate_review e560 651 | receipt e8b0 652 | recent_actors e03f 653 | record_voice_over e91f 654 | redeem e8b1 655 | redo e15a 656 | refresh e5d5 657 | remove e15b 658 | remove_circle e15c 659 | remove_circle_outline e15d 660 | remove_from_queue e067 661 | remove_red_eye e417 662 | reorder e8fe 663 | repeat e040 664 | repeat_one e041 665 | replay e042 666 | replay_10 e059 667 | replay_30 e05a 668 | replay_5 e05b 669 | reply e15e 670 | reply_all e15f 671 | report e160 672 | report_problem e8b2 673 | restaurant_menu e561 674 | restore e8b3 675 | ring_volume e0d1 676 | room e8b4 677 | room_service eb49 678 | rotate_90_degrees_ccw e418 679 | rotate_left e419 680 | rotate_right e41a 681 | rounded_corner e920 682 | router e328 683 | rowing e921 684 | rv_hookup e642 685 | satellite e562 686 | save e161 687 | scanner e329 688 | schedule e8b5 689 | school e80c 690 | screen_lock_landscape e1be 691 | screen_lock_portrait e1bf 692 | screen_lock_rotation e1c0 693 | screen_rotation e1c1 694 | screen_share e0e2 695 | sd_card e623 696 | sd_storage e1c2 697 | search e8b6 698 | security e32a 699 | select_all e162 700 | send e163 701 | settings e8b8 702 | settings_applications e8b9 703 | settings_backup_restore e8ba 704 | settings_bluetooth e8bb 705 | settings_brightness e8bd 706 | settings_cell e8bc 707 | settings_ethernet e8be 708 | settings_input_antenna e8bf 709 | settings_input_component e8c0 710 | settings_input_composite e8c1 711 | settings_input_hdmi e8c2 712 | settings_input_svideo e8c3 713 | settings_overscan e8c4 714 | settings_phone e8c5 715 | settings_power e8c6 716 | settings_remote e8c7 717 | settings_system_daydream e1c3 718 | settings_voice e8c8 719 | share e80d 720 | shop e8c9 721 | shop_two e8ca 722 | shopping_basket e8cb 723 | shopping_cart e8cc 724 | short_text e261 725 | shuffle e043 726 | signal_cellular_4_bar e1c8 727 | signal_cellular_connected_no_internet_4_bar e1cd 728 | signal_cellular_no_sim e1ce 729 | signal_cellular_null e1cf 730 | signal_cellular_off e1d0 731 | signal_wifi_4_bar e1d8 732 | signal_wifi_4_bar_lock e1d9 733 | signal_wifi_off e1da 734 | sim_card e32b 735 | sim_card_alert e624 736 | skip_next e044 737 | skip_previous e045 738 | slideshow e41b 739 | slow_motion_video e068 740 | smartphone e32c 741 | smoke_free eb4a 742 | smoking_rooms eb4b 743 | sms e625 744 | sms_failed e626 745 | snooze e046 746 | sort e164 747 | sort_by_alpha e053 748 | spa eb4c 749 | space_bar e256 750 | speaker e32d 751 | speaker_group e32e 752 | speaker_notes e8cd 753 | speaker_phone e0d2 754 | spellcheck e8ce 755 | star e838 756 | star_border e83a 757 | star_half e839 758 | stars e8d0 759 | stay_current_landscape e0d3 760 | stay_current_portrait e0d4 761 | stay_primary_landscape e0d5 762 | stay_primary_portrait e0d6 763 | stop e047 764 | stop_screen_share e0e3 765 | storage e1db 766 | store e8d1 767 | store_mall_directory e563 768 | straighten e41c 769 | strikethrough_s e257 770 | style e41d 771 | subdirectory_arrow_left e5d9 772 | subdirectory_arrow_right e5da 773 | subject e8d2 774 | subscriptions e064 775 | subtitles e048 776 | supervisor_account e8d3 777 | surround_sound e049 778 | swap_calls e0d7 779 | swap_horiz e8d4 780 | swap_vert e8d5 781 | swap_vertical_circle e8d6 782 | switch_camera e41e 783 | switch_video e41f 784 | sync e627 785 | sync_disabled e628 786 | sync_problem e629 787 | system_update e62a 788 | system_update_alt e8d7 789 | tab e8d8 790 | tab_unselected e8d9 791 | tablet e32f 792 | tablet_android e330 793 | tablet_mac e331 794 | tag_faces e420 795 | tap_and_play e62b 796 | terrain e564 797 | text_fields e262 798 | text_format e165 799 | textsms e0d8 800 | texture e421 801 | theaters e8da 802 | thumb_down e8db 803 | thumb_up e8dc 804 | thumbs_up_down e8dd 805 | time_to_leave e62c 806 | timelapse e422 807 | timeline e922 808 | timer e425 809 | timer_10 e423 810 | timer_3 e424 811 | timer_off e426 812 | toc e8de 813 | today e8df 814 | toll e8e0 815 | tonality e427 816 | touch_app e913 817 | toys e332 818 | track_changes e8e1 819 | traffic e565 820 | transform e428 821 | translate e8e2 822 | trending_down e8e3 823 | trending_flat e8e4 824 | trending_up e8e5 825 | tune e429 826 | turned_in e8e6 827 | turned_in_not e8e7 828 | tv e333 829 | unarchive e169 830 | undo e166 831 | unfold_less e5d6 832 | unfold_more e5d7 833 | update e923 834 | usb e1e0 835 | verified_user e8e8 836 | vertical_align_bottom e258 837 | vertical_align_center e259 838 | vertical_align_top e25a 839 | vibration e62d 840 | video_library e04a 841 | videocam e04b 842 | videocam_off e04c 843 | videogame_asset e338 844 | view_agenda e8e9 845 | view_array e8ea 846 | view_carousel e8eb 847 | view_column e8ec 848 | view_comfy e42a 849 | view_compact e42b 850 | view_day e8ed 851 | view_headline e8ee 852 | view_list e8ef 853 | view_module e8f0 854 | view_quilt e8f1 855 | view_stream e8f2 856 | view_week e8f3 857 | vignette e435 858 | visibility e8f4 859 | visibility_off e8f5 860 | voice_chat e62e 861 | voicemail e0d9 862 | volume_down e04d 863 | volume_mute e04e 864 | volume_off e04f 865 | volume_up e050 866 | vpn_key e0da 867 | vpn_lock e62f 868 | wallpaper e1bc 869 | warning e002 870 | watch e334 871 | watch_later e924 872 | wb_auto e42c 873 | wb_cloudy e42d 874 | wb_incandescent e42e 875 | wb_iridescent e436 876 | wb_sunny e430 877 | wc e63d 878 | web e051 879 | web_asset e069 880 | weekend e16b 881 | whatshot e80e 882 | widgets e1bd 883 | wifi e63e 884 | wifi_lock e1e1 885 | wifi_tethering e1e2 886 | work e8f9 887 | wrap_text e25b 888 | youtube_searched_for e8fa 889 | zoom_in e8ff 890 | zoom_out e900 891 | zoom_out_map e56b 892 | -------------------------------------------------------------------------------- /icons/materialicons/util/gen_icon_symbols.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | success = False 4 | 5 | with open("../materialicons.go", "w") as output: 6 | output.write("""package materialicons 7 | 8 | import "github.com/shibukawa/nanogui.go" 9 | 10 | // generated by util/gen_icon_symbols.py 11 | 12 | const ( 13 | """) 14 | for line in open("./codepoints").readlines(): 15 | name, codepoint = line.split(" ") 16 | output.write("Icon%s nanogui.Icon = 0x%s" % (name.title().replace("_", ""), codepoint)) 17 | output.write(")") 18 | success = True 19 | 20 | if success: 21 | os.system("go fmt ../materialicons.go") 22 | 23 | -------------------------------------------------------------------------------- /imagepanel.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/glfw" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type Image struct { 9 | ImageID int 10 | Name string 11 | } 12 | 13 | type ImagePanel struct { 14 | WidgetImplement 15 | 16 | images []Image 17 | callback func(int) 18 | thumbSize int 19 | spacing int 20 | margin int 21 | mouseIndex int 22 | } 23 | 24 | func NewImagePanel(parent Widget) *ImagePanel { 25 | panel := &ImagePanel{ 26 | thumbSize: 64, 27 | spacing: 10, 28 | margin: 10, 29 | mouseIndex: -1, 30 | } 31 | 32 | InitWidget(panel, parent) 33 | return panel 34 | } 35 | 36 | func (i *ImagePanel) Images() []Image { 37 | return i.images 38 | } 39 | 40 | func (i *ImagePanel) SetImages(images []Image) { 41 | i.images = images 42 | } 43 | 44 | func (i *ImagePanel) SetCallback(callback func(int)) { 45 | i.callback = callback 46 | } 47 | 48 | func (i *ImagePanel) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 49 | index := i.indexForPosition(x, y) 50 | if index >= 0 && i.callback != nil && down { 51 | i.callback(index) 52 | } 53 | return true 54 | } 55 | 56 | func (i *ImagePanel) MouseMotionEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 57 | i.mouseIndex = i.indexForPosition(x, y) 58 | return true 59 | } 60 | 61 | func (i *ImagePanel) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 62 | cols, rows := i.gridSize() 63 | w := cols*i.thumbSize + (cols-1)*i.spacing + 2*i.margin 64 | h := rows*i.thumbSize + (rows-1)*i.spacing + 2*i.margin 65 | return w, h 66 | } 67 | 68 | func (i *ImagePanel) Draw(self Widget, ctx *nanovgo.Context) { 69 | cols, _ := i.gridSize() 70 | 71 | x := float32(i.x) 72 | y := float32(i.y) 73 | thumbSize := float32(i.thumbSize) 74 | 75 | for j, image := range i.images { 76 | pX := x + float32(i.margin+(j%cols)*(i.thumbSize+i.spacing)) 77 | pY := y + float32(i.margin+(j/cols)*(i.thumbSize+i.spacing)) 78 | 79 | imgW, imgH, _ := ctx.ImageSize(image.ImageID) 80 | var iw, ih, ix, iy float32 81 | if imgW < imgH { 82 | iw = thumbSize 83 | ih = iw * float32(imgH) / float32(imgW) 84 | ix = 0 85 | iy = -(ih - thumbSize) * 0.5 86 | } else { 87 | ih = thumbSize 88 | iw = ih * float32(imgH) / float32(imgW) 89 | iy = 0 90 | ix = -(iw - thumbSize) * 0.5 91 | } 92 | imgPaint := nanovgo.ImagePattern(pX+ix, pY+iy, iw, ih, 0, image.ImageID, toF(i.mouseIndex == j, 1.0, 0.7)) 93 | ctx.BeginPath() 94 | ctx.RoundedRect(pX, pY, thumbSize, thumbSize, 5) 95 | ctx.SetFillPaint(imgPaint) 96 | ctx.Fill() 97 | 98 | shadowPaint := nanovgo.BoxGradient(pX-1, pY, thumbSize+2, thumbSize+2, 5, 3, nanovgo.MONO(0, 128), nanovgo.MONO(0, 0)) 99 | 100 | ctx.BeginPath() 101 | ctx.Rect(pX-5, pY-5, thumbSize+10, thumbSize+10) 102 | ctx.RoundedRect(pX, pY, thumbSize, thumbSize, 6) 103 | ctx.PathWinding(nanovgo.Hole) 104 | ctx.SetFillPaint(shadowPaint) 105 | ctx.Fill() 106 | 107 | ctx.BeginPath() 108 | ctx.RoundedRect(pX+0.5, pY+0.5, thumbSize-1, thumbSize-1, 4-0.5) 109 | ctx.SetStrokeWidth(1.0) 110 | ctx.SetStrokeColor(nanovgo.MONO(255, 80)) 111 | ctx.Stroke() 112 | } 113 | } 114 | 115 | func (i *ImagePanel) String() string { 116 | return i.StringHelper("ImagePanel", "") 117 | } 118 | 119 | func (i *ImagePanel) gridSize() (int, int) { 120 | nCols := 1 + maxI(0, int(float32(i.w-2*i.margin-i.thumbSize)/float32(i.thumbSize+i.spacing))) 121 | nRows := (len(i.images) + nCols - 1) / nCols 122 | return nCols, nRows 123 | 124 | } 125 | 126 | func (i *ImagePanel) indexForPosition(x, y int) int { 127 | pX := float32(x-i.margin) / float32(i.thumbSize+i.margin) 128 | pY := float32(y-i.margin) / float32(i.thumbSize+i.margin) 129 | iconRegion := float32(i.thumbSize) / float32(i.thumbSize+i.spacing) 130 | overImage := pX-floorF(pX) < iconRegion && pY-floorF(pY) < iconRegion 131 | gridPosX := int(pX) 132 | gridPosY := int(pY) 133 | gridCols, gridRows := i.gridSize() 134 | overImage = overImage && gridPosX >= 0 && gridPosY >= 0 && gridPosX < gridCols && gridPosY < gridRows 135 | if overImage { 136 | return gridPosX + gridPosY*gridCols 137 | } 138 | return -1 139 | } 140 | -------------------------------------------------------------------------------- /imageview.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | ) 6 | 7 | type ImageSizePolicy int 8 | 9 | const ( 10 | ImageSizePolicyFixed ImageSizePolicy = iota 11 | ImageSizePolicyExpand 12 | ) 13 | 14 | type ImageView struct { 15 | WidgetImplement 16 | 17 | image int 18 | policy ImageSizePolicy 19 | } 20 | 21 | func NewImageView(parent Widget, images ...int) *ImageView { 22 | var image int 23 | switch len(images) { 24 | case 0: 25 | case 1: 26 | image = images[0] 27 | default: 28 | panic("NewImageView can accept only one extra parameter (image)") 29 | } 30 | 31 | imageView := &ImageView{ 32 | image: image, 33 | policy: ImageSizePolicyFixed, 34 | } 35 | InitWidget(imageView, parent) 36 | return imageView 37 | } 38 | 39 | func (i *ImageView) Image() int { 40 | return i.image 41 | } 42 | 43 | func (i *ImageView) SetImage(image int) { 44 | i.image = image 45 | } 46 | 47 | func (i *ImageView) Policy() ImageSizePolicy { 48 | return i.policy 49 | } 50 | 51 | func (i *ImageView) SetPolicy(policy ImageSizePolicy) { 52 | i.policy = policy 53 | } 54 | 55 | func (i *ImageView) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 56 | if i.image == 0 { 57 | return 0, 0 58 | } 59 | w, h, _ := ctx.ImageSize(i.image) 60 | return w, h 61 | } 62 | 63 | func (i *ImageView) Draw(self Widget, ctx *nanovgo.Context) { 64 | if i.image == 0 { 65 | return 66 | } 67 | x := float32(i.x) 68 | y := float32(i.y) 69 | ow := float32(i.w) 70 | oh := float32(i.h) 71 | 72 | var w, h float32 73 | { 74 | iw, ih, _ := ctx.ImageSize(i.image) 75 | w = float32(iw) 76 | h = float32(ih) 77 | } 78 | 79 | if i.policy == ImageSizePolicyFixed { 80 | if ow < w { 81 | h = float32(int(h * ow / w)) 82 | w = ow 83 | } 84 | if oh < h { 85 | w = float32(int(w * oh / h)) 86 | h = oh 87 | } 88 | } else { // mPolicy == Expand 89 | // expand to width 90 | h = float32(int(h * ow / w)) 91 | w = ow 92 | // shrink to height, if necessary 93 | if oh < h { 94 | w = float32(int(w * oh / h)) 95 | h = oh 96 | } 97 | } 98 | 99 | imgPaint := nanovgo.ImagePattern(x, y, w, h, 0, i.image, 1.0) 100 | 101 | ctx.BeginPath() 102 | ctx.Rect(x, y, w, h) 103 | ctx.SetFillPaint(imgPaint) 104 | ctx.Fill() 105 | } 106 | 107 | func (i *ImageView) String() string { 108 | return i.StringHelper("ImageView", "") 109 | } 110 | -------------------------------------------------------------------------------- /keyboard.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/glfw" 5 | "runtime" 6 | ) 7 | 8 | type EditAction int 9 | 10 | const ( 11 | EditActionNone EditAction = iota 12 | EditActionMoveLeft 13 | EditActionMoveRight 14 | EditActionMoveLineTop 15 | EditActionMoveLineEnd 16 | EditActionBackspace 17 | EditActionDelete 18 | EditActionCutUntilLineEnd 19 | EditActionYank 20 | EditActionDeleteLeftWord 21 | EditActionEnter 22 | EditActionSelectAll 23 | EditActionCopy 24 | EditActionCut 25 | EditActionPaste 26 | ) 27 | 28 | func DetectEditAction(key glfw.Key, modifier glfw.ModifierKey) EditAction { 29 | isMac := runtime.GOOS == "darwin" 30 | switch key { 31 | case glfw.KeyLeft: 32 | return EditActionMoveLeft 33 | case glfw.KeyB: 34 | if modifier == glfw.ModControl { 35 | return EditActionMoveLeft 36 | } 37 | case glfw.KeyRight: 38 | return EditActionMoveRight 39 | case glfw.KeyF: 40 | if modifier == glfw.ModControl { 41 | return EditActionMoveRight 42 | } 43 | case glfw.KeyHome: 44 | return EditActionMoveLineTop 45 | case glfw.KeyA: 46 | if modifier == glfw.ModControl { 47 | if isMac { 48 | return EditActionMoveLineTop 49 | } else { 50 | return EditActionSelectAll 51 | } 52 | } else if modifier == glfw.ModSuper { 53 | return EditActionSelectAll 54 | } 55 | case glfw.KeyEnd: 56 | return EditActionMoveLineEnd 57 | case glfw.KeyE: 58 | if modifier == glfw.ModControl { 59 | return EditActionMoveLineEnd 60 | } 61 | case glfw.KeyBackspace: 62 | return EditActionBackspace 63 | case glfw.KeyH: 64 | if modifier == glfw.ModControl { 65 | return EditActionBackspace 66 | } 67 | case glfw.KeyDelete: 68 | if modifier == glfw.ModAlt { 69 | return EditActionDeleteLeftWord 70 | } 71 | return EditActionDelete 72 | case glfw.KeyD: 73 | if modifier == glfw.ModControl { 74 | return EditActionDelete 75 | } 76 | case glfw.KeyK: 77 | if modifier == glfw.ModControl { 78 | return EditActionCutUntilLineEnd 79 | } 80 | case glfw.KeyY: 81 | if modifier == glfw.ModControl { 82 | return EditActionYank 83 | } 84 | case glfw.KeyEnter: 85 | return EditActionEnter 86 | case glfw.KeyC: 87 | if (!isMac && modifier == glfw.ModControl) || (isMac && modifier == glfw.ModSuper) { 88 | return EditActionCopy 89 | } 90 | case glfw.KeyX: 91 | if (!isMac && modifier == glfw.ModControl) || (isMac && modifier == glfw.ModSuper) { 92 | return EditActionCut 93 | } 94 | case glfw.KeyV: 95 | if (!isMac && modifier == glfw.ModControl) || (isMac && modifier == glfw.ModSuper) { 96 | return EditActionPaste 97 | } 98 | } 99 | return EditActionNone 100 | } 101 | -------------------------------------------------------------------------------- /label.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | ) 6 | 7 | // Text label widget 8 | // The font and color can be customized. When SetFixedWidth() 9 | // is used, the text is wrapped when it surpasses the specified width 10 | // 11 | type Label struct { 12 | WidgetImplement 13 | caption string 14 | fontFace string 15 | color nanovgo.Color 16 | columnWidth int 17 | wrap bool 18 | } 19 | 20 | func NewLabel(parent Widget, caption string) *Label { 21 | label := &Label{ 22 | caption: caption, 23 | color: parent.Theme().TextColor, 24 | wrap: true, 25 | } 26 | InitWidget(label, parent) 27 | return label 28 | } 29 | 30 | // Caption() gets the label's text caption 31 | func (l *Label) Caption() string { 32 | return l.caption 33 | } 34 | 35 | // SetCaption() sets the label's text caption 36 | func (l *Label) SetCaption(caption string) { 37 | l.caption = caption 38 | } 39 | 40 | // Font() gets the currently active font 41 | func (l *Label) Font() string { 42 | if l.fontFace == "" { 43 | return l.theme.FontBold 44 | } 45 | return l.fontFace 46 | } 47 | 48 | // SetFont() sets the currently active font (2 are available by default: 'sans' and 'sans-bold') 49 | func (l *Label) SetFont(fontFace string) { 50 | l.fontFace = fontFace 51 | } 52 | 53 | // Color() gets the label color 54 | func (l *Label) Color() nanovgo.Color { 55 | return l.color 56 | } 57 | 58 | // SetColor() sets the label color 59 | func (l *Label) SetColor(color nanovgo.Color) { 60 | l.color = color 61 | } 62 | 63 | func (l *Label) ColumnWidth() int { 64 | return l.columnWidth 65 | } 66 | 67 | func (l *Label) SetColumnWidth(width int) { 68 | l.columnWidth = width 69 | } 70 | 71 | func (l *Label) Wrap() bool { 72 | return l.wrap 73 | } 74 | 75 | func (l *Label) SetWrap(wrap bool) { 76 | l.wrap = wrap 77 | } 78 | 79 | func (l *Label) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 80 | if l.caption == "" { 81 | return 0, 0 82 | } 83 | ctx.SetFontSize(float32(l.FontSize())) 84 | ctx.SetFontFace(l.Font()) 85 | 86 | width := 0 87 | if l.FixedWidth() > 0 { 88 | width = l.FixedWidth() 89 | } else if l.columnWidth > 0 && l.wrap { 90 | width = l.columnWidth 91 | } 92 | 93 | if width > 0 { 94 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 95 | bounds := ctx.TextBoxBounds(0, 0, float32(width), l.caption) 96 | return width, int(bounds[3] - bounds[1]) 97 | } else { 98 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 99 | w, _ := ctx.TextBounds(0, 0, l.caption) 100 | return int(w), l.Theme().StandardFontSize 101 | } 102 | } 103 | 104 | func (l *Label) Draw(self Widget, ctx *nanovgo.Context) { 105 | l.WidgetImplement.Draw(self, ctx) 106 | ctx.SetFontSize(float32(l.FontSize())) 107 | ctx.SetFontFace(l.Font()) 108 | ctx.SetFillColor(l.color) 109 | 110 | width := 0 111 | if l.FixedWidth() > 0 { 112 | width = l.FixedWidth() 113 | } else if l.columnWidth > 0 && l.wrap { 114 | width = l.columnWidth 115 | } 116 | 117 | if width > 0 { 118 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 119 | ctx.TextBox(float32(l.x), float32(l.y), float32(width), l.caption) 120 | } else { 121 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 122 | ctx.Text(float32(l.x), float32(l.y)+float32(l.h)*0.5, l.caption) 123 | } 124 | } 125 | 126 | func (l *Label) String() string { 127 | return l.StringHelper("Label", l.caption) 128 | } 129 | -------------------------------------------------------------------------------- /nanoguiext/layout.go: -------------------------------------------------------------------------------- 1 | package nanoguiext 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanogui.go" 6 | "github.com/shibukawa/nanovgo" 7 | ) 8 | 9 | type FlexibleWidget interface { 10 | nanogui.Widget 11 | SetColumnWidth(columnWidth int) 12 | } 13 | 14 | type ExpandPolicy int 15 | 16 | const ( 17 | ExpandAll ExpandPolicy = iota 18 | ExpandLast 19 | ) 20 | 21 | type ExpandBoxLayout struct { 22 | orientation nanogui.Orientation 23 | alignment nanogui.Alignment 24 | margin int 25 | spacing int 26 | } 27 | 28 | func NewExpandBoxLayout(orientation nanogui.Orientation, alignment nanogui.Alignment, setting ...int) *ExpandBoxLayout { 29 | var margin, spacing int 30 | switch len(setting) { 31 | case 0: 32 | case 1: 33 | margin = setting[0] 34 | case 2: 35 | margin = setting[0] 36 | spacing = setting[1] 37 | default: 38 | panic("NewExpandBoxLayout can accept extra parameter upto 2 (margin, spacing).") 39 | } 40 | return &ExpandBoxLayout{ 41 | orientation: orientation, 42 | alignment: alignment, 43 | margin: margin, 44 | spacing: spacing, 45 | } 46 | } 47 | 48 | func (b *ExpandBoxLayout) Orientation() nanogui.Orientation { 49 | return b.orientation 50 | } 51 | 52 | func (b *ExpandBoxLayout) SetOrientation(o nanogui.Orientation) { 53 | b.orientation = o 54 | } 55 | 56 | func (b *ExpandBoxLayout) Alignment() nanogui.Alignment { 57 | return b.alignment 58 | } 59 | 60 | func (b *ExpandBoxLayout) SetAlignment(a nanogui.Alignment) { 61 | b.alignment = a 62 | } 63 | 64 | func (b *ExpandBoxLayout) Margin() int { 65 | return b.margin 66 | } 67 | 68 | func (b *ExpandBoxLayout) SetMargin(m int) { 69 | b.margin = m 70 | } 71 | 72 | func (b *ExpandBoxLayout) Spacing() int { 73 | return b.spacing 74 | } 75 | 76 | func (b *ExpandBoxLayout) SetSpacing(s int) { 77 | b.spacing = s 78 | } 79 | 80 | func (b *ExpandBoxLayout) OnPerformLayout(widget nanogui.Widget, ctx *nanovgo.Context) { 81 | fW, fH := widget.FixedSize() 82 | var containerSize [2]int 83 | if fW > 0 { 84 | containerSize[0] = fW 85 | } else { 86 | containerSize[0] = widget.Width() 87 | } 88 | if fH > 0 { 89 | containerSize[1] = fH 90 | } else { 91 | containerSize[1] = widget.Height() 92 | } 93 | axis1 := int(b.orientation) 94 | axis2 := (int(b.orientation) + 1) % 2 95 | position := b.margin 96 | 97 | var yOffset int 98 | 99 | if _, ok := widget.(*nanogui.Window); ok { 100 | if b.orientation == nanogui.Vertical { 101 | position += widget.Theme().WindowHeaderHeight - b.margin/2 102 | } else { 103 | yOffset = widget.Theme().WindowHeaderHeight 104 | } 105 | } 106 | childCount := 0 107 | fixedChildren := make([]bool, widget.ChildCount()) 108 | preferredLength := make([][2]int, widget.ChildCount()) 109 | remainedLength := containerSize[axis1] - position - b.margin + b.spacing 110 | for i, child := range widget.Children() { 111 | if child.Visible() && !child.IsPositionAbsolute() { 112 | childCount++ 113 | if _, isScroll := child.(*nanogui.VScrollPanel); !isScroll { 114 | fW, fH := child.FixedSize() 115 | fs := [2]int{fW, fH} 116 | preferredLength[i] = fs 117 | if fs[axis1] > 0 { 118 | remainedLength -= fs[axis1] 119 | fixedChildren[i] = true 120 | childCount-- 121 | } else if child.Clamp()[axis1] { 122 | pW, pH := child.PreferredSize(child, ctx) 123 | ps := [2]int{pW, pH} 124 | remainedLength -= ps[axis1] 125 | preferredLength[i] = ps 126 | fixedChildren[i] = true 127 | childCount-- 128 | } else { 129 | pW, pH := child.PreferredSize(child, ctx) 130 | ps := [2]int{pW, pH} 131 | preferredLength[i] = ps 132 | } 133 | } 134 | remainedLength -= b.spacing 135 | } else { 136 | fixedChildren[i] = true 137 | } 138 | } 139 | 140 | var averageSize int 141 | if childCount > 0 { 142 | averageSize = remainedLength / childCount 143 | } 144 | for i, child := range widget.Children() { 145 | if !child.Visible() || child.IsPositionAbsolute() { 146 | child.OnPerformLayout(child, ctx) 147 | continue 148 | } 149 | var pos [2]int 150 | pos[1] = yOffset 151 | pos[axis1] = position 152 | var targetSize [2]int 153 | if fixedChildren[i] && preferredLength[i][axis1] > 0 { 154 | targetSize[axis1] = preferredLength[i][axis1] 155 | } else { 156 | targetSize[axis1] = averageSize 157 | } 158 | targetSize[axis2] = preferredLength[i][axis2] 159 | 160 | switch b.alignment { 161 | case nanogui.Minimum: 162 | pos[axis2] += b.margin 163 | case nanogui.Middle: 164 | pos[axis2] += (containerSize[axis2] - yOffset - targetSize[axis2]) / 2 165 | case nanogui.Maximum: 166 | pos[axis2] += containerSize[axis2] - yOffset - targetSize[axis2] - b.margin*2 167 | case nanogui.Fill: 168 | pos[axis2] += b.margin 169 | targetSize[axis2] = containerSize[axis2] - yOffset - b.margin*2 170 | } 171 | child.SetPosition(pos[0], pos[1]) 172 | child.SetSize(targetSize[0], targetSize[1]) 173 | child.OnPerformLayout(child, ctx) 174 | position += targetSize[axis1] + b.spacing 175 | } 176 | } 177 | 178 | func (b *ExpandBoxLayout) PreferredSize(widget nanogui.Widget, ctx *nanovgo.Context) (int, int) { 179 | fW, fH := widget.FixedSize() 180 | var containerSize [2]int 181 | if fW > 0 { 182 | containerSize[0] = fW 183 | } else { 184 | containerSize[0] = widget.Width() 185 | } 186 | if fH > 0 { 187 | containerSize[1] = fH 188 | } else { 189 | containerSize[1] = widget.Height() 190 | } 191 | axis1 := int(b.orientation) 192 | axis2 := (int(b.orientation) + 1) % 2 193 | minimumContainerSize := []int{0, 0} 194 | minimumContainerSize[axis1] += b.margin*2 195 | 196 | childCount := 0 197 | for _, child := range widget.Children() { 198 | if !child.Visible() || child.IsPositionAbsolute() { 199 | continue 200 | } 201 | childCount++ 202 | w, h := child.PreferredSize(child, ctx) 203 | size := []int{w, h} 204 | minimumContainerSize[axis1] += size[axis1] 205 | if size[axis2] > minimumContainerSize[axis2] { 206 | minimumContainerSize[axis2] = size[axis2] 207 | } 208 | } 209 | if childCount > 0 { 210 | minimumContainerSize[axis1] += (childCount-1) * b.spacing 211 | } 212 | minimumContainerSize[axis2] += b.margin * 2 213 | for i := 0; i < 2; i++ { 214 | if minimumContainerSize[i] > containerSize[i] { 215 | containerSize[i] = minimumContainerSize[i] 216 | } 217 | } 218 | return containerSize[0], containerSize[1] 219 | } 220 | 221 | func (b *ExpandBoxLayout) String() string { 222 | return fmt.Sprintf("ExpandBoxLayout[%s,%s]", b.orientation, b.alignment) 223 | } 224 | 225 | type ExpandListLayout struct { 226 | widths []int 227 | stretches []float32 228 | alignments [2]nanogui.Alignment 229 | expandPolicy [2]ExpandPolicy 230 | margin int 231 | spacing []int 232 | } 233 | 234 | func NewExpandListLayout(widths []int, setting ...int) *ExpandListLayout { 235 | var margin, spacing int 236 | switch len(setting) { 237 | case 0: 238 | case 1: 239 | margin = setting[0] 240 | case 2: 241 | margin = setting[0] 242 | spacing = setting[1] 243 | default: 244 | panic("NewExpandListLayout can accept extra parameter upto 2 (margin, spacing).") 245 | } 246 | return &ExpandListLayout{ 247 | alignments: [2]nanogui.Alignment{nanogui.Minimum, nanogui.Minimum}, 248 | widths: widths, 249 | margin: margin, 250 | spacing: []int{spacing, spacing}, 251 | } 252 | } 253 | 254 | func (g *ExpandListLayout) Resolution() int { 255 | return len(g.widths) 256 | } 257 | 258 | func (g *ExpandListLayout) ColAlignment() nanogui.Alignment { 259 | return g.alignments[0] 260 | } 261 | 262 | func (g *ExpandListLayout) RowAlignment() nanogui.Alignment { 263 | return g.alignments[1] 264 | } 265 | 266 | func (g *ExpandListLayout) SetColAlignment(a nanogui.Alignment) { 267 | g.alignments[0] = a 268 | } 269 | 270 | func (g *ExpandListLayout) SetRowAlignment(a nanogui.Alignment) { 271 | g.alignments[1] = a 272 | } 273 | 274 | func (g *ExpandListLayout) Margin() int { 275 | return g.margin 276 | } 277 | 278 | func (g *ExpandListLayout) SetMargin(m int) { 279 | g.margin = m 280 | } 281 | 282 | func (g *ExpandListLayout) ColSpacing() int { 283 | return g.spacing[0] 284 | } 285 | 286 | func (g *ExpandListLayout) RowSpacing() int { 287 | return g.spacing[1] 288 | } 289 | 290 | func (g *ExpandListLayout) SetColSpacing(s int) { 291 | g.spacing[0] = s 292 | } 293 | 294 | func (g *ExpandListLayout) SetRowSpacing(s int) { 295 | g.spacing[1] = s 296 | } 297 | 298 | func (g *ExpandListLayout) ColumnWidths() []int { 299 | return g.widths 300 | } 301 | 302 | func (g *ExpandListLayout) SeColumnWidths(lengths []int) { 303 | g.widths = lengths 304 | } 305 | 306 | func (g *ExpandListLayout) SetStretches(stretches []float32) { 307 | g.stretches = stretches 308 | } 309 | 310 | func (g *ExpandListLayout) Stretches() []float32 { 311 | return g.stretches 312 | } 313 | 314 | func (g *ExpandListLayout) ExpandPolicy(axis int) ExpandPolicy { 315 | return g.expandPolicy[axis] 316 | } 317 | 318 | func (g *ExpandListLayout) SetExpandPolicy(axis int, policy ExpandPolicy) { 319 | g.expandPolicy[axis] = policy 320 | } 321 | 322 | func (g *ExpandListLayout) OnPerformLayout(widget nanogui.Widget, ctx *nanovgo.Context) { 323 | widths, heights, _, _ := g.computeSize(widget, ctx) 324 | 325 | nCols := len(g.widths) 326 | 327 | xOffset := g.margin 328 | yOffset := g.margin 329 | window, ok := widget.(*nanogui.Window) 330 | if ok && window.Title() != "" { 331 | yOffset += widget.Theme().WindowHeaderHeight - g.margin/2 332 | } 333 | 334 | row := 0 335 | for i, child := range widget.Children() { 336 | column := i % nCols 337 | width := widths[column] 338 | height := heights[row] 339 | pw, ph := child.PreferredSize(child, ctx) 340 | childXOffset, childWidth := alignment(g.alignments[0], width, pw) 341 | childYOffset, childHeight := alignment(g.alignments[1], height, ph) 342 | child.SetPosition(xOffset+childXOffset, yOffset+childYOffset) 343 | child.SetSize(childWidth, childHeight) 344 | 345 | if column+1 == nCols { 346 | yOffset += g.spacing[1] + height 347 | xOffset = g.margin 348 | row++ 349 | } else { 350 | xOffset += g.spacing[0] + width 351 | } 352 | child.OnPerformLayout(child, ctx) 353 | } 354 | } 355 | 356 | func (g *ExpandListLayout) PreferredSize(widget nanogui.Widget, ctx *nanovgo.Context) (int, int) { 357 | _, _, totalWidth, totalHeight := g.computeSize(widget, ctx) 358 | return totalWidth, totalHeight 359 | } 360 | 361 | func (g *ExpandListLayout) computeSize(widget nanogui.Widget, ctx *nanovgo.Context) (widths, heights []int, totalWidth, totalHeight int) { 362 | if widget.ChildCount() == 0 { 363 | return nil, nil, 0, 0 364 | } 365 | nCols := len(g.widths) 366 | var stretches []float32 367 | if len(g.stretches) < nCols { 368 | stretches = append(append([]float32{}, g.stretches...), make([]float32, nCols-len(g.stretches))...) 369 | } 370 | isEmpty := true 371 | for _, stretch := range stretches { 372 | if stretch > 0 { 373 | isEmpty = false 374 | break 375 | } 376 | } 377 | if isEmpty { 378 | if g.expandPolicy[0] == ExpandLast { 379 | stretches[nCols-1] = 100.0 380 | } else { 381 | for i := 0; i < nCols; i++ { 382 | stretches[i] = 1.0 383 | } 384 | } 385 | } 386 | 387 | widths = make([]int, nCols) 388 | totalWidth = 2*g.margin + (nCols-1)*g.spacing[0] 389 | var totalStretch float32 390 | for i, columnWidth := range g.widths { 391 | totalWidth += columnWidth 392 | totalStretch += stretches[i] 393 | } 394 | tableWidth := widget.Width() 395 | remainedWidth := tableWidth - totalWidth 396 | if remainedWidth < 0 { 397 | remainedWidth = 0 398 | } else { 399 | totalWidth = tableWidth 400 | } 401 | nRows := (widget.ChildCount() + nCols - 1) / nCols 402 | for i, columnWidth := range g.widths { 403 | widths[i] = columnWidth + int(float32(remainedWidth)*stretches[i]/totalStretch) 404 | } 405 | totalHeight = 2*g.margin + (nRows-1)*g.spacing[1] 406 | window, ok := widget.(*nanogui.Window) 407 | if ok && window.Title() != "" { 408 | totalHeight += widget.Theme().WindowHeaderHeight - g.margin/2 409 | } 410 | maxRowHeight := 0 411 | heights = make([]int, nRows) 412 | row := 0 413 | for i, child := range widget.Children() { 414 | column := i % nCols 415 | if fWidget, ok := child.(FlexibleWidget); ok { 416 | fWidget.SetColumnWidth(widths[column]) 417 | } 418 | _, h := child.PreferredSize(child, ctx) 419 | if h > maxRowHeight { 420 | maxRowHeight = h 421 | } 422 | if column+1 == nCols || i+1 == widget.ChildCount() { 423 | heights[row] = maxRowHeight 424 | totalHeight += maxRowHeight 425 | maxRowHeight = 0 426 | row++ 427 | } 428 | } 429 | return 430 | } 431 | 432 | func (g *ExpandListLayout) String() string { 433 | return fmt.Sprintf("ExpandListLayout[%d]", len(g.widths)) 434 | } 435 | 436 | func alignment(align nanogui.Alignment, space, preferredSize int) (offset, size int) { 437 | if space < preferredSize { 438 | align = nanogui.Fill 439 | } 440 | switch align { 441 | case nanogui.Middle: 442 | offset = (space - preferredSize) / 2 443 | size = preferredSize 444 | case nanogui.Minimum: 445 | offset = 0 446 | size = preferredSize 447 | case nanogui.Maximum: 448 | offset = space - preferredSize 449 | size = preferredSize 450 | case nanogui.Fill: 451 | offset = 0 452 | size = space 453 | } 454 | return 455 | } 456 | -------------------------------------------------------------------------------- /nanoguiext/spinner.go: -------------------------------------------------------------------------------- 1 | package nanoguiext 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/glfw" 6 | "github.com/shibukawa/nanogui.go" 7 | "github.com/shibukawa/nanovgo" 8 | "math" 9 | "runtime" 10 | ) 11 | 12 | type SpinnerState int 13 | 14 | const ( 15 | SpinnerStop SpinnerState = iota 16 | SpinnerFadeIn 17 | SpinnerFadeOut 18 | ) 19 | 20 | func finalizeSpinner(spinner *Spinner) { 21 | if spinner.filter != nil { 22 | parent := spinner.filter.Parent() 23 | parent.RemoveChild(spinner.filter) 24 | spinner.filter = nil 25 | } 26 | } 27 | 28 | type Spinner struct { 29 | nanogui.WidgetImplement 30 | filter *SpinnerFilter 31 | } 32 | 33 | func NewSpinner(parent nanogui.Widget) *Spinner { 34 | spinner := &Spinner{} 35 | nanogui.InitWidget(spinner, parent) 36 | spinner.SetVisible(false) 37 | 38 | screen, ok := parent.(*nanogui.Screen) 39 | if !ok { 40 | screen = parent.FindWindow().Parent().(*nanogui.Screen) 41 | } 42 | 43 | filter := &SpinnerFilter{ 44 | state: SpinnerStop, 45 | c1: 18, 46 | c2: 24, 47 | num: 25, 48 | speed: 1, 49 | lineWidth: 3.0, 50 | } 51 | nanogui.InitWidget(filter, screen) 52 | spinner.filter = filter 53 | filter.SetVisible(false) 54 | runtime.SetFinalizer(spinner, finalizeSpinner) 55 | 56 | return spinner 57 | } 58 | 59 | func (s *Spinner) SetActive(flag bool) { 60 | if flag == s.Active() { 61 | return 62 | } 63 | s.filter.startTime = nanogui.GetTime() 64 | if flag { 65 | s.filter.state = SpinnerFadeIn 66 | s.filter.RequestFocus(s.filter) 67 | s.filter.SetVisible(true) 68 | } else { 69 | s.filter.state = SpinnerFadeOut 70 | } 71 | } 72 | 73 | func (s *Spinner) Active() bool { 74 | return s.filter.isActive() 75 | } 76 | 77 | func (s *Spinner) SetRadius(c1, c2 int) { 78 | s.filter.c1 = float32(c1) 79 | s.filter.c2 = float32(c2) 80 | } 81 | 82 | func (s *Spinner) Radius() (int, int) { 83 | return int(s.filter.c1), int(s.filter.c2) 84 | } 85 | 86 | func (s *Spinner) SetLineCount(num int) { 87 | s.filter.num = num 88 | } 89 | 90 | func (s *Spinner) LineCount() int { 91 | return s.filter.num 92 | } 93 | 94 | func (s *Spinner) SetSpeed(speed float32) { 95 | s.filter.speed = speed 96 | } 97 | 98 | func (s *Spinner) Speed() float32 { 99 | return s.filter.speed 100 | } 101 | 102 | func (s *Spinner) SetLineWidth(lineWidth float32) { 103 | s.filter.lineWidth = lineWidth 104 | } 105 | 106 | func (s *Spinner) LineWidth() float32 { 107 | return s.filter.lineWidth 108 | } 109 | 110 | func (s *Spinner) OnPerformLayout(self nanogui.Widget, ctx *nanovgo.Context) { 111 | s.filter.SetPosition(s.Parent().AbsolutePosition()) 112 | } 113 | 114 | func (s *Spinner) PreferredSize(self nanogui.Widget, ctx *nanovgo.Context) (int, int) { 115 | return 0, 0 116 | } 117 | 118 | func (s *Spinner) Draw(self nanogui.Widget, ctx *nanovgo.Context) { 119 | } 120 | 121 | func (s *Spinner) IsPositionAbsolute() bool { 122 | return true 123 | } 124 | 125 | func (s *Spinner) String() string { 126 | return fmt.Sprintf("Spinner") 127 | } 128 | 129 | type SpinnerFilter struct { 130 | nanogui.WidgetImplement 131 | startTime float32 132 | state SpinnerState 133 | c1, c2 float32 134 | num int 135 | speed float32 136 | lineWidth float32 137 | } 138 | 139 | func (sf *SpinnerFilter) isActive() bool { 140 | currentTime := nanogui.GetTime() - sf.startTime 141 | return sf.state == SpinnerFadeIn || (sf.state == SpinnerFadeOut && currentTime < 1.0) 142 | } 143 | 144 | func (sf *SpinnerFilter) IsPositionAbsolute() bool { 145 | return true 146 | } 147 | 148 | func (sf *SpinnerFilter) PreferredSize(self nanogui.Widget, ctx *nanovgo.Context) (int, int) { 149 | if sf.isActive() { 150 | fw, fh := sf.Parent().Size() 151 | if window, ok := sf.Parent().(*nanogui.Window); ok { 152 | hh := window.Theme().WindowHeaderHeight 153 | fh -= hh 154 | } 155 | return fw, fh 156 | } else { 157 | return 0, 0 158 | } 159 | } 160 | 161 | func (sf *SpinnerFilter) Draw(self nanogui.Widget, ctx *nanovgo.Context) { 162 | if sf.isActive() { 163 | var py int 164 | fw, fh := sf.Parent().Size() 165 | if window, ok := sf.Parent().(*nanogui.Window); ok { 166 | hh := window.Theme().WindowHeaderHeight 167 | py += hh 168 | fh -= hh 169 | } 170 | sf.SetPosition(0, py) 171 | sf.SetSize(fw, fh) 172 | 173 | currentTime := nanogui.GetTime() - sf.startTime 174 | 175 | var alpha float32 176 | var showSpinner bool 177 | if sf.state == SpinnerFadeIn { 178 | if currentTime > 1 { 179 | alpha = 0.7 180 | showSpinner = true 181 | } else { 182 | alpha = currentTime * 0.7 183 | } 184 | } else { 185 | if currentTime > 1 { 186 | alpha = 0.7 187 | } else { 188 | alpha = (1.0 - currentTime) * 0.7 189 | } 190 | } 191 | ctx.Save() 192 | ctx.BeginPath() 193 | ctx.SetFillColor(nanovgo.MONOf(0, alpha)) 194 | ctx.Rect(0, float32(py), float32(fw), float32(fh)) 195 | ctx.Fill() 196 | if showSpinner { 197 | cx := float32(fw / 2) 198 | cy := float32(py + fh/2) 199 | rotation := 2 * math.Pi * float64(currentTime*float32(sf.speed)*float32(sf.num)) / float64(sf.num) 200 | dr := float64(2 * math.Pi / float64(sf.num)) 201 | ctx.SetStrokeWidth(sf.lineWidth) 202 | for i := 0; i < sf.num; i++ { 203 | ctx.BeginPath() 204 | ctx.MoveTo(cx+float32(math.Cos(rotation))*sf.c1, cy+float32(math.Sin(rotation))*sf.c1) 205 | ctx.LineTo(cx+float32(math.Cos(rotation))*sf.c2, cy+float32(math.Sin(rotation))*sf.c2) 206 | ctx.SetStrokeColor(nanovgo.MONOf(1.0, float32(i)/float32(sf.num))) 207 | ctx.Stroke() 208 | rotation += dr 209 | } 210 | } 211 | ctx.Restore() 212 | } else { 213 | sf.SetSize(0, 0) 214 | sf.SetVisible(false) 215 | return 216 | } 217 | } 218 | 219 | func (sf *SpinnerFilter) MouseButtonEvent(self nanogui.Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 220 | return true 221 | } 222 | 223 | func (sf *SpinnerFilter) MouseMotionEvent(self nanogui.Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 224 | return true 225 | } 226 | 227 | func (sf *SpinnerFilter) MouseDragEvent(self nanogui.Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 228 | return true 229 | } 230 | 231 | func (sf *SpinnerFilter) MouseEnterEvent(self nanogui.Widget, x, y int, enter bool) bool { 232 | return true 233 | } 234 | 235 | func (sf *SpinnerFilter) ScrollEvent(self nanogui.Widget, x, y, relX, relY int) bool { 236 | return true 237 | } 238 | 239 | func (sf *SpinnerFilter) FocusEvent(self nanogui.Widget, f bool) bool { 240 | return true 241 | } 242 | 243 | func (sf *SpinnerFilter) KeyboardEvent(self nanogui.Widget, key glfw.Key, scanCode int, action glfw.Action, modifier glfw.ModifierKey) bool { 244 | return true 245 | } 246 | 247 | func (sf *SpinnerFilter) KeyboardCharacterEvent(self nanogui.Widget, codePoint rune) bool { 248 | return true 249 | } 250 | 251 | func (sf *SpinnerFilter) IMEPreeditEvent(self nanogui.Widget, text []rune, blocks []int, focusedBlock int) bool { 252 | return true 253 | } 254 | 255 | func (sf *SpinnerFilter) IMEStatusEvent(self nanogui.Widget) bool { 256 | return true 257 | } 258 | 259 | func (sf *SpinnerFilter) String() string { 260 | return sf.StringHelper("SpinnerFilter", "") 261 | } 262 | -------------------------------------------------------------------------------- /popup.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type Popup struct { 9 | Window 10 | parentWindow IWindow 11 | anchorX int 12 | anchorY int 13 | anchorHeight int 14 | vScroll *VScrollPanel 15 | panel Widget 16 | } 17 | 18 | func NewPopup(parent Widget, parentWindow IWindow) *Popup { 19 | popup := &Popup{ 20 | parentWindow: parentWindow, 21 | anchorHeight: 30, 22 | } 23 | InitWidget(popup, parent) 24 | popup.vScroll = NewVScrollPanel(popup) 25 | popup.panel = NewVScrollPanelChild(popup.vScroll) 26 | return popup 27 | } 28 | 29 | // SetAnchorPosition() sets the anchor position in the parent window; the placement of the popup is relative to it 30 | func (p *Popup) SetAnchorPosition(x, y int) { 31 | p.anchorX = x 32 | p.anchorY = y 33 | } 34 | 35 | // AnchorPosition() returns the anchor position in the parent window; the placement of the popup is relative to it 36 | func (p *Popup) AnchorPosition() (int, int) { 37 | return p.anchorX, p.anchorY 38 | } 39 | 40 | // SetAnchorHeight() sets the anchor height; this determines the vertical shift relative to the anchor position 41 | func (p *Popup) SetAnchorHeight(h int) { 42 | p.anchorHeight = h 43 | } 44 | 45 | // AnchorHeight() returns the anchor height; this determines the vertical shift relative to the anchor position 46 | func (p *Popup) AnchorHeight() int { 47 | return p.anchorHeight 48 | } 49 | 50 | // SetParentWindow() sets the parent window of the popup 51 | func (p *Popup) SetParentWindow(w *Window) { 52 | p.parentWindow = w 53 | } 54 | 55 | // ParentWindow() returns the parent window of the popup 56 | func (p *Popup) ParentWindow() IWindow { 57 | return p.parentWindow 58 | } 59 | 60 | func (p *Popup) OnPerformLayout(self Widget, ctx *nanovgo.Context) { 61 | if p.layout != nil || len(p.children) != 1 { 62 | p.WidgetImplement.OnPerformLayout(self, ctx) 63 | } else { 64 | p.children[0].SetPosition(0, 0) 65 | p.children[0].SetSize(p.w, p.h) 66 | p.children[0].OnPerformLayout(p.children[0], ctx) 67 | } 68 | } 69 | 70 | func (p *Popup) IsPositionAbsolute() bool { 71 | return true 72 | } 73 | 74 | func (p *Popup) Draw(self Widget, ctx *nanovgo.Context) { 75 | p.RefreshRelativePlacement() 76 | 77 | if !p.visible { 78 | return 79 | } 80 | ds := float32(p.theme.WindowDropShadowSize) 81 | cr := float32(p.theme.WindowCornerRadius) 82 | 83 | px := float32(p.x) 84 | py := float32(p.y) 85 | pw := float32(p.w) 86 | ph := float32(p.h) 87 | ah := float32(p.anchorHeight) 88 | 89 | /* Draw a drop shadow */ 90 | shadowPaint := nanovgo.BoxGradient(px, py, pw, ph, cr*2, ds*2, p.theme.DropShadow, p.theme.Transparent) 91 | ctx.BeginPath() 92 | ctx.Rect(px-ds, py-ds, pw+ds*2, ph+ds*2) 93 | ctx.RoundedRect(px, py, pw, ph, cr) 94 | ctx.PathWinding(nanovgo.Hole) 95 | ctx.SetFillPaint(shadowPaint) 96 | ctx.Fill() 97 | 98 | /* Draw window */ 99 | ctx.BeginPath() 100 | ctx.RoundedRect(px, py, pw, ph, cr) 101 | 102 | ctx.MoveTo(px-15, py+ah) 103 | ctx.LineTo(px+1, py+ah-15) 104 | ctx.LineTo(px+1, py+ah+15) 105 | 106 | ctx.SetFillColor(p.theme.WindowPopup) 107 | 108 | ctx.Fill() 109 | 110 | p.WidgetImplement.Draw(self, ctx) 111 | } 112 | 113 | // RefreshRelativePlacement is internal helper function to maintain nested window position values; overridden in \ref Popup 114 | func (p *Popup) RefreshRelativePlacement() { 115 | p.parentWindow.RefreshRelativePlacement() 116 | p.visible = p.visible && p.parentWindow.VisibleRecursive() 117 | x, y := p.parentWindow.Position() 118 | p.x = x + p.anchorX 119 | p.y = y + p.anchorY - p.anchorHeight 120 | } 121 | 122 | func (p *Popup) FindWindow() IWindow { 123 | return p 124 | } 125 | 126 | func (p *Popup) String() string { 127 | return p.StringHelper(fmt.Sprintf("Popup(%d)", p.Depth()), "") 128 | } 129 | -------------------------------------------------------------------------------- /popupbutton.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | "runtime" 6 | ) 7 | 8 | type PopupButton struct { 9 | Button 10 | chevronIcon Icon 11 | popup *Popup 12 | } 13 | 14 | func finalizePopupButton(button *PopupButton) { 15 | if button.popup != nil { 16 | parent := button.popup.Parent() 17 | parent.RemoveChild(button.popup) 18 | button.popup = nil 19 | } 20 | } 21 | 22 | func NewPopupButton(parent Widget, captions ...string) *PopupButton { 23 | var caption string 24 | switch len(captions) { 25 | case 0: 26 | caption = "Untitled" 27 | case 1: 28 | caption = captions[0] 29 | default: 30 | panic("NewPopupButton can accept only one extra parameter (caption)") 31 | } 32 | 33 | button := &PopupButton{ 34 | chevronIcon: IconRightOpen, 35 | } 36 | button.SetCaption(caption) 37 | button.SetIconPosition(ButtonIconLeftCentered) 38 | button.SetFlags(ToggleButtonType | PopupButtonType) 39 | 40 | parentWindow := parent.FindWindow() 41 | button.popup = NewPopup(parentWindow.Parent(), parentWindow) 42 | button.popup.SetSize(320, 250) 43 | 44 | InitWidget(button, parent) 45 | 46 | runtime.SetFinalizer(button, finalizePopupButton) 47 | 48 | return button 49 | } 50 | 51 | func (p *PopupButton) ChevronIcon() Icon { 52 | return p.chevronIcon 53 | } 54 | 55 | func (p *PopupButton) SetChevronIcon(icon Icon) { 56 | p.chevronIcon = icon 57 | } 58 | 59 | func (p *PopupButton) Popup() Widget { 60 | return p.popup.panel 61 | } 62 | 63 | func (p *PopupButton) Draw(self Widget, ctx *nanovgo.Context) { 64 | if !p.enabled && p.pushed { 65 | p.pushed = false 66 | } 67 | p.popup.SetVisible(p.pushed) 68 | p.Button.Draw(self, ctx) 69 | if p.chevronIcon != 0 { 70 | ctx.SetFillColor(p.TextColor()) 71 | ctx.SetFontSize(float32(p.FontSize())) 72 | ctx.SetFontFace(p.theme.FontIcons) 73 | ctx.SetTextAlign(nanovgo.AlignMiddle | nanovgo.AlignLeft) 74 | fontString := string([]rune{rune(p.chevronIcon)}) 75 | iw, _ := ctx.TextBounds(0, 0, fontString) 76 | px, py := p.Position() 77 | w, h := p.Size() 78 | ix := px + w - int(iw) - 8 79 | iy := py + h/2 - 1 80 | ctx.Text(float32(ix), float32(iy), fontString) 81 | } 82 | } 83 | 84 | func (p *PopupButton) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 85 | w, h := p.Button.PreferredSize(self, ctx) 86 | return w + 15, h 87 | } 88 | 89 | func (p *PopupButton) OnPerformLayout(self Widget, ctx *nanovgo.Context) { 90 | p.Button.WidgetImplement.OnPerformLayout(self, ctx) 91 | parentWindow := self.FindWindow() 92 | x := parentWindow.Width() + 15 93 | _, ay := p.AbsolutePosition() 94 | _, py := parentWindow.Position() 95 | y := ay - py + p.Height()/2 96 | p.popup.SetAnchorPosition(x, y) 97 | } 98 | 99 | func (p *PopupButton) String() string { 100 | return p.StringHelper("PopupButton", p.caption) 101 | } 102 | -------------------------------------------------------------------------------- /progressbar.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanovgo" 6 | ) 7 | 8 | type ProgressBar struct { 9 | WidgetImplement 10 | 11 | value float32 12 | } 13 | 14 | func NewProgressBar(parent Widget) *ProgressBar { 15 | progressBar := &ProgressBar{} 16 | InitWidget(progressBar, parent) 17 | return progressBar 18 | } 19 | 20 | func (p *ProgressBar) Value() float32 { 21 | return p.value 22 | } 23 | 24 | func (p *ProgressBar) SetValue(value float32) { 25 | p.value = value 26 | } 27 | 28 | func (p *ProgressBar) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 29 | return 70, 12 30 | } 31 | 32 | func (p *ProgressBar) Draw(self Widget, ctx *nanovgo.Context) { 33 | px := float32(p.x) 34 | py := float32(p.y) 35 | pw := float32(p.w) 36 | ph := float32(p.h) 37 | p.WidgetImplement.Draw(self, ctx) 38 | paint := nanovgo.BoxGradient(px+1, py+1, pw-2, ph, 3, 4, nanovgo.MONO(0, 32), nanovgo.MONO(0, 92)) 39 | ctx.BeginPath() 40 | ctx.RoundedRect(px, py, pw, ph, 3) 41 | ctx.SetFillPaint(paint) 42 | ctx.Fill() 43 | 44 | value := clampF(p.value, 0.0, 1.0) 45 | barPos := (pw - 2) * value 46 | barPaint := nanovgo.BoxGradient(px, py, barPos+1.5, ph-1, 3, 4, nanovgo.MONO(220, 100), nanovgo.MONO(128, 100)) 47 | ctx.BeginPath() 48 | ctx.RoundedRect(px+1, py+1, barPos, ph-2, 3) 49 | ctx.SetFillPaint(barPaint) 50 | ctx.Fill() 51 | } 52 | 53 | func (p *ProgressBar) String() string { 54 | return p.StringHelper("ProgressBar", fmt.Sprintf("%f", p.value)) 55 | } 56 | -------------------------------------------------------------------------------- /sample1/demo/demo.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanogui.go" 6 | "github.com/shibukawa/nanovgo" 7 | "math" 8 | "strconv" 9 | ) 10 | 11 | func ButtonDemo(screen *nanogui.Screen) { 12 | window := nanogui.NewWindow(screen, "Button demo") 13 | window.SetPosition(15, 15) 14 | window.SetLayout(nanogui.NewGroupLayout()) 15 | 16 | nanogui.NewLabel(window, "Push buttons").SetFont("sans-bold") 17 | 18 | b1 := nanogui.NewButton(window, "Plain button") 19 | b1.SetCallback(func() { 20 | fmt.Println("pushed!") 21 | }) 22 | 23 | b2 := nanogui.NewButton(window, "Styled") 24 | b2.SetBackgroundColor(nanovgo.RGBA(0, 0, 255, 25)) 25 | b2.SetIcon(nanogui.IconRocket) 26 | b2.SetCallback(func() { 27 | fmt.Println("pushed!") 28 | }) 29 | 30 | nanogui.NewLabel(window, "Toggle button").SetFont("sans-bold") 31 | b3 := nanogui.NewButton(window, "Toggle me") 32 | b3.SetFlags(nanogui.ToggleButtonType) 33 | b3.SetChangeCallback(func(state bool) { 34 | fmt.Println("Toggle button state:", state) 35 | }) 36 | 37 | nanogui.NewLabel(window, "Radio buttons").SetFont("sans-bold") 38 | b4 := nanogui.NewButton(window, "Radio button 1") 39 | b4.SetFlags(nanogui.RadioButtonType) 40 | b5 := nanogui.NewButton(window, "Radio button 2") 41 | b5.SetFlags(nanogui.RadioButtonType) 42 | 43 | nanogui.NewLabel(window, "A tool palette").SetFont("sans-bold") 44 | tools := nanogui.NewWidget(window) 45 | tools.SetLayout(nanogui.NewBoxLayout(nanogui.Horizontal, nanogui.Middle, 0, 6)) 46 | 47 | nanogui.NewToolButton(tools, nanogui.IconCloud) 48 | nanogui.NewToolButton(tools, nanogui.IconFastForward) 49 | nanogui.NewToolButton(tools, nanogui.IconCompass) 50 | nanogui.NewToolButton(tools, nanogui.IconInstall) 51 | 52 | nanogui.NewLabel(window, "Popup buttons").SetFont("sans-bold") 53 | b6 := nanogui.NewPopupButton(window, "Popup") 54 | b6.SetIcon(nanogui.IconExport) 55 | popup := b6.Popup() 56 | popup.SetLayout(nanogui.NewGroupLayout()) 57 | 58 | nanogui.NewLabel(popup, "Arbitrary widgets can be placed here").SetFont("sans-bold") 59 | nanogui.NewCheckBox(popup, "A check box") 60 | b7 := nanogui.NewPopupButton(popup, "Recursive popup") 61 | b7.SetIcon(nanogui.IconFlash) 62 | popup2 := b7.Popup() 63 | 64 | popup2.SetLayout(nanogui.NewGroupLayout()) 65 | nanogui.NewCheckBox(popup2, "Another check box") 66 | } 67 | 68 | func BasicWidgetsDemo(screen *nanogui.Screen, images []nanogui.Image) (*nanogui.PopupButton, *nanogui.ImagePanel, *nanogui.ProgressBar) { 69 | window := nanogui.NewWindow(screen, "Basic widgets") 70 | window.SetPosition(230, 15) 71 | window.SetLayout(nanogui.NewGroupLayout()) 72 | 73 | nanogui.NewLabel(window, "Message dialog").SetFont("sans-bold") 74 | 75 | tools := nanogui.NewWidget(window) 76 | tools.SetLayout(nanogui.NewBoxLayout(nanogui.Horizontal, nanogui.Middle, 0, 6)) 77 | 78 | b1 := nanogui.NewButton(tools, "Info") 79 | b1.SetCallback(func() { 80 | 81 | }) 82 | b2 := nanogui.NewButton(tools, "Warn") 83 | b2.SetCallback(func() { 84 | 85 | }) 86 | b3 := nanogui.NewButton(tools, "Ask") 87 | b3.SetCallback(func() { 88 | 89 | }) 90 | 91 | nanogui.NewLabel(window, "Image panel & scroll panel").SetFont("sans-bold") 92 | imagePanelButton := nanogui.NewPopupButton(window, "Image Panel") 93 | imagePanelButton.SetIcon(nanogui.IconFolder) 94 | popup := imagePanelButton.Popup() 95 | imgPanel := nanogui.NewImagePanel(popup) 96 | imgPanel.SetImages(images) 97 | popup.SetFixedSize(245, 150) 98 | 99 | nanogui.NewLabel(window, "File dialog").SetFont("sans-bold") 100 | 101 | tools2 := nanogui.NewWidget(window) 102 | tools2.SetLayout(nanogui.NewBoxLayout(nanogui.Horizontal, nanogui.Middle, 0, 6)) 103 | 104 | b4 := nanogui.NewButton(tools2, "Open") 105 | b4.SetCallback(func() { 106 | 107 | }) 108 | b5 := nanogui.NewButton(tools2, "Save") 109 | b5.SetCallback(func() { 110 | 111 | }) 112 | 113 | nanogui.NewLabel(window, "Combo box").SetFont("sans-bold") 114 | nanogui.NewComboBox(window, []string{"Combo box item 1", "Combo box item 2", "Combo box item 3"}) 115 | 116 | nanogui.NewLabel(window, "Check box").SetFont("sans-bold") 117 | cb1 := nanogui.NewCheckBox(window, "Flag 1") 118 | cb1.SetCallback(func(checked bool) { 119 | fmt.Println("Check box 1 state:", checked) 120 | }) 121 | cb1.SetChecked(true) 122 | 123 | cb2 := nanogui.NewCheckBox(window, "Flag 2") 124 | cb2.SetCallback(func(checked bool) { 125 | fmt.Println("Check box 2 state:", checked) 126 | }) 127 | nanogui.NewLabel(window, "Progress bar").SetFont("sans-bold") 128 | progress := nanogui.NewProgressBar(window) 129 | 130 | nanogui.NewLabel(window, "Slider and text box").SetFont("sans-bold") 131 | panel := nanogui.NewWidget(window) 132 | panel.SetLayout(nanogui.NewBoxLayout(nanogui.Horizontal, nanogui.Middle, 0, 20)) 133 | slider := nanogui.NewSlider(panel) 134 | slider.SetValue(0.5) 135 | slider.SetFixedWidth(80) 136 | 137 | textBox := nanogui.NewTextBox(panel) 138 | textBox.SetFixedSize(60, 25) 139 | textBox.SetFontSize(20) 140 | textBox.SetAlignment(nanogui.TextRight) 141 | textBox.SetValue("50") 142 | textBox.SetUnits("%") 143 | 144 | slider.SetCallback(func(value float32) { 145 | textBox.SetValue(strconv.FormatInt(int64(value*100), 10)) 146 | }) 147 | slider.SetFinalCallback(func(value float32) { 148 | fmt.Printf("Final slider value: %d\n", int(value*100)) 149 | }) 150 | 151 | return imagePanelButton, imgPanel, progress 152 | } 153 | 154 | func MiscWidgetsDemo(screen *nanogui.Screen) { 155 | window := nanogui.NewWindow(screen, "Misc. widgets") 156 | window.SetPosition(445, 15) 157 | window.SetLayout(nanogui.NewGroupLayout()) 158 | 159 | nanogui.NewLabel(window, "Color wheel").SetFont("sans-bold") 160 | nanogui.NewColorWheel(window) 161 | 162 | nanogui.NewLabel(window, "Color picker").SetFont("sans-bold") 163 | nanogui.NewColorPicker(window) 164 | 165 | nanogui.NewLabel(window, "Function graph").SetFont("sans-bold") 166 | graph := nanogui.NewGraph(window, "Some function") 167 | graph.SetHeader("E = 2.35e-3") 168 | graph.SetFooter("Iteration 89") 169 | fValues := make([]float32, 100) 170 | for i := 0; i < 100; i++ { 171 | x := float64(i) 172 | fValues[i] = 0.5 * float32(0.5*math.Sin(x/10.0)+0.5*math.Cos(x/23.0)+1.0) 173 | } 174 | graph.SetValues(fValues) 175 | } 176 | 177 | func GridDemo(screen *nanogui.Screen) { 178 | window := nanogui.NewWindow(screen, "Grid of small widgets") 179 | window.SetPosition(445, 358) 180 | layout := nanogui.NewGridLayout(nanogui.Horizontal, 2, nanogui.Middle, 15, 5) 181 | layout.SetColAlignment(nanogui.Maximum, nanogui.Fill) 182 | layout.SetColSpacing(10) 183 | window.SetLayout(layout) 184 | 185 | { 186 | nanogui.NewLabel(window, "Regular text :").SetFont("sans-bold") 187 | textBox := nanogui.NewTextBox(window, "日本語") 188 | textBox.SetFont("japanese") 189 | textBox.SetEditable(true) 190 | textBox.SetFixedSize(100, 20) 191 | textBox.SetDefaultValue("0.0") 192 | textBox.SetFontSize(16) 193 | } 194 | { 195 | nanogui.NewLabel(window, "Floating point :").SetFont("sans-bold") 196 | textBox := nanogui.NewTextBox(window, "50.0") 197 | textBox.SetEditable(true) 198 | textBox.SetFixedSize(100, 20) 199 | textBox.SetUnits("GiB") 200 | textBox.SetDefaultValue("0.0") 201 | textBox.SetFontSize(16) 202 | textBox.SetFormat(`^[-]?[0-9]*\.?[0-9]+$`) 203 | } 204 | { 205 | nanogui.NewLabel(window, "Positive integer :").SetFont("sans-bold") 206 | textBox := nanogui.NewTextBox(window, "50") 207 | textBox.SetEditable(true) 208 | textBox.SetFixedSize(100, 20) 209 | textBox.SetUnits("MHz") 210 | textBox.SetDefaultValue("0.0") 211 | textBox.SetFontSize(16) 212 | textBox.SetFormat(`^[1-9][0-9]*$`) 213 | } 214 | { 215 | nanogui.NewLabel(window, "Float box :").SetFont("sans-bold") 216 | floatBox := nanogui.NewFloatBox(window, 10.0) 217 | floatBox.SetEditable(true) 218 | floatBox.SetFixedSize(100, 20) 219 | floatBox.SetUnits("GiB") 220 | floatBox.SetDefaultValue(0.0) 221 | floatBox.SetFontSize(16) 222 | } 223 | { 224 | nanogui.NewLabel(window, "Int box :").SetFont("sans-bold") 225 | intBox := nanogui.NewIntBox(window, true, 50) 226 | intBox.SetEditable(true) 227 | intBox.SetFixedSize(100, 20) 228 | intBox.SetUnits("MHz") 229 | intBox.SetDefaultValue(0) 230 | intBox.SetFontSize(16) 231 | } 232 | { 233 | nanogui.NewLabel(window, "Checkbox :").SetFont("sans-bold") 234 | checkbox := nanogui.NewCheckBox(window, "Check me") 235 | checkbox.SetFontSize(16) 236 | checkbox.SetChecked(true) 237 | } 238 | { 239 | nanogui.NewLabel(window, "Combobox :").SetFont("sans-bold") 240 | combobox := nanogui.NewComboBox(window, []string{"Item 1", "Item 2", "Item 3"}) 241 | combobox.SetFontSize(16) 242 | combobox.SetFixedSize(100, 20) 243 | } 244 | { 245 | nanogui.NewLabel(window, "Color button :").SetFont("sans-bold") 246 | 247 | popupButton := nanogui.NewPopupButton(window, "") 248 | popupButton.SetBackgroundColor(nanovgo.RGBA(255, 120, 0, 255)) 249 | popupButton.SetFontSize(16) 250 | popupButton.SetFixedSize(100, 20) 251 | popup := popupButton.Popup() 252 | popup.SetLayout(nanogui.NewGroupLayout()) 253 | 254 | colorWheel := nanogui.NewColorWheel(popup) 255 | colorWheel.SetColor(popupButton.BackgroundColor()) 256 | 257 | colorButton := nanogui.NewButton(popup, "Pick") 258 | colorButton.SetFixedSize(100, 25) 259 | colorButton.SetBackgroundColor(colorWheel.Color()) 260 | 261 | colorWheel.SetCallback(func(color nanovgo.Color) { 262 | colorButton.SetBackgroundColor(color) 263 | }) 264 | 265 | colorButton.SetChangeCallback(func(pushed bool) { 266 | if pushed { 267 | popupButton.SetBackgroundColor(colorButton.BackgroundColor()) 268 | popupButton.SetPushed(false) 269 | } 270 | }) 271 | } 272 | } 273 | 274 | func SelectedImageDemo(screen *nanogui.Screen, imageButton *nanogui.PopupButton, imagePanel *nanogui.ImagePanel) { 275 | window := nanogui.NewWindow(screen, "Selected image") 276 | window.SetPosition(685, 15) 277 | window.SetLayout(nanogui.NewGroupLayout()) 278 | 279 | img := nanogui.NewImageView(window) 280 | img.SetPolicy(nanogui.ImageSizePolicyExpand) 281 | img.SetFixedSize(300, 300) 282 | img.SetImage(imagePanel.Images()[0].ImageID) 283 | 284 | imagePanel.SetCallback(func(index int) { 285 | img.SetImage(imagePanel.Images()[index].ImageID) 286 | }) 287 | 288 | cb := nanogui.NewCheckBox(window, "Expand") 289 | cb.SetCallback(func(checked bool) { 290 | if checked { 291 | img.SetPolicy(nanogui.ImageSizePolicyExpand) 292 | } else { 293 | img.SetPolicy(nanogui.ImageSizePolicyFixed) 294 | } 295 | }) 296 | cb.SetChecked(true) 297 | } 298 | -------------------------------------------------------------------------------- /sample1/font/GenShinGothic-P-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/font/GenShinGothic-P-Regular.ttf -------------------------------------------------------------------------------- /sample1/icons/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon1.png -------------------------------------------------------------------------------- /sample1/icons/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon2.png -------------------------------------------------------------------------------- /sample1/icons/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon3.png -------------------------------------------------------------------------------- /sample1/icons/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon4.png -------------------------------------------------------------------------------- /sample1/icons/icon5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon5.png -------------------------------------------------------------------------------- /sample1/icons/icon6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon6.png -------------------------------------------------------------------------------- /sample1/icons/icon7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon7.png -------------------------------------------------------------------------------- /sample1/icons/icon8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/gui4go/057970537e4802445246fd822d6947f38bb656a4/sample1/icons/icon8.png -------------------------------------------------------------------------------- /sample1/sample.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/shibukawa/glfw" 8 | "github.com/shibukawa/nanogui.go" 9 | "github.com/shibukawa/nanogui.go/sample1/demo" 10 | "github.com/shibukawa/nanovgo" 11 | "io/ioutil" 12 | "math" 13 | "path" 14 | ) 15 | 16 | type Application struct { 17 | screen *nanogui.Screen 18 | progress *nanogui.ProgressBar 19 | shader *nanogui.GLShader 20 | } 21 | 22 | func (a *Application) init() { 23 | glfw.WindowHint(glfw.Samples, 4) 24 | a.screen = nanogui.NewScreen(1024, 768, "NanoGUI.Go Test", true, false) 25 | 26 | a.screen.NVGContext().CreateFont("japanese", "font/GenShinGothic-P-Regular.ttf") 27 | 28 | demo.ButtonDemo(a.screen) 29 | images := loadImageDirectory(a.screen.NVGContext(), "icons") 30 | imageButton, imagePanel, progressBar := demo.BasicWidgetsDemo(a.screen, images) 31 | a.progress = progressBar 32 | demo.SelectedImageDemo(a.screen, imageButton, imagePanel) 33 | demo.MiscWidgetsDemo(a.screen) 34 | demo.GridDemo(a.screen) 35 | 36 | a.screen.SetDrawContentsCallback(func() { 37 | a.progress.SetValue(float32(math.Mod(float64(nanogui.GetTime())/10, 1.0))) 38 | }) 39 | 40 | a.screen.PerformLayout() 41 | a.screen.DebugPrint() 42 | 43 | /* All NanoGUI widgets are initialized at this point. Now 44 | create an OpenGL shader to draw the main window contents. 45 | 46 | NanoGUI comes with a simple Eigen-based wrapper around OpenGL 3, 47 | which eliminates most of the tedious and error-prone shader and 48 | buffer object management. 49 | */ 50 | } 51 | 52 | func main() { 53 | nanogui.Init() 54 | //nanogui.SetDebug(true) 55 | app := Application{} 56 | app.init() 57 | app.screen.DrawAll() 58 | app.screen.SetVisible(true) 59 | nanogui.MainLoop() 60 | } 61 | 62 | func loadImageDirectory(ctx *nanovgo.Context, dir string) []nanogui.Image { 63 | var images []nanogui.Image 64 | files, err := ioutil.ReadDir(dir) 65 | if err != nil { 66 | panic(fmt.Sprintf("loadImageDirectory: read error %v\n", err)) 67 | } 68 | for _, file := range files { 69 | if file.IsDir() { 70 | continue 71 | } 72 | ext := path.Ext(file.Name()) 73 | if ext != ".png" { 74 | continue 75 | } 76 | fullPath := path.Join(dir, file.Name()) 77 | img := ctx.CreateImage(fullPath, 0) 78 | if img == 0 { 79 | panic("Could not open image data!") 80 | } 81 | images = append(images, nanogui.Image{ 82 | ImageID: img, 83 | Name: fullPath[:len(fullPath)-4], 84 | }) 85 | } 86 | return images 87 | } 88 | -------------------------------------------------------------------------------- /sample1/web.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/shibukawa/glfw" 8 | "github.com/shibukawa/nanogui.go" 9 | "github.com/shibukawa/nanogui.go/sample1/demo" 10 | "github.com/shibukawa/nanovgo" 11 | "math" 12 | ) 13 | 14 | type Application struct { 15 | screen *nanogui.Screen 16 | progress *nanogui.ProgressBar 17 | shader *nanogui.GLShader 18 | } 19 | 20 | func (a *Application) init() { 21 | glfw.WindowHint(glfw.Samples, 4) 22 | a.screen = nanogui.NewScreen(1024, 768, "NanoGUI.Go Test", true, false) 23 | 24 | demo.ButtonDemo(a.screen) 25 | images := loadImageDirectory(a.screen.NVGContext(), "icons") 26 | imageButton, imagePanel, progressBar := demo.BasicWidgetsDemo(a.screen, images) 27 | a.progress = progressBar 28 | demo.SelectedImageDemo(a.screen, imageButton, imagePanel) 29 | demo.MiscWidgetsDemo(a.screen) 30 | demo.GridDemo(a.screen) 31 | 32 | a.screen.SetDrawContentsCallback(func() { 33 | a.progress.SetValue(float32(math.Mod(float64(nanogui.GetTime())/10, 1.0))) 34 | }) 35 | 36 | a.screen.DebugPrint() 37 | 38 | a.screen.PerformLayout() 39 | 40 | /* All NanoGUI widgets are initialized at this point. Now 41 | create an OpenGL shader to draw the main window contents. 42 | 43 | NanoGUI comes with a simple Eigen-based wrapper around OpenGL 3, 44 | which eliminates most of the tedious and error-prone shader and 45 | buffer object management. 46 | */ 47 | } 48 | 49 | func main() { 50 | nanogui.Init() 51 | //nanogui.SetDebug(true) 52 | app := Application{} 53 | app.init() 54 | app.screen.DrawAll() 55 | app.screen.SetVisible(true) 56 | nanogui.MainLoop() 57 | } 58 | 59 | func loadImageDirectory(ctx *nanovgo.Context, dir string) []nanogui.Image { 60 | var images []nanogui.Image 61 | files, err := AssetDir("icons") 62 | if err != nil { 63 | panic(err) 64 | } 65 | for _, file := range files { 66 | fullPath := fmt.Sprintf("%s/%s", "icons", file) 67 | img := ctx.CreateImageFromMemory(0, MustAsset(fullPath)) 68 | if img == 0 { 69 | panic("Could not open image data!") 70 | } 71 | images = append(images, nanogui.Image{ 72 | ImageID: img, 73 | Name: fullPath[:len(fullPath)-4], 74 | }) 75 | } 76 | return images 77 | } 78 | -------------------------------------------------------------------------------- /screen.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "bytes" 5 | "github.com/goxjs/gl" 6 | "github.com/shibukawa/glfw" 7 | "github.com/shibukawa/nanovgo" 8 | "runtime" 9 | ) 10 | 11 | var nanoguiScreens map[*glfw.Window]*Screen = map[*glfw.Window]*Screen{} 12 | 13 | type Screen struct { 14 | WidgetImplement 15 | window *glfw.Window 16 | context *nanovgo.Context 17 | cursors [3]int 18 | cursor Cursor 19 | focusPath []Widget 20 | fbW, fbH int 21 | pixelRatio float32 22 | modifiers glfw.ModifierKey 23 | mouseState int 24 | mousePosX, mousePosY int 25 | dragActive bool 26 | dragWidget Widget 27 | lastInteraction float32 28 | backgroundColor nanovgo.Color 29 | caption string 30 | shutdownGLFWOnDestruct bool 31 | 32 | drawContentsCallback func() 33 | dropEventCallback func([]string) bool 34 | resizeEventCallback func(x, y int) bool 35 | } 36 | 37 | func NewScreen(width, height int, caption string, resizable, fullScreen bool) *Screen { 38 | screen := &Screen{ 39 | //cursor: glfw.CursorNormal, 40 | caption: caption, 41 | } 42 | 43 | if runtime.GOARCH == "js" { 44 | glfw.WindowHint(glfw.Hint(0x00021101), 1) // enable stencil for nanovgo 45 | } 46 | glfw.WindowHint(glfw.Samples, 4) 47 | //glfw.WindowHint(glfw.RedBits, 8) 48 | //glfw.WindowHint(glfw.GreenBits, 8) 49 | //glfw.WindowHint(glfw.BlueBits, 8) 50 | glfw.WindowHint(glfw.AlphaBits, 8) 51 | //glfw.WindowHint(glfw.StencilBits, 8) 52 | //glfw.WindowHint(glfw.DepthBits, 8) 53 | //glfw.WindowHint(glfw.Visible, 0) 54 | if resizable { 55 | //glfw.WindowHint(glfw.Resizable, 1) 56 | } else { 57 | //glfw.WindowHint(glfw.Resizable, 0) 58 | } 59 | 60 | var err error 61 | if fullScreen { 62 | monitor := glfw.GetPrimaryMonitor() 63 | mode := monitor.GetVideoMode() 64 | screen.window, err = glfw.CreateWindow(mode.Width, mode.Height, caption, monitor, nil) 65 | } else { 66 | screen.window, err = glfw.CreateWindow(width, height, caption, nil, nil) 67 | } 68 | if err != nil { 69 | panic(err) 70 | } 71 | screen.window.MakeContextCurrent() 72 | gl.Viewport(0, 0, screen.fbW, screen.fbH) 73 | gl.ClearColor(0, 0, 0, 1) 74 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) 75 | glfw.SwapInterval(0) 76 | screen.window.SwapBuffers() 77 | 78 | /* Poll for events once before starting a potentially 79 | lengthy loading process. This is needed to be 80 | classified as "interactive" by other software such 81 | as iTerm2 */ 82 | 83 | if runtime.GOOS == "darwin" { 84 | glfw.PollEvents() 85 | } 86 | 87 | screen.window.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) { 88 | if screen, ok := nanoguiScreens[w]; ok { 89 | screen.cursorPositionCallbackEvent(xpos, ypos) 90 | } 91 | }) 92 | 93 | screen.window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 94 | if screen, ok := nanoguiScreens[w]; ok { 95 | screen.mouseButtonCallbackEvent(button, action, mods) 96 | } 97 | }) 98 | 99 | screen.window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scanCode int, action glfw.Action, mods glfw.ModifierKey) { 100 | if screen, ok := nanoguiScreens[w]; ok { 101 | screen.keyCallbackEvent(key, scanCode, action, mods) 102 | } 103 | }) 104 | 105 | screen.window.SetCharCallback(func(w *glfw.Window, char rune) { 106 | if screen, ok := nanoguiScreens[w]; ok { 107 | screen.charCallbackEvent(char) 108 | } 109 | }) 110 | 111 | screen.window.SetPreeditCallback(func(w *glfw.Window, text []rune, blocks []int, focusedBlock int) { 112 | if screen, ok := nanoguiScreens[w]; ok { 113 | screen.preeditCallbackEvent(text, blocks, focusedBlock) 114 | } 115 | }) 116 | 117 | screen.window.SetIMEStatusCallback(func(w *glfw.Window) { 118 | if screen, ok := nanoguiScreens[w]; ok { 119 | screen.imeStatusCallbackEvent() 120 | } 121 | }) 122 | 123 | screen.window.SetDropCallback(func(w *glfw.Window, names []string) { 124 | if screen, ok := nanoguiScreens[w]; ok { 125 | screen.dropCallbackEvent(names) 126 | } 127 | }) 128 | 129 | screen.window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 130 | if screen, ok := nanoguiScreens[w]; ok { 131 | screen.scrollCallbackEvent(float32(xoff), float32(yoff)) 132 | } 133 | }) 134 | 135 | screen.window.SetFramebufferSizeCallback(func(w *glfw.Window, width int, height int) { 136 | if screen, ok := nanoguiScreens[w]; ok { 137 | screen.resizeCallbackEvent(width, height) 138 | } 139 | }) 140 | 141 | screen.Initialize(screen.window, true) 142 | InitWidget(screen, nil) 143 | return screen 144 | } 145 | 146 | func finalizeScreen(s *Screen) { 147 | delete(nanoguiScreens, s.window) 148 | if s.context != nil { 149 | s.context.Delete() 150 | s.context = nil 151 | } 152 | if s.window != nil && s.shutdownGLFWOnDestruct { 153 | s.window.Destroy() 154 | s.window = nil 155 | } 156 | } 157 | 158 | func (s *Screen) Initialize(window *glfw.Window, shutdownGLFWOnDestruct bool) { 159 | s.window = window 160 | s.shutdownGLFWOnDestruct = shutdownGLFWOnDestruct 161 | s.w, s.h = window.GetSize() 162 | s.fbW, s.fbH = window.GetFramebufferSize() 163 | var err error 164 | s.context, err = nanovgo.NewContext(nanovgo.StencilStrokes | nanovgo.AntiAlias) 165 | if err != nil { 166 | panic(err) 167 | } 168 | s.visible = true //window.GetAttrib(glfw.Visible) 169 | s.theme = NewStandardTheme(s.context) 170 | s.mousePosX = 0 171 | s.mousePosY = 0 172 | s.mouseState = 0 173 | s.modifiers = 0 174 | s.dragActive = false 175 | s.lastInteraction = GetTime() 176 | nanoguiScreens[window] = s 177 | runtime.SetFinalizer(s, finalizeScreen) 178 | } 179 | 180 | // Caption() gets the window title bar caption 181 | func (s *Screen) Caption() string { 182 | return s.caption 183 | } 184 | 185 | // SetCaption() sets the window title bar caption 186 | func (s *Screen) SetCaption(caption string) { 187 | if s.caption != caption { 188 | s.window.SetTitle(caption) 189 | s.caption = caption 190 | } 191 | } 192 | 193 | // BackgroundColor() returns the screen's background color 194 | func (s *Screen) BackgroundColor() nanovgo.Color { 195 | return s.backgroundColor 196 | } 197 | 198 | // SetBackgroundColor() sets the screen's background color 199 | func (s *Screen) SetBackgroundColor(color nanovgo.Color) { 200 | s.backgroundColor = color 201 | s.backgroundColor.A = 1.0 202 | } 203 | 204 | // SetVisible() sets the top-level window visibility (no effect on full-screen windows) 205 | func (s *Screen) SetVisible(flag bool) { 206 | if s.visible != flag { 207 | s.visible = flag 208 | if flag { 209 | s.window.Show() 210 | } else { 211 | s.window.Hide() 212 | } 213 | } 214 | } 215 | 216 | // SetSize() sets window size 217 | func (s *Screen) SetSize(w, h int) { 218 | s.WidgetImplement.SetSize(w, h) 219 | s.window.SetSize(w, h) 220 | } 221 | 222 | // DrawAll() draws the Screen contents 223 | func (s *Screen) DrawAll() { 224 | gl.ClearColor(s.backgroundColor.R, s.backgroundColor.G, s.backgroundColor.B, 1.0) 225 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) 226 | 227 | if s.drawContentsCallback != nil { 228 | s.drawContentsCallback() 229 | } 230 | s.drawWidgets() 231 | s.window.SwapBuffers() 232 | } 233 | 234 | // SetResizeEventCallback() sets window resize event handler 235 | func (s *Screen) SetResizeEventCallback(callback func(x, y int) bool) { 236 | s.resizeEventCallback = callback 237 | } 238 | 239 | // SetDrawContentsCallback() sets event handler to use OpenGL draw call 240 | func (s *Screen) SetDrawContentsCallback(callback func()) { 241 | s.drawContentsCallback = callback 242 | } 243 | 244 | // SetDropEventCallback() sets event handler to handle a file drop event 245 | func (s *Screen) SetDropEventCallback(callback func(files []string) bool) { 246 | s.dropEventCallback = callback 247 | } 248 | 249 | // KeyboardEvent() is a default key event handler 250 | func (s *Screen) KeyboardEvent(self Widget, key glfw.Key, scanCode int, action glfw.Action, modifiers glfw.ModifierKey) bool { 251 | if len(s.focusPath) > 1 { 252 | for i := len(s.focusPath) - 2; i >= 0; i-- { 253 | path := s.focusPath[i] 254 | if path.Focused() && path.KeyboardEvent(path, key, scanCode, action, modifiers) { 255 | return true 256 | } 257 | } 258 | } 259 | return false 260 | } 261 | 262 | // KeyboardCharacterEvent() is a text input event handler: codepoint is native endian UTF-32 format 263 | func (s *Screen) KeyboardCharacterEvent(self Widget, codePoint rune) bool { 264 | if len(s.focusPath) > 1 { 265 | for i := len(s.focusPath) - 2; i >= 0; i-- { 266 | path := s.focusPath[i] 267 | if path.Focused() && path.KeyboardCharacterEvent(path, codePoint) { 268 | return true 269 | } 270 | } 271 | } 272 | return false 273 | } 274 | 275 | // IMEPreeditEvent() handles preedit text changes of IME (default implementation: do nothing) 276 | func (s *Screen) IMEPreeditEvent(self Widget, text []rune, blocks []int, focusedBlock int) bool { 277 | if len(s.focusPath) > 1 { 278 | for i := len(s.focusPath) - 2; i >= 0; i-- { 279 | path := s.focusPath[i] 280 | if path.Focused() && path.IMEPreeditEvent(path, text, blocks, focusedBlock) { 281 | return true 282 | } 283 | } 284 | } 285 | return false 286 | } 287 | 288 | // IMEStatusEvent() handles IME status change event (default implementation: do nothing) 289 | func (s *Screen) IMEStatusEvent(self Widget) bool { 290 | if len(s.focusPath) > 1 { 291 | for i := len(s.focusPath) - 2; i >= 0; i-- { 292 | path := s.focusPath[i] 293 | if path.Focused() && path.IMEStatusEvent(path) { 294 | return true 295 | } 296 | } 297 | } 298 | return false 299 | } 300 | 301 | // MousePosition() returns the last observed mouse position value 302 | func (s *Screen) MousePosition() (int, int) { 303 | return s.mousePosX, s.mousePosY 304 | } 305 | 306 | // GLFWWindow() returns a pointer to the underlying GLFW window data structure 307 | func (s *Screen) GLFWWindow() *glfw.Window { 308 | return s.window 309 | } 310 | 311 | // NVGContext() returns a pointer to the underlying nanoVGo draw context 312 | func (s *Screen) NVGContext() *nanovgo.Context { 313 | return s.context 314 | } 315 | 316 | func (s *Screen) SetShutdownGLFWOnDestruct(v bool) { 317 | s.shutdownGLFWOnDestruct = v 318 | } 319 | 320 | func (s *Screen) ShutdownGLFWOnDestruct() bool { 321 | return s.shutdownGLFWOnDestruct 322 | } 323 | 324 | // UpdateFocus is an internal helper function 325 | func (s *Screen) UpdateFocus(widget Widget) { 326 | for _, w := range s.focusPath { 327 | if w.Focused() { 328 | w.FocusEvent(w, false) 329 | } 330 | } 331 | s.focusPath = s.focusPath[:0] 332 | var window *Window 333 | for widget != nil { 334 | s.focusPath = append(s.focusPath, widget) 335 | if _, ok := widget.(*Window); ok { 336 | window = widget.(*Window) 337 | } 338 | widget = widget.Parent() 339 | } 340 | for _, w := range s.focusPath { 341 | w.FocusEvent(w, true) 342 | } 343 | if window != nil { 344 | s.MoveWindowToFront(window) 345 | } 346 | } 347 | 348 | // DisposeWindow is an internal helper function 349 | func (s *Screen) DisposeWindow(window *Window) { 350 | find := false 351 | for _, w := range s.focusPath { 352 | if w == window { 353 | find = true 354 | break 355 | } 356 | } 357 | if find { 358 | s.focusPath = s.focusPath[:0] 359 | } 360 | if s.dragWidget == window { 361 | s.dragWidget = nil 362 | } 363 | window.Parent().RemoveChild(window) 364 | } 365 | 366 | // CenterWindow is an internal helper function 367 | func (s *Screen) CenterWindow(window *Window) { 368 | w, h := window.Size() 369 | if w == 0 && h == 0 { 370 | window.SetSize(window.PreferredSize(window, s.context)) 371 | window.OnPerformLayout(window, s.context) 372 | } 373 | ww, wh := window.Size() 374 | pw, ph := window.Parent().Size() 375 | window.SetPosition((pw-ww)/2, (ph-wh)/2) 376 | } 377 | 378 | // MoveWindowToFront is an internal helper function 379 | func (s *Screen) MoveWindowToFront(window IWindow) { 380 | parent := window.Parent() 381 | maxDepth := 0 382 | for _, child := range parent.Children() { 383 | depth := child.Depth() 384 | if child != window && maxDepth < depth { 385 | maxDepth = depth 386 | } 387 | } 388 | window.SetDepth(maxDepth + 1) 389 | changed := true 390 | for changed { 391 | baseDepth := 0 392 | for _, child := range parent.Children() { 393 | if child == window { 394 | baseDepth = child.Depth() 395 | } 396 | } 397 | changed = false 398 | for _, child := range parent.Children() { 399 | pw, ok := child.(*Popup) 400 | if ok && pw.ParentWindow() == window && pw.Depth() < baseDepth { 401 | s.MoveWindowToFront(pw) 402 | changed = true 403 | break 404 | } 405 | } 406 | } 407 | } 408 | 409 | func (s *Screen) PreeditCursorPos() (int, int, int) { 410 | return s.window.GetPreeditCursorPos() 411 | } 412 | 413 | func (s *Screen) SetPreeditCursorPos(x, y, h int) { 414 | s.window.SetPreeditCursorPos(x, y, h) 415 | } 416 | 417 | func (s *Screen) drawWidgets() { 418 | if !s.visible { 419 | return 420 | } 421 | s.window.MakeContextCurrent() 422 | s.fbW, s.fbH = s.window.GetFramebufferSize() 423 | s.w, s.h = s.window.GetSize() 424 | gl.Viewport(0, 0, s.fbW, s.fbH) 425 | 426 | s.pixelRatio = float32(s.fbW) / float32(s.w) 427 | s.context.BeginFrame(s.w, s.h, s.pixelRatio) 428 | s.Draw(s, s.context) 429 | elapsed := GetTime() - s.lastInteraction 430 | 431 | if elapsed > 0.5 { 432 | // Draw tooltips 433 | widget := s.FindWidget(s, s.mousePosX, s.mousePosY) 434 | if widget != nil && widget.Tooltip() != "" { 435 | var tooltipWidth float32 = 150 436 | ctx := s.context 437 | ctx.SetFontFace(s.theme.FontNormal) 438 | ctx.SetFontSize(15.0) 439 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignTop) 440 | ctx.SetTextLineHeight(1.1) 441 | posX, posY := widget.AbsolutePosition() 442 | posX += widget.Width() / 2 443 | posY += widget.Height() + 10 444 | bounds := ctx.TextBoxBounds(float32(posX), float32(posY), tooltipWidth, widget.Tooltip()) 445 | ctx.SetGlobalAlpha(minF(1.0, 2*(elapsed-0.5)) * 0.8) 446 | ctx.BeginPath() 447 | ctx.SetFillColor(nanovgo.MONO(0, 255)) 448 | h := (bounds[2] - bounds[0]) / 2 449 | ctx.RoundedRect(bounds[0]-4-h, bounds[1]-4, bounds[2]-bounds[0]+8, bounds[3]-bounds[1]+8, 3) 450 | px := (bounds[2]+bounds[0])/2 - h 451 | ctx.MoveTo(px, bounds[1]-10) 452 | ctx.LineTo(px+7, bounds[1]+1) 453 | ctx.LineTo(px-7, bounds[1]+1) 454 | ctx.Fill() 455 | 456 | ctx.SetFillColor(nanovgo.MONO(255, 255)) 457 | ctx.SetFontBlur(0.0) 458 | ctx.TextBox(float32(posX)-h, float32(posY), tooltipWidth, widget.Tooltip()) 459 | 460 | } 461 | } 462 | 463 | s.context.EndFrame() 464 | } 465 | 466 | func (s *Screen) cursorPositionCallbackEvent(x, y float64) bool { 467 | ret := false 468 | s.lastInteraction = GetTime() 469 | 470 | px := int(x) - 1 471 | py := int(y) - 2 472 | if !s.dragActive { 473 | widget := s.FindWidget(s, int(x), int(y)) 474 | if widget != nil && widget.Cursor() != s.cursor { 475 | //s.cursor = widget.Cursor() 476 | //s.window.SetCursor() 477 | } 478 | } else { 479 | ax, ay := s.dragWidget.Parent().AbsolutePosition() 480 | ret = s.dragWidget.MouseDragEvent(s.dragWidget, px-ax, py-ay, px-s.mousePosX, py-s.mousePosY, s.mouseState, s.modifiers) 481 | } 482 | if !ret { 483 | ret = s.MouseMotionEvent(s, px, py, px-s.mousePosX, py-s.mousePosY, s.mouseState, s.modifiers) 484 | } 485 | s.mousePosX = px 486 | s.mousePosY = py 487 | return ret 488 | } 489 | 490 | func (s *Screen) mouseButtonCallbackEvent(button glfw.MouseButton, action glfw.Action, modifiers glfw.ModifierKey) bool { 491 | s.modifiers = modifiers 492 | s.lastInteraction = GetTime() 493 | 494 | if len(s.focusPath) > 1 { 495 | window, ok := s.focusPath[len(s.focusPath)-2].(*Window) 496 | if ok && window.Modal() { 497 | if !window.Contains(s.mousePosX, s.mousePosY) { 498 | return false 499 | } 500 | } 501 | } 502 | 503 | if action == glfw.Press { 504 | s.mouseState |= 1 << uint(button) 505 | } else { 506 | s.mouseState &= ^(1 << uint(button)) 507 | } 508 | 509 | dropWidget := s.FindWidget(s, s.mousePosX, s.mousePosY) 510 | if s.dragActive && action == glfw.Release && dropWidget != s.dragWidget { 511 | ax, ay := s.dragWidget.Parent().AbsolutePosition() 512 | s.dragWidget.MouseButtonEvent(s.dragWidget, s.mousePosX-ax, s.mousePosY-ay, button, false, modifiers) 513 | } 514 | 515 | if dropWidget != nil && dropWidget.Cursor() != s.cursor { 516 | //s.cursor = widget.Cursor() 517 | //s.window.SetCursor() 518 | } 519 | 520 | if action == glfw.Press && button == glfw.MouseButton1 { 521 | s.dragWidget = s.FindWidget(s, s.mousePosX, s.mousePosY) 522 | if s.dragWidget == s { 523 | s.dragWidget = nil 524 | } 525 | s.dragActive = s.dragWidget != nil 526 | if !s.dragActive { 527 | s.UpdateFocus(nil) 528 | } 529 | } else { 530 | s.dragActive = false 531 | s.dragWidget = nil 532 | } 533 | return s.MouseButtonEvent(s, s.mousePosX, s.mousePosY, button, action == glfw.Press, modifiers) 534 | } 535 | 536 | func (s *Screen) keyCallbackEvent(key glfw.Key, scanCode int, action glfw.Action, modifiers glfw.ModifierKey) bool { 537 | s.lastInteraction = GetTime() 538 | return s.KeyboardEvent(s, key, scanCode, action, modifiers) 539 | } 540 | 541 | func (s *Screen) charCallbackEvent(codePoint rune) bool { 542 | s.lastInteraction = GetTime() 543 | return s.KeyboardCharacterEvent(s, codePoint) 544 | } 545 | 546 | func (s *Screen) preeditCallbackEvent(text []rune, blocks []int, focusedBlock int) { 547 | s.lastInteraction = GetTime() 548 | s.IMEPreeditEvent(s, text, blocks, focusedBlock) 549 | } 550 | 551 | func (s *Screen) imeStatusCallbackEvent() { 552 | s.lastInteraction = GetTime() 553 | s.IMEStatusEvent(s) 554 | } 555 | 556 | func (s *Screen) dropCallbackEvent(fileNames []string) bool { 557 | if s.dropEventCallback != nil { 558 | return s.dropEventCallback(fileNames) 559 | } 560 | return false 561 | } 562 | 563 | func (s *Screen) scrollCallbackEvent(x, y float32) bool { 564 | s.lastInteraction = GetTime() 565 | 566 | if runtime.GOOS == "windows" { 567 | x *= 32 568 | y *= 32 569 | } 570 | 571 | if len(s.focusPath) > 1 { 572 | window, ok := s.focusPath[len(s.focusPath)-2].(*Window) 573 | if ok && window.Modal() { 574 | if !window.Contains(s.mousePosX, s.mousePosY) { 575 | return false 576 | } 577 | } 578 | } 579 | return s.ScrollEvent(s, s.mousePosX, s.mousePosY, int(x), int(y)) 580 | } 581 | 582 | func (s *Screen) resizeCallbackEvent(width, height int) bool { 583 | fbW, fbH := s.window.GetFramebufferSize() 584 | w, h := s.window.GetSize() 585 | 586 | if (fbW == 0 && fbH == 0) && (w == 0 && h == 0) { 587 | return false 588 | } 589 | s.fbW = fbW 590 | s.fbH = fbH 591 | s.w = w 592 | s.h = h 593 | s.lastInteraction = GetTime() 594 | if s.resizeEventCallback != nil { 595 | return s.resizeEventCallback(int(float32(fbW)/s.pixelRatio), int(float32(fbH)/s.pixelRatio)) 596 | } 597 | return false 598 | } 599 | 600 | func (s *Screen) PerformLayout() { 601 | s.OnPerformLayout(s, s.context) 602 | } 603 | 604 | func (s *Screen) String() string { 605 | return s.StringHelper("Screen", "") 606 | } 607 | 608 | func (s *Screen) IsClipped(cx, cy, cw, ch int) bool { 609 | if cy+ch < 0 { 610 | return true 611 | } 612 | if cy > s.h { 613 | return true 614 | } 615 | if cx+cw < 0 { 616 | return true 617 | } 618 | if cx > s.w { 619 | return true 620 | } 621 | return false 622 | } 623 | 624 | func traverse(buffer *bytes.Buffer, w Widget, indent int) { 625 | for i := 0; i < indent; i++ { 626 | buffer.WriteString(" ") 627 | } 628 | buffer.WriteString(w.String()) 629 | buffer.WriteByte('\n') 630 | for _, c := range w.Children() { 631 | traverse(buffer, c, indent+1) 632 | } 633 | } 634 | 635 | func (s *Screen) DebugPrint() { 636 | var buffer bytes.Buffer 637 | buffer.WriteString(s.String()) 638 | buffer.WriteByte('\n') 639 | for _, c := range s.Children() { 640 | traverse(&buffer, c, 1) 641 | } 642 | println(buffer.String()) 643 | } 644 | -------------------------------------------------------------------------------- /slider.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/glfw" 6 | "github.com/shibukawa/nanovgo" 7 | ) 8 | 9 | type Slider struct { 10 | WidgetImplement 11 | 12 | value float32 13 | highlightColor nanovgo.Color 14 | highlightedRange [2]float32 15 | callback func(float32) 16 | finalCallback func(float32) 17 | } 18 | 19 | func NewSlider(parent Widget) *Slider { 20 | slider := &Slider{} 21 | InitWidget(slider, parent) 22 | return slider 23 | } 24 | 25 | func (s *Slider) Value() float32 { 26 | return s.value 27 | } 28 | 29 | func (s *Slider) SetValue(v float32) { 30 | s.value = v 31 | } 32 | 33 | func (s *Slider) HighlightColor() nanovgo.Color { 34 | return s.highlightColor 35 | } 36 | 37 | func (s *Slider) SetHighlightColor(c nanovgo.Color) { 38 | s.highlightColor = c 39 | } 40 | 41 | func (s *Slider) HighlightedRange() (float32, float32) { 42 | return s.highlightedRange[0], s.highlightedRange[1] 43 | } 44 | 45 | func (s *Slider) SetHighlightedRange(l, h float32) { 46 | s.highlightedRange[0] = l 47 | s.highlightedRange[1] = h 48 | } 49 | 50 | func (s *Slider) SetCallback(callback func(float32)) { 51 | s.callback = callback 52 | } 53 | 54 | func (s *Slider) SetFinalCallback(callback func(float32)) { 55 | s.finalCallback = callback 56 | } 57 | 58 | func (s *Slider) MouseDragEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 59 | if !s.enabled { 60 | return false 61 | } 62 | s.value = clampF(float32(x-s.x)/float32(s.w), 0.0, 1.0) 63 | if s.callback != nil { 64 | s.callback(s.value) 65 | } 66 | return true 67 | } 68 | 69 | func (s *Slider) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 70 | if !s.enabled { 71 | return false 72 | } 73 | s.value = clampF(float32(x-s.x)/float32(s.w), 0.0, 1.0) 74 | if s.callback != nil { 75 | s.callback(s.value) 76 | } 77 | if s.finalCallback != nil { 78 | s.finalCallback(s.value) 79 | } 80 | return true 81 | } 82 | 83 | func (s *Slider) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 84 | return 70, 12 85 | } 86 | 87 | func (s *Slider) Draw(self Widget, ctx *nanovgo.Context) { 88 | sx := float32(s.x) 89 | sy := float32(s.y) 90 | sw := float32(s.w) 91 | sh := float32(s.h) 92 | cy := sy + sh*0.5 93 | kx := sx + s.value*sw 94 | ky := cy + 0.5 95 | kr := sh * 0.5 96 | 97 | var a1, a2, a3 uint8 98 | if s.enabled { 99 | a1 = 32 100 | a2 = 128 101 | a3 = 255 102 | } else { 103 | a1 = 10 104 | a2 = 210 105 | a3 = 100 106 | } 107 | background := nanovgo.BoxGradient(sx, cy-3+1, sw, 6, 3, 3, nanovgo.MONO(0, a1), nanovgo.MONO(0, a2)) 108 | 109 | ctx.BeginPath() 110 | ctx.RoundedRect(sx, cy-3+1, sw, 6, 2) 111 | ctx.SetFillPaint(background) 112 | ctx.Fill() 113 | 114 | if s.highlightedRange[0] != s.highlightedRange[1] { 115 | ctx.BeginPath() 116 | ctx.RoundedRect(sx+s.highlightedRange[0]*sw, cy-3+1, sw*(s.highlightedRange[1]-s.highlightedRange[0]), 6, 2) 117 | ctx.SetFillColor(s.highlightColor) 118 | ctx.Fill() 119 | } 120 | 121 | knobShadow := nanovgo.RadialGradient(kx, ky, kr-3, kr+3, nanovgo.MONO(0, 64), s.theme.Transparent) 122 | ctx.BeginPath() 123 | ctx.Rect(kx-kr-5, ky-kr-5, kr*2+10, kr*2+10+3) 124 | ctx.Circle(kx, ky, kr) 125 | ctx.PathWinding(nanovgo.Hole) 126 | ctx.SetFillPaint(knobShadow) 127 | ctx.Fill() 128 | 129 | knobPaint := nanovgo.LinearGradient(sx, cy-kr, sx, cy+kr, s.theme.BorderLight, s.theme.BorderMedium) 130 | knobReversePaint := nanovgo.LinearGradient(sx, cy-kr, sx, cy+kr, s.theme.BorderMedium, s.theme.BorderLight) 131 | 132 | ctx.BeginPath() 133 | ctx.Circle(kx, ky, kr) 134 | ctx.SetStrokeColor(s.theme.BorderDark) 135 | ctx.SetFillPaint(knobPaint) 136 | ctx.Stroke() 137 | ctx.Fill() 138 | 139 | ctx.BeginPath() 140 | ctx.Circle(kx, ky, kr/2) 141 | ctx.SetStrokePaint(knobReversePaint) 142 | ctx.SetFillColor(nanovgo.MONO(150, a3)) 143 | ctx.Stroke() 144 | ctx.Fill() 145 | } 146 | 147 | func (s *Slider) String() string { 148 | return s.StringHelper("Slider", fmt.Sprintf("%f", s.value)) 149 | } 150 | -------------------------------------------------------------------------------- /theme.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | ) 6 | 7 | type Theme struct { 8 | StandardFontSize int 9 | ButtonFontSize int 10 | TextBoxFontSize int 11 | WindowCornerRadius int 12 | WindowHeaderHeight int 13 | WindowDropShadowSize int 14 | ButtonCornerRadius int 15 | 16 | DropShadow nanovgo.Color 17 | Transparent nanovgo.Color 18 | BorderDark nanovgo.Color 19 | BorderLight nanovgo.Color 20 | BorderMedium nanovgo.Color 21 | TextColor nanovgo.Color 22 | DisabledTextColor nanovgo.Color 23 | TextColorShadow nanovgo.Color 24 | IconColor nanovgo.Color 25 | 26 | ButtonGradientTopFocused nanovgo.Color 27 | ButtonGradientBotFocused nanovgo.Color 28 | ButtonGradientTopUnfocused nanovgo.Color 29 | ButtonGradientBotUnfocused nanovgo.Color 30 | ButtonGradientTopPushed nanovgo.Color 31 | ButtonGradientBotPushed nanovgo.Color 32 | 33 | /* Window-related */ 34 | WindowFillUnfocused nanovgo.Color 35 | WindowFillFocused nanovgo.Color 36 | WindowTitleUnfocused nanovgo.Color 37 | WindowTitleFocused nanovgo.Color 38 | 39 | WindowHeaderGradientTop nanovgo.Color 40 | WindowHeaderGradientBot nanovgo.Color 41 | WindowHeaderSepTop nanovgo.Color 42 | WindowHeaderSepBot nanovgo.Color 43 | 44 | WindowPopup nanovgo.Color 45 | WindowPopupTransparent nanovgo.Color 46 | 47 | FontNormal string 48 | FontBold string 49 | FontIcons string 50 | } 51 | 52 | func NewStandardTheme(ctx *nanovgo.Context) *Theme { 53 | ctx.CreateFontFromMemory("sans", MustAsset("fonts/Roboto-Regular.ttf"), 0) 54 | ctx.CreateFontFromMemory("sans-bold", MustAsset("fonts/Roboto-Bold.ttf"), 0) 55 | ctx.CreateFontFromMemory("icons", MustAsset("fonts/entypo.ttf"), 0) 56 | return &Theme{ 57 | StandardFontSize: 16, 58 | ButtonFontSize: 20, 59 | TextBoxFontSize: 20, 60 | WindowCornerRadius: 2, 61 | WindowHeaderHeight: 30, 62 | WindowDropShadowSize: 10, 63 | ButtonCornerRadius: 2, 64 | 65 | DropShadow: nanovgo.MONO(0, 128), 66 | Transparent: nanovgo.MONO(0, 0), 67 | BorderDark: nanovgo.MONO(29, 255), 68 | BorderLight: nanovgo.MONO(92, 255), 69 | BorderMedium: nanovgo.MONO(35, 255), 70 | TextColor: nanovgo.MONO(255, 160), 71 | DisabledTextColor: nanovgo.MONO(255, 80), 72 | TextColorShadow: nanovgo.MONO(0, 160), 73 | IconColor: nanovgo.MONO(255, 160), 74 | 75 | ButtonGradientTopFocused: nanovgo.MONO(64, 255), 76 | ButtonGradientBotFocused: nanovgo.MONO(48, 255), 77 | ButtonGradientTopUnfocused: nanovgo.MONO(74, 255), 78 | ButtonGradientBotUnfocused: nanovgo.MONO(58, 255), 79 | ButtonGradientTopPushed: nanovgo.MONO(41, 255), 80 | ButtonGradientBotPushed: nanovgo.MONO(29, 255), 81 | 82 | WindowFillUnfocused: nanovgo.MONO(43, 230), 83 | WindowFillFocused: nanovgo.MONO(45, 230), 84 | WindowTitleUnfocused: nanovgo.MONO(220, 160), 85 | WindowTitleFocused: nanovgo.MONO(255, 190), 86 | 87 | WindowHeaderGradientTop: nanovgo.MONO(74, 255), 88 | WindowHeaderGradientBot: nanovgo.MONO(58, 255), 89 | WindowHeaderSepTop: nanovgo.MONO(92, 255), 90 | WindowHeaderSepBot: nanovgo.MONO(29, 255), 91 | 92 | WindowPopup: nanovgo.MONO(50, 255), 93 | WindowPopupTransparent: nanovgo.MONO(50, 0), 94 | 95 | FontNormal: "sans", 96 | FontBold: "sans-bold", 97 | FontIcons: "icons", 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import "math" 4 | 5 | func minF(a, b float32) float32 { 6 | if a < b { 7 | return a 8 | } 9 | return b 10 | } 11 | 12 | func maxF(a, b float32) float32 { 13 | if a > b { 14 | return a 15 | } 16 | return b 17 | } 18 | 19 | func maxI(a, b int) int { 20 | if a > b { 21 | return a 22 | } 23 | return b 24 | } 25 | 26 | func clampI(a, min, max int) int { 27 | if a > max { 28 | return max 29 | } else if a < min { 30 | return min 31 | } 32 | return a 33 | } 34 | 35 | func clampF(a, min, max float32) float32 { 36 | if a > max { 37 | return max 38 | } else if a < min { 39 | return min 40 | } 41 | return a 42 | } 43 | 44 | func toB(condition bool, a, b uint8) uint8 { 45 | if condition { 46 | return a 47 | } 48 | return b 49 | } 50 | func toI(condition bool, a, b int) int { 51 | if condition { 52 | return a 53 | } 54 | return b 55 | } 56 | 57 | func toF(condition bool, a, b float32) float32 { 58 | if condition { 59 | return a 60 | } 61 | return b 62 | } 63 | 64 | func maxFs(v float32, values ...float32) float32 { 65 | max := v 66 | for _, value := range values { 67 | if max < value { 68 | max = value 69 | } 70 | } 71 | return max 72 | } 73 | 74 | func minFs(v float32, values ...float32) float32 { 75 | min := v 76 | for _, value := range values { 77 | if min > value { 78 | min = value 79 | } 80 | } 81 | return min 82 | } 83 | 84 | func sqrtF(a float32) float32 { 85 | return float32(math.Sqrt(float64(a))) 86 | } 87 | 88 | func sinCosF(a float32) (float32, float32) { 89 | sin, cos := math.Sincos(float64(a)) 90 | return float32(sin), float32(cos) 91 | } 92 | 93 | func absF(a float32) float32 { 94 | if a < 0 { 95 | return -a 96 | } 97 | return a 98 | } 99 | 100 | func floorF(a float32) float32 { 101 | return float32(math.Floor(float64(a))) 102 | } 103 | -------------------------------------------------------------------------------- /util/gen_icon_symbols.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | success = False 5 | 6 | with open("../entypoicons.go", "w") as output: 7 | output.write("""package nanogui 8 | 9 | // generated by util/gen_icon_symbols.py 10 | 11 | type Icon rune 12 | 13 | const ( 14 | """) 15 | name = "" 16 | for line in open("./entypo.yml").readlines(): 17 | if line.startswith(" - css: "): 18 | name = line.split(":")[1].strip() 19 | elif line.startswith(" code: "): 20 | codepoint = line.split(":")[1].strip() 21 | output.write("Icon%s Icon = %s\n" % (name.title().replace("-", ""), codepoint)) 22 | output.write(")") 23 | success = True 24 | 25 | if success: 26 | os.system("go fmt ../entypoicons.go") 27 | -------------------------------------------------------------------------------- /vscrollpanel.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "github.com/shibukawa/glfw" 5 | "github.com/shibukawa/nanovgo" 6 | "runtime" 7 | ) 8 | 9 | type VScrollPanel struct { 10 | WidgetImplement 11 | 12 | childPreferredHeight int 13 | scroll float32 14 | scrollPosition float32 15 | } 16 | 17 | func NewVScrollPanel(parent Widget) *VScrollPanel { 18 | panel := new(VScrollPanel) 19 | InitWidget(panel, parent) 20 | return panel 21 | } 22 | 23 | func (v *VScrollPanel) Scroll() float32 { 24 | return v.scroll 25 | } 26 | 27 | func (v *VScrollPanel) SetScroll(scroll float32) { 28 | v.scroll = scroll 29 | } 30 | 31 | func (v *VScrollPanel) OnPerformLayout(self Widget, ctx *nanovgo.Context) { 32 | v.WidgetImplement.OnPerformLayout(self, ctx) 33 | 34 | if len(v.children) == 0 { 35 | return 36 | } 37 | child := v.children[0] 38 | _, v.childPreferredHeight = child.PreferredSize(child, ctx) 39 | child.SetPosition(0, 0) 40 | child.SetSize(v.w, v.childPreferredHeight) 41 | } 42 | 43 | func (v *VScrollPanel) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 44 | if len(v.children) == 0 { 45 | return 0, 0 46 | } 47 | layout := self.Layout() 48 | if layout != nil { 49 | w, h := layout.PreferredSize(self, ctx) 50 | return w + 12, h 51 | } 52 | child := v.children[0] 53 | w, h := child.PreferredSize(child, ctx) 54 | return w + 12, h 55 | } 56 | 57 | func (v *VScrollPanel) MouseDragEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 58 | if len(v.children) == 0 { 59 | return false 60 | } 61 | if v.h < v.childPreferredHeight { 62 | h := float32(v.h) 63 | ph := float32(v.childPreferredHeight) 64 | if runtime.GOOS != "darwin" { 65 | relY = -relY 66 | } 67 | scrollAmount := float32(relY) * 2 68 | v.scrollPosition = clampF(v.scrollPosition-scrollAmount, 0.0, ph-h) 69 | v.scroll = clampF(v.scrollPosition/(ph-h), 0.0, 1.0) 70 | } else { 71 | v.scroll = 0.0 72 | } 73 | return true 74 | } 75 | 76 | func (v *VScrollPanel) ScrollEvent(self Widget, x, y, relX, relY int) bool { 77 | if v.h < v.childPreferredHeight { 78 | h := float32(v.h) 79 | ph := float32(v.childPreferredHeight) 80 | scrollAmount := float32(relY) * 2 81 | 82 | v.scrollPosition = clampF(v.scrollPosition-scrollAmount, 0.0, ph-h) 83 | v.scroll = clampF(v.scrollPosition/(ph-h), 0.0, 1.0) 84 | } else { 85 | v.scroll = 0.0 86 | v.scrollPosition = 0.0 87 | } 88 | return true 89 | } 90 | 91 | func (v *VScrollPanel) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 92 | if len(v.children) == 0 { 93 | return false 94 | } 95 | child := v.children[0] 96 | shift := int(v.scroll * float32(v.childPreferredHeight-v.h)) 97 | return child.MouseButtonEvent(child, x, y+shift, button, down, modifier) 98 | } 99 | 100 | func (v *VScrollPanel) MouseMotionEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 101 | if len(v.children) == 0 { 102 | return false 103 | } 104 | child := v.children[0] 105 | shift := int(v.scroll * float32(v.childPreferredHeight-v.h)) 106 | return child.MouseMotionEvent(child, x, y+shift, relX, relY, button, modifier) 107 | } 108 | 109 | func (v *VScrollPanel) Draw(self Widget, ctx *nanovgo.Context) { 110 | if len(v.children) == 0 { 111 | return 112 | } 113 | x := float32(v.x) 114 | y := float32(v.y) 115 | w := float32(v.w) 116 | h := float32(v.h) 117 | 118 | child := v.children[0] 119 | layout := self.Layout() 120 | if layout != nil { 121 | _, v.childPreferredHeight = layout.PreferredSize(self, ctx) 122 | } else { 123 | _, v.childPreferredHeight = child.PreferredSize(child, ctx) 124 | } 125 | 126 | ctx.Save() 127 | ctx.Translate(x, y) 128 | ctx.Scissor(0, 0, w, h) 129 | ctx.Translate(0, -v.scroll*(float32(v.childPreferredHeight)-h)) 130 | if child.Visible() { 131 | child.Draw(child, ctx) 132 | } 133 | ctx.Restore() 134 | if v.childPreferredHeight > v.h { 135 | scrollH := h * minF(1.0, h/float32(v.childPreferredHeight)) 136 | scrollH = minF(maxF(20.0, scrollH), h) 137 | paint := nanovgo.BoxGradient(x+w-12+1, y+4+1, 8, h-8, 3, 4, nanovgo.MONO(0, 32), nanovgo.MONO(0, 92)) 138 | ctx.BeginPath() 139 | ctx.RoundedRect(x+w-12, y+4, 8, h-8, 3) 140 | ctx.SetFillPaint(paint) 141 | ctx.Fill() 142 | 143 | barPaint := nanovgo.BoxGradient(x+y-12-1, y+4+1+(h-8-scrollH)*v.scroll-1, 8, scrollH, 3, 4, nanovgo.MONO(220, 100), nanovgo.MONO(128, 100)) 144 | ctx.BeginPath() 145 | ctx.RoundedRect(x+w-12+1, y+4+1+(h-8-scrollH)*v.scroll, 8-2, scrollH-2, 2) 146 | ctx.SetFillPaint(barPaint) 147 | ctx.Fill() 148 | } 149 | } 150 | 151 | func (v *VScrollPanel) IsClipped(x, y, w, h int) bool { 152 | scroll := int(v.scroll * (float32(v.childPreferredHeight) - float32(v.h))) 153 | return v.Parent().IsClipped(x+v.x, y-scroll+v.y, w, h) 154 | } 155 | 156 | func (v *VScrollPanel) String() string { 157 | return v.StringHelper("VScrollPanel", "") 158 | } 159 | 160 | type VScrollPanelChild struct { 161 | WidgetImplement 162 | } 163 | 164 | func NewVScrollPanelChild(parent *VScrollPanel) *VScrollPanelChild { 165 | result := &VScrollPanelChild{} 166 | InitWidget(result, parent) 167 | return result 168 | } 169 | 170 | func (v *VScrollPanelChild) Size() (int, int) { 171 | return v.Parent().Size() 172 | } 173 | 174 | func (v *VScrollPanelChild) SetSize(w, h int) { 175 | if h > 400 { 176 | v.Parent().Parent().SetSize(w, 400) 177 | v.Parent().SetSize(w, 400) 178 | } else { 179 | v.Parent().Parent().SetSize(w, h) 180 | v.Parent().SetSize(w, h) 181 | } 182 | } 183 | 184 | func (v *VScrollPanelChild) Width() int { 185 | return v.Parent().Width() 186 | } 187 | 188 | func (v *VScrollPanelChild) SetWidth(w int) { 189 | v.Parent().Parent().SetWidth(w) 190 | v.Parent().SetWidth(w) 191 | } 192 | 193 | func (v *VScrollPanelChild) Height() int { 194 | return v.Parent().Height() 195 | } 196 | 197 | func (v *VScrollPanelChild) SetHeight(h int) { 198 | v.Parent().Parent().SetHeight(h) 199 | v.Parent().SetHeight(h) 200 | } 201 | 202 | func (v *VScrollPanelChild) FixedSize() (int, int) { 203 | return v.Parent().Parent().FixedSize() 204 | } 205 | 206 | func (v *VScrollPanelChild) SetFixedSize(w, h int) { 207 | v.Parent().Parent().SetFixedSize(w, h) 208 | } 209 | 210 | func (v *VScrollPanelChild) FixedWidth() int { 211 | return v.Parent().Parent().FixedWidth() 212 | } 213 | 214 | func (v *VScrollPanelChild) FixedHeight() int { 215 | return v.Parent().Parent().FixedHeight() 216 | } 217 | 218 | func (v *VScrollPanelChild) SetFixedWidth(w int) { 219 | v.Parent().Parent().SetFixedWidth(w) 220 | } 221 | 222 | func (v *VScrollPanelChild) SetFixedHeight(h int) { 223 | v.Parent().Parent().SetFixedHeight(h) 224 | } 225 | 226 | func (v *VScrollPanelChild) Visible() bool { 227 | return v.Parent().Parent().Visible() 228 | } 229 | 230 | func (v *VScrollPanelChild) SetVisible(flag bool) { 231 | v.Parent().Parent().SetVisible(flag) 232 | } 233 | 234 | func (v *VScrollPanelChild) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 235 | v.WidgetImplement.MouseButtonEvent(self, x, y, button, down, modifier) 236 | return true 237 | } 238 | 239 | func (v *VScrollPanelChild) MouseMotionEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 240 | v.WidgetImplement.MouseMotionEvent(self, x, y, relX, relY, button, modifier) 241 | return true 242 | } 243 | 244 | func (v *VScrollPanelChild) ScrollEvent(self Widget, x, y, relX, relY int) bool { 245 | v.WidgetImplement.ScrollEvent(self, x, y, relX, relY) 246 | return true 247 | } 248 | 249 | func (v *VScrollPanelChild) String() string { 250 | return v.StringHelper("VScrollPanelChild", "") 251 | } 252 | 253 | func (v *VScrollPanelChild) IsClipped(cx, cy, cw, ch int) bool { 254 | parent := v.Parent() 255 | if parent == nil { 256 | return false 257 | } 258 | return parent.IsClipped(cx+v.x, cy+v.y, cw, ch) 259 | } 260 | -------------------------------------------------------------------------------- /widget.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/glfw" 6 | "github.com/shibukawa/nanovgo" 7 | "sort" 8 | ) 9 | 10 | // Widget is base class of all widgets 11 | // 12 | // Widget is the base class of all widgets in nanogui. It can 13 | // also be used as an panel to arrange an arbitrary number of child 14 | // widgets using a layout generator (see Layout) 15 | type Widget interface { 16 | Parent() Widget 17 | SetParent(parent Widget) 18 | 19 | Layout() Layout 20 | SetLayout(layout Layout) 21 | 22 | Theme() *Theme 23 | SetTheme(theme *Theme) 24 | 25 | Position() (int, int) 26 | SetPosition(x, y int) 27 | AbsolutePosition() (int, int) 28 | IsPositionAbsolute() bool 29 | 30 | Size() (int, int) 31 | SetSize(w, h int) 32 | Width() int 33 | SetWidth(w int) 34 | Height() int 35 | SetHeight(h int) 36 | FixedSize() (int, int) 37 | SetFixedSize(w, h int) 38 | FixedWidth() int 39 | SetFixedWidth(w int) 40 | FixedHeight() int 41 | SetFixedHeight(h int) 42 | Clamp() [2]bool 43 | SetClampWidth(clamp bool) 44 | SetClampHeight(clamp bool) 45 | 46 | Visible() bool 47 | SetVisible(v bool) 48 | VisibleRecursive() bool 49 | 50 | ChildCount() int 51 | Children() []Widget 52 | SetChildren([]Widget) 53 | AddChild(self, w Widget) 54 | RemoveChildByIndex(i int) 55 | RemoveChild(w Widget) 56 | 57 | FindWindow() IWindow 58 | 59 | SetID(id string) 60 | ID() string 61 | 62 | Enabled() bool 63 | SetEnabled(e bool) 64 | 65 | Focused() bool 66 | SetFocused(f bool) 67 | RequestFocus(self Widget) 68 | 69 | Tooltip() string 70 | SetTooltip(s string) 71 | 72 | FontSize() int 73 | SetFontSize(s int) 74 | HasFontSize() bool 75 | 76 | Cursor() Cursor 77 | SetCursor(c Cursor) 78 | 79 | Contains(x, y int) bool 80 | IsClipped(x, y, w, h int) bool 81 | 82 | FindWidget(self Widget, x, y int) Widget 83 | MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool 84 | MouseMotionEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool 85 | MouseDragEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool 86 | MouseEnterEvent(self Widget, x, y int, enter bool) bool 87 | ScrollEvent(self Widget, x, y, relX, relY int) bool 88 | FocusEvent(self Widget, f bool) bool 89 | KeyboardEvent(self Widget, key glfw.Key, scanCode int, action glfw.Action, modifier glfw.ModifierKey) bool 90 | KeyboardCharacterEvent(self Widget, codePoint rune) bool 91 | IMEPreeditEvent(self Widget, text []rune, blocks []int, focusedBlock int) bool 92 | IMEStatusEvent(self Widget) bool 93 | 94 | PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) 95 | OnPerformLayout(self Widget, ctx *nanovgo.Context) 96 | Draw(self Widget, ctx *nanovgo.Context) 97 | Depth() int 98 | 99 | String() string 100 | } 101 | 102 | type WidgetImplement struct { 103 | parent Widget 104 | layout Layout 105 | theme *Theme 106 | x, y, w, h, fixedW, fixedH int 107 | clamp [2]bool 108 | visible, enabled bool 109 | focused, mouseFocus bool 110 | id string 111 | tooltip string 112 | fontSize int 113 | cursor Cursor 114 | children []Widget 115 | } 116 | 117 | func NewWidget(parent Widget) Widget { 118 | widget := &WidgetImplement{} 119 | InitWidget(widget, parent) 120 | return widget 121 | } 122 | 123 | // Parent() returns the parent widget 124 | func (w *WidgetImplement) Parent() Widget { 125 | return w.parent 126 | } 127 | 128 | // SetParent() set the parent widget 129 | func (w *WidgetImplement) SetParent(parent Widget) { 130 | w.parent = parent 131 | } 132 | 133 | // Layout() returns the used layout generator 134 | func (w *WidgetImplement) Layout() Layout { 135 | return w.layout 136 | } 137 | 138 | // SetLayout() set the used layout generator 139 | func (w *WidgetImplement) SetLayout(layout Layout) { 140 | w.layout = layout 141 | } 142 | 143 | // Theme() returns the theme used to draw this widget 144 | func (w *WidgetImplement) Theme() *Theme { 145 | return w.theme 146 | } 147 | 148 | // SetTheme() set the theme used to draw this widget 149 | func (w *WidgetImplement) SetTheme(theme *Theme) { 150 | w.theme = theme 151 | } 152 | 153 | // Position() returns the position relative to the parent widget 154 | func (w *WidgetImplement) Position() (int, int) { 155 | return w.x, w.y 156 | } 157 | 158 | // SetPosition() set the position relative to the parent widget 159 | func (w *WidgetImplement) SetPosition(x, y int) { 160 | w.x = x 161 | w.y = y 162 | } 163 | 164 | // AbsolutePosition() returns the absolute position on screen 165 | func (w *WidgetImplement) AbsolutePosition() (int, int) { 166 | if w.parent != nil { 167 | x, y := w.parent.AbsolutePosition() 168 | return x + w.x, y + w.y 169 | } 170 | return w.x, w.y 171 | } 172 | 173 | // AbsolutePosition() returns whether the the object should be skipped by layout engines. 174 | func (w *WidgetImplement) IsPositionAbsolute() bool { 175 | return false 176 | } 177 | 178 | // Size() returns the size of the widget 179 | func (w *WidgetImplement) Size() (int, int) { 180 | return w.w, w.h 181 | } 182 | 183 | // SetSize() set the size of the widget 184 | func (wg *WidgetImplement) SetSize(w, h int) { 185 | wg.w = w 186 | wg.h = h 187 | } 188 | 189 | // Width() returns the width of the widget 190 | func (w *WidgetImplement) Width() int { 191 | return w.w 192 | } 193 | 194 | // SetWidth() set the width of the widget 195 | func (wg *WidgetImplement) SetWidth(w int) { 196 | wg.w = w 197 | } 198 | 199 | // Height() returns the height of the widget 200 | func (w *WidgetImplement) Height() int { 201 | return w.h 202 | } 203 | 204 | // SetHeight() set the height of the widget 205 | func (w *WidgetImplement) SetHeight(h int) { 206 | w.h = h 207 | } 208 | 209 | // Return the fixed size (see SetFixedSize()) 210 | func (w *WidgetImplement) FixedSize() (int, int) { 211 | return w.fixedW, w.fixedH 212 | } 213 | 214 | // SetFixedSize() set the fixed size of this widget. 215 | // If nonzero, components of the fixed size attribute override any values 216 | // computed by a layout generator associated with this widget. Note that 217 | // just setting the fixed size alone is not enough to actually change its 218 | // size; this is done with a call to \ref SetSize or a call to PerformLayout() 219 | // in the parent widget. 220 | func (wg *WidgetImplement) SetFixedSize(w, h int) { 221 | wg.fixedW = w 222 | wg.fixedH = h 223 | } 224 | 225 | // FixedWidth() returns the fixed width (see SetFixedSize()) 226 | func (w *WidgetImplement) FixedWidth() int { 227 | return w.fixedW 228 | } 229 | 230 | // FixedHeight() returns the fixed height (see SetFixedSize()) 231 | func (w *WidgetImplement) FixedHeight() int { 232 | return w.fixedH 233 | } 234 | 235 | // SetFixedWidth() set the fixed width (see SetFixedSize()) 236 | func (wg *WidgetImplement) SetFixedWidth(w int) { 237 | wg.fixedW = w 238 | } 239 | 240 | // SetFixedSize() set the fixed height (see SetFixedSize()) 241 | func (w *WidgetImplement) SetFixedHeight(h int) { 242 | w.fixedH = h 243 | } 244 | 245 | // Clamp() returns whether preferred size is used as fixed size 246 | func (w *WidgetImplement) Clamp() [2]bool { 247 | return w.clamp 248 | } 249 | 250 | // SetClampWidth() set the preferred width as fixed width 251 | func (w *WidgetImplement) SetClampWidth(clamp bool) { 252 | w.clamp[0] = clamp 253 | } 254 | 255 | // SetClampHeight() set the preferred height as fixed height 256 | func (w *WidgetImplement) SetClampHeight(clamp bool) { 257 | w.clamp[1] = clamp 258 | } 259 | 260 | // Visible() returns whether or not the widget is currently visible (assuming all parents are visible) 261 | func (w *WidgetImplement) Visible() bool { 262 | return w.visible 263 | } 264 | 265 | // SetVisible() set whether or not the widget is currently visible (assuming all parents are visible) 266 | func (w *WidgetImplement) SetVisible(v bool) { 267 | w.visible = v 268 | } 269 | 270 | // VisibleRecursive() checks if this widget is currently visible, taking parent widgets into account 271 | func (w *WidgetImplement) VisibleRecursive() bool { 272 | if w.parent != nil { 273 | return w.Visible() && w.parent.VisibleRecursive() 274 | } 275 | return w.Visible() 276 | } 277 | 278 | // ChildCount() returns the number of child widgets 279 | func (w *WidgetImplement) ChildCount() int { 280 | return len(w.children) 281 | } 282 | 283 | // Children() returns the list of child widgets of the current widget 284 | func (w *WidgetImplement) Children() []Widget { 285 | return w.children 286 | } 287 | 288 | func (w *WidgetImplement) SetChildren(children []Widget) { 289 | w.children = children 290 | } 291 | 292 | // AddChild() adds a child widget to the current widget 293 | // This function almost never needs to be called by hand, 294 | // since the constructor of \ref Widget automatically 295 | // adds the current widget to its parent 296 | func (w *WidgetImplement) AddChild(self, child Widget) { 297 | w.children = append(w.children, child) 298 | child.SetParent(self) 299 | } 300 | 301 | // RemoveChildByIndex() removes a child widget by index 302 | func (w *WidgetImplement) RemoveChildByIndex(index int) { 303 | w.children[index].SetParent(nil) 304 | // w.children, w.children[len(w.children)-1] = append(w.children[:i], w.children[i+1:]...), nil 305 | // https://github.com/gopherjs/gopherjs/issues/358 306 | // The following code is work around of the above issue 307 | var newChildren []Widget 308 | for i, child := range w.children { 309 | if i != index { 310 | newChildren = append(newChildren, child) 311 | } 312 | } 313 | w.children = newChildren 314 | } 315 | 316 | // RemoveChild() removes a child widget by value 317 | func (wg *WidgetImplement) RemoveChild(w Widget) { 318 | for i, child := range wg.children { 319 | if w == child { 320 | wg.RemoveChildByIndex(i) 321 | return 322 | } 323 | } 324 | } 325 | 326 | // FindWindow() walks up the hierarchy and return the parent window 327 | func (w *WidgetImplement) FindWindow() IWindow { 328 | parent := w.Parent() 329 | if parent == nil { 330 | panic("Widget:internal error (could not find parent window)") 331 | } 332 | return parent.FindWindow() 333 | } 334 | 335 | // SetID() associates this widget with an ID value (optional) 336 | func (w *WidgetImplement) SetID(id string) { 337 | w.id = id 338 | } 339 | 340 | // ID() returns the ID value associated with this widget, if any 341 | func (w *WidgetImplement) ID() string { 342 | return w.id 343 | } 344 | 345 | // Enabled() returns whether or not this widget is currently enabled 346 | func (w *WidgetImplement) Enabled() bool { 347 | return w.enabled 348 | } 349 | 350 | /// SetEnabled() set whether or not this widget is currently enabled 351 | func (w *WidgetImplement) SetEnabled(e bool) { 352 | w.enabled = e 353 | } 354 | 355 | // Focused() returns whether or not this widget is currently focused 356 | func (w *WidgetImplement) Focused() bool { 357 | return w.focused 358 | } 359 | 360 | // SetFocused() set whether or not this widget is currently focused 361 | func (w *WidgetImplement) SetFocused(f bool) { 362 | w.focused = f 363 | } 364 | 365 | // RequestFocus() requests the focus to be moved to this widget 366 | func (w *WidgetImplement) RequestFocus(self Widget) { 367 | var widget Widget = self 368 | var parent Widget = self.Parent() 369 | for parent != nil { 370 | widget = parent 371 | parent = widget.Parent() 372 | } 373 | screen := widget.(*Screen) 374 | screen.UpdateFocus(self) 375 | } 376 | 377 | // Tooltip() returns tooltip string 378 | func (w *WidgetImplement) Tooltip() string { 379 | return w.tooltip 380 | } 381 | 382 | // SetTooltip() set tooltip string 383 | func (w *WidgetImplement) SetTooltip(s string) { 384 | w.tooltip = s 385 | } 386 | 387 | // FontSize() returns current font size. If not set the default of the current theme will be returned 388 | func (w *WidgetImplement) FontSize() int { 389 | if w.fontSize > 0 { 390 | return w.fontSize 391 | } 392 | return w.theme.StandardFontSize 393 | } 394 | 395 | // SetFontSize() set the font size of this widget 396 | func (w *WidgetImplement) SetFontSize(s int) { 397 | w.fontSize = s 398 | } 399 | 400 | // HasFontSize() return whether the font size is explicitly specified for this widget 401 | func (w *WidgetImplement) HasFontSize() bool { 402 | return w.fontSize > 0 403 | } 404 | 405 | // Cursor() returns a pointer to the cursor of the widget 406 | func (w *WidgetImplement) Cursor() Cursor { 407 | return w.cursor 408 | } 409 | 410 | // SetCursor() set the cursor of the widget 411 | func (w *WidgetImplement) SetCursor(c Cursor) { 412 | w.cursor = c 413 | } 414 | 415 | // Contains() checks if the widget contains a certain position 416 | func (w *WidgetImplement) Contains(x, y int) bool { 417 | return w.x <= x && w.y <= y && x <= w.x+w.w && y <= w.y+w.h 418 | } 419 | 420 | func childrenReverseDepthOrder(self Widget) []Widget { 421 | children := self.Children() 422 | 423 | widgets := make([]Widget, 0, len(children)) 424 | var windows widgetsAsc = make([]Widget, 0, len(children)) 425 | 426 | for _, child := range children { 427 | if child.Visible() { 428 | if child.Depth() == 0 { 429 | widgets = append(widgets, child) 430 | } else { 431 | windows = append(windows, child) 432 | } 433 | } 434 | } 435 | sort.Sort(sort.Reverse(windows)) 436 | for i := len(widgets) - 1; i > -1; i-- { 437 | windows = append(windows, widgets[i]) 438 | } 439 | return windows 440 | } 441 | 442 | // FindWidget() determines the widget located at the given position value (recursive) 443 | func (w *WidgetImplement) FindWidget(self Widget, x, y int) Widget { 444 | for _, child := range childrenReverseDepthOrder(self) { 445 | if child.Contains(x-w.x, y-w.y) { 446 | return child.FindWidget(child, x-w.x, y-w.y) 447 | } 448 | } 449 | if self.Contains(x, y) { 450 | return self 451 | } 452 | return nil 453 | } 454 | 455 | // MouseButtonEvent() handles a mouse button event (default implementation: propagate to children) 456 | func (w *WidgetImplement) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 457 | for _, child := range childrenReverseDepthOrder(self) { 458 | if child.Contains(x-w.x, y-w.y) && child.MouseButtonEvent(child, x-w.x, y-w.y, button, down, modifier) { 459 | return true 460 | } 461 | } 462 | if button == glfw.MouseButton1 && down && !w.focused { 463 | self.RequestFocus(self) 464 | } 465 | return false 466 | } 467 | 468 | // MouseMotionEvent() handles a mouse motion event (default implementation: propagate to children) 469 | func (w *WidgetImplement) MouseMotionEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 470 | for _, child := range childrenReverseDepthOrder(self) { 471 | contained := child.Contains(x-w.x, y-w.y) 472 | prevContained := child.Contains(x-w.x-relX, y-w.y-relY) 473 | if contained != prevContained { 474 | child.MouseEnterEvent(child, x, y, contained) 475 | } 476 | if (contained || prevContained) && child.MouseMotionEvent(child, x-w.x, y-w.y, relX, relY, button, modifier) { 477 | return true 478 | } 479 | } 480 | return false 481 | } 482 | 483 | // MouseDragEvent() handles a mouse drag event (default implementation: do nothing) 484 | func (w *WidgetImplement) MouseDragEvent(self Widget, x, y, relX, relY int, button int, modifier glfw.ModifierKey) bool { 485 | return false 486 | } 487 | 488 | // MouseEnterEvent() handles a mouse enter/leave event (default implementation: record this fact, but do nothing) 489 | func (w *WidgetImplement) MouseEnterEvent(self Widget, x, y int, enter bool) bool { 490 | w.mouseFocus = enter 491 | return false 492 | } 493 | 494 | // ScrollEvent() handles a mouse scroll event (default implementation: propagate to children) 495 | func (w *WidgetImplement) ScrollEvent(self Widget, x, y, relX, relY int) bool { 496 | for _, child := range childrenReverseDepthOrder(self) { 497 | if child.Contains(x-w.x, y-w.y) && child.ScrollEvent(child, x-w.x, y-w.y, relX, relY) { 498 | return true 499 | } 500 | } 501 | return false 502 | } 503 | 504 | // FocusEvent() handles a focus change event (default implementation: record the focus status, but do nothing) 505 | func (w *WidgetImplement) FocusEvent(self Widget, f bool) bool { 506 | w.focused = f 507 | return false 508 | } 509 | 510 | // KeyboardEvent() handles a keyboard event (default implementation: do nothing) 511 | func (w *WidgetImplement) KeyboardEvent(self Widget, key glfw.Key, scanCode int, action glfw.Action, modifier glfw.ModifierKey) bool { 512 | return false 513 | } 514 | 515 | // KeyboardCharacterEvent() handles text input (UTF-32 format) (default implementation: do nothing) 516 | func (w *WidgetImplement) KeyboardCharacterEvent(self Widget, codePoint rune) bool { 517 | return false 518 | } 519 | 520 | // IMEPreeditEvent() handles preedit text changes of IME (default implementation: do nothing) 521 | func (w *WidgetImplement) IMEPreeditEvent(self Widget, text []rune, blocks []int, focusedBlock int) bool { 522 | return false 523 | } 524 | 525 | // IMEStatusEvent() handles IME status change event (default implementation: do nothing) 526 | func (w *WidgetImplement) IMEStatusEvent(self Widget) bool { 527 | return false 528 | } 529 | 530 | // PreferredSize() computes the preferred size of the widget 531 | func (w *WidgetImplement) PreferredSize(self Widget, ctx *nanovgo.Context) (int, int) { 532 | if w.layout != nil { 533 | return w.layout.PreferredSize(self, ctx) 534 | } 535 | return w.w, w.h 536 | } 537 | 538 | // PerformLayout() invokes the associated layout generator to properly place child widgets, if any 539 | func (w *WidgetImplement) OnPerformLayout(self Widget, ctx *nanovgo.Context) { 540 | if w.layout != nil { 541 | w.layout.OnPerformLayout(self, ctx) 542 | } else { 543 | for _, child := range w.children { 544 | prefW, prefH := child.PreferredSize(child, ctx) 545 | fixW, fixH := child.FixedSize() 546 | w := toI(fixW > 0, fixW, prefW) 547 | h := toI(fixH > 0, fixH, prefH) 548 | child.SetSize(w, h) 549 | child.OnPerformLayout(child, ctx) 550 | } 551 | } 552 | } 553 | 554 | // Draw() draws the widget (and all child widgets) 555 | func (w *WidgetImplement) Draw(self Widget, ctx *nanovgo.Context) { 556 | if debugFlag { 557 | ctx.SetStrokeWidth(1.0) 558 | ctx.BeginPath() 559 | ctx.Rect(float32(w.x)-0.5, float32(w.y)-0.5, float32(w.w)+1.0, float32(w.h)+1.0) 560 | ctx.SetStrokeColor(nanovgo.RGBA(255, 0, 0, 255)) 561 | ctx.Stroke() 562 | } 563 | 564 | if len(w.children) == 0 { 565 | return 566 | } 567 | ctx.Translate(float32(w.x), float32(w.y)) 568 | // draw depth 0 items 569 | var drawLater widgetsAsc = make([]Widget, 0, len(w.children)) 570 | for _, child := range w.children { 571 | if child.Visible() { 572 | depth := child.Depth() 573 | if depth == 0 { 574 | cx, cy := child.Position() 575 | cw, ch := child.Size() 576 | if !self.IsClipped(cx, cy, cw, ch) { 577 | child.Draw(child, ctx) 578 | } 579 | } else { 580 | drawLater = append(drawLater, child) 581 | } 582 | } 583 | } 584 | // draw by depth order 585 | sort.Sort(drawLater) 586 | for _, child := range drawLater { 587 | cx, cy := child.Position() 588 | cw, ch := child.Size() 589 | if !self.IsClipped(cx, cy, cw, ch) { 590 | child.Draw(child, ctx) 591 | } 592 | } 593 | ctx.Translate(-float32(w.x), -float32(w.y)) 594 | } 595 | 596 | func (w *WidgetImplement) String() string { 597 | return w.StringHelper("Widget", "") 598 | } 599 | 600 | func (w *WidgetImplement) StringHelper(name, extra string) string { 601 | if w.layout != nil { 602 | if extra != "" { 603 | return fmt.Sprintf("%s [%d,%d-%d,%d] (%s) - %s", name, w.x, w.y, w.w, w.h, w.layout.String(), extra) 604 | } else { 605 | return fmt.Sprintf("%s [%d,%d-%d,%d] (%s)", name, w.x, w.y, w.w, w.h, w.layout.String()) 606 | } 607 | } else { 608 | if extra != "" { 609 | return fmt.Sprintf("%s [%d,%d-%d,%d] - %s", name, w.x, w.y, w.w, w.h, extra) 610 | } else { 611 | return fmt.Sprintf("%s [%d,%d-%d,%d]", name, w.x, w.y, w.w, w.h) 612 | } 613 | } 614 | } 615 | 616 | func (w *WidgetImplement) Depth() int { 617 | return 0 618 | } 619 | 620 | func (w WidgetImplement) IsClipped(cx, cy, cw, ch int) bool { 621 | if cy+ch < 0 { 622 | return true 623 | } 624 | if cy > w.h { 625 | return true 626 | } 627 | if cx+cw < 0 { 628 | return true 629 | } 630 | if cx > w.w { 631 | return true 632 | } 633 | return w.Parent().IsClipped(cx+w.x, cy+w.y, cw, ch) 634 | } 635 | 636 | // Sort Interface 637 | type widgetsAsc []Widget 638 | 639 | func (w widgetsAsc) Len() int { 640 | return len(w) 641 | } 642 | 643 | func (w widgetsAsc) Less(i, j int) bool { 644 | return w[i].Depth() < w[j].Depth() 645 | } 646 | 647 | func (w widgetsAsc) Swap(i, j int) { 648 | w[i], w[j] = w[j], w[i] 649 | } 650 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | package nanogui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/glfw" 6 | "github.com/shibukawa/nanovgo" 7 | ) 8 | 9 | type Window struct { 10 | WidgetImplement 11 | title string 12 | buttonPanel Widget 13 | modal bool 14 | drag bool 15 | draggable bool 16 | depth int 17 | } 18 | 19 | type IWindow interface { 20 | Widget 21 | RefreshRelativePlacement() 22 | SetDepth(d int) 23 | } 24 | 25 | func NewWindow(parent Widget, title string) *Window { 26 | if title == "" { 27 | title = "Untitled" 28 | } 29 | window := &Window{ 30 | title: title, 31 | draggable: true, 32 | } 33 | InitWidget(window, parent) 34 | return window 35 | } 36 | 37 | // Title() returns the window title 38 | func (w *Window) Title() string { 39 | return w.title 40 | } 41 | 42 | // SetTitle() sets the window title 43 | func (w *Window) SetTitle(title string) { 44 | w.title = title 45 | } 46 | 47 | // Modal() returns is this a model dialog? 48 | func (w *Window) Modal() bool { 49 | return w.modal 50 | } 51 | 52 | // SetModal() set whether or not this is a modal dialog 53 | func (w *Window) SetModal(m bool) { 54 | w.modal = m 55 | } 56 | 57 | func (w *Window) Draggable() bool { 58 | return w.draggable 59 | } 60 | 61 | func (w *Window) SetDraggable(flag bool) { 62 | w.draggable = flag 63 | } 64 | 65 | func (w *Window) ButtonPanel() Widget { 66 | if w.buttonPanel == nil { 67 | w.buttonPanel = NewWidget(w) 68 | w.buttonPanel.SetLayout(NewBoxLayout(Horizontal, Middle, 0, 4)) 69 | } 70 | return w.buttonPanel 71 | } 72 | 73 | // Dispose() disposes the window 74 | func (w *Window) Dispose() { 75 | var widget Widget = w 76 | var parent Widget = w.Parent() 77 | for parent != nil { 78 | widget = parent 79 | parent = widget.Parent() 80 | } 81 | screen := widget.(*Screen) 82 | screen.DisposeWindow(w) 83 | } 84 | 85 | // Center() makes the window center in the current Screen 86 | func (w *Window) Center() { 87 | var widget Widget = w 88 | var parent Widget = w.Parent() 89 | for parent != nil { 90 | widget = parent 91 | parent = widget.Parent() 92 | } 93 | screen := widget.(*Screen) 94 | screen.CenterWindow(w) 95 | } 96 | 97 | // RefreshRelativePlacement is internal helper function to maintain nested window position values; overridden in \ref Popup 98 | func (w *Window) RefreshRelativePlacement() { 99 | // overridden in Popup 100 | } 101 | 102 | func (w *Window) MouseButtonEvent(self Widget, x, y int, button glfw.MouseButton, down bool, modifier glfw.ModifierKey) bool { 103 | if w.WidgetImplement.MouseButtonEvent(self, x, y, button, down, modifier) { 104 | return true 105 | } 106 | if button == glfw.MouseButton1 && w.draggable { 107 | w.drag = down && (y-w.y) < w.theme.WindowHeaderHeight 108 | return true 109 | } 110 | return false 111 | } 112 | 113 | func (w *Window) MouseDragEvent(self Widget, x, y, relX, relY, button int, modifier glfw.ModifierKey) bool { 114 | if w.drag && (button&1<