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.
 
 
 
 
 

385 lines
8.8 KiB

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