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.
 
 
 
 
 

188 lines
4.1 KiB

  1. // +build !js
  2. package ui
  3. import (
  4. "fmt"
  5. "time"
  6. "git.kirsle.net/go/render"
  7. "git.kirsle.net/go/render/event"
  8. "git.kirsle.net/go/render/sdl"
  9. )
  10. // Target frames per second for the MainWindow to render at.
  11. var (
  12. FPS = 60
  13. )
  14. // Default width and height for MainWindow.
  15. var (
  16. DefaultWidth = 640
  17. DefaultHeight = 480
  18. )
  19. // MainWindow is the parent window of a UI application.
  20. type MainWindow struct {
  21. Engine render.Engine
  22. supervisor *Supervisor
  23. frame *Frame
  24. loopCallbacks []func(*event.State)
  25. w int
  26. h int
  27. }
  28. // NewMainWindow initializes the MainWindow. You should probably only have one
  29. // of these per application. Dimensions are the width and height of the window.
  30. //
  31. // Example: NewMainWindow("Title Bar") // default 640x480 window
  32. // NewMainWindow("Title", 800, 600) // both required
  33. func NewMainWindow(title string, dimensions ...int) (*MainWindow, error) {
  34. var (
  35. width = DefaultWidth
  36. height = DefaultHeight
  37. )
  38. if len(dimensions) > 0 {
  39. if len(dimensions) != 2 {
  40. return nil, fmt.Errorf("provide width and height dimensions, like NewMainWindow(title, 800, 600)")
  41. }
  42. width, height = dimensions[0], dimensions[1]
  43. }
  44. mw := &MainWindow{
  45. w: width,
  46. h: height,
  47. supervisor: NewSupervisor(),
  48. loopCallbacks: []func(*event.State){},
  49. }
  50. mw.Engine = sdl.New(
  51. title,
  52. mw.w,
  53. mw.h,
  54. )
  55. if err := mw.Engine.Setup(); err != nil {
  56. return nil, err
  57. }
  58. // Add a default frame to the window.
  59. mw.frame = NewFrame("MainWindow Body")
  60. mw.frame.SetBackground(render.RGBA(0, 153, 255, 100))
  61. mw.Add(mw.frame)
  62. // Compute initial window size.
  63. mw.resized()
  64. return mw, nil
  65. }
  66. // SetTitle changes the title of the window.
  67. func (mw *MainWindow) SetTitle(title string) {
  68. mw.Engine.SetTitle(title)
  69. }
  70. // Add a child widget to the window.
  71. func (mw *MainWindow) Add(w Widget) {
  72. mw.supervisor.Add(w)
  73. }
  74. // Pack a child widget into the window's default frame.
  75. func (mw *MainWindow) Pack(w Widget, pack Pack) {
  76. mw.Add(w)
  77. mw.frame.Pack(w, pack)
  78. }
  79. // Place a child widget into the window's default frame.
  80. func (mw *MainWindow) Place(w Widget, config Place) {
  81. mw.Add(w)
  82. mw.frame.Place(w, config)
  83. }
  84. // Frame returns the window's main frame, if needed.
  85. func (mw *MainWindow) Frame() *Frame {
  86. return mw.frame
  87. }
  88. // resized handles the window being resized.
  89. func (mw *MainWindow) resized() {
  90. mw.frame.Resize(render.Rect{
  91. W: mw.w,
  92. H: mw.h,
  93. })
  94. }
  95. // SetBackground changes the window's frame's background color.
  96. func (mw *MainWindow) SetBackground(color render.Color) {
  97. mw.frame.SetBackground(color)
  98. }
  99. // Present the window.
  100. func (mw *MainWindow) Present() {
  101. mw.supervisor.Present(mw.Engine)
  102. }
  103. // OnLoop registers a function to be called on every loop of the main window.
  104. // This enables your application to register global event handlers or whatnot.
  105. // The function is called between the event polling and the updating of any UI
  106. // elements.
  107. func (mw *MainWindow) OnLoop(callback func(*event.State)) {
  108. mw.loopCallbacks = append(mw.loopCallbacks, callback)
  109. }
  110. // MainLoop starts the main event loop and blocks until there's an error.
  111. func (mw *MainWindow) MainLoop() error {
  112. for true {
  113. if err := mw.Loop(); err != nil {
  114. return err
  115. }
  116. }
  117. return nil
  118. }
  119. // Loop does one loop of the UI.
  120. func (mw *MainWindow) Loop() error {
  121. mw.Engine.Clear(render.White)
  122. // Record how long this loop took.
  123. start := time.Now()
  124. // Poll for events.
  125. ev, err := mw.Engine.Poll()
  126. if err != nil {
  127. return fmt.Errorf("event poll error: %s", err)
  128. }
  129. if ev.WindowResized {
  130. w, h := mw.Engine.WindowSize()
  131. if w != mw.w || h != mw.h {
  132. mw.w = w
  133. mw.h = h
  134. mw.resized()
  135. }
  136. }
  137. // Ping any loop callbacks.
  138. for _, cb := range mw.loopCallbacks {
  139. cb(ev)
  140. }
  141. mw.frame.Compute(mw.Engine)
  142. // Render the child widgets.
  143. mw.supervisor.Loop(ev)
  144. mw.frame.Present(mw.Engine, mw.frame.Point())
  145. mw.Engine.Present()
  146. // Delay to maintain target frames per second.
  147. var delay uint32
  148. var targetFPS = 1000 / FPS
  149. elapsed := time.Now().Sub(start) / time.Millisecond
  150. if targetFPS-int(elapsed) > 0 {
  151. delay = uint32(targetFPS - int(elapsed))
  152. }
  153. mw.Engine.Delay(delay)
  154. return nil
  155. }