split.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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. startDragOff *fyne.Position
  196. currentDragPos fyne.Position
  197. }
  198. func newDivider(split *Split) *divider {
  199. d := &divider{
  200. split: split,
  201. }
  202. d.ExtendBaseWidget(d)
  203. return d
  204. }
  205. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  206. func (d *divider) CreateRenderer() fyne.WidgetRenderer {
  207. d.ExtendBaseWidget(d)
  208. background := canvas.NewRectangle(theme.ShadowColor())
  209. foreground := canvas.NewRectangle(theme.ForegroundColor())
  210. return &dividerRenderer{
  211. divider: d,
  212. background: background,
  213. foreground: foreground,
  214. objects: []fyne.CanvasObject{background, foreground},
  215. }
  216. }
  217. func (d *divider) Cursor() desktop.Cursor {
  218. if d.split.Horizontal {
  219. return desktop.HResizeCursor
  220. }
  221. return desktop.VResizeCursor
  222. }
  223. func (d *divider) DragEnd() {
  224. d.startDragOff = nil
  225. }
  226. func (d *divider) Dragged(e *fyne.DragEvent) {
  227. if d.startDragOff == nil {
  228. d.currentDragPos = d.Position().Add(e.Position)
  229. start := e.Position.Subtract(e.Dragged)
  230. d.startDragOff = &start
  231. } else {
  232. d.currentDragPos = d.currentDragPos.Add(e.Dragged)
  233. }
  234. x, y := d.currentDragPos.Components()
  235. var offset, leadingRatio, trailingRatio float64
  236. if d.split.Horizontal {
  237. widthFree := float64(d.split.Size().Width - dividerThickness())
  238. leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
  239. trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
  240. offset = float64(x-d.startDragOff.X) / widthFree
  241. } else {
  242. heightFree := float64(d.split.Size().Height - dividerThickness())
  243. leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
  244. trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
  245. offset = float64(y-d.startDragOff.Y) / heightFree
  246. }
  247. if offset < leadingRatio {
  248. offset = leadingRatio
  249. }
  250. if offset > trailingRatio {
  251. offset = trailingRatio
  252. }
  253. d.split.SetOffset(offset)
  254. }
  255. func (d *divider) MouseIn(event *desktop.MouseEvent) {
  256. d.hovered = true
  257. d.split.Refresh()
  258. }
  259. func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
  260. func (d *divider) MouseOut() {
  261. d.hovered = false
  262. d.split.Refresh()
  263. }
  264. var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
  265. type dividerRenderer struct {
  266. divider *divider
  267. background *canvas.Rectangle
  268. foreground *canvas.Rectangle
  269. objects []fyne.CanvasObject
  270. }
  271. func (r *dividerRenderer) Destroy() {
  272. }
  273. func (r *dividerRenderer) Layout(size fyne.Size) {
  274. r.background.Resize(size)
  275. var x, y, w, h float32
  276. if r.divider.split.Horizontal {
  277. x = (dividerThickness() - handleThickness()) / 2
  278. y = (size.Height - handleLength()) / 2
  279. w = handleThickness()
  280. h = handleLength()
  281. } else {
  282. x = (size.Width - handleLength()) / 2
  283. y = (dividerThickness() - handleThickness()) / 2
  284. w = handleLength()
  285. h = handleThickness()
  286. }
  287. r.foreground.Move(fyne.NewPos(x, y))
  288. r.foreground.Resize(fyne.NewSize(w, h))
  289. }
  290. func (r *dividerRenderer) MinSize() fyne.Size {
  291. if r.divider.split.Horizontal {
  292. return fyne.NewSize(dividerThickness(), dividerLength())
  293. }
  294. return fyne.NewSize(dividerLength(), dividerThickness())
  295. }
  296. func (r *dividerRenderer) Objects() []fyne.CanvasObject {
  297. return r.objects
  298. }
  299. func (r *dividerRenderer) Refresh() {
  300. if r.divider.hovered {
  301. r.background.FillColor = theme.HoverColor()
  302. } else {
  303. r.background.FillColor = theme.ShadowColor()
  304. }
  305. r.background.Refresh()
  306. r.foreground.FillColor = theme.ForegroundColor()
  307. r.foreground.Refresh()
  308. r.Layout(r.divider.Size())
  309. }
  310. func dividerThickness() float32 {
  311. return theme.Padding() * 2
  312. }
  313. func dividerLength() float32 {
  314. return theme.Padding() * 6
  315. }
  316. func handleThickness() float32 {
  317. return theme.Padding() / 2
  318. }
  319. func handleLength() float32 {
  320. return theme.Padding() * 4
  321. }