split.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. package container
  2. import (
  3. "fyne.io/fyne/v2"
  4. "fyne.io/fyne/v2/canvas"
  5. "fyne.io/fyne/v2/driver/desktop"
  6. "fyne.io/fyne/v2/theme"
  7. "fyne.io/fyne/v2/widget"
  8. )
  9. // Declare conformity with CanvasObject interface
  10. var _ fyne.CanvasObject = (*Split)(nil)
  11. // Split defines a container whose size is split between two children.
  12. //
  13. // Since: 1.4
  14. type Split struct {
  15. widget.BaseWidget
  16. Offset float64
  17. Horizontal bool
  18. Leading fyne.CanvasObject
  19. Trailing fyne.CanvasObject
  20. }
  21. // NewHSplit creates a horizontally arranged container with the specified leading and trailing elements.
  22. // A vertical split bar that can be dragged will be added between the elements.
  23. //
  24. // Since: 1.4
  25. func NewHSplit(leading, trailing fyne.CanvasObject) *Split {
  26. return newSplitContainer(true, leading, trailing)
  27. }
  28. // NewVSplit creates a vertically arranged container with the specified top and bottom elements.
  29. // A horizontal split bar that can be dragged will be added between the elements.
  30. //
  31. // Since: 1.4
  32. func NewVSplit(top, bottom fyne.CanvasObject) *Split {
  33. return newSplitContainer(false, top, bottom)
  34. }
  35. func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Split {
  36. s := &Split{
  37. Offset: 0.5, // Sensible default, can be overridden with SetOffset
  38. Horizontal: horizontal,
  39. Leading: leading,
  40. Trailing: trailing,
  41. }
  42. s.BaseWidget.ExtendBaseWidget(s)
  43. return s
  44. }
  45. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  46. func (s *Split) CreateRenderer() fyne.WidgetRenderer {
  47. s.BaseWidget.ExtendBaseWidget(s)
  48. d := newDivider(s)
  49. return &splitContainerRenderer{
  50. split: s,
  51. divider: d,
  52. objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
  53. }
  54. }
  55. // ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
  56. //
  57. // Deprecated: Support for extending containers is being removed
  58. func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
  59. s.BaseWidget.ExtendBaseWidget(wid)
  60. }
  61. // SetOffset sets the offset (0.0 to 1.0) of the Split divider.
  62. // 0.0 - Leading is min size, Trailing uses all remaining space.
  63. // 0.5 - Leading & Trailing equally share the available space.
  64. // 1.0 - Trailing is min size, Leading uses all remaining space.
  65. func (s *Split) SetOffset(offset float64) {
  66. if s.Offset == offset {
  67. return
  68. }
  69. s.Offset = offset
  70. s.Refresh()
  71. }
  72. var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
  73. type splitContainerRenderer struct {
  74. split *Split
  75. divider *divider
  76. objects []fyne.CanvasObject
  77. }
  78. func (r *splitContainerRenderer) Destroy() {
  79. }
  80. func (r *splitContainerRenderer) Layout(size fyne.Size) {
  81. var dividerPos, leadingPos, trailingPos fyne.Position
  82. var dividerSize, leadingSize, trailingSize fyne.Size
  83. if r.split.Horizontal {
  84. lw, tw := r.computeSplitLengths(size.Width, r.minLeadingWidth(), r.minTrailingWidth())
  85. leadingPos.X = 0
  86. leadingSize.Width = lw
  87. leadingSize.Height = size.Height
  88. dividerPos.X = lw
  89. dividerSize.Width = dividerThickness()
  90. dividerSize.Height = size.Height
  91. trailingPos.X = lw + dividerSize.Width
  92. trailingSize.Width = tw
  93. trailingSize.Height = size.Height
  94. } else {
  95. lh, th := r.computeSplitLengths(size.Height, r.minLeadingHeight(), r.minTrailingHeight())
  96. leadingPos.Y = 0
  97. leadingSize.Width = size.Width
  98. leadingSize.Height = lh
  99. dividerPos.Y = lh
  100. dividerSize.Width = size.Width
  101. dividerSize.Height = dividerThickness()
  102. trailingPos.Y = lh + dividerSize.Height
  103. trailingSize.Width = size.Width
  104. trailingSize.Height = th
  105. }
  106. r.divider.Move(dividerPos)
  107. r.divider.Resize(dividerSize)
  108. r.split.Leading.Move(leadingPos)
  109. r.split.Leading.Resize(leadingSize)
  110. r.split.Trailing.Move(trailingPos)
  111. r.split.Trailing.Resize(trailingSize)
  112. canvas.Refresh(r.divider)
  113. }
  114. func (r *splitContainerRenderer) MinSize() fyne.Size {
  115. s := fyne.NewSize(0, 0)
  116. for _, o := range r.objects {
  117. min := o.MinSize()
  118. if r.split.Horizontal {
  119. s.Width += min.Width
  120. s.Height = fyne.Max(s.Height, min.Height)
  121. } else {
  122. s.Width = fyne.Max(s.Width, min.Width)
  123. s.Height += min.Height
  124. }
  125. }
  126. return s
  127. }
  128. func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
  129. return r.objects
  130. }
  131. func (r *splitContainerRenderer) Refresh() {
  132. r.objects[0] = r.split.Leading
  133. // [1] is divider which doesn't change
  134. r.objects[2] = r.split.Trailing
  135. r.Layout(r.split.Size())
  136. canvas.Refresh(r.split)
  137. }
  138. func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
  139. available := float64(total - dividerThickness())
  140. if available <= 0 {
  141. return 0, 0
  142. }
  143. ld := float64(lMin)
  144. tr := float64(tMin)
  145. offset := r.split.Offset
  146. min := ld / available
  147. max := 1 - tr/available
  148. if min <= max {
  149. if offset < min {
  150. offset = min
  151. }
  152. if offset > max {
  153. offset = max
  154. }
  155. } else {
  156. offset = ld / (ld + tr)
  157. }
  158. ld = offset * available
  159. tr = available - ld
  160. return float32(ld), float32(tr)
  161. }
  162. func (r *splitContainerRenderer) minLeadingWidth() float32 {
  163. if r.split.Leading.Visible() {
  164. return r.split.Leading.MinSize().Width
  165. }
  166. return 0
  167. }
  168. func (r *splitContainerRenderer) minLeadingHeight() float32 {
  169. if r.split.Leading.Visible() {
  170. return r.split.Leading.MinSize().Height
  171. }
  172. return 0
  173. }
  174. func (r *splitContainerRenderer) minTrailingWidth() float32 {
  175. if r.split.Trailing.Visible() {
  176. return r.split.Trailing.MinSize().Width
  177. }
  178. return 0
  179. }
  180. func (r *splitContainerRenderer) minTrailingHeight() float32 {
  181. if r.split.Trailing.Visible() {
  182. return r.split.Trailing.MinSize().Height
  183. }
  184. return 0
  185. }
  186. // Declare conformity with interfaces
  187. var _ fyne.CanvasObject = (*divider)(nil)
  188. var _ fyne.Draggable = (*divider)(nil)
  189. var _ desktop.Cursorable = (*divider)(nil)
  190. var _ desktop.Hoverable = (*divider)(nil)
  191. type divider struct {
  192. widget.BaseWidget
  193. split *Split
  194. hovered bool
  195. }
  196. func newDivider(split *Split) *divider {
  197. d := &divider{
  198. split: split,
  199. }
  200. d.ExtendBaseWidget(d)
  201. return d
  202. }
  203. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  204. func (d *divider) CreateRenderer() fyne.WidgetRenderer {
  205. d.ExtendBaseWidget(d)
  206. background := canvas.NewRectangle(theme.ShadowColor())
  207. foreground := canvas.NewRectangle(theme.ForegroundColor())
  208. return &dividerRenderer{
  209. divider: d,
  210. background: background,
  211. foreground: foreground,
  212. objects: []fyne.CanvasObject{background, foreground},
  213. }
  214. }
  215. func (d *divider) Cursor() desktop.Cursor {
  216. if d.split.Horizontal {
  217. return desktop.HResizeCursor
  218. }
  219. return desktop.VResizeCursor
  220. }
  221. func (d *divider) DragEnd() {
  222. }
  223. func (d *divider) Dragged(event *fyne.DragEvent) {
  224. offset := d.split.Offset
  225. if d.split.Horizontal {
  226. if leadingRatio := float64(d.split.Leading.Size().Width) / float64(d.split.Size().Width); offset < leadingRatio {
  227. offset = leadingRatio
  228. }
  229. if trailingRatio := 1. - (float64(d.split.Trailing.Size().Width) / float64(d.split.Size().Width)); offset > trailingRatio {
  230. offset = trailingRatio
  231. }
  232. offset += float64(event.Dragged.DX) / float64(d.split.Size().Width)
  233. } else {
  234. if leadingRatio := float64(d.split.Leading.Size().Height) / float64(d.split.Size().Height); offset < leadingRatio {
  235. offset = leadingRatio
  236. }
  237. if trailingRatio := 1. - (float64(d.split.Trailing.Size().Height) / float64(d.split.Size().Height)); offset > trailingRatio {
  238. offset = trailingRatio
  239. }
  240. offset += float64(event.Dragged.DY) / float64(d.split.Size().Height)
  241. }
  242. d.split.SetOffset(offset)
  243. }
  244. func (d *divider) MouseIn(event *desktop.MouseEvent) {
  245. d.hovered = true
  246. d.split.Refresh()
  247. }
  248. func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
  249. func (d *divider) MouseOut() {
  250. d.hovered = false
  251. d.split.Refresh()
  252. }
  253. var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
  254. type dividerRenderer struct {
  255. divider *divider
  256. background *canvas.Rectangle
  257. foreground *canvas.Rectangle
  258. objects []fyne.CanvasObject
  259. }
  260. func (r *dividerRenderer) Destroy() {
  261. }
  262. func (r *dividerRenderer) Layout(size fyne.Size) {
  263. r.background.Resize(size)
  264. var x, y, w, h float32
  265. if r.divider.split.Horizontal {
  266. x = (dividerThickness() - handleThickness()) / 2
  267. y = (size.Height - handleLength()) / 2
  268. w = handleThickness()
  269. h = handleLength()
  270. } else {
  271. x = (size.Width - handleLength()) / 2
  272. y = (dividerThickness() - handleThickness()) / 2
  273. w = handleLength()
  274. h = handleThickness()
  275. }
  276. r.foreground.Move(fyne.NewPos(x, y))
  277. r.foreground.Resize(fyne.NewSize(w, h))
  278. }
  279. func (r *dividerRenderer) MinSize() fyne.Size {
  280. if r.divider.split.Horizontal {
  281. return fyne.NewSize(dividerThickness(), dividerLength())
  282. }
  283. return fyne.NewSize(dividerLength(), dividerThickness())
  284. }
  285. func (r *dividerRenderer) Objects() []fyne.CanvasObject {
  286. return r.objects
  287. }
  288. func (r *dividerRenderer) Refresh() {
  289. if r.divider.hovered {
  290. r.background.FillColor = theme.HoverColor()
  291. } else {
  292. r.background.FillColor = theme.ShadowColor()
  293. }
  294. r.background.Refresh()
  295. r.foreground.FillColor = theme.ForegroundColor()
  296. r.foreground.Refresh()
  297. r.Layout(r.divider.Size())
  298. }
  299. func dividerThickness() float32 {
  300. return theme.Padding() * 2
  301. }
  302. func dividerLength() float32 {
  303. return theme.Padding() * 6
  304. }
  305. func handleThickness() float32 {
  306. return theme.Padding() / 2
  307. }
  308. func handleLength() float32 {
  309. return theme.Padding() * 4
  310. }