User interface toolkit for Go with support for SDL2 and HTML Canvas render targets.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 

211 rindas
5.2 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. // Compute initial window size.
  62. mw.resized()
  63. return mw, nil
  64. }
  65. // SetTitle changes the title of the window.
  66. func (mw *MainWindow) SetTitle(title string) {
  67. mw.Engine.SetTitle(title)
  68. }
  69. // Add a child widget to the window's supervisor. This alone does not make the
  70. // child widget render each frame; use Pack, Place or Attach for that.
  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. // Attach a child widget to the window without its position managed. The
  85. // widget's Present() method will be called each time the window Presents, but
  86. // the positioning of the child widget must be handled manually by the caller.
  87. //
  88. // Pack and Place are usually the methods you want to use to put a child widget
  89. // into the window. One example use case for Attach is when you want to create
  90. // child Window widgets which can be dragged by their title bars; their dynamic
  91. // drag-drop positioning is best managed manually, and Pack or Place would
  92. // interfere with their positioning otherwise.
  93. //
  94. // This also calls .Add() to add the widget to the MainWindow's Supervisor.
  95. //
  96. // Implementation details:
  97. // - Adds the widget to the MainWindow's Supervisor.
  98. // - Calls Frame.Add(w) so it will Present each time the main frame Presents.
  99. // - Calls w.Compute() on your widget so it can calculate its initial size.
  100. func (mw *MainWindow) Attach(w Widget) {
  101. mw.Add(w)
  102. mw.frame.Add(w)
  103. w.Compute(mw.Engine)
  104. }
  105. // Frame returns the window's main frame, if needed.
  106. func (mw *MainWindow) Frame() *Frame {
  107. return mw.frame
  108. }
  109. // Supervisor returns the window's Supervisor instance.
  110. func (mw *MainWindow) Supervisor() *Supervisor {
  111. return mw.supervisor
  112. }
  113. // resized handles the window being resized.
  114. func (mw *MainWindow) resized() {
  115. mw.frame.Resize(render.Rect{
  116. W: mw.w,
  117. H: mw.h,
  118. })
  119. }
  120. // SetBackground changes the window's frame's background color.
  121. func (mw *MainWindow) SetBackground(color render.Color) {
  122. mw.frame.SetBackground(color)
  123. }
  124. // OnLoop registers a function to be called on every loop of the main window.
  125. // This enables your application to register global event handlers or whatnot.
  126. // The function is called between the event polling and the updating of any UI
  127. // elements.
  128. func (mw *MainWindow) OnLoop(callback func(*event.State)) {
  129. mw.loopCallbacks = append(mw.loopCallbacks, callback)
  130. }
  131. // MainLoop starts the main event loop and blocks until there's an error.
  132. func (mw *MainWindow) MainLoop() error {
  133. for true {
  134. if err := mw.Loop(); err != nil {
  135. return err
  136. }
  137. }
  138. return nil
  139. }
  140. // Loop does one loop of the UI.
  141. func (mw *MainWindow) Loop() error {
  142. mw.Engine.Clear(render.White)
  143. // Record how long this loop took.
  144. start := time.Now()
  145. // Poll for events.
  146. ev, err := mw.Engine.Poll()
  147. if err != nil {
  148. return fmt.Errorf("event poll error: %s", err)
  149. }
  150. if ev.WindowResized {
  151. w, h := mw.Engine.WindowSize()
  152. if w != mw.w || h != mw.h {
  153. mw.w = w
  154. mw.h = h
  155. mw.resized()
  156. }
  157. }
  158. // Ping any loop callbacks.
  159. for _, cb := range mw.loopCallbacks {
  160. cb(ev)
  161. }
  162. mw.frame.Compute(mw.Engine)
  163. // Render the child widgets.
  164. mw.supervisor.Loop(ev)
  165. mw.frame.Present(mw.Engine, mw.frame.Point())
  166. mw.supervisor.Present(mw.Engine)
  167. mw.Engine.Present()
  168. // Delay to maintain target frames per second.
  169. var delay uint32
  170. var targetFPS = 1000 / FPS
  171. elapsed := time.Now().Sub(start) / time.Millisecond
  172. if targetFPS-int(elapsed) > 0 {
  173. delay = uint32(targetFPS - int(elapsed))
  174. }
  175. mw.Engine.Delay(delay)
  176. return nil
  177. }