User interface toolkit for Go with support for SDL2 and HTML Canvas render targets.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 

211 satır
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. }