| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- package container
- import (
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/driver/desktop"
- "fyne.io/fyne/v2/theme"
- "fyne.io/fyne/v2/widget"
- )
- // Declare conformity with CanvasObject interface
- var _ fyne.CanvasObject = (*Split)(nil)
- // Split defines a container whose size is split between two children.
- //
- // Since: 1.4
- type Split struct {
- widget.BaseWidget
- Offset float64
- Horizontal bool
- Leading fyne.CanvasObject
- Trailing fyne.CanvasObject
- }
- // NewHSplit creates a horizontally arranged container with the specified leading and trailing elements.
- // A vertical split bar that can be dragged will be added between the elements.
- //
- // Since: 1.4
- func NewHSplit(leading, trailing fyne.CanvasObject) *Split {
- return newSplitContainer(true, leading, trailing)
- }
- // NewVSplit creates a vertically arranged container with the specified top and bottom elements.
- // A horizontal split bar that can be dragged will be added between the elements.
- //
- // Since: 1.4
- func NewVSplit(top, bottom fyne.CanvasObject) *Split {
- return newSplitContainer(false, top, bottom)
- }
- func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Split {
- s := &Split{
- Offset: 0.5, // Sensible default, can be overridden with SetOffset
- Horizontal: horizontal,
- Leading: leading,
- Trailing: trailing,
- }
- s.BaseWidget.ExtendBaseWidget(s)
- return s
- }
- // CreateRenderer is a private method to Fyne which links this widget to its renderer
- func (s *Split) CreateRenderer() fyne.WidgetRenderer {
- s.BaseWidget.ExtendBaseWidget(s)
- d := newDivider(s)
- return &splitContainerRenderer{
- split: s,
- divider: d,
- objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
- }
- }
- // ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
- //
- // Deprecated: Support for extending containers is being removed
- func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
- s.BaseWidget.ExtendBaseWidget(wid)
- }
- // SetOffset sets the offset (0.0 to 1.0) of the Split divider.
- // 0.0 - Leading is min size, Trailing uses all remaining space.
- // 0.5 - Leading & Trailing equally share the available space.
- // 1.0 - Trailing is min size, Leading uses all remaining space.
- func (s *Split) SetOffset(offset float64) {
- if s.Offset == offset {
- return
- }
- s.Offset = offset
- s.Refresh()
- }
- var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
- type splitContainerRenderer struct {
- split *Split
- divider *divider
- objects []fyne.CanvasObject
- }
- func (r *splitContainerRenderer) Destroy() {
- }
- func (r *splitContainerRenderer) Layout(size fyne.Size) {
- var dividerPos, leadingPos, trailingPos fyne.Position
- var dividerSize, leadingSize, trailingSize fyne.Size
- if r.split.Horizontal {
- lw, tw := r.computeSplitLengths(size.Width, r.minLeadingWidth(), r.minTrailingWidth())
- leadingPos.X = 0
- leadingSize.Width = lw
- leadingSize.Height = size.Height
- dividerPos.X = lw
- dividerSize.Width = dividerThickness()
- dividerSize.Height = size.Height
- trailingPos.X = lw + dividerSize.Width
- trailingSize.Width = tw
- trailingSize.Height = size.Height
- } else {
- lh, th := r.computeSplitLengths(size.Height, r.minLeadingHeight(), r.minTrailingHeight())
- leadingPos.Y = 0
- leadingSize.Width = size.Width
- leadingSize.Height = lh
- dividerPos.Y = lh
- dividerSize.Width = size.Width
- dividerSize.Height = dividerThickness()
- trailingPos.Y = lh + dividerSize.Height
- trailingSize.Width = size.Width
- trailingSize.Height = th
- }
- r.divider.Move(dividerPos)
- r.divider.Resize(dividerSize)
- r.split.Leading.Move(leadingPos)
- r.split.Leading.Resize(leadingSize)
- r.split.Trailing.Move(trailingPos)
- r.split.Trailing.Resize(trailingSize)
- canvas.Refresh(r.divider)
- }
- func (r *splitContainerRenderer) MinSize() fyne.Size {
- s := fyne.NewSize(0, 0)
- for _, o := range r.objects {
- min := o.MinSize()
- if r.split.Horizontal {
- s.Width += min.Width
- s.Height = fyne.Max(s.Height, min.Height)
- } else {
- s.Width = fyne.Max(s.Width, min.Width)
- s.Height += min.Height
- }
- }
- return s
- }
- func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
- return r.objects
- }
- func (r *splitContainerRenderer) Refresh() {
- r.objects[0] = r.split.Leading
- // [1] is divider which doesn't change
- r.objects[2] = r.split.Trailing
- r.Layout(r.split.Size())
- canvas.Refresh(r.split)
- }
- func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
- available := float64(total - dividerThickness())
- if available <= 0 {
- return 0, 0
- }
- ld := float64(lMin)
- tr := float64(tMin)
- offset := r.split.Offset
- min := ld / available
- max := 1 - tr/available
- if min <= max {
- if offset < min {
- offset = min
- }
- if offset > max {
- offset = max
- }
- } else {
- offset = ld / (ld + tr)
- }
- ld = offset * available
- tr = available - ld
- return float32(ld), float32(tr)
- }
- func (r *splitContainerRenderer) minLeadingWidth() float32 {
- if r.split.Leading.Visible() {
- return r.split.Leading.MinSize().Width
- }
- return 0
- }
- func (r *splitContainerRenderer) minLeadingHeight() float32 {
- if r.split.Leading.Visible() {
- return r.split.Leading.MinSize().Height
- }
- return 0
- }
- func (r *splitContainerRenderer) minTrailingWidth() float32 {
- if r.split.Trailing.Visible() {
- return r.split.Trailing.MinSize().Width
- }
- return 0
- }
- func (r *splitContainerRenderer) minTrailingHeight() float32 {
- if r.split.Trailing.Visible() {
- return r.split.Trailing.MinSize().Height
- }
- return 0
- }
- // Declare conformity with interfaces
- var _ fyne.CanvasObject = (*divider)(nil)
- var _ fyne.Draggable = (*divider)(nil)
- var _ desktop.Cursorable = (*divider)(nil)
- var _ desktop.Hoverable = (*divider)(nil)
- type divider struct {
- widget.BaseWidget
- split *Split
- hovered bool
- startDragOff *fyne.Position
- currentDragPos fyne.Position
- }
- func newDivider(split *Split) *divider {
- d := ÷r{
- split: split,
- }
- d.ExtendBaseWidget(d)
- return d
- }
- // CreateRenderer is a private method to Fyne which links this widget to its renderer
- func (d *divider) CreateRenderer() fyne.WidgetRenderer {
- d.ExtendBaseWidget(d)
- background := canvas.NewRectangle(theme.ShadowColor())
- foreground := canvas.NewRectangle(theme.ForegroundColor())
- return ÷rRenderer{
- divider: d,
- background: background,
- foreground: foreground,
- objects: []fyne.CanvasObject{background, foreground},
- }
- }
- func (d *divider) Cursor() desktop.Cursor {
- if d.split.Horizontal {
- return desktop.HResizeCursor
- }
- return desktop.VResizeCursor
- }
- func (d *divider) DragEnd() {
- d.startDragOff = nil
- }
- func (d *divider) Dragged(e *fyne.DragEvent) {
- if d.startDragOff == nil {
- d.currentDragPos = d.Position().Add(e.Position)
- start := e.Position.Subtract(e.Dragged)
- d.startDragOff = &start
- } else {
- d.currentDragPos = d.currentDragPos.Add(e.Dragged)
- }
- x, y := d.currentDragPos.Components()
- var offset, leadingRatio, trailingRatio float64
- if d.split.Horizontal {
- widthFree := float64(d.split.Size().Width - dividerThickness())
- leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
- trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
- offset = float64(x-d.startDragOff.X) / widthFree
- } else {
- heightFree := float64(d.split.Size().Height - dividerThickness())
- leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
- trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
- offset = float64(y-d.startDragOff.Y) / heightFree
- }
- if offset < leadingRatio {
- offset = leadingRatio
- }
- if offset > trailingRatio {
- offset = trailingRatio
- }
- d.split.SetOffset(offset)
- }
- func (d *divider) MouseIn(event *desktop.MouseEvent) {
- d.hovered = true
- d.split.Refresh()
- }
- func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
- func (d *divider) MouseOut() {
- d.hovered = false
- d.split.Refresh()
- }
- var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
- type dividerRenderer struct {
- divider *divider
- background *canvas.Rectangle
- foreground *canvas.Rectangle
- objects []fyne.CanvasObject
- }
- func (r *dividerRenderer) Destroy() {
- }
- func (r *dividerRenderer) Layout(size fyne.Size) {
- r.background.Resize(size)
- var x, y, w, h float32
- if r.divider.split.Horizontal {
- x = (dividerThickness() - handleThickness()) / 2
- y = (size.Height - handleLength()) / 2
- w = handleThickness()
- h = handleLength()
- } else {
- x = (size.Width - handleLength()) / 2
- y = (dividerThickness() - handleThickness()) / 2
- w = handleLength()
- h = handleThickness()
- }
- r.foreground.Move(fyne.NewPos(x, y))
- r.foreground.Resize(fyne.NewSize(w, h))
- }
- func (r *dividerRenderer) MinSize() fyne.Size {
- if r.divider.split.Horizontal {
- return fyne.NewSize(dividerThickness(), dividerLength())
- }
- return fyne.NewSize(dividerLength(), dividerThickness())
- }
- func (r *dividerRenderer) Objects() []fyne.CanvasObject {
- return r.objects
- }
- func (r *dividerRenderer) Refresh() {
- if r.divider.hovered {
- r.background.FillColor = theme.HoverColor()
- } else {
- r.background.FillColor = theme.ShadowColor()
- }
- r.background.Refresh()
- r.foreground.FillColor = theme.ForegroundColor()
- r.foreground.Refresh()
- r.Layout(r.divider.Size())
- }
- func dividerThickness() float32 {
- return theme.Padding() * 2
- }
- func dividerLength() float32 {
- return theme.Padding() * 6
- }
- func handleThickness() float32 {
- return theme.Padding() / 2
- }
- func handleLength() float32 {
- return theme.Padding() * 4
- }
|