Utils.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package giu
  2. import (
  3. "fmt"
  4. "image"
  5. "image/color"
  6. "image/draw"
  7. "image/png"
  8. "log"
  9. "os"
  10. "path/filepath"
  11. "github.com/AllenDang/imgui-go"
  12. "github.com/pkg/browser"
  13. )
  14. // LoadImage loads image from file and returns *image.RGBA.
  15. func LoadImage(imgPath string) (*image.RGBA, error) {
  16. imgFile, err := os.Open(filepath.Clean(imgPath))
  17. if err != nil {
  18. return nil, fmt.Errorf("LoadImage: error opening image file %s: %w", imgPath, err)
  19. }
  20. defer func() {
  21. // nolint:govet // we want to reuse this err variable here
  22. if err := imgFile.Close(); err != nil {
  23. panic(fmt.Sprintf("error closing image file: %s", imgPath))
  24. }
  25. }()
  26. img, err := png.Decode(imgFile)
  27. if err != nil {
  28. return nil, fmt.Errorf("LoadImage: error decoding png image: %w", err)
  29. }
  30. return ImageToRgba(img), nil
  31. }
  32. // ImageToRgba converts image.Image to *image.RGBA.
  33. func ImageToRgba(img image.Image) *image.RGBA {
  34. switch trueImg := img.(type) {
  35. case *image.RGBA:
  36. return trueImg
  37. default:
  38. rgba := image.NewRGBA(trueImg.Bounds())
  39. draw.Draw(rgba, trueImg.Bounds(), trueImg, image.Pt(0, 0), draw.Src)
  40. return rgba
  41. }
  42. }
  43. // ToVec4Color converts rgba color to imgui.Vec4.
  44. func ToVec4Color(col color.Color) imgui.Vec4 {
  45. const mask = 0xffff
  46. r, g, b, a := col.RGBA()
  47. return imgui.Vec4{
  48. X: float32(r) / mask,
  49. Y: float32(g) / mask,
  50. Z: float32(b) / mask,
  51. W: float32(a) / mask,
  52. }
  53. }
  54. // ToVec2 converts image.Point to imgui.Vec2.
  55. func ToVec2(pt image.Point) imgui.Vec2 {
  56. return imgui.Vec2{
  57. X: float32(pt.X),
  58. Y: float32(pt.Y),
  59. }
  60. }
  61. // Vec4ToRGBA converts imgui's Vec4 to golang rgba color.
  62. func Vec4ToRGBA(vec4 imgui.Vec4) color.RGBA {
  63. return color.RGBA{
  64. R: uint8(vec4.X * 255),
  65. G: uint8(vec4.Y * 255),
  66. B: uint8(vec4.Z * 255),
  67. A: uint8(vec4.W * 255),
  68. }
  69. }
  70. // Update updates giu app
  71. // it is done by default after each frame.
  72. // However because frames stops rendering, when no user
  73. // action is done, it may be necessary to
  74. // Update ui manually at some point.
  75. func Update() {
  76. if Context.isAlive {
  77. Context.platform.Update()
  78. }
  79. }
  80. // GetCursorScreenPos returns imgui drawing cursor on the screen.
  81. func GetCursorScreenPos() image.Point {
  82. pos := imgui.CursorScreenPos()
  83. return image.Pt(int(pos.X), int(pos.Y))
  84. }
  85. // SetCursorScreenPos sets imgui drawing cursor on the screen.
  86. func SetCursorScreenPos(pos image.Point) {
  87. imgui.SetCursorScreenPos(imgui.Vec2{X: float32(pos.X), Y: float32(pos.Y)})
  88. }
  89. // GetCursorPos gets imgui drawing cursor inside of current window.
  90. func GetCursorPos() image.Point {
  91. pos := imgui.CursorPos()
  92. return image.Pt(int(pos.X), int(pos.Y))
  93. }
  94. // SetCursorPos sets imgui drawing cursor inside of current window.
  95. func SetCursorPos(pos image.Point) {
  96. imgui.SetCursorPos(imgui.Vec2{X: float32(pos.X), Y: float32(pos.Y)})
  97. }
  98. // GetMousePos returns mouse position.
  99. func GetMousePos() image.Point {
  100. pos := imgui.MousePos()
  101. return image.Pt(int(pos.X), int(pos.Y))
  102. }
  103. // GetAvailableRegion returns region available for rendering.
  104. // it is always WindowSize-WindowPadding*2.
  105. func GetAvailableRegion() (width, height float32) {
  106. region := imgui.ContentRegionAvail()
  107. return region.X, region.Y
  108. }
  109. // CalcTextSize calls CalcTextSizeV(text, false, -1).
  110. func CalcTextSize(text string) (width, height float32) {
  111. return CalcTextSizeV(text, false, -1)
  112. }
  113. // CalcTextSizeV calculates text dimensions.
  114. func CalcTextSizeV(text string, hideAfterDoubleHash bool, wrapWidth float32) (w, h float32) {
  115. size := imgui.CalcTextSize(text, hideAfterDoubleHash, wrapWidth)
  116. return size.X, size.Y
  117. }
  118. // SetNextWindowSize sets size of the next window.
  119. func SetNextWindowSize(width, height float32) {
  120. imgui.SetNextWindowSize(imgui.Vec2{X: width, Y: height})
  121. }
  122. // ExecCondition represents imgui.Condition.
  123. type ExecCondition imgui.Condition
  124. // imgui conditions.
  125. const (
  126. ConditionAlways ExecCondition = ExecCondition(imgui.ConditionAlways)
  127. ConditionOnce ExecCondition = ExecCondition(imgui.ConditionOnce)
  128. ConditionFirstUseEver ExecCondition = ExecCondition(imgui.ConditionFirstUseEver)
  129. ConditionAppearing ExecCondition = ExecCondition(imgui.ConditionAppearing)
  130. )
  131. // SetNextWindowPos sets position of next window.
  132. func SetNextWindowPos(x, y float32) {
  133. imgui.SetNextWindowPos(imgui.Vec2{X: x, Y: y})
  134. }
  135. // SetNextWindowSizeV does similar to SetNextWIndowSize but allows to specify imgui.Condition.
  136. func SetNextWindowSizeV(width, height float32, condition ExecCondition) {
  137. imgui.SetNextWindowSizeV(
  138. imgui.Vec2{
  139. X: width,
  140. Y: height,
  141. },
  142. imgui.Condition(condition),
  143. )
  144. }
  145. // SetItemDefaultFocus set the item focused by default.
  146. func SetItemDefaultFocus() {
  147. imgui.SetItemDefaultFocus()
  148. }
  149. // SetKeyboardFocusHere sets keyboard focus at *NEXT* widget.
  150. func SetKeyboardFocusHere() {
  151. SetKeyboardFocusHereV(0)
  152. }
  153. // SetKeyboardFocusHereV sets keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget.
  154. func SetKeyboardFocusHereV(i int) {
  155. imgui.SetKeyboardFocusHereV(i)
  156. }
  157. // PushClipRect pushes a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
  158. func PushClipRect(clipRectMin, clipRectMax image.Point, intersectWithClipRect bool) {
  159. imgui.PushClipRect(ToVec2(clipRectMin), ToVec2(clipRectMax), intersectWithClipRect)
  160. }
  161. // PopClipRect should be called to end PushClipRect.
  162. func PopClipRect() {
  163. imgui.PopClipRect()
  164. }
  165. // Assert checks if cond. If not cond, it alls golang panic.
  166. func Assert(cond bool, t, method, msg string, args ...any) {
  167. if !cond {
  168. fatal(t, method, msg, args...)
  169. }
  170. }
  171. func fatal(widgetName, method, message string, args ...any) {
  172. if widgetName != "" {
  173. widgetName = fmt.Sprintf("(*%s)", widgetName)
  174. }
  175. log.Panicf("giu: %s.%s: %s", widgetName, method, fmt.Sprintf(message, args...))
  176. }
  177. // OpenURL opens `url` in default browser.
  178. func OpenURL(url string) {
  179. if err := browser.OpenURL(url); err != nil {
  180. log.Printf("Error opening %s: %v", url, err)
  181. }
  182. }