window.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. import (
  6. "github.com/g3n/engine/math32"
  7. "github.com/g3n/engine/window"
  8. )
  9. /*********************************************
  10. Window panel
  11. +-----------------------------------------+
  12. | Title panel |
  13. +-----------------------------------------+
  14. | Content panel |
  15. | +-----------------------------------+ |
  16. | | | |
  17. | | | |
  18. | | | |
  19. | | | |
  20. | | | |
  21. | | | |
  22. | +-----------------------------------+ |
  23. | |
  24. +-----------------------------------------+
  25. *********************************************/
  26. // Window represents a window GUI element
  27. type Window struct {
  28. Panel // Embedded Panel
  29. styles *WindowStyles
  30. title *WindowTitle // internal optional title panel
  31. client Panel // internal client panel
  32. resizable ResizeBorders
  33. overBorder string
  34. drag bool
  35. mouseX float32
  36. mouseY float32
  37. }
  38. // WindowStyle contains the styling of a Window
  39. type WindowStyle struct {
  40. Border RectBounds
  41. Paddings RectBounds
  42. BorderColor math32.Color4
  43. TitleBorders RectBounds
  44. TitleBorderColor math32.Color4
  45. TitleBgColor math32.Color4
  46. TitleFgColor math32.Color4
  47. }
  48. // WindowStyles contains a WindowStyle for each valid GUI state
  49. type WindowStyles struct {
  50. Normal WindowStyle
  51. Over WindowStyle
  52. Focus WindowStyle
  53. Disabled WindowStyle
  54. }
  55. // ResizeBorders specifies which window borders can be resized
  56. type ResizeBorders int
  57. // Resizing can be allowed or disallowed on each window edge
  58. const (
  59. ResizeTop = ResizeBorders(1 << (iota + 1))
  60. ResizeRight
  61. ResizeBottom
  62. ResizeLeft
  63. ResizeAll = ResizeTop | ResizeRight | ResizeBottom | ResizeLeft
  64. )
  65. // NewWindow creates and returns a pointer to a new window with the
  66. // specified dimensions
  67. func NewWindow(width, height float32) *Window {
  68. w := new(Window)
  69. w.styles = &StyleDefault().Window
  70. w.Panel.Initialize(width, height)
  71. w.Panel.Subscribe(OnMouseDown, w.onMouse)
  72. w.Panel.Subscribe(OnMouseUp, w.onMouse)
  73. w.Panel.Subscribe(OnCursor, w.onCursor)
  74. w.Panel.Subscribe(OnCursorEnter, w.onCursor)
  75. w.Panel.Subscribe(OnCursorLeave, w.onCursor)
  76. w.Panel.Subscribe(OnResize, func(evname string, ev interface{}) { w.recalc() })
  77. w.client.Initialize(0, 0)
  78. w.Panel.Add(&w.client)
  79. w.recalc()
  80. w.update()
  81. return w
  82. }
  83. // SetResizable set the borders which are resizable
  84. func (w *Window) SetResizable(res ResizeBorders) {
  85. w.resizable = res
  86. }
  87. // SetTitle sets the title of this window
  88. func (w *Window) SetTitle(text string) {
  89. if w.title == nil {
  90. w.title = newWindowTitle(w, text)
  91. w.Panel.Add(w.title)
  92. } else {
  93. w.title.label.SetText(text)
  94. }
  95. w.update()
  96. w.recalc()
  97. }
  98. // Add adds a child panel to the client area of this window
  99. func (w *Window) Add(ichild IPanel) *Window {
  100. w.client.Add(ichild)
  101. return w
  102. }
  103. // SetLayout set the layout of this window content area
  104. func (w *Window) SetLayout(layout ILayout) {
  105. w.client.SetLayout(layout)
  106. }
  107. // onMouse process subscribed mouse events over the window
  108. func (w *Window) onMouse(evname string, ev interface{}) {
  109. mev := ev.(*window.MouseEvent)
  110. switch evname {
  111. case OnMouseDown:
  112. par := w.Parent().(IPanel).GetPanel()
  113. par.SetTopChild(w)
  114. if w.overBorder != "" {
  115. w.drag = true
  116. w.mouseX = mev.Xpos
  117. w.mouseY = mev.Ypos
  118. w.root.SetMouseFocus(w)
  119. }
  120. case OnMouseUp:
  121. w.drag = false
  122. w.root.SetCursorNormal()
  123. w.root.SetMouseFocus(nil)
  124. default:
  125. return
  126. }
  127. w.root.StopPropagation(StopAll)
  128. }
  129. // onCursor process subscribed cursor events over the window
  130. func (w *Window) onCursor(evname string, ev interface{}) {
  131. if evname == OnCursor {
  132. cev := ev.(*window.CursorEvent)
  133. if !w.drag {
  134. cx := cev.Xpos - w.pospix.X
  135. cy := cev.Ypos - w.pospix.Y
  136. if cy <= w.borderSizes.Top {
  137. if w.resizable&ResizeTop != 0 {
  138. w.overBorder = "top"
  139. w.root.SetCursorVResize()
  140. }
  141. } else if cy >= w.height-w.borderSizes.Bottom {
  142. if w.resizable&ResizeBottom != 0 {
  143. w.overBorder = "bottom"
  144. w.root.SetCursorVResize()
  145. }
  146. } else if cx <= w.borderSizes.Left {
  147. if w.resizable&ResizeLeft != 0 {
  148. w.overBorder = "left"
  149. w.root.SetCursorHResize()
  150. }
  151. } else if cx >= w.width-w.borderSizes.Right {
  152. if w.resizable&ResizeRight != 0 {
  153. w.overBorder = "right"
  154. w.root.SetCursorHResize()
  155. }
  156. } else {
  157. if w.overBorder != "" {
  158. w.root.SetCursorNormal()
  159. w.overBorder = ""
  160. }
  161. }
  162. } else {
  163. switch w.overBorder {
  164. case "top":
  165. delta := cev.Ypos - w.mouseY
  166. w.mouseY = cev.Ypos
  167. newHeight := w.Height() - delta
  168. if newHeight < w.MinHeight() {
  169. return
  170. }
  171. w.SetPositionY(w.Position().Y + delta)
  172. w.SetHeight(newHeight)
  173. case "right":
  174. delta := cev.Xpos - w.mouseX
  175. w.mouseX = cev.Xpos
  176. newWidth := w.Width() + delta
  177. w.SetWidth(newWidth)
  178. case "bottom":
  179. delta := cev.Ypos - w.mouseY
  180. w.mouseY = cev.Ypos
  181. newHeight := w.Height() + delta
  182. w.SetHeight(newHeight)
  183. case "left":
  184. delta := cev.Xpos - w.mouseX
  185. w.mouseX = cev.Xpos
  186. newWidth := w.Width() - delta
  187. if newWidth < w.MinWidth() {
  188. return
  189. }
  190. w.SetPositionX(w.Position().X + delta)
  191. w.SetWidth(newWidth)
  192. }
  193. }
  194. } else if evname == OnCursorLeave {
  195. if !w.drag {
  196. w.root.SetCursorNormal()
  197. }
  198. }
  199. w.root.StopPropagation(StopAll)
  200. }
  201. // update updates the button visual state
  202. func (w *Window) update() {
  203. if !w.Enabled() {
  204. w.applyStyle(&w.styles.Disabled)
  205. return
  206. }
  207. w.applyStyle(&w.styles.Normal)
  208. }
  209. func (w *Window) applyStyle(s *WindowStyle) {
  210. w.SetBordersColor4(&s.BorderColor)
  211. w.SetBordersFrom(&s.Border)
  212. w.SetPaddingsFrom(&s.Paddings)
  213. if w.title != nil {
  214. w.title.applyStyle(s)
  215. }
  216. }
  217. // recalc recalculates the sizes and positions of the internal panels
  218. // from the outside to the inside.
  219. func (w *Window) recalc() {
  220. // Window title
  221. height := w.content.Height
  222. width := w.content.Width
  223. cx := float32(0)
  224. cy := float32(0)
  225. if w.title != nil {
  226. w.title.SetWidth(w.content.Width)
  227. w.title.recalc()
  228. height -= w.title.height
  229. cy = w.title.height
  230. }
  231. // Content area
  232. w.client.SetPosition(cx, cy)
  233. w.client.SetSize(width, height)
  234. }
  235. // WindowTitle represents the title bar of a Window
  236. type WindowTitle struct {
  237. Panel // Embedded panel
  238. win *Window
  239. label Label
  240. pressed bool
  241. drag bool
  242. mouseX float32
  243. mouseY float32
  244. }
  245. // newWindowTitle creates and returns a pointer to a window title panel
  246. func newWindowTitle(win *Window, text string) *WindowTitle {
  247. wt := new(WindowTitle)
  248. wt.win = win
  249. wt.Panel.Initialize(0, 0)
  250. wt.label.initialize(text, StyleDefault().Font)
  251. wt.Panel.Add(&wt.label)
  252. wt.Subscribe(OnMouseDown, wt.onMouse)
  253. wt.Subscribe(OnMouseUp, wt.onMouse)
  254. wt.Subscribe(OnCursor, wt.onCursor)
  255. wt.Subscribe(OnCursorEnter, wt.onCursor)
  256. wt.Subscribe(OnCursorLeave, wt.onCursor)
  257. wt.recalc()
  258. return wt
  259. }
  260. // onMouse process subscribed mouse button events over the window title
  261. func (wt *WindowTitle) onMouse(evname string, ev interface{}) {
  262. mev := ev.(*window.MouseEvent)
  263. switch evname {
  264. case OnMouseDown:
  265. wt.pressed = true
  266. wt.mouseX = mev.Xpos
  267. wt.mouseY = mev.Ypos
  268. wt.win.root.SetMouseFocus(wt)
  269. case OnMouseUp:
  270. wt.pressed = false
  271. wt.win.root.SetMouseFocus(nil)
  272. default:
  273. return
  274. }
  275. wt.win.root.StopPropagation(Stop3D)
  276. }
  277. // onCursor process subscribed cursor events over the window title
  278. func (wt *WindowTitle) onCursor(evname string, ev interface{}) {
  279. if evname == OnCursorEnter {
  280. wt.win.root.SetCursorHand()
  281. } else if evname == OnCursorLeave {
  282. wt.win.root.SetCursorNormal()
  283. } else if evname == OnCursor {
  284. if !wt.pressed {
  285. wt.win.root.StopPropagation(Stop3D)
  286. return
  287. }
  288. cev := ev.(*window.CursorEvent)
  289. dy := wt.mouseY - cev.Ypos
  290. dx := wt.mouseX - cev.Xpos
  291. wt.mouseX = cev.Xpos
  292. wt.mouseY = cev.Ypos
  293. posX := wt.win.Position().X - dx
  294. posY := wt.win.Position().Y - dy
  295. wt.win.SetPosition(posX, posY)
  296. }
  297. wt.win.root.StopPropagation(Stop3D)
  298. }
  299. // applyStyles sets the specified window title style
  300. func (wt *WindowTitle) applyStyle(s *WindowStyle) {
  301. wt.SetBordersFrom(&s.TitleBorders)
  302. wt.SetBordersColor4(&s.TitleBorderColor)
  303. wt.SetColor4(&s.TitleBgColor)
  304. wt.label.SetColor4(&s.TitleFgColor)
  305. }
  306. // recalc recalculates the height and position of the label in the title bar.
  307. func (wt *WindowTitle) recalc() {
  308. xpos := (wt.width - wt.label.width) / 2
  309. wt.label.SetPositionX(xpos)
  310. wt.SetContentHeight(wt.label.Height())
  311. }