| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673 |
- package widget
- import (
- "math"
- "strconv"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/driver/desktop"
- "fyne.io/fyne/v2/driver/mobile"
- "fyne.io/fyne/v2/internal/cache"
- "fyne.io/fyne/v2/internal/widget"
- "fyne.io/fyne/v2/theme"
- )
- const noCellMatch = math.MaxInt32 // TODO make this MaxInt once we move to newer Go version
- // allTableCellsID represents all table cells when refreshing requested cells
- var allTableCellsID = TableCellID{-1, -1}
- // Declare conformity with interfaces
- var _ desktop.Cursorable = (*Table)(nil)
- var _ fyne.Draggable = (*Table)(nil)
- var _ fyne.Focusable = (*Table)(nil)
- var _ desktop.Hoverable = (*Table)(nil)
- var _ fyne.Tappable = (*Table)(nil)
- var _ fyne.Widget = (*Table)(nil)
- // TableCellID is a type that represents a cell's position in a table based on its row and column location.
- type TableCellID struct {
- Row int
- Col int
- }
- // Table widget is a grid of items that can be scrolled and a cell selected.
- // Its performance is provided by caching cell templates created with CreateCell and re-using them with UpdateCell.
- // The size of the content rows/columns is returned by the Length callback.
- //
- // Since: 1.4
- type Table struct {
- BaseWidget
- Length func() (int, int) `json:"-"`
- CreateCell func() fyne.CanvasObject `json:"-"`
- UpdateCell func(id TableCellID, template fyne.CanvasObject) `json:"-"`
- OnSelected func(id TableCellID) `json:"-"`
- OnUnselected func(id TableCellID) `json:"-"`
- // ShowHeaderRow specifies that a row should be added to the table with header content.
- // This will default to an A-Z style content, unless overridden with `CreateHeader` and `UpdateHeader` calls.
- //
- // Since: 2.4
- ShowHeaderRow bool
- // ShowHeaderColumn specifies that a column should be added to the table with header content.
- // This will default to an 1-10 style numeric content, unless overridden with `CreateHeader` and `UpdateHeader` calls.
- //
- // Since: 2.4
- ShowHeaderColumn bool
- // CreateHeader is an optional function that allows overriding of the default header widget.
- // Developers must also override `UpdateHeader`.
- //
- // Since: 2.4
- CreateHeader func() fyne.CanvasObject `json:"-"`
- // UpdateHeader is used with `CreateHeader` to support custom header content.
- // The `id` parameter will have `-1` value to indicate a header, and `> 0` where the column or row refer to data.
- //
- // Since: 2.4
- UpdateHeader func(id TableCellID, template fyne.CanvasObject) `json:"-"`
- // StickyRowCount specifies how many data rows should not scroll when the content moves.
- // If `ShowHeaderRow` us `true` then the stuck row will appear immediately underneath.
- //
- // Since: 2.4
- StickyRowCount int
- // StickyColumnCount specifies how many data columns should not scroll when the content moves.
- // If `ShowHeaderColumn` us `true` then the stuck column will appear immediately next to the header.
- //
- // Since: 2.4
- StickyColumnCount int
- currentFocus TableCellID
- focused bool
- selectedCell, hoveredCell *TableCellID
- cells *tableCells
- columnWidths, rowHeights map[int]float32
- moveCallback func()
- offset fyne.Position
- content *widget.Scroll
- cellSize, headerSize fyne.Size
- stuckXOff, stuckYOff, stuckWidth, stuckHeight, dragStartSize float32
- top, left, corner, dividerLayer *clip
- hoverHeaderRow, hoverHeaderCol, dragCol, dragRow int
- dragStartPos fyne.Position
- }
- // NewTable returns a new performant table widget defined by the passed functions.
- // The first returns the data size in rows and columns, second parameter is a function that returns cell
- // template objects that can be cached and the third is used to apply data at specified data location to the
- // passed template CanvasObject.
- //
- // Since: 1.4
- func NewTable(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
- t := &Table{Length: length, CreateCell: create, UpdateCell: update}
- t.ExtendBaseWidget(t)
- return t
- }
- // NewTableWithHeaders returns a new performant table widget defined by the passed functions including sticky headers.
- // The first returns the data size in rows and columns, second parameter is a function that returns cell
- // template objects that can be cached and the third is used to apply data at specified data location to the
- // passed template CanvasObject.
- // The row and column headers will stick to the leading and top edges of the table and contain "1-10" and "A-Z" formatted labels.
- //
- // Since: 2.4
- func NewTableWithHeaders(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table {
- t := NewTable(length, create, update)
- t.ShowHeaderRow = true
- t.ShowHeaderColumn = true
- return t
- }
- // CreateRenderer returns a new renderer for the table.
- //
- // Implements: fyne.Widget
- func (t *Table) CreateRenderer() fyne.WidgetRenderer {
- t.ExtendBaseWidget(t)
- t.propertyLock.Lock()
- t.headerSize = t.createHeader().MinSize()
- t.cellSize = t.templateSize()
- t.cells = newTableCells(t)
- t.content = widget.NewScroll(t.cells)
- t.top = newClip(t, &fyne.Container{})
- t.left = newClip(t, &fyne.Container{})
- t.corner = newClip(t, &fyne.Container{})
- t.dividerLayer = newClip(t, &fyne.Container{})
- t.propertyLock.Unlock()
- t.dragCol = noCellMatch
- t.dragRow = noCellMatch
- r := &tableRenderer{t: t}
- r.SetObjects([]fyne.CanvasObject{t.top, t.left, t.corner, t.dividerLayer, t.content})
- t.content.OnScrolled = func(pos fyne.Position) {
- t.offset = pos
- t.cells.Refresh()
- }
- r.Layout(t.Size())
- return r
- }
- func (t *Table) Cursor() desktop.Cursor {
- if t.hoverHeaderRow != noCellMatch {
- return desktop.VResizeCursor
- } else if t.hoverHeaderCol != noCellMatch {
- return desktop.HResizeCursor
- }
- return desktop.DefaultCursor
- }
- func (t *Table) Dragged(e *fyne.DragEvent) {
- t.propertyLock.Lock()
- min := t.cellSize
- col := t.dragCol
- row := t.dragRow
- startPos := t.dragStartPos
- startSize := t.dragStartSize
- t.propertyLock.Unlock()
- if col != noCellMatch {
- newSize := startSize + (e.Position.X - startPos.X)
- if newSize < min.Width {
- newSize = min.Width
- }
- t.SetColumnWidth(t.dragCol, newSize)
- }
- if row != noCellMatch {
- newSize := startSize + (e.Position.Y - startPos.Y)
- if newSize < min.Height {
- newSize = min.Height
- }
- t.SetRowHeight(t.dragRow, newSize)
- }
- }
- func (t *Table) DragEnd() {
- t.dragCol = noCellMatch
- t.dragRow = noCellMatch
- }
- // FocusGained is called after this table has gained focus.
- //
- // Implements: fyne.Focusable
- func (t *Table) FocusGained() {
- t.focused = true
- t.ScrollTo(t.currentFocus)
- t.RefreshItem(t.currentFocus)
- }
- // FocusLost is called after this Table has lost focus.
- //
- // Implements: fyne.Focusable
- func (t *Table) FocusLost() {
- t.focused = false
- t.Refresh() //Item(t.currentFocus)
- }
- func (t *Table) MouseIn(ev *desktop.MouseEvent) {
- t.hoverAt(ev.Position)
- }
- // MouseDown response to desktop mouse event
- func (t *Table) MouseDown(e *desktop.MouseEvent) {
- t.tapped(e.Position)
- }
- func (t *Table) MouseMoved(ev *desktop.MouseEvent) {
- t.hoverAt(ev.Position)
- }
- func (t *Table) MouseOut() {
- t.hoverOut()
- }
- // MouseUp response to desktop mouse event
- func (t *Table) MouseUp(*desktop.MouseEvent) {
- }
- // RefreshItem refreshes a single item, specified by the item ID passed in.
- //
- // Since: 2.4
- func (t *Table) RefreshItem(id TableCellID) {
- if t.cells == nil {
- return
- }
- r := cache.Renderer(t.cells)
- if r == nil {
- return
- }
- r.(*tableCellsRenderer).refreshForID(id)
- }
- // Select will mark the specified cell as selected.
- func (t *Table) Select(id TableCellID) {
- if t.Length == nil {
- return
- }
- rows, cols := t.Length()
- if id.Row >= rows || id.Col >= cols {
- return
- }
- if t.selectedCell != nil && *t.selectedCell == id {
- return
- }
- if f := t.OnUnselected; f != nil && t.selectedCell != nil {
- f(*t.selectedCell)
- }
- t.selectedCell = &id
- t.ScrollTo(id)
- if f := t.OnSelected; f != nil {
- f(id)
- }
- }
- // SetColumnWidth supports changing the width of the specified column. Columns normally take the width of the template
- // cell returned from the CreateCell callback. The width parameter uses the same units as a fyne.Size type and refers
- // to the internal content width not including the divider size.
- //
- // Since: 1.4.1
- func (t *Table) SetColumnWidth(id int, width float32) {
- t.propertyLock.Lock()
- if t.columnWidths == nil {
- t.columnWidths = make(map[int]float32)
- }
- t.columnWidths[id] = width
- t.propertyLock.Unlock()
- t.Refresh()
- }
- // SetRowHeight supports changing the height of the specified row. Rows normally take the height of the template
- // cell returned from the CreateCell callback. The height parameter uses the same units as a fyne.Size type and refers
- // to the internal content height not including the divider size.
- //
- // Since: 2.3
- func (t *Table) SetRowHeight(id int, height float32) {
- t.propertyLock.Lock()
- if t.rowHeights == nil {
- t.rowHeights = make(map[int]float32)
- }
- t.rowHeights[id] = height
- t.propertyLock.Unlock()
- t.Refresh()
- }
- // TouchDown response to mobile touch event
- func (t *Table) TouchDown(e *mobile.TouchEvent) {
- t.tapped(e.Position)
- }
- // TouchUp response to mobile touch event
- func (t *Table) TouchUp(*mobile.TouchEvent) {
- }
- // TouchCancel response to mobile touch event
- func (t *Table) TouchCancel(*mobile.TouchEvent) {
- }
- // TypedKey is called if a key event happens while this Table is focused.
- //
- // Implements: fyne.Focusable
- func (t *Table) TypedKey(event *fyne.KeyEvent) {
- switch event.Name {
- case fyne.KeySpace:
- t.Select(t.currentFocus)
- case fyne.KeyDown:
- if f := t.Length; f != nil {
- rows, _ := f()
- if t.currentFocus.Row >= rows-1 {
- return
- }
- }
- t.RefreshItem(t.currentFocus)
- t.currentFocus.Row++
- t.ScrollTo(t.currentFocus)
- t.RefreshItem(t.currentFocus)
- case fyne.KeyLeft:
- if t.currentFocus.Col <= 0 {
- return
- }
- t.RefreshItem(t.currentFocus)
- t.currentFocus.Col--
- t.ScrollTo(t.currentFocus)
- t.RefreshItem(t.currentFocus)
- case fyne.KeyRight:
- if f := t.Length; f != nil {
- _, cols := f()
- if t.currentFocus.Col >= cols-1 {
- return
- }
- }
- t.RefreshItem(t.currentFocus)
- t.currentFocus.Col++
- t.ScrollTo(t.currentFocus)
- t.RefreshItem(t.currentFocus)
- case fyne.KeyUp:
- if t.currentFocus.Row <= 0 {
- return
- }
- t.RefreshItem(t.currentFocus)
- t.currentFocus.Row--
- t.ScrollTo(t.currentFocus)
- t.RefreshItem(t.currentFocus)
- }
- }
- // TypedRune is called if a text event happens while this Table is focused.
- //
- // Implements: fyne.Focusable
- func (t *Table) TypedRune(_ rune) {
- // intentionally left blank
- }
- // Unselect will mark the cell provided by id as unselected.
- func (t *Table) Unselect(id TableCellID) {
- if t.selectedCell == nil || id != *t.selectedCell {
- return
- }
- t.selectedCell = nil
- if t.moveCallback != nil {
- t.moveCallback()
- }
- if f := t.OnUnselected; f != nil {
- f(id)
- }
- }
- // UnselectAll will mark all cells as unselected.
- //
- // Since: 2.1
- func (t *Table) UnselectAll() {
- if t.selectedCell == nil {
- return
- }
- selected := *t.selectedCell
- t.selectedCell = nil
- if t.moveCallback != nil {
- t.moveCallback()
- }
- if f := t.OnUnselected; f != nil {
- f(selected)
- }
- }
- // ScrollTo will scroll to the given cell without changing the selection.
- // Attempting to scroll beyond the limits of the table will scroll to
- // the edge of the table instead.
- //
- // Since: 2.1
- func (t *Table) ScrollTo(id TableCellID) {
- if t.Length == nil {
- return
- }
- if t.content == nil {
- return
- }
- rows, cols := t.Length()
- if id.Row >= rows {
- id.Row = rows - 1
- }
- if id.Col >= cols {
- id.Col = cols - 1
- }
- scrollPos := t.offset
- cellX, cellWidth := t.findX(id.Col)
- stickCols := t.StickyColumnCount
- if stickCols > 0 {
- cellX -= t.stuckXOff + t.stuckWidth
- }
- if t.ShowHeaderColumn {
- cellX += t.headerSize.Width
- stickCols--
- }
- if stickCols == 0 || id.Col > stickCols {
- if cellX < scrollPos.X {
- scrollPos.X = cellX
- } else if cellX+cellWidth > scrollPos.X+t.content.Size().Width {
- scrollPos.X = cellX + cellWidth - t.content.Size().Width
- }
- }
- cellY, cellHeight := t.findY(id.Row)
- stickRows := t.StickyRowCount
- if stickRows > 0 {
- cellY -= t.stuckYOff + t.stuckHeight
- }
- if t.ShowHeaderRow {
- cellY += t.headerSize.Height
- stickRows--
- }
- if stickRows == 0 || id.Row >= stickRows {
- if cellY < scrollPos.Y {
- scrollPos.Y = cellY
- } else if cellY+cellHeight > scrollPos.Y+t.content.Size().Height {
- scrollPos.Y = cellY + cellHeight - t.content.Size().Height
- }
- }
- t.offset = scrollPos
- t.content.Offset = scrollPos
- t.content.Refresh()
- t.finishScroll()
- }
- // ScrollToBottom scrolls to the last row in the table
- //
- // Since: 2.1
- func (t *Table) ScrollToBottom() {
- if t.Length == nil || t.content == nil {
- return
- }
- rows, _ := t.Length()
- cellY, cellHeight := t.findY(rows - 1)
- y := cellY + cellHeight - t.content.Size().Height
- if y <= 0 {
- return
- }
- t.content.Offset.Y = y
- t.offset.Y = y
- t.finishScroll()
- }
- // ScrollToLeading scrolls horizontally to the leading edge of the table
- //
- // Since: 2.1
- func (t *Table) ScrollToLeading() {
- if t.content == nil {
- return
- }
- t.content.Offset.X = 0
- t.offset.X = 0
- t.finishScroll()
- }
- // ScrollToTop scrolls to the first row in the table
- //
- // Since: 2.1
- func (t *Table) ScrollToTop() {
- if t.content == nil {
- return
- }
- t.content.Offset.Y = 0
- t.offset.Y = 0
- t.finishScroll()
- }
- // ScrollToTrailing scrolls horizontally to the trailing edge of the table
- //
- // Since: 2.1
- func (t *Table) ScrollToTrailing() {
- if t.content == nil || t.Length == nil {
- return
- }
- _, cols := t.Length()
- cellX, cellWidth := t.findX(cols - 1)
- scrollX := cellX + cellWidth - t.content.Size().Width
- if scrollX <= 0 {
- return
- }
- t.content.Offset.X = scrollX
- t.offset.X = scrollX
- t.finishScroll()
- }
- func (t *Table) Tapped(e *fyne.PointEvent) {
- if e.Position.X < 0 || e.Position.X >= t.Size().Width || e.Position.Y < 0 || e.Position.Y >= t.Size().Height {
- t.selectedCell = nil
- t.Refresh()
- return
- }
- col := t.columnAt(e.Position)
- if col == -1 {
- return // out of col range
- }
- row := t.rowAt(e.Position)
- if row == -1 {
- return // out of row range
- }
- t.Select(TableCellID{row, col})
- t.RefreshItem(t.currentFocus)
- canvas := fyne.CurrentApp().Driver().CanvasForObject(t)
- if canvas != nil {
- canvas.Focus(t)
- }
- t.currentFocus = TableCellID{row, col}
- t.RefreshItem(t.currentFocus)
- }
- // columnAt returns a positive integer (or 0) for the column that is found at the `pos` X position.
- // If the position is between cells the method will return a negative integer representing the next column,
- // i.e. -1 means the gap between 0 and 1.
- func (t *Table) columnAt(pos fyne.Position) int {
- dataCols := 0
- if f := t.Length; f != nil {
- _, dataCols = t.Length()
- }
- visibleColWidths, offX, minCol, maxCol := t.visibleColumnWidths(t.cellSize.Width, dataCols)
- i := minCol
- end := maxCol
- if pos.X < t.stuckXOff+t.stuckWidth {
- offX = t.stuckXOff
- end = t.StickyColumnCount
- i = 0
- } else {
- pos.X += t.content.Offset.X
- offX += t.stuckXOff
- }
- padding := theme.Padding()
- for x := offX; i < end; x += visibleColWidths[i-1] + padding {
- if pos.X < x {
- return -i // the space between i-1 and i
- } else if pos.X < x+visibleColWidths[i] {
- return i
- }
- i++
- }
- return noCellMatch
- }
- func (t *Table) createHeader() fyne.CanvasObject {
- if f := t.CreateHeader; f != nil {
- return f()
- }
- l := NewLabel("00")
- l.TextStyle.Bold = true
- l.Alignment = fyne.TextAlignCenter
- return l
- }
- func (t *Table) findX(col int) (cellX float32, cellWidth float32) {
- cellSize := t.templateSize()
- padding := theme.Padding()
- for i := 0; i <= col; i++ {
- if cellWidth > 0 {
- cellX += cellWidth + padding
- }
- width := cellSize.Width
- if w, ok := t.columnWidths[i]; ok {
- width = w
- }
- cellWidth = width
- }
- return
- }
- func (t *Table) findY(row int) (cellY float32, cellHeight float32) {
- cellSize := t.templateSize()
- padding := theme.Padding()
- for i := 0; i <= row; i++ {
- if cellHeight > 0 {
- cellY += cellHeight + padding
- }
- height := cellSize.Height
- if h, ok := t.rowHeights[i]; ok {
- height = h
- }
- cellHeight = height
- }
- return
- }
- func (t *Table) finishScroll() {
- if t.moveCallback != nil {
- t.moveCallback()
- }
- t.cells.Refresh()
- }
- func (t *Table) hoverAt(pos fyne.Position) {
- col := t.columnAt(pos)
- row := t.rowAt(pos)
- t.hoveredCell = &TableCellID{row, col}
- overHeaderRow := t.ShowHeaderRow && pos.Y < t.headerSize.Height
- overHeaderCol := t.ShowHeaderColumn && pos.X < t.headerSize.Width
- if overHeaderRow && !overHeaderCol {
- if col >= 0 {
- t.hoverHeaderCol = noCellMatch
- } else {
- t.hoverHeaderCol = -col - 1
- }
- } else {
- t.hoverHeaderCol = noCellMatch
- }
- if overHeaderCol && !overHeaderRow {
- if row >= 0 {
- t.hoverHeaderRow = noCellMatch
- } else {
- t.hoverHeaderRow = -row - 1
- }
- } else {
- t.hoverHeaderRow = noCellMatch
- }
- rows, cols := 0, 0
- if f := t.Length; f != nil {
- rows, cols = t.Length()
- }
- if t.hoveredCell.Col >= cols || t.hoveredCell.Row >= rows || t.hoveredCell.Col < 0 || t.hoveredCell.Row < 0 {
- t.hoverOut()
- return
- }
- if t.moveCallback != nil {
- t.moveCallback()
- }
- }
- func (t *Table) hoverOut() {
- t.hoveredCell = nil
- if t.moveCallback != nil {
- t.moveCallback()
- }
- }
- // rowAt returns a positive integer (or 0) for the row that is found at the `pos` Y position.
- // If the position is between cells the method will return a negative integer representing the next row,
- // i.e. -1 means the gap between rows 0 and 1.
- func (t *Table) rowAt(pos fyne.Position) int {
- dataRows := 0
- if f := t.Length; f != nil {
- dataRows, _ = t.Length()
- }
- visibleRowHeights, offY, minRow, maxRow := t.visibleRowHeights(t.cellSize.Height, dataRows)
- i := minRow
- end := maxRow
- if pos.Y < t.stuckYOff+t.stuckHeight {
- offY = t.stuckYOff
- end = t.StickyRowCount
- i = 0
- } else {
- pos.Y += t.content.Offset.Y
- offY += t.stuckYOff
- }
- padding := theme.Padding()
- for y := offY; i < end; y += visibleRowHeights[i-1] + padding {
- if pos.Y < y {
- return -i // the space between i-1 and i
- } else if pos.Y >= y && pos.Y < y+visibleRowHeights[i] {
- return i
- }
- i++
- }
- return noCellMatch
- }
- func (t *Table) tapped(pos fyne.Position) {
- if t.dragCol == noCellMatch && t.dragRow == noCellMatch {
- t.dragStartPos = pos
- if t.hoverHeaderRow != noCellMatch {
- t.dragCol = noCellMatch
- t.dragRow = t.hoverHeaderRow
- size, ok := t.rowHeights[t.hoverHeaderRow]
- if !ok {
- size = t.cellSize.Height
- }
- t.dragStartSize = size
- } else if t.hoverHeaderCol != noCellMatch {
- t.dragCol = t.hoverHeaderCol
- t.dragRow = noCellMatch
- size, ok := t.columnWidths[t.hoverHeaderCol]
- if !ok {
- size = t.cellSize.Width
- }
- t.dragStartSize = size
- }
- }
- }
- func (t *Table) templateSize() fyne.Size {
- if f := t.CreateCell; f != nil {
- template := f() // don't use cache, we need new template
- if !t.ShowHeaderRow && !t.ShowHeaderColumn {
- return template.MinSize()
- }
- return template.MinSize().Max(t.createHeader().MinSize())
- }
- fyne.LogError("Missing CreateCell callback required for Table", nil)
- return fyne.Size{}
- }
- func (t *Table) updateHeader(id TableCellID, o fyne.CanvasObject) {
- if f := t.UpdateHeader; f != nil {
- f(id, o)
- return
- }
- l := o.(*Label)
- if id.Row < 0 {
- ids := []rune{'A' + rune(id.Col%26)}
- pre := (id.Col - id.Col%26) / 26
- for pre > 0 {
- ids = append([]rune{'A' - 1 + rune(pre%26)}, ids...)
- pre = (pre - pre%26) / 26
- }
- l.SetText(string(ids))
- } else if id.Col < 0 {
- l.SetText(strconv.Itoa(id.Row + 1))
- } else {
- l.SetText("")
- }
- }
- func (t *Table) stickyColumnWidths(colWidth float32, cols int) (visible []float32) {
- if cols == 0 {
- return []float32{}
- }
- max := t.StickyColumnCount
- if max > cols {
- max = cols
- }
- visible = make([]float32, max)
- if len(t.columnWidths) == 0 {
- for i := 0; i < max; i++ {
- visible[i] = colWidth
- }
- return
- }
- for i := 0; i < max; i++ {
- height := colWidth
- if h, ok := t.columnWidths[i]; ok {
- height = h
- }
- visible[i] = height
- }
- return
- }
- func (t *Table) visibleColumnWidths(colWidth float32, cols int) (visible map[int]float32, offX float32, minCol, maxCol int) {
- maxCol = cols
- colOffset, headWidth := float32(0), float32(0)
- isVisible := false
- visible = make(map[int]float32)
- if t.content.Size().Width <= 0 {
- return
- }
- // theme.Padding is a slow call, so we cache it
- padding := theme.Padding()
- stick := t.StickyColumnCount
- if len(t.columnWidths) == 0 {
- paddedWidth := colWidth + padding
- offX = float32(math.Floor(float64(t.offset.X/paddedWidth))) * paddedWidth
- minCol = int(math.Floor(float64(offX / paddedWidth)))
- maxCol = int(math.Ceil(float64((t.offset.X + t.size.Width) / paddedWidth)))
- if minCol > cols-1 {
- minCol = cols - 1
- }
- if minCol < 0 {
- minCol = 0
- }
- if maxCol > cols {
- maxCol = cols
- }
- visible = make(map[int]float32, maxCol-minCol+stick)
- for i := minCol; i < maxCol; i++ {
- visible[i] = colWidth
- }
- for i := 0; i < stick; i++ {
- visible[i] = colWidth
- }
- return
- }
- for i := 0; i < cols; i++ {
- width := colWidth
- if w, ok := t.columnWidths[i]; ok {
- width = w
- }
- if colOffset <= t.offset.X-width-padding {
- // before visible content
- } else if colOffset <= headWidth || colOffset <= t.offset.X {
- minCol = i
- offX = colOffset
- isVisible = true
- }
- if colOffset < t.offset.X+t.size.Width {
- maxCol = i + 1
- } else {
- break
- }
- colOffset += width + padding
- if isVisible || i < stick {
- visible[i] = width
- }
- }
- return
- }
- func (t *Table) stickyRowHeights(rowHeight float32, rows int) (visible []float32) {
- if rows == 0 {
- return []float32{}
- }
- max := t.StickyRowCount
- if max > rows {
- max = rows
- }
- visible = make([]float32, max)
- if len(t.rowHeights) == 0 {
- for i := 0; i < max; i++ {
- visible[i] = rowHeight
- }
- return
- }
- for i := 0; i < max; i++ {
- height := rowHeight
- if h, ok := t.rowHeights[i]; ok {
- height = h
- }
- visible[i] = height
- }
- return
- }
- func (t *Table) visibleRowHeights(rowHeight float32, rows int) (visible map[int]float32, offY float32, minRow, maxRow int) {
- maxRow = rows
- rowOffset, headHeight := float32(0), float32(0)
- isVisible := false
- visible = make(map[int]float32)
- if t.content.Size().Height <= 0 {
- return
- }
- // theme.Padding is a slow call, so we cache it
- padding := theme.Padding()
- stick := t.StickyRowCount
- if len(t.rowHeights) == 0 {
- paddedHeight := rowHeight + padding
- offY = float32(math.Floor(float64(t.offset.Y/paddedHeight))) * paddedHeight
- minRow = int(math.Floor(float64(offY / paddedHeight)))
- maxRow = int(math.Ceil(float64((t.offset.Y + t.size.Height) / paddedHeight)))
- if minRow > rows-1 {
- minRow = rows - 1
- }
- if minRow < 0 {
- minRow = 0
- }
- if maxRow > rows {
- maxRow = rows
- }
- visible = make(map[int]float32, maxRow-minRow+stick)
- for i := minRow; i < maxRow; i++ {
- visible[i] = rowHeight
- }
- for i := 0; i < stick; i++ {
- visible[i] = rowHeight
- }
- return
- }
- for i := 0; i < rows; i++ {
- height := rowHeight
- if h, ok := t.rowHeights[i]; ok {
- height = h
- }
- if rowOffset <= t.offset.Y-height-padding {
- // before visible content
- } else if rowOffset <= headHeight || rowOffset <= t.offset.Y {
- minRow = i
- offY = rowOffset
- isVisible = true
- }
- if rowOffset < t.offset.Y+t.size.Height {
- maxRow = i + 1
- } else {
- break
- }
- rowOffset += height + padding
- if isVisible || i < stick {
- visible[i] = height
- }
- }
- return
- }
- // Declare conformity with WidgetRenderer interface.
- var _ fyne.WidgetRenderer = (*tableRenderer)(nil)
- type tableRenderer struct {
- widget.BaseRenderer
- t *Table
- }
- func (t *tableRenderer) Layout(s fyne.Size) {
- t.t.propertyLock.RLock()
- t.calculateHeaderSizes()
- off := fyne.NewPos(t.t.stuckWidth, t.t.stuckHeight)
- if t.t.ShowHeaderRow {
- off.Y += t.t.headerSize.Height
- }
- if t.t.ShowHeaderColumn {
- off.X += t.t.headerSize.Width
- }
- t.t.propertyLock.RUnlock()
- t.t.content.Move(off)
- t.t.content.Resize(s.SubtractWidthHeight(off.X, off.Y))
- t.t.top.Move(fyne.NewPos(off.X, 0))
- t.t.top.Resize(fyne.NewSize(s.Width-off.X, off.Y))
- t.t.left.Move(fyne.NewPos(0, off.Y))
- t.t.left.Resize(fyne.NewSize(off.X, s.Height-off.Y))
- t.t.corner.Resize(fyne.NewSize(off.X, off.Y))
- t.t.dividerLayer.Resize(s)
- }
- func (t *tableRenderer) MinSize() fyne.Size {
- sep := theme.Padding()
- t.t.propertyLock.RLock()
- defer t.t.propertyLock.RUnlock()
- min := t.t.content.MinSize().Max(t.t.cellSize)
- if t.t.ShowHeaderRow {
- min.Height += t.t.headerSize.Height + sep
- }
- if t.t.ShowHeaderColumn {
- min.Width += t.t.headerSize.Width + sep
- }
- if t.t.StickyRowCount > 0 {
- for i := 0; i < t.t.StickyRowCount; i++ {
- height := t.t.cellSize.Height
- if h, ok := t.t.rowHeights[i]; ok {
- height = h
- }
- min.Height += height + sep
- }
- }
- if t.t.StickyColumnCount > 0 {
- for i := 0; i < t.t.StickyColumnCount; i++ {
- width := t.t.cellSize.Width
- if w, ok := t.t.columnWidths[i]; ok {
- width = w
- }
- min.Width += width + sep
- }
- }
- return min
- }
- func (t *tableRenderer) Refresh() {
- t.t.propertyLock.Lock()
- t.t.headerSize = t.t.createHeader().MinSize()
- t.t.cellSize = t.t.templateSize()
- t.calculateHeaderSizes()
- t.t.propertyLock.Unlock()
- t.Layout(t.t.Size())
- t.t.cells.Refresh()
- }
- func (t *tableRenderer) calculateHeaderSizes() {
- t.t.stuckXOff = 0
- t.t.stuckYOff = 0
- if t.t.ShowHeaderRow {
- t.t.stuckYOff = t.t.headerSize.Height
- }
- if t.t.ShowHeaderColumn {
- t.t.stuckXOff = t.t.headerSize.Width
- }
- separatorThickness := theme.Padding()
- stickyColWidths := t.t.stickyColumnWidths(t.t.cellSize.Width, t.t.StickyColumnCount)
- stickyRowHeights := t.t.stickyRowHeights(t.t.cellSize.Height, t.t.StickyRowCount)
- var stuckHeight float32
- for _, rowHeight := range stickyRowHeights {
- stuckHeight += rowHeight + separatorThickness
- }
- t.t.stuckHeight = stuckHeight
- var stuckWidth float32
- for _, colWidth := range stickyColWidths {
- stuckWidth += colWidth + separatorThickness
- }
- t.t.stuckWidth = stuckWidth
- }
- // Declare conformity with Widget interface.
- var _ fyne.Widget = (*tableCells)(nil)
- type tableCells struct {
- BaseWidget
- t *Table
- }
- func newTableCells(t *Table) *tableCells {
- c := &tableCells{t: t}
- c.ExtendBaseWidget(c)
- return c
- }
- func (c *tableCells) CreateRenderer() fyne.WidgetRenderer {
- marker := canvas.NewRectangle(theme.SelectionColor())
- marker.CornerRadius = theme.SelectionRadiusSize()
- hover := canvas.NewRectangle(theme.HoverColor())
- hover.CornerRadius = theme.SelectionRadiusSize()
- r := &tableCellsRenderer{cells: c, pool: &syncPool{}, headerPool: &syncPool{},
- visible: make(map[TableCellID]fyne.CanvasObject), headers: make(map[TableCellID]fyne.CanvasObject),
- headRowBG: canvas.NewRectangle(theme.HeaderBackgroundColor()), headColBG: canvas.NewRectangle(theme.HeaderBackgroundColor()),
- headRowStickyBG: canvas.NewRectangle(theme.HeaderBackgroundColor()), headColStickyBG: canvas.NewRectangle(theme.HeaderBackgroundColor()),
- marker: marker, hover: hover}
- c.t.moveCallback = r.moveIndicators
- return r
- }
- func (c *tableCells) Resize(s fyne.Size) {
- c.BaseWidget.Resize(s)
- c.Refresh() // trigger a redraw
- }
- // Declare conformity with WidgetRenderer interface.
- var _ fyne.WidgetRenderer = (*tableCellsRenderer)(nil)
- type tableCellsRenderer struct {
- widget.BaseRenderer
- cells *tableCells
- pool, headerPool pool
- visible, headers map[TableCellID]fyne.CanvasObject
- hover, marker *canvas.Rectangle
- dividers []fyne.CanvasObject
- headColBG, headRowBG, headRowStickyBG, headColStickyBG *canvas.Rectangle
- }
- func (r *tableCellsRenderer) Layout(fyne.Size) {
- r.cells.propertyLock.Lock()
- r.moveIndicators()
- r.cells.propertyLock.Unlock()
- }
- func (r *tableCellsRenderer) MinSize() fyne.Size {
- r.cells.propertyLock.RLock()
- defer r.cells.propertyLock.RUnlock()
- rows, cols := 0, 0
- if f := r.cells.t.Length; f != nil {
- rows, cols = r.cells.t.Length()
- } else {
- fyne.LogError("Missing Length callback required for Table", nil)
- }
- stickRows := r.cells.t.StickyRowCount
- stickCols := r.cells.t.StickyColumnCount
- width := float32(0)
- if len(r.cells.t.columnWidths) == 0 {
- width = r.cells.t.cellSize.Width * float32(cols-stickCols)
- } else {
- cellWidth := r.cells.t.cellSize.Width
- for col := stickCols; col < cols; col++ {
- colWidth, ok := r.cells.t.columnWidths[col]
- if ok {
- width += colWidth
- } else {
- width += cellWidth
- }
- }
- }
- height := float32(0)
- if len(r.cells.t.rowHeights) == 0 {
- height = r.cells.t.cellSize.Height * float32(rows-stickRows)
- } else {
- cellHeight := r.cells.t.cellSize.Height
- for row := stickRows; row < rows; row++ {
- rowHeight, ok := r.cells.t.rowHeights[row]
- if ok {
- height += rowHeight
- } else {
- height += cellHeight
- }
- }
- }
- separatorSize := theme.Padding()
- return fyne.NewSize(width+float32(cols-stickCols-1)*separatorSize, height+float32(rows-stickRows-1)*separatorSize)
- }
- func (r *tableCellsRenderer) Refresh() {
- r.refreshForID(allTableCellsID)
- }
- func (r *tableCellsRenderer) refreshForID(toDraw TableCellID) {
- r.cells.propertyLock.Lock()
- separatorThickness := theme.Padding()
- dataRows, dataCols := 0, 0
- if f := r.cells.t.Length; f != nil {
- dataRows, dataCols = r.cells.t.Length()
- }
- visibleColWidths, offX, minCol, maxCol := r.cells.t.visibleColumnWidths(r.cells.t.cellSize.Width, dataCols)
- if len(visibleColWidths) == 0 && dataCols > 0 { // we can't show anything until we have some dimensions
- r.cells.propertyLock.Unlock()
- return
- }
- visibleRowHeights, offY, minRow, maxRow := r.cells.t.visibleRowHeights(r.cells.t.cellSize.Height, dataRows)
- if len(visibleRowHeights) == 0 && dataRows > 0 { // we can't show anything until we have some dimensions
- r.cells.propertyLock.Unlock()
- return
- }
- updateCell := r.cells.t.UpdateCell
- if updateCell == nil {
- fyne.LogError("Missing UpdateCell callback required for Table", nil)
- }
- var cellXOffset, cellYOffset float32
- stickRows := r.cells.t.StickyRowCount
- if r.cells.t.ShowHeaderRow {
- cellYOffset += r.cells.t.headerSize.Height
- }
- stickCols := r.cells.t.StickyColumnCount
- if r.cells.t.ShowHeaderColumn {
- cellXOffset += r.cells.t.headerSize.Width
- }
- startRow := minRow + stickRows
- if startRow < stickRows {
- startRow = stickRows
- }
- startCol := minCol + stickCols
- if startCol < stickCols {
- startCol = stickCols
- }
- wasVisible := r.visible
- r.visible = make(map[TableCellID]fyne.CanvasObject)
- var cells []fyne.CanvasObject
- displayCol := func(row, col int, rowHeight float32, cells *[]fyne.CanvasObject) {
- id := TableCellID{row, col}
- colWidth := visibleColWidths[col]
- c, ok := wasVisible[id]
- if !ok {
- c = r.pool.Obtain()
- if f := r.cells.t.CreateCell; f != nil && c == nil {
- c = f()
- }
- if c == nil {
- return
- }
- }
- c.Move(fyne.NewPos(cellXOffset, cellYOffset))
- c.Resize(fyne.NewSize(colWidth, rowHeight))
- r.visible[id] = c
- *cells = append(*cells, c)
- cellXOffset += colWidth + separatorThickness
- }
- displayRow := func(row int, cells *[]fyne.CanvasObject) {
- rowHeight := visibleRowHeights[row]
- cellXOffset = offX
- for col := startCol; col < maxCol; col++ {
- displayCol(row, col, rowHeight, cells)
- }
- cellXOffset = r.cells.t.content.Offset.X
- stick := r.cells.t.StickyColumnCount
- if r.cells.t.ShowHeaderColumn {
- cellXOffset += r.cells.t.headerSize.Width
- stick--
- }
- cellYOffset += rowHeight + separatorThickness
- }
- cellYOffset = offY
- for row := startRow; row < maxRow; row++ {
- displayRow(row, &cells)
- }
- inline := r.refreshHeaders(visibleRowHeights, visibleColWidths, offX, offY, startRow, maxRow, startCol, maxCol, separatorThickness)
- cells = append(cells, inline...)
- offX -= r.cells.t.content.Offset.X
- cellYOffset = r.cells.t.stuckYOff
- for row := 0; row < stickRows; row++ {
- displayRow(row, &r.cells.t.top.Content.(*fyne.Container).Objects)
- }
- cellYOffset = offY - r.cells.t.content.Offset.Y
- for row := startRow; row < maxRow; row++ {
- cellXOffset = r.cells.t.stuckXOff
- rowHeight := visibleRowHeights[row]
- for col := 0; col < stickCols; col++ {
- displayCol(row, col, rowHeight, &r.cells.t.left.Content.(*fyne.Container).Objects)
- }
- cellYOffset += rowHeight + separatorThickness
- }
- cellYOffset = r.cells.t.stuckYOff
- for row := 0; row < stickRows; row++ {
- cellXOffset = r.cells.t.stuckXOff
- rowHeight := visibleRowHeights[row]
- for col := 0; col < stickCols; col++ {
- displayCol(row, col, rowHeight, &r.cells.t.corner.Content.(*fyne.Container).Objects)
- }
- cellYOffset += rowHeight + separatorThickness
- }
- for id, old := range wasVisible {
- if _, ok := r.visible[id]; !ok {
- r.pool.Release(old)
- }
- }
- visible := r.visible
- headers := r.headers
- r.cells.propertyLock.Unlock()
- r.SetObjects(cells)
- if updateCell != nil {
- for id, cell := range visible {
- if toDraw != allTableCellsID && toDraw != id {
- continue
- }
- updateCell(id, cell)
- }
- }
- for id, head := range headers {
- r.cells.t.updateHeader(id, head)
- }
- r.moveIndicators()
- r.marker.FillColor = theme.SelectionColor()
- r.marker.CornerRadius = theme.SelectionRadiusSize()
- r.marker.Refresh()
- r.hover.FillColor = theme.HoverColor()
- r.hover.CornerRadius = theme.SelectionRadiusSize()
- r.hover.Refresh()
- }
- func (r *tableCellsRenderer) moveIndicators() {
- rows, cols := 0, 0
- if f := r.cells.t.Length; f != nil {
- rows, cols = r.cells.t.Length()
- }
- visibleColWidths, offX, minCol, maxCol := r.cells.t.visibleColumnWidths(r.cells.t.cellSize.Width, cols)
- visibleRowHeights, offY, minRow, maxRow := r.cells.t.visibleRowHeights(r.cells.t.cellSize.Height, rows)
- separatorThickness := theme.SeparatorThicknessSize()
- padding := theme.Padding()
- dividerOff := (padding - separatorThickness) / 2
- stickRows := r.cells.t.StickyRowCount
- stickCols := r.cells.t.StickyColumnCount
- if r.cells.t.ShowHeaderColumn {
- offX += r.cells.t.headerSize.Width
- }
- if r.cells.t.ShowHeaderRow {
- offY += r.cells.t.headerSize.Height
- }
- if r.cells.t.selectedCell == nil {
- r.moveMarker(r.marker, -1, -1, offX, offY, minCol, minRow, visibleColWidths, visibleRowHeights)
- } else {
- r.moveMarker(r.marker, r.cells.t.selectedCell.Row, r.cells.t.selectedCell.Col, offX, offY, minCol, minRow, visibleColWidths, visibleRowHeights)
- }
- if r.cells.t.hoveredCell == nil && !r.cells.t.focused {
- r.moveMarker(r.hover, -1, -1, offX, offY, minCol, minRow, visibleColWidths, visibleRowHeights)
- } else if r.cells.t.focused {
- r.moveMarker(r.hover, r.cells.t.currentFocus.Row, r.cells.t.currentFocus.Col, offX, offY, minCol, minRow, visibleColWidths, visibleRowHeights)
- } else {
- r.moveMarker(r.hover, r.cells.t.hoveredCell.Row, r.cells.t.hoveredCell.Col, offX, offY, minCol, minRow, visibleColWidths, visibleRowHeights)
- }
- colDivs := stickCols + maxCol - minCol - 1
- if colDivs < 0 {
- colDivs = 0
- }
- rowDivs := stickRows + maxRow - minRow - 1
- if rowDivs < 0 {
- rowDivs = 0
- }
- if colDivs < 0 {
- colDivs = 0
- }
- if rowDivs < 0 {
- rowDivs = 0
- }
- if len(r.dividers) < colDivs+rowDivs {
- for i := len(r.dividers); i < colDivs+rowDivs; i++ {
- r.dividers = append(r.dividers, NewSeparator())
- }
- objs := []fyne.CanvasObject{r.marker, r.hover}
- r.cells.t.dividerLayer.Content.(*fyne.Container).Objects = append(objs, r.dividers...)
- r.cells.t.dividerLayer.Content.Refresh()
- }
- divs := 0
- i := 0
- if stickCols > 0 {
- for x := r.cells.t.stuckXOff + visibleColWidths[i]; i < stickCols && divs < colDivs; x += visibleColWidths[i] + padding {
- i++
- xPos := x + dividerOff
- r.dividers[divs].Resize(fyne.NewSize(separatorThickness, r.cells.t.size.Height))
- r.dividers[divs].Move(fyne.NewPos(xPos, 0))
- r.dividers[divs].Show()
- divs++
- }
- }
- i = minCol + stickCols
- for x := offX + r.cells.t.stuckWidth + visibleColWidths[i]; i < maxCol-1 && divs < colDivs; x += visibleColWidths[i] + padding {
- i++
- xPos := x - r.cells.t.content.Offset.X + dividerOff
- r.dividers[divs].Resize(fyne.NewSize(separatorThickness, r.cells.t.size.Height))
- r.dividers[divs].Move(fyne.NewPos(xPos, 0))
- r.dividers[divs].Show()
- divs++
- }
- i = 0
- if stickRows > 0 {
- for y := r.cells.t.stuckYOff + visibleRowHeights[i]; i < stickRows && divs-colDivs < rowDivs; y += visibleRowHeights[i] + padding {
- i++
- yPos := y + dividerOff
- r.dividers[divs].Resize(fyne.NewSize(r.cells.t.size.Width, separatorThickness))
- r.dividers[divs].Move(fyne.NewPos(0, yPos))
- r.dividers[divs].Show()
- divs++
- }
- }
- i = minRow + stickRows
- for y := offY + r.cells.t.stuckHeight + visibleRowHeights[i]; i < maxRow-1 && divs-colDivs < rowDivs; y += visibleRowHeights[i] + padding {
- i++
- yPos := y - r.cells.t.content.Offset.Y + dividerOff
- r.dividers[divs].Resize(fyne.NewSize(r.cells.t.size.Width, separatorThickness))
- r.dividers[divs].Move(fyne.NewPos(0, yPos))
- r.dividers[divs].Show()
- divs++
- }
- for i := divs; i < len(r.dividers); i++ {
- r.dividers[i].Hide()
- }
- }
- func (r *tableCellsRenderer) moveMarker(marker fyne.CanvasObject, row, col int, offX, offY float32, minCol, minRow int, widths, heights map[int]float32) {
- if col == -1 || row == -1 {
- marker.Hide()
- marker.Refresh()
- return
- }
- xPos := offX
- stickCols := r.cells.t.StickyColumnCount
- if col < stickCols {
- if r.cells.t.ShowHeaderColumn {
- xPos = r.cells.t.stuckXOff
- } else {
- xPos = 0
- }
- minCol = 0
- }
- padding := theme.Padding()
- for i := minCol; i < col; i++ {
- xPos += widths[i]
- xPos += padding
- }
- x1 := xPos
- if col >= stickCols {
- x1 -= r.cells.t.content.Offset.X
- }
- x2 := x1 + widths[col]
- yPos := offY
- stickRows := r.cells.t.StickyRowCount
- if row < stickRows {
- if r.cells.t.ShowHeaderRow {
- yPos = r.cells.t.stuckYOff
- } else {
- yPos = 0
- }
- minRow = 0
- }
- for i := minRow; i < row; i++ {
- yPos += heights[i]
- yPos += padding
- }
- y1 := yPos
- if row >= stickRows {
- y1 -= r.cells.t.content.Offset.Y
- }
- y2 := y1 + heights[row]
- if x2 < 0 || x1 > r.cells.t.size.Width || y2 < 0 || y1 > r.cells.t.size.Height {
- marker.Hide()
- } else {
- left := x1
- if col >= stickCols { // clip X
- left = fyne.Max(r.cells.t.stuckXOff+r.cells.t.stuckWidth, x1)
- }
- top := y1
- if row >= stickRows { // clip Y
- top = fyne.Max(r.cells.t.stuckYOff+r.cells.t.stuckHeight, y1)
- }
- marker.Move(fyne.NewPos(left, top))
- marker.Resize(fyne.NewSize(x2-left, y2-top))
- marker.Show()
- }
- marker.Refresh()
- }
- func (r *tableCellsRenderer) refreshHeaders(visibleRowHeights, visibleColWidths map[int]float32, offX, offY float32,
- startRow, maxRow, startCol, maxCol int, separatorThickness float32) []fyne.CanvasObject {
- wasVisible := r.headers
- r.headers = make(map[TableCellID]fyne.CanvasObject)
- headerMin := r.cells.t.createHeader().MinSize()
- rowHeight := headerMin.Height
- colWidth := headerMin.Width
- var cells, over []fyne.CanvasObject
- corner := []fyne.CanvasObject{r.headColStickyBG, r.headRowStickyBG}
- over = []fyne.CanvasObject{r.headRowBG}
- if r.cells.t.ShowHeaderRow {
- cellXOffset := offX - r.cells.t.content.Offset.X
- displayColHeader := func(col int, list *[]fyne.CanvasObject) {
- id := TableCellID{-1, col}
- colWidth := visibleColWidths[col]
- c, ok := wasVisible[id]
- if !ok {
- c = r.headerPool.Obtain()
- if c == nil {
- c = r.cells.t.createHeader()
- }
- if c == nil {
- return
- }
- }
- c.Move(fyne.NewPos(cellXOffset, 0))
- c.Resize(fyne.NewSize(colWidth, rowHeight))
- r.headers[id] = c
- *list = append(*list, c)
- cellXOffset += colWidth + separatorThickness
- }
- for col := startCol; col < maxCol; col++ {
- displayColHeader(col, &over)
- }
- if r.cells.t.StickyColumnCount > 0 {
- cellXOffset = 0
- if r.cells.t.ShowHeaderColumn {
- cellXOffset += r.cells.t.headerSize.Width
- }
- for col := 0; col < r.cells.t.StickyColumnCount; col++ {
- displayColHeader(col, &corner)
- }
- }
- }
- r.cells.t.top.Content.(*fyne.Container).Objects = over
- r.cells.t.top.Content.Refresh()
- over = []fyne.CanvasObject{r.headColBG}
- if r.cells.t.ShowHeaderColumn {
- cellYOffset := offY - r.cells.t.content.Offset.Y
- displayRowHeader := func(row int, list *[]fyne.CanvasObject) {
- id := TableCellID{row, -1}
- rowHeight := visibleRowHeights[row]
- c, ok := wasVisible[id]
- if !ok {
- c = r.headerPool.Obtain()
- if c == nil {
- c = r.cells.t.createHeader()
- }
- if c == nil {
- return
- }
- }
- c.Move(fyne.NewPos(0, cellYOffset))
- c.Resize(fyne.NewSize(colWidth, rowHeight))
- r.headers[id] = c
- *list = append(*list, c)
- cellYOffset += rowHeight + separatorThickness
- }
- for row := startRow; row < maxRow; row++ {
- displayRowHeader(row, &over)
- }
- if r.cells.t.StickyRowCount > 0 {
- cellYOffset = 0
- if r.cells.t.ShowHeaderRow {
- cellYOffset += r.cells.t.headerSize.Height
- }
- for row := 0; row < r.cells.t.StickyRowCount; row++ {
- displayRowHeader(row, &corner)
- }
- }
- }
- r.cells.t.left.Content.(*fyne.Container).Objects = over
- r.cells.t.left.Content.Refresh()
- r.headColBG.Hidden = !r.cells.t.ShowHeaderColumn
- r.headColBG.FillColor = theme.HeaderBackgroundColor()
- r.headColBG.Resize(fyne.NewSize(colWidth, r.cells.t.Size().Height))
- r.headColStickyBG.Hidden = !r.cells.t.ShowHeaderColumn
- r.headColStickyBG.FillColor = theme.HeaderBackgroundColor()
- r.headColStickyBG.Resize(fyne.NewSize(colWidth, r.cells.t.stuckHeight+rowHeight))
- r.headRowBG.Hidden = !r.cells.t.ShowHeaderRow
- r.headRowBG.FillColor = theme.HeaderBackgroundColor()
- r.headRowBG.Resize(fyne.NewSize(r.cells.t.Size().Width, rowHeight))
- r.headRowStickyBG.Hidden = !r.cells.t.ShowHeaderRow
- r.headRowStickyBG.FillColor = theme.HeaderBackgroundColor()
- r.headRowStickyBG.Resize(fyne.NewSize(r.cells.t.stuckWidth+colWidth, rowHeight))
- r.cells.t.corner.Content.(*fyne.Container).Objects = corner
- r.cells.t.corner.Content.Refresh()
- for id, old := range wasVisible {
- if _, ok := r.headers[id]; !ok {
- r.headerPool.Release(old)
- }
- }
- return cells
- }
- type clip struct {
- widget.Scroll
- t *Table
- }
- func newClip(t *Table, o fyne.CanvasObject) *clip {
- c := &clip{t: t}
- c.Content = o
- c.Direction = widget.ScrollNone
- return c
- }
- func (c *clip) DragEnd() {
- c.t.DragEnd()
- c.t.dragCol = noCellMatch
- c.t.dragRow = noCellMatch
- }
- func (c *clip) Dragged(e *fyne.DragEvent) {
- c.t.Dragged(e)
- }
|