Alignment.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package giu
  2. import (
  3. "fmt"
  4. "image"
  5. "github.com/AllenDang/imgui-go"
  6. )
  7. // AlignmentType represents a bype of alignment to use with AlignSetter.
  8. type AlignmentType byte
  9. const (
  10. // AlignLeft is here just for clearity.
  11. // if set, no action is taken so don't use it.
  12. AlignLeft AlignmentType = iota
  13. // AlignCenter centers widget.
  14. AlignCenter
  15. // AlignRight aligns a widget to right side of window.
  16. AlignRight
  17. )
  18. // AlignManually allows to apply alignment manually.
  19. // As long as AlignSetter is really EXPERIMENTAL feature
  20. // and may fail randomly, the following method is supposed to
  21. // always work, as long as you set it up correctly.
  22. // To use it just pass a single widget with its exact width.
  23. // be sure to apply widget's size by using "Size" method!
  24. // forceApplyWidth argument allows you to ask giu to force-set width
  25. // of `widget`
  26. // NOTE that forcing width doesn't work for each widget type! For example
  27. // Button won't work because its size is set by argument to imgui call
  28. // not PushWidth api.
  29. func AlignManually(alignmentType AlignmentType, widget Widget, widgetWidth float32, forceApplyWidth bool) Widget {
  30. return Custom(func() {
  31. spacingX, _ := GetItemSpacing()
  32. availableW, _ := GetAvailableRegion()
  33. var dummyX float32
  34. switch alignmentType {
  35. case AlignLeft:
  36. widget.Build()
  37. return
  38. case AlignCenter:
  39. dummyX = (availableW-widgetWidth)/2 - spacingX
  40. case AlignRight:
  41. dummyX = availableW - widgetWidth - spacingX
  42. }
  43. Dummy(dummyX, 0).Build()
  44. if forceApplyWidth {
  45. PushItemWidth(widgetWidth)
  46. defer PopItemWidth()
  47. }
  48. imgui.SameLine()
  49. widget.Build()
  50. })
  51. }
  52. var _ Widget = &AlignmentSetter{}
  53. // AlignmentSetter allows to align to right / center a widget or widgets group.
  54. // NOTE: Because of AlignSetter uses experimental GetWidgetWidth,
  55. // it is experimental too.
  56. // usage: see examples/align
  57. //
  58. // list of known bugs:
  59. // - BUG: DatePickerWidget doesn't work properly
  60. // - BUG: there is some bug with SelectableWidget
  61. // - BUG: ComboWidget and ComboCustomWidgets doesn't work properly.
  62. type AlignmentSetter struct {
  63. alignType AlignmentType
  64. layout Layout
  65. id string
  66. }
  67. // Align sets widgets alignment.
  68. func Align(at AlignmentType) *AlignmentSetter {
  69. return &AlignmentSetter{
  70. alignType: at,
  71. id: GenAutoID("alignSetter"),
  72. }
  73. }
  74. // To sets a layout, alignment should be applied to.
  75. func (a *AlignmentSetter) To(widgets ...Widget) *AlignmentSetter {
  76. a.layout = Layout(widgets)
  77. return a
  78. }
  79. // ID allows to manually set AlignmentSetter ID
  80. // NOTE: there isn't any known reason to use this method, however
  81. // it is here for some random cases. YOU DON'T NEED TO USE IT
  82. // in normal conditions.
  83. func (a *AlignmentSetter) ID(id string) *AlignmentSetter {
  84. a.id = id
  85. return a
  86. }
  87. // Build implements Widget interface.
  88. func (a *AlignmentSetter) Build() {
  89. if a.layout == nil {
  90. return
  91. }
  92. a.layout.Range(func(item Widget) {
  93. // if item is inil, just skip it
  94. if item == nil {
  95. return
  96. }
  97. switch item.(type) {
  98. // ok, it doesn't make sense to align again :-)
  99. case *AlignmentSetter:
  100. item.Build()
  101. return
  102. // there is a bug with selectables and combos, so skip them for now
  103. case *SelectableWidget, *ComboWidget, *ComboCustomWidget:
  104. item.Build()
  105. return
  106. }
  107. currentPos := GetCursorPos()
  108. w := GetWidgetWidth(item)
  109. availableW, _ := GetAvailableRegion()
  110. // we need to increase available region by 2 * window padding (X),
  111. // because GetCursorPos considers it
  112. paddingW, _ := GetWindowPadding()
  113. availableW += 2 * paddingW
  114. // set cursor position to align the widget
  115. switch a.alignType {
  116. case AlignLeft:
  117. SetCursorPos(currentPos)
  118. case AlignCenter:
  119. SetCursorPos(image.Pt(int(availableW/2-w/2), currentPos.Y))
  120. case AlignRight:
  121. SetCursorPos(image.Pt(int(availableW-w), currentPos.Y))
  122. default:
  123. panic(fmt.Sprintf("giu: (*AlignSetter).Build: unknown align type %d", a.alignType))
  124. }
  125. // build aligned widget
  126. item.Build()
  127. })
  128. }
  129. // GetWidgetWidth returns a width of widget
  130. // NOTE: THIS IS A BETA SOLUTION and may contain bugs
  131. // in most cases, you may want to use supported by imgui GetItemRectSize.
  132. // There is an upstream issue for this problem:
  133. // https://github.com/ocornut/imgui/issues/3714
  134. //
  135. // This function is just a workaround used in giu.
  136. //
  137. // NOTE: user-definied widgets, which contains more than one
  138. // giu widget will be processed incorrectly (only width of the last built
  139. // widget will be processed)
  140. //
  141. // here is a list of known bugs:
  142. // - BUG: user can interact with invisible widget (created by GetWidgetWidth)
  143. // - https://github.com/AllenDang/giu/issues/341
  144. // - https://github.com/ocornut/imgui/issues/4588
  145. //
  146. // if you find anything else, please report it on
  147. // https://github.com/AllenDang/giu Any contribution is appreciated!
  148. func GetWidgetWidth(w Widget) (result float32) {
  149. imgui.PushID(GenAutoID("GetWIdgetWidthMeasurement"))
  150. defer imgui.PopID()
  151. // save cursor position before rendering
  152. currentPos := GetCursorPos()
  153. // render widget in `dry` mode
  154. imgui.PushStyleVarFloat(imgui.StyleVarAlpha, 0)
  155. w.Build()
  156. imgui.PopStyleVar()
  157. // save widget's width
  158. // check cursor position
  159. imgui.SameLine()
  160. spacingW, _ := GetItemSpacing()
  161. result = float32(GetCursorPos().X-currentPos.X) - spacingW
  162. SetCursorPos(currentPos)
  163. return result
  164. }