User interface toolkit for Go with support for SDL2 and HTML Canvas render targets.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

360 lines
8.1 KiB

  1. package ui
  2. import (
  3. "fmt"
  4. "git.kirsle.net/go/render"
  5. )
  6. // Window is a frame with a title bar.
  7. type Window struct {
  8. BaseWidget
  9. Title string
  10. // Title bar colors. Sensible defaults are chosen in NewWindow but you
  11. // may customize after the fact.
  12. ActiveTitleBackground render.Color
  13. ActiveTitleForeground render.Color
  14. InactiveTitleBackground render.Color
  15. InactiveTitleForeground render.Color
  16. // Private widgets.
  17. body *Frame
  18. titleBar *Frame
  19. titleLabel *Label
  20. titleButtons []*Button
  21. content *Frame
  22. // Configured title bar buttons.
  23. buttonsEnabled int
  24. // Window manager controls.
  25. dragging bool
  26. startDragAt render.Point // cursor position when drag began
  27. dragOrigPoint render.Point // original position of window at drag start
  28. focused bool
  29. managed bool // window is managed by Supervisor
  30. maximized bool // toggled by MaximizeButton
  31. origPoint render.Point // placement before a maximize
  32. origSize render.Rect // size before a maximize
  33. engine render.Engine // hang onto the render engine, for Maximize support.
  34. }
  35. // NewWindow creates a new window.
  36. func NewWindow(title string) *Window {
  37. w := &Window{
  38. Title: title,
  39. body: NewFrame("body:" + title),
  40. // Default title bar colors.
  41. ActiveTitleBackground: render.Blue,
  42. ActiveTitleForeground: render.White,
  43. InactiveTitleBackground: render.DarkGrey,
  44. InactiveTitleForeground: render.Grey,
  45. }
  46. w.IDFunc(func() string {
  47. return fmt.Sprintf("Window<%s %+v>",
  48. w.Title, w.focused,
  49. )
  50. })
  51. w.body.Configure(Config{
  52. Background: render.Grey,
  53. BorderSize: 2,
  54. BorderStyle: BorderRaised,
  55. })
  56. // Title bar widget.
  57. titleBar, titleLabel := w.setupTitleBar()
  58. w.body.Pack(titleBar, Pack{
  59. Side: N,
  60. Fill: true,
  61. })
  62. w.titleBar = titleBar
  63. w.titleLabel = titleLabel
  64. // Window content frame.
  65. content := NewFrame("content:" + title)
  66. content.Configure(Config{
  67. Background: render.Grey,
  68. })
  69. w.body.Pack(content, Pack{
  70. Side: N,
  71. Fill: true,
  72. })
  73. w.content = content
  74. // Set up parent/child relationships
  75. w.body.SetParent(w)
  76. return w
  77. }
  78. // setupTitlebar creates the title bar frame of the window.
  79. func (w *Window) setupTitleBar() (*Frame, *Label) {
  80. frame := NewFrame("Titlebar for Window: " + w.Title)
  81. frame.Configure(Config{
  82. Background: w.ActiveTitleBackground,
  83. })
  84. // Title label.
  85. label := NewLabel(Label{
  86. TextVariable: &w.Title,
  87. Font: render.Text{
  88. Color: w.ActiveTitleForeground,
  89. Size: 10,
  90. Stroke: w.ActiveTitleBackground.Darken(40),
  91. Padding: 2,
  92. },
  93. })
  94. frame.Pack(label, Pack{
  95. Side: W,
  96. })
  97. // Window buttons.
  98. var buttons = []struct {
  99. If bool
  100. Label string
  101. Event Event
  102. }{
  103. {
  104. Label: "×",
  105. Event: CloseWindow,
  106. },
  107. {
  108. Label: "+",
  109. Event: MaximizeWindow,
  110. },
  111. {
  112. Label: "_",
  113. Event: MinimizeWindow,
  114. },
  115. }
  116. w.titleButtons = make([]*Button, len(buttons))
  117. for i, cfg := range buttons {
  118. cfg := cfg
  119. btn := NewButton(
  120. fmt.Sprintf("Title Button %d for Window: %s", i, w.Title),
  121. NewLabel(Label{
  122. Text: cfg.Label,
  123. Font: render.Text{
  124. // Color: w.ActiveTitleForeground,
  125. Size: 8,
  126. Padding: 2,
  127. },
  128. }),
  129. )
  130. btn.SetBorderSize(0)
  131. btn.Handle(Click, func(ed EventData) error {
  132. w.Event(cfg.Event, ed)
  133. return ErrStopPropagation // TODO: doesn't work :(
  134. })
  135. btn.Hide()
  136. w.titleButtons[i] = btn
  137. frame.Pack(btn, Pack{
  138. Side: E,
  139. })
  140. }
  141. return frame, label
  142. }
  143. // SetButtons sets the title bar buttons to show in the window.
  144. //
  145. // The value should be the OR of CloseButton, MaximizeButton and MinimizeButton
  146. // that you want to be enabled.
  147. //
  148. // Window buttons only work if the window is managed by Supervisor and you have
  149. // called the Supervise() method of the window.
  150. func (w *Window) SetButtons(buttons int) {
  151. // Show/hide each button based on the value given.
  152. var toggle = []struct {
  153. Value int
  154. Index int
  155. }{
  156. {
  157. Value: CloseButton,
  158. Index: 0,
  159. },
  160. {
  161. Value: MaximizeButton,
  162. Index: 1,
  163. },
  164. {
  165. Value: MinimizeButton,
  166. Index: 2,
  167. },
  168. }
  169. for _, item := range toggle {
  170. if buttons&item.Value == item.Value {
  171. w.titleButtons[item.Index].Show()
  172. } else {
  173. w.titleButtons[item.Index].Hide()
  174. }
  175. }
  176. }
  177. // Supervise enables the window to be dragged around by its title bar by
  178. // adding its relevant event hooks to your Supervisor.
  179. func (w *Window) Supervise(s *Supervisor) {
  180. // Add a click handler to the title bar to enable dragging.
  181. w.titleBar.Handle(MouseDown, func(ed EventData) error {
  182. w.startDragAt = ed.Point
  183. w.dragOrigPoint = w.Point()
  184. s.DragStartWidget(w)
  185. return nil
  186. })
  187. // Clicking anywhere in the window focuses the window.
  188. w.Handle(MouseDown, func(ed EventData) error {
  189. s.FocusWindow(w)
  190. return nil
  191. })
  192. // Window as a whole receives DragMove events while being dragged.
  193. w.Handle(DragMove, func(ed EventData) error {
  194. // Get the delta of movement from where we began.
  195. delta := w.startDragAt.Compare(ed.Point)
  196. if delta != render.Origin {
  197. moveTo := w.dragOrigPoint
  198. moveTo.Add(delta)
  199. w.MoveTo(moveTo)
  200. }
  201. return nil
  202. })
  203. // Window button handlers.
  204. w.Handle(CloseWindow, func(ed EventData) error {
  205. w.Hide()
  206. return nil
  207. })
  208. w.Handle(MaximizeWindow, func(ed EventData) error {
  209. w.SetMaximized(!w.maximized)
  210. return nil
  211. })
  212. // Add the title bar to the supervisor.
  213. s.Add(w.titleBar)
  214. for _, btn := range w.titleButtons {
  215. s.Add(btn)
  216. }
  217. s.Add(w)
  218. // Add the window to the focus list of the supervisor.
  219. s.addWindow(w)
  220. }
  221. // Focused returns whether the window is focused.
  222. func (w *Window) Focused() bool {
  223. return w.focused
  224. }
  225. // SetFocus sets the window's focus value. Note: if you're using the Supervisor
  226. // to manage the windows, do NOT call this method -- window focus is managed
  227. // by the Supervisor.
  228. func (w *Window) SetFocus(v bool) {
  229. w.focused = v
  230. // Update the title bar colors.
  231. var (
  232. bg = w.ActiveTitleBackground
  233. fg = w.ActiveTitleForeground
  234. )
  235. if !w.focused {
  236. bg = w.InactiveTitleBackground
  237. fg = w.InactiveTitleForeground
  238. }
  239. w.titleBar.SetBackground(bg)
  240. w.titleLabel.Font.Color = fg
  241. w.titleLabel.Font.Stroke = bg.Darken(40)
  242. }
  243. // Maximized returns whether the window is maximized.
  244. func (w *Window) Maximized() bool {
  245. return w.maximized
  246. }
  247. // SetMaximized sets the state of the maximized window.
  248. // Must have called Compute() once before so the window can hang on to the
  249. // render.Engine, to calculate the size of the parent window.
  250. func (w *Window) SetMaximized(v bool) {
  251. w.maximized = v
  252. if v && w.engine != nil {
  253. w.origPoint = w.Point()
  254. w.origSize = w.Size()
  255. w.MoveTo(render.Origin)
  256. w.Resize(render.NewRect(w.engine.WindowSize()))
  257. w.Compute(w.engine)
  258. } else if w.engine != nil {
  259. w.MoveTo(w.origPoint)
  260. w.Resize(w.origSize)
  261. w.Compute(w.engine)
  262. }
  263. }
  264. // Close the window, hiding it from display and calling its CloseWindow handler.
  265. func (w *Window) Close() {
  266. w.Hide()
  267. w.Event(CloseWindow, EventData{})
  268. }
  269. // Children returns the window's child widgets.
  270. func (w *Window) Children() []Widget {
  271. return []Widget{
  272. w.body,
  273. }
  274. }
  275. // Pack a child widget into the window's main frame.
  276. func (w *Window) Pack(child Widget, config ...Pack) {
  277. w.content.Pack(child, config...)
  278. }
  279. // Place a child widget into the window's main frame.
  280. func (w *Window) Place(child Widget, config Place) {
  281. w.content.Place(child, config)
  282. }
  283. // TitleBar returns the title bar widget.
  284. func (w *Window) TitleBar() *Frame {
  285. return w.titleBar
  286. }
  287. // Configure the widget. Color and style changes are passed down to the inner
  288. // content frame of the window.
  289. func (w *Window) Configure(C Config) {
  290. w.BaseWidget.Configure(C)
  291. w.body.Configure(C)
  292. // Don't pass dimensions down any further than the body.
  293. C.Width = 0
  294. C.Height = 0
  295. w.content.Configure(C)
  296. }
  297. // ConfigureTitle configures the title bar widget.
  298. func (w *Window) ConfigureTitle(C Config) {
  299. w.titleBar.Configure(C)
  300. }
  301. // Compute the window.
  302. func (w *Window) Compute(e render.Engine) {
  303. w.engine = e // hang onto it in case of maximize
  304. w.body.Compute(e)
  305. // Call the BaseWidget Compute in case we have subscribers.
  306. w.BaseWidget.Compute(e)
  307. }
  308. // Present the window.
  309. func (w *Window) Present(e render.Engine, P render.Point) {
  310. w.body.Present(e, P)
  311. // Call the BaseWidget Present in case we have subscribers.
  312. w.BaseWidget.Present(e, P)
  313. }