adam-gui/vendor/fyne.io/fyne/v2/container/doctabs.go

499 lines
12 KiB
Go
Raw Permalink Normal View History

2024-04-29 19:13:50 +02:00
package container
import (
"image/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
// Declare conformity with Widget interface.
var _ fyne.Widget = (*DocTabs)(nil)
// DocTabs container is used to display various pieces of content identified by tabs.
// The tabs contain text and/or an icon and allow the user to switch between the content specified in each TabItem.
// Each item is represented by a button at the edge of the container.
//
// Since: 2.1
type DocTabs struct {
widget.BaseWidget
Items []*TabItem
CreateTab func() *TabItem
CloseIntercept func(*TabItem)
OnClosed func(*TabItem)
OnSelected func(*TabItem)
OnUnselected func(*TabItem)
current int
location TabLocation
isTransitioning bool
popUpMenu *widget.PopUpMenu
}
// NewDocTabs creates a new tab container that allows the user to choose between various pieces of content.
//
// Since: 2.1
func NewDocTabs(items ...*TabItem) *DocTabs {
tabs := &DocTabs{}
tabs.ExtendBaseWidget(tabs)
tabs.SetItems(items)
return tabs
}
// Append adds a new TabItem to the end of the tab bar.
func (t *DocTabs) Append(item *TabItem) {
t.SetItems(append(t.Items, item))
}
// CreateRenderer is a private method to Fyne which links this widget to its renderer
//
// Implements: fyne.Widget
func (t *DocTabs) CreateRenderer() fyne.WidgetRenderer {
t.ExtendBaseWidget(t)
r := &docTabsRenderer{
baseTabsRenderer: baseTabsRenderer{
bar: &fyne.Container{},
divider: canvas.NewRectangle(theme.ShadowColor()),
indicator: canvas.NewRectangle(theme.PrimaryColor()),
},
docTabs: t,
scroller: NewScroll(&fyne.Container{}),
}
r.action = r.buildAllTabsButton()
r.create = r.buildCreateTabsButton()
r.tabs = t
r.box = NewHBox(r.create, r.action)
r.scroller.OnScrolled = func(offset fyne.Position) {
r.updateIndicator(false)
}
r.updateAllTabs()
r.updateCreateTab()
r.updateTabs()
r.updateIndicator(false)
r.applyTheme(t)
return r
}
// DisableIndex disables the TabItem at the specified index.
//
// Since: 2.3
func (t *DocTabs) DisableIndex(i int) {
disableIndex(t, i)
}
// DisableItem disables the specified TabItem.
//
// Since: 2.3
func (t *DocTabs) DisableItem(item *TabItem) {
disableItem(t, item)
}
// EnableIndex enables the TabItem at the specified index.
//
// Since: 2.3
func (t *DocTabs) EnableIndex(i int) {
enableIndex(t, i)
}
// EnableItem enables the specified TabItem.
//
// Since: 2.3
func (t *DocTabs) EnableItem(item *TabItem) {
enableItem(t, item)
}
// Hide hides the widget.
//
// Implements: fyne.CanvasObject
func (t *DocTabs) Hide() {
if t.popUpMenu != nil {
t.popUpMenu.Hide()
t.popUpMenu = nil
}
t.BaseWidget.Hide()
}
// MinSize returns the size that this widget should not shrink below
//
// Implements: fyne.CanvasObject
func (t *DocTabs) MinSize() fyne.Size {
t.ExtendBaseWidget(t)
return t.BaseWidget.MinSize()
}
// Remove tab by value.
func (t *DocTabs) Remove(item *TabItem) {
removeItem(t, item)
t.Refresh()
}
// RemoveIndex removes tab by index.
func (t *DocTabs) RemoveIndex(index int) {
removeIndex(t, index)
t.Refresh()
}
// Select sets the specified TabItem to be selected and its content visible.
func (t *DocTabs) Select(item *TabItem) {
selectItem(t, item)
t.Refresh()
}
// SelectIndex sets the TabItem at the specific index to be selected and its content visible.
func (t *DocTabs) SelectIndex(index int) {
selectIndex(t, index)
t.Refresh()
}
// Selected returns the currently selected TabItem.
func (t *DocTabs) Selected() *TabItem {
return selected(t)
}
// SelectedIndex returns the index of the currently selected TabItem.
func (t *DocTabs) SelectedIndex() int {
return t.current
}
// SetItems sets the containers items and refreshes.
func (t *DocTabs) SetItems(items []*TabItem) {
setItems(t, items)
t.Refresh()
}
// SetTabLocation sets the location of the tab bar
func (t *DocTabs) SetTabLocation(l TabLocation) {
t.location = tabsAdjustedLocation(l)
t.Refresh()
}
// Show this widget, if it was previously hidden
//
// Implements: fyne.CanvasObject
func (t *DocTabs) Show() {
t.BaseWidget.Show()
t.SelectIndex(t.current)
}
func (t *DocTabs) close(item *TabItem) {
if f := t.CloseIntercept; f != nil {
f(item)
} else {
t.Remove(item)
if f := t.OnClosed; f != nil {
f(item)
}
}
}
func (t *DocTabs) onUnselected() func(*TabItem) {
return t.OnUnselected
}
func (t *DocTabs) onSelected() func(*TabItem) {
return t.OnSelected
}
func (t *DocTabs) items() []*TabItem {
return t.Items
}
func (t *DocTabs) selected() int {
return t.current
}
func (t *DocTabs) setItems(items []*TabItem) {
t.Items = items
}
func (t *DocTabs) setSelected(selected int) {
t.current = selected
}
func (t *DocTabs) setTransitioning(transitioning bool) {
t.isTransitioning = transitioning
}
func (t *DocTabs) tabLocation() TabLocation {
return t.location
}
func (t *DocTabs) transitioning() bool {
return t.isTransitioning
}
// Declare conformity with WidgetRenderer interface.
var _ fyne.WidgetRenderer = (*docTabsRenderer)(nil)
type docTabsRenderer struct {
baseTabsRenderer
docTabs *DocTabs
scroller *Scroll
box *fyne.Container
create *widget.Button
lastSelected int
}
func (r *docTabsRenderer) Layout(size fyne.Size) {
r.updateAllTabs()
r.updateCreateTab()
r.updateTabs()
r.layout(r.docTabs, size)
// lay out buttons before updating indicator, which is relative to their position
buttons := r.scroller.Content.(*fyne.Container)
buttons.Layout.Layout(buttons.Objects, buttons.Size())
r.updateIndicator(r.docTabs.transitioning())
if r.docTabs.transitioning() {
r.docTabs.setTransitioning(false)
}
}
func (r *docTabsRenderer) MinSize() fyne.Size {
return r.minSize(r.docTabs)
}
func (r *docTabsRenderer) Objects() []fyne.CanvasObject {
return r.objects(r.docTabs)
}
func (r *docTabsRenderer) Refresh() {
r.Layout(r.docTabs.Size())
if c := r.docTabs.current; c != r.lastSelected {
if c >= 0 && c < len(r.docTabs.Items) {
r.scrollToSelected()
}
r.lastSelected = c
}
r.refresh(r.docTabs)
canvas.Refresh(r.docTabs)
}
func (r *docTabsRenderer) buildAllTabsButton() (all *widget.Button) {
all = &widget.Button{Importance: widget.LowImportance, OnTapped: func() {
// Show pop up containing all tabs
items := make([]*fyne.MenuItem, len(r.docTabs.Items))
for i := 0; i < len(r.docTabs.Items); i++ {
index := i // capture
// FIXME MenuItem doesn't support icons (#1752)
items[i] = fyne.NewMenuItem(r.docTabs.Items[i].Text, func() {
r.docTabs.SelectIndex(index)
if r.docTabs.popUpMenu != nil {
r.docTabs.popUpMenu.Hide()
r.docTabs.popUpMenu = nil
}
})
items[i].Checked = index == r.docTabs.current
}
r.docTabs.popUpMenu = buildPopUpMenu(r.docTabs, all, items)
}}
return all
}
func (r *docTabsRenderer) buildCreateTabsButton() *widget.Button {
create := widget.NewButton("", func() {
if f := r.docTabs.CreateTab; f != nil {
if tab := f(); tab != nil {
r.docTabs.Append(tab)
r.docTabs.SelectIndex(len(r.docTabs.Items) - 1)
}
}
})
create.Importance = widget.LowImportance
return create
}
func (r *docTabsRenderer) buildTabButtons(count int, buttons *fyne.Container) {
buttons.Objects = nil
var iconPos buttonIconPosition
if fyne.CurrentDevice().IsMobile() {
cells := count
if cells == 0 {
cells = 1
}
if r.docTabs.location == TabLocationTop || r.docTabs.location == TabLocationBottom {
buttons.Layout = layout.NewGridLayoutWithColumns(cells)
} else {
buttons.Layout = layout.NewGridLayoutWithRows(cells)
}
iconPos = buttonIconTop
} else if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
buttons.Layout = layout.NewVBoxLayout()
iconPos = buttonIconTop
} else {
buttons.Layout = layout.NewHBoxLayout()
iconPos = buttonIconInline
}
for i := 0; i < count; i++ {
item := r.docTabs.Items[i]
if item.button == nil {
item.button = &tabButton{
onTapped: func() { r.docTabs.Select(item) },
onClosed: func() { r.docTabs.close(item) },
}
}
button := item.button
button.icon = item.Icon
button.iconPosition = iconPos
if i == r.docTabs.current {
button.importance = widget.HighImportance
} else {
button.importance = widget.MediumImportance
}
button.text = item.Text
button.textAlignment = fyne.TextAlignLeading
button.Refresh()
buttons.Objects = append(buttons.Objects, button)
}
}
func (r *docTabsRenderer) scrollToSelected() {
buttons := r.scroller.Content.(*fyne.Container)
// https://github.com/fyne-io/fyne/issues/3909
// very dirty temporary fix to this crash!
if r.docTabs.current < 0 || r.docTabs.current >= len(buttons.Objects) {
return
}
button := buttons.Objects[r.docTabs.current]
pos := button.Position()
size := button.Size()
offset := r.scroller.Offset
viewport := r.scroller.Size()
if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
if pos.Y < offset.Y {
offset.Y = pos.Y
} else if pos.Y+size.Height > offset.Y+viewport.Height {
offset.Y = pos.Y + size.Height - viewport.Height
}
} else {
if pos.X < offset.X {
offset.X = pos.X
} else if pos.X+size.Width > offset.X+viewport.Width {
offset.X = pos.X + size.Width - viewport.Width
}
}
r.scroller.Offset = offset
r.updateIndicator(false)
}
func (r *docTabsRenderer) updateIndicator(animate bool) {
if r.docTabs.current < 0 {
r.indicator.FillColor = color.Transparent
r.moveIndicator(fyne.NewPos(0, 0), fyne.NewSize(0, 0), animate)
return
}
var selectedPos fyne.Position
var selectedSize fyne.Size
buttons := r.scroller.Content.(*fyne.Container).Objects
if r.docTabs.current >= len(buttons) {
if a := r.action; a != nil {
selectedPos = a.Position()
selectedSize = a.Size()
minSize := a.MinSize()
if minSize.Width > selectedSize.Width {
selectedSize = minSize
}
}
} else {
selected := buttons[r.docTabs.current]
selectedPos = selected.Position()
selectedSize = selected.Size()
minSize := selected.MinSize()
if minSize.Width > selectedSize.Width {
selectedSize = minSize
}
}
scrollOffset := r.scroller.Offset
scrollSize := r.scroller.Size()
var indicatorPos fyne.Position
var indicatorSize fyne.Size
switch r.docTabs.location {
case TabLocationTop:
indicatorPos = fyne.NewPos(selectedPos.X-scrollOffset.X, r.bar.MinSize().Height)
indicatorSize = fyne.NewSize(fyne.Min(selectedSize.Width, scrollSize.Width-indicatorPos.X), theme.Padding())
case TabLocationLeading:
indicatorPos = fyne.NewPos(r.bar.MinSize().Width, selectedPos.Y-scrollOffset.Y)
indicatorSize = fyne.NewSize(theme.Padding(), fyne.Min(selectedSize.Height, scrollSize.Height-indicatorPos.Y))
case TabLocationBottom:
indicatorPos = fyne.NewPos(selectedPos.X-scrollOffset.X, r.bar.Position().Y-theme.Padding())
indicatorSize = fyne.NewSize(fyne.Min(selectedSize.Width, scrollSize.Width-indicatorPos.X), theme.Padding())
case TabLocationTrailing:
indicatorPos = fyne.NewPos(r.bar.Position().X-theme.Padding(), selectedPos.Y-scrollOffset.Y)
indicatorSize = fyne.NewSize(theme.Padding(), fyne.Min(selectedSize.Height, scrollSize.Height-indicatorPos.Y))
}
if indicatorPos.X < 0 {
indicatorSize.Width = indicatorSize.Width + indicatorPos.X
indicatorPos.X = 0
}
if indicatorPos.Y < 0 {
indicatorSize.Height = indicatorSize.Height + indicatorPos.Y
indicatorPos.Y = 0
}
if indicatorSize.Width < 0 || indicatorSize.Height < 0 {
r.indicator.FillColor = color.Transparent
r.indicator.Refresh()
return
}
r.moveIndicator(indicatorPos, indicatorSize, animate)
}
func (r *docTabsRenderer) updateAllTabs() {
if len(r.docTabs.Items) > 0 {
r.action.Show()
} else {
r.action.Hide()
}
}
func (r *docTabsRenderer) updateCreateTab() {
if r.docTabs.CreateTab != nil {
r.create.SetIcon(theme.ContentAddIcon())
r.create.Show()
} else {
r.create.Hide()
}
}
func (r *docTabsRenderer) updateTabs() {
tabCount := len(r.docTabs.Items)
r.buildTabButtons(tabCount, r.scroller.Content.(*fyne.Container))
// Set layout of tab bar containing tab buttons and overflow action
if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
r.bar.Layout = layout.NewBorderLayout(nil, r.box, nil, nil)
r.scroller.Direction = ScrollVerticalOnly
} else {
r.bar.Layout = layout.NewBorderLayout(nil, nil, nil, r.box)
r.scroller.Direction = ScrollHorizontalOnly
}
r.bar.Objects = []fyne.CanvasObject{r.scroller, r.box}
r.bar.Refresh()
}