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.
 
 
 
 
 

526 lines
13 KiB

  1. package ui
  2. import (
  3. "git.kirsle.net/go/render"
  4. "git.kirsle.net/go/ui/theme"
  5. )
  6. // BorderStyle options for widget.SetBorderStyle()
  7. type BorderStyle string
  8. // Styles for a widget border.
  9. const (
  10. BorderNone BorderStyle = ""
  11. BorderSolid BorderStyle = "solid"
  12. BorderRaised = "raised"
  13. BorderSunken = "sunken"
  14. )
  15. // Widget is a user interface element.
  16. type Widget interface {
  17. ID() string // Get the widget's string ID.
  18. IDFunc(func() string) // Set a function that returns the widget's ID.
  19. String() string
  20. Point() render.Point
  21. MoveTo(render.Point)
  22. MoveBy(render.Point)
  23. Size() render.Rect // Return the Width and Height of the widget.
  24. FixedSize() bool // Return whether the size is fixed (true) or automatic (false)
  25. BoxSize() render.Rect // Return the full size including the border and outline.
  26. Resize(render.Rect)
  27. ResizeBy(render.Rect)
  28. ResizeAuto(render.Rect)
  29. Rect() render.Rect // Return the full absolute rect combining the Size() and Point()
  30. Handle(Event, func(EventData) error)
  31. Event(Event, EventData) error // called internally to trigger an event
  32. // Thickness of the padding + border + outline.
  33. BoxThickness(multiplier int) int
  34. DrawBox(render.Engine, render.Point)
  35. // Widget configuration getters.
  36. Margin() int // Margin away from other widgets
  37. SetMargin(int) //
  38. Background() render.Color // Background color
  39. SetBackground(render.Color) //
  40. Foreground() render.Color // Foreground color
  41. SetForeground(render.Color) //
  42. BorderStyle() BorderStyle // Border style: none, raised, sunken
  43. SetBorderStyle(BorderStyle) //
  44. BorderColor() render.Color // Border color (default is Background)
  45. SetBorderColor(render.Color) //
  46. BorderSize() int // Border size (default 0)
  47. SetBorderSize(int) //
  48. OutlineColor() render.Color // Outline color (default Invisible)
  49. SetOutlineColor(render.Color) //
  50. OutlineSize() int // Outline size (default 0)
  51. SetOutlineSize(int) //
  52. // Visibility
  53. Hide()
  54. Show()
  55. Hidden() bool
  56. // Container widgets like Frames can wire up associations between the
  57. // child widgets and the parent.
  58. Parent() (parent Widget, ok bool)
  59. SetParent(parent Widget) // for the container to assign itself the parent
  60. Children() []Widget // for containers to return their children
  61. // Run any render computations; by the end the widget must know its
  62. // Width and Height. For example the Label widget will render itself onto
  63. // an SDL Surface and then it will know its bounding box, but not before.
  64. Compute(render.Engine)
  65. // Render the final widget onto the drawing engine.
  66. Present(render.Engine, render.Point)
  67. }
  68. // Config holds common base widget configs for quick configuration.
  69. type Config struct {
  70. // Size management. If you provide a non-zero value for Width and Height,
  71. // the widget will be resized and the "fixedSize" flag is set, meaning it
  72. // will not re-compute its size dynamically. To set the size while also
  73. // keeping the auto-resize property, pass AutoResize=true too. This is
  74. // mainly used internally when widgets are calculating their automatic sizes.
  75. AutoResize bool
  76. Width int
  77. Height int
  78. Margin int
  79. MarginX int
  80. MarginY int
  81. Background render.Color
  82. Foreground render.Color
  83. BorderSize int
  84. BorderStyle BorderStyle
  85. BorderColor render.Color
  86. OutlineSize int
  87. OutlineColor render.Color
  88. }
  89. // BaseWidget holds common functionality for all widgets, such as managing
  90. // their widths and heights.
  91. type BaseWidget struct {
  92. id string
  93. idFunc func() string
  94. fixedSize bool
  95. hidden bool
  96. width int
  97. height int
  98. point render.Point
  99. margin int
  100. background render.Color
  101. foreground render.Color
  102. borderStyle BorderStyle
  103. borderColor render.Color
  104. borderSize int
  105. outlineColor render.Color
  106. outlineSize int
  107. handlers map[Event][]func(EventData) error
  108. hasParent bool
  109. parent Widget
  110. }
  111. // SetID sets a string name for your widget, helpful for debugging purposes.
  112. func (w *BaseWidget) SetID(id string) {
  113. w.id = id
  114. }
  115. // ID returns the ID that the widget calls itself by.
  116. func (w *BaseWidget) ID() string {
  117. if w.idFunc == nil {
  118. w.IDFunc(func() string {
  119. return "Widget<Untitled>"
  120. })
  121. }
  122. return w.idFunc()
  123. }
  124. // IDFunc sets an ID function.
  125. func (w *BaseWidget) IDFunc(fn func() string) {
  126. w.idFunc = fn
  127. }
  128. func (w *BaseWidget) String() string {
  129. return w.ID()
  130. }
  131. // Configure the base widget with all the common properties at once. Any
  132. // property left as the zero value will not update the widget.
  133. func (w *BaseWidget) Configure(c Config) {
  134. if c.Width != 0 || c.Height != 0 {
  135. w.fixedSize = !c.AutoResize
  136. if c.Width != 0 {
  137. w.width = c.Width
  138. }
  139. if c.Height != 0 {
  140. w.height = c.Height
  141. }
  142. }
  143. if c.Margin != 0 {
  144. w.margin = c.Margin
  145. }
  146. if c.Background != render.Invisible {
  147. w.background = c.Background
  148. }
  149. if c.Foreground != render.Invisible {
  150. w.foreground = c.Foreground
  151. }
  152. if c.BorderColor != render.Invisible {
  153. w.borderColor = c.BorderColor
  154. }
  155. if c.OutlineColor != render.Invisible {
  156. w.outlineColor = c.OutlineColor
  157. }
  158. if c.BorderSize != 0 {
  159. w.borderSize = c.BorderSize
  160. }
  161. if c.BorderStyle != BorderNone {
  162. w.borderStyle = c.BorderStyle
  163. }
  164. if c.OutlineSize != 0 {
  165. w.outlineSize = c.OutlineSize
  166. }
  167. }
  168. // Rect returns the widget's absolute rectangle, the combined Size and Point.
  169. func (w *BaseWidget) Rect() render.Rect {
  170. return render.Rect{
  171. X: w.point.X,
  172. Y: w.point.Y,
  173. W: w.width,
  174. H: w.height,
  175. }
  176. }
  177. // Point returns the X,Y position of the widget on the window.
  178. func (w *BaseWidget) Point() render.Point {
  179. return w.point
  180. }
  181. // MoveTo updates the X,Y position to the new point.
  182. func (w *BaseWidget) MoveTo(v render.Point) {
  183. w.point = v
  184. }
  185. // MoveBy adds the X,Y values to the widget's current position.
  186. func (w *BaseWidget) MoveBy(v render.Point) {
  187. w.point.X += v.X
  188. w.point.Y += v.Y
  189. }
  190. // Size returns the box with W and H attributes containing the size of the
  191. // widget. The X,Y attributes of the box are ignored and zero.
  192. func (w *BaseWidget) Size() render.Rect {
  193. return render.Rect{
  194. W: w.width,
  195. H: w.height,
  196. }
  197. }
  198. // BoxSize returns the full rendered size of the widget including its box
  199. // thickness (border, padding and outline).
  200. func (w *BaseWidget) BoxSize() render.Rect {
  201. return render.Rect{
  202. W: w.width + w.BoxThickness(2),
  203. H: w.height + w.BoxThickness(2),
  204. }
  205. }
  206. // FixedSize returns whether the widget's size has been hard-coded by the user
  207. // (true) or if it automatically resizes based on its contents (false).
  208. func (w *BaseWidget) FixedSize() bool {
  209. return w.fixedSize
  210. }
  211. // Resize sets the size of the widget to the .W and .H attributes of a rect.
  212. func (w *BaseWidget) Resize(v render.Rect) {
  213. w.fixedSize = true
  214. w.width = v.W
  215. w.height = v.H
  216. }
  217. // ResizeBy resizes by a relative amount.
  218. func (w *BaseWidget) ResizeBy(v render.Rect) {
  219. w.fixedSize = true
  220. w.width += v.W
  221. w.height += v.H
  222. }
  223. // ResizeAuto sets the size of the widget but doesn't set the fixedSize flag.
  224. func (w *BaseWidget) ResizeAuto(v render.Rect) {
  225. w.width = v.W
  226. w.height = v.H
  227. }
  228. // BoxThickness returns the full sum of the padding, border and outline.
  229. // m = multiplier, i.e., 1 or 2. If m=1 this returns the box thickness of one
  230. // edge of the widget, if m=2 it would account for both edges of the widget.
  231. func (w *BaseWidget) BoxThickness(m int) int {
  232. if m == 0 {
  233. m = 1
  234. }
  235. return (w.Margin() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m)
  236. }
  237. // Parent returns the parent widget, like a Frame, and a boolean indicating
  238. // whether the widget had a parent.
  239. func (w *BaseWidget) Parent() (Widget, bool) {
  240. return w.parent, w.hasParent
  241. }
  242. // SetParent sets the widget's parent. This function is called by container
  243. // widgets like Frame when they add a child widget to their care.
  244. // Pass a nil parent to unset the parent.
  245. func (w *BaseWidget) SetParent(parent Widget) {
  246. if parent == nil {
  247. w.hasParent = false
  248. w.parent = nil
  249. } else {
  250. w.hasParent = true
  251. w.parent = parent
  252. }
  253. }
  254. // Children returns the widget's children, to be implemented by containers.
  255. // The default implementation returns an empty slice.
  256. func (w *BaseWidget) Children() []Widget {
  257. return []Widget{}
  258. }
  259. // Hide the widget from being rendered.
  260. func (w *BaseWidget) Hide() {
  261. w.hidden = true
  262. }
  263. // Show the widget.
  264. func (w *BaseWidget) Show() {
  265. w.hidden = false
  266. }
  267. // Hidden returns whether the widget is hidden. If this widget is not hidden,
  268. // but it has a parent, this will recursively crawl the parents to see if any
  269. // of them are hidden.
  270. func (w *BaseWidget) Hidden() bool {
  271. if w.hidden {
  272. return true
  273. }
  274. // Return if any parents are hidden.
  275. parent, ok := w.Parent()
  276. for ok {
  277. if parent.Hidden() {
  278. return true
  279. }
  280. parent, ok = parent.Parent()
  281. }
  282. return false
  283. }
  284. // DrawBox draws the border and outline.
  285. func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
  286. var (
  287. S = w.Size()
  288. outline = w.OutlineSize()
  289. border = w.BorderSize()
  290. borderColor = w.BorderColor()
  291. highlight = borderColor.Lighten(theme.BorderColorOffset)
  292. shadow = borderColor.Darken(theme.BorderColorOffset)
  293. color render.Color
  294. box = render.Rect{
  295. X: P.X,
  296. Y: P.Y,
  297. W: S.W,
  298. H: S.H,
  299. }
  300. )
  301. if borderColor == render.Invisible {
  302. borderColor = render.Red
  303. }
  304. // Draw the outline layer as the full size of the widget.
  305. if outline > 0 && w.OutlineColor() != render.Invisible {
  306. e.DrawBox(w.OutlineColor(), render.Rect{
  307. X: P.X,
  308. Y: P.Y,
  309. W: S.W,
  310. H: S.H,
  311. })
  312. }
  313. box.X += outline
  314. box.Y += outline
  315. box.W -= outline * 2
  316. box.H -= outline * 2
  317. // Highlight on the top left edge.
  318. if border > 0 {
  319. if w.BorderStyle() == BorderRaised {
  320. color = highlight
  321. } else if w.BorderStyle() == BorderSunken {
  322. color = shadow
  323. } else {
  324. color = borderColor
  325. }
  326. e.DrawBox(color, box)
  327. }
  328. // Shadow on the bottom right edge.
  329. box.X += border
  330. box.Y += border
  331. box.W -= border
  332. box.H -= border
  333. if w.BorderSize() > 0 {
  334. if w.BorderStyle() == BorderRaised {
  335. color = shadow
  336. } else if w.BorderStyle() == BorderSunken {
  337. color = highlight
  338. } else {
  339. color = borderColor
  340. }
  341. e.DrawBox(color, box)
  342. }
  343. // Background color of the button.
  344. box.W -= border
  345. box.H -= border
  346. if w.Background() != render.Invisible {
  347. e.DrawBox(w.Background(), box)
  348. }
  349. }
  350. // Margin returns the margin width.
  351. func (w *BaseWidget) Margin() int {
  352. return w.margin
  353. }
  354. // SetMargin sets the margin width.
  355. func (w *BaseWidget) SetMargin(v int) {
  356. w.margin = v
  357. }
  358. // Background returns the background color.
  359. func (w *BaseWidget) Background() render.Color {
  360. return w.background
  361. }
  362. // SetBackground sets the color.
  363. func (w *BaseWidget) SetBackground(c render.Color) {
  364. w.background = c
  365. }
  366. // Foreground returns the foreground color.
  367. func (w *BaseWidget) Foreground() render.Color {
  368. return w.foreground
  369. }
  370. // SetForeground sets the color.
  371. func (w *BaseWidget) SetForeground(c render.Color) {
  372. w.foreground = c
  373. }
  374. // BorderStyle returns the border style.
  375. func (w *BaseWidget) BorderStyle() BorderStyle {
  376. return w.borderStyle
  377. }
  378. // SetBorderStyle sets the border style.
  379. func (w *BaseWidget) SetBorderStyle(v BorderStyle) {
  380. w.borderStyle = v
  381. }
  382. // BorderColor returns the border color, or defaults to the background color.
  383. func (w *BaseWidget) BorderColor() render.Color {
  384. if w.borderColor == render.Invisible {
  385. return w.Background()
  386. }
  387. return w.borderColor
  388. }
  389. // SetBorderColor sets the border color.
  390. func (w *BaseWidget) SetBorderColor(c render.Color) {
  391. w.borderColor = c
  392. }
  393. // BorderSize returns the border thickness.
  394. func (w *BaseWidget) BorderSize() int {
  395. return w.borderSize
  396. }
  397. // SetBorderSize sets the border thickness.
  398. func (w *BaseWidget) SetBorderSize(v int) {
  399. w.borderSize = v
  400. }
  401. // OutlineColor returns the background color.
  402. func (w *BaseWidget) OutlineColor() render.Color {
  403. return w.outlineColor
  404. }
  405. // SetOutlineColor sets the color.
  406. func (w *BaseWidget) SetOutlineColor(c render.Color) {
  407. w.outlineColor = c
  408. }
  409. // OutlineSize returns the outline thickness.
  410. func (w *BaseWidget) OutlineSize() int {
  411. return w.outlineSize
  412. }
  413. // SetOutlineSize sets the outline thickness.
  414. func (w *BaseWidget) SetOutlineSize(v int) {
  415. w.outlineSize = v
  416. }
  417. // Compute calls the base widget's Compute function, which just triggers
  418. // events on widgets that want to be notified when the widget computes.
  419. func (w *BaseWidget) Compute(e render.Engine) {
  420. w.Event(Compute, EventData{
  421. Engine: e,
  422. })
  423. }
  424. // Present calls the base widget's Present function, which just triggers
  425. // events on widgets that want to be notified when the widget presents.
  426. func (w *BaseWidget) Present(e render.Engine, p render.Point) {
  427. w.Event(Present, EventData{
  428. Point: p,
  429. Engine: e,
  430. })
  431. }
  432. // Event is called internally by Doodle to trigger an event.
  433. // Handlers can return ErrStopPropagation to prevent further widgets being
  434. // notified of events.
  435. func (w *BaseWidget) Event(event Event, e EventData) error {
  436. if handlers, ok := w.handlers[event]; ok {
  437. for _, fn := range handlers {
  438. res := fn(e)
  439. if res == ErrStopPropagation {
  440. return res
  441. }
  442. }
  443. }
  444. return ErrNoEventHandler
  445. }
  446. // Handle an event in the widget.
  447. func (w *BaseWidget) Handle(event Event, fn func(EventData) error) {
  448. if w.handlers == nil {
  449. w.handlers = map[Event][]func(EventData) error{}
  450. }
  451. if _, ok := w.handlers[event]; !ok {
  452. w.handlers[event] = []func(EventData) error{}
  453. }
  454. w.handlers[event] = append(w.handlers[event], fn)
  455. }
  456. // OnMouseOut should be overridden on widgets who want this event.
  457. func (w *BaseWidget) OnMouseOut(render.Point) {}