Made a new demo. This one is modified slightly as its going to be part of a larger app I'm doing, so no title bar. Positioning is manually set. But I've added alternate row colors, selection, and doubleclick events. Also a function to add data from an array instead of json and to retrieve data from the table. To do the row colors, I use an image and tile it across the sprite. In order to do this the image size has to be a multiple of 2 to use the UV commands. This limits the text size as well.
Required "rows.png" image
main.agc
// Project: ftp_client
// Created: 2024-03-08
SetErrorMode(2)
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 )
SetVirtualResolution( 1024, 768 )
SetSyncRate(0, 0 )
SetScissor( 0,0,0,0 )
UseNewDefaultFonts( 1 )
#include 'table.agc'
defaultSkin as Table_Skin
defaultSkin.bgColor = 0xAA4b433b // ABGR
defaultSkin.borderColor = 0xFF3a3a3a
defaultSkin.headHeight = 24
defaultSkin.trackWidth = 14
defaultSkin.trackColor = makeColor(27,27,27)
defaultSkin.thumbColor = makeColor(88,88,88)
t as Table
t.skin = defaultSkin
t.x = 250
t.y = 200
t.width = 400
t.height = 200
t.minColWidth = 10
t.padding = 4
t.vScroll = 1
addColumn(t, "Name")
addColumn(t, "Size")
addColumn(t, "Date")
t.columns[0].width = t.width*0.6
t.columns[1].width = t.width*0.2
t.columns[2].width = t.width*0.2
imgRows = loadImage("rows.png")
setImageWrapU(imgRows, 1)
setImageWrapV(imgRows, 1)
setImageMagFilter(imgRows, 0)
setImageMinFilter(imgRows, 0)
t.bgSpr = createSprite(imgRows)
setSpriteSize(t.bgSpr, t.width, t.height-t.skin.headHeight+32)
setSpriteUVScale(t.bgSpr, 32.0/t.width, 32.0/getSpriteHeight(t.bgSpr))
`setSpritePosition(t.bgSpr, t.x, t.y+t.skin.headHeight)
t.selectSpr = createSprite(0)
setSpriteSize(t.selectSpr, t.width, 16)
setSpriteColor(t.selectSpr, 18, 108, 121, 255)
t.colBgSpr = createSprite(0)
setSpriteSize(t.colBgSpr, t.width, t.skin.headHeight)
setSpriteColor(t.colBgSpr, 4, 4, 4,255)
`setSpritePosition(t.colBgSpr, t.x, t.y)
setTablePosition(t, 250, 200)
d$ as string[5] = [".txt",".dat",".jpg",".html",".mp3",".mkv"]
r as string[2]
for i = 1 to 30
r[0] = randomString() + d$[random(0,5)]
r[1] = str(random(20,999999))
r[2] = str(random(1,12))+"/"+str(random(1,30))+"/"+str(random(2018,2023))
insertRow(t, r)
next i
do
print(msg$)
if t.dblClickRow > -1
msg$ = getCellValue(t, t.dblClickRow, 0)
t.dblClickRow = -1
endif
if getrawkeypressed(32)
x = random(1,1000)
y = random(1,750)
setTablePosition(t, x, y)
endif
updateTable(t)
Sync()
loop
function randomString()
l = random(3, 18)
s$ = ""
for i = 1 to l
s$ = s$ + chr(random(97,122))
next i
endfunction s$
table.agc
Type TableColumn
width as integer
sort as integer
textId as integer
name as string
EndType
Type Table_Skin
bgColor as integer // background color
borderColor as integer // border/grid color
` altColor as integer // alternate row color
` altRows as integer // 1 to alternate row colors
headHeight as integer // height of column headers
trackWidth as integer // width of vertical scroll bar track
trackColor as integer
thumbColor as integer
EndType
Type Table
x as integer
y as integer
padding as integer
voffset as float
width as integer
height as integer
columns as TableColumn[]
cells as integer[]
skin as Table_Skin
dragCol as integer
dragOffsetX as integer
dragBase as integer
minColWidth as integer
ox as float // used to store offset when dragging gui components
oy as float // used to store offset when dragging gui components
vScroll as integer // 1 if showing vertical scrollbar
vx as integer //
vy as integer // vertical thumb position (relative to track)
z as float // used for smooth mouse wheel scrolling
lmb as integer // left mouse button status
dragging as integer // flag for dragging column widths
moveTable as integer // flag for moving window
resizeTable as integer // flag for resizing table
vscrolling as integer // flag for vertical scroll bar
bgSpr as integer // window background sprite
colBgSpr as integer
selectedRow as integer
selectSpr as integer
clickTime as integer
clickCount as integer
dblClickRow as integer
EndType
function updateTable(t ref as Table)
rowCount = (t.cells.length+1) / (t.columns.length+1)
totalHeight = rowCount*16
viewportHeight# = t.height - t.skin.headHeight
thumbHeight = viewportHeight# * (viewportHeight# / totalHeight)
if thumbHeight >= viewportHeight#
t.vScroll = 0
else
t.vScroll = 1
endif
/* Start vertical scrolling */
z = floor(GetRawMouseWheelDelta())
if z
if z < 0 // scroll down
t.z = -4.0
else // scroll up
t.z = 4.0
endif
endif
if t.z <> 0
if t.z < 0
inc t.z, 0.2
if t.z > 0 then t.z = 0
else
dec t.z, 0.2
if t.z < 0 then t.z = 0
endif
setVerticalThumb(t, t.vy-t.z)
endif
/* End vertical scrolling */
// clear flags on mouse up
t.lmb = getRawMouseLeftState()
if t.lmb = 0
t.dragging = 0
t.moveTable = 0
t.resizeTable = 0
t.vscrolling = 0
endif
// if left mouse pressed, check if its in a component trigger area
if getRawMouseLeftPressed()
ms = GetMilliseconds()
if ms - t.clickTime > 500
t.clickCount = 0
endif
inc t.clickCount, 1
t.clickTime = ms
mx = getRawMouseX()
my = getRawMouseY()
thumbY = t.y+t.skin.headheight + t.vy
if mx > t.x and mx < t.x+t.width-t.skin.trackWidth and my > t.y+t.skin.headHeight and my < t.y+t.height
lastSelected = t.selectedRow
x = my - (t.y + t.skin.headHeight + t.voffset)
t.selectedRow = floor(x / 16)
sy = t.y + t.skin.headheight + t.selectedRow*16 + t.voffset
setSpritePosition(t.selectSpr, t.x, sy)
// double-clicked row
if lastSelected = t.selectedRow and t.clickCount = 2
t.dblClickRow = t.selectedRow
endif
// lower-right corner (resize)
elseif abs(mx - (t.x+t.width)) < 5 and abs(my - (t.y+t.height)) < 5
t.resizeTable = 1
t.ox = mx - (t.x+t.width)
t.oy = my - (t.y+t.height)
// vertical scroll thumb
elseif t.vScroll = 1 and mx > t.x+t.width-t.skin.trackWidth and mx < t.x+t.width and my > thumbY and my < thumbY+thumbHeight
t.vscrolling = 1
t.oy = my - thumbY
endif
if t.clickCount >= 2 then t.clickCount = 0
endif
// Handle different component drag events
if t.resizeTable = 1
t.width = getRawMouseX() - t.x - t.ox
t.height = getRawMouseY() - t.y - t.oy
// minimum window size
if t.width < 100 then t.width = 100
if t.height < 60 then t.height = 60
setVerticalThumb(t, t.vy)
setSpriteSize(t.bgSpr, t.width, t.height-t.skin.headHeight+32)
setSpriteSize(t.colBgSpr, t.width, t.skin.headHeight)
setSpriteUVScale(t.bgSpr, 32.0/t.width, 32.0/getSpriteHeight(t.bgSpr))
setSpriteSize(t.selectSpr, t.width, 16)
setSpriteScissor(t.bgSpr, t.x, t.y+t.skin.headHeight, t.x+t.width, t.y+t.height)
setSpriteScissor(t.selectSpr, t.x, t.y+t.skin.headHeight, t.x+t.width, t.y+t.height)
refactorTable(t)
elseif t.vscrolling = 1
setVerticalThumb(t, getRawMouseY() - (t.y+t.skin.headheight) - t.oy)
elseif t.moveTable = 1
t.x = getRawMouseX() - t.ox
t.y = getRawMouseY() - t.oy
setSpritePosition(t.bgSpr, t.x, t.y+t.skin.headHeight)
setSpritePosition(t.colBgSpr, t.x, t.y)
refactorTable(t)
elseif t.dragging = 1
// udpate width of dragging column and do boundary check
t.columns[t.dragCol].width = getRawMouseX() - t.dragBase - t.dragOffsetX
if t.columns[t.dragCol].width < t.minColWidth then t.columns[t.dragCol].width = t.minColWidth
refactorTable(t)
elseif t.lmb = 1
x = t.x
for i = 0 to t.columns.length-1
x = x + t.columns[i].width
if getRawMouseY() > t.y and getRawMouseY() < t.y+t.skin.headHeight
if abs(getRawMouseX()-x) < 4 // mouse within 4px of column line
t.dragging = 1
t.dragOffsetX = getRawMouseX() - x
t.dragCol = i
t.dragBase = x - t.columns[i].width
endif
endif
next i
endif
drawTable(t)
endfunction
function getCellValue(t ref as Table, row as integer, col as integer)
id = row*3 + col
s$ = getTextString(t.cells[id])
endfunction s$
function setTablePosition(t ref as Table, x, y)
t.x = x
t.y = y
setSpritePosition(t.bgSpr, t.x, t.y+t.skin.headHeight)
setSpritePosition(t.colBgSpr, t.x, t.y)
setSpriteScissor(t.bgSpr, t.x, t.y+t.skin.headHeight, t.x+t.width, t.y+t.height)
setSpriteScissor(t.selectSpr, t.x, t.y+t.skin.headHeight, t.x+t.width, t.y+t.height)
refactorTable(t)
endfunction
/**
* Handles the 2d drawing routines,
* must be called on every sync
*/
function drawTable(t ref as Table)
x = t.x + t.width - t.skin.trackWidth
y = t.y+t.skin.headHeight+1
rowCount = (t.cells.length+1) / (t.columns.length+1)
totalHeight = rowCount*16
viewportHeight# = t.height - t.skin.headHeight
thumbHeight = viewportHeight# * (viewportHeight# / totalHeight)
// vertical scrollbar
if t.vScroll = 1
drawBox(x, y, x+t.skin.trackWidth, t.y+t.height, t.skin.trackColor,t.skin.trackColor,t.skin.trackColor,t.skin.trackColor,1) // track
drawBox(x+2, y+t.vy, x+t.skin.trackWidth-1, y+t.vy+thumbHeight-1, t.skin.thumbColor,t.skin.thumbColor,t.skin.thumbColor,t.skin.thumbColor,1) // thumb
drawLine(x, y, x, t.y+t.height, t.skin.borderColor, t.skin.borderColor)
endif
// column headers
drawLine(t.x, t.y+t.skin.headHeight, t.x+t.width, t.y+t.skin.headHeight, t.skin.borderColor, t.skin.borderColor)
// window border
drawBox(t.x, t.y, t.x+t.width, t.y+t.height, t.skin.borderColor,t.skin.borderColor,t.skin.borderColor,t.skin.borderColor, 0)
// column lines
x = t.x
for i = 0 to t.columns.length-1
x = x + t.columns[i].width
if x < t.x+t.width
drawLine(x, t.y+1, x, t.y+t.skin.headHeight, t.skin.borderColor,t.skin.borderColor)
endif
next i
endfunction
function refactorTable(t ref as Table)
// total number of rows
rowCount = (t.cells.length+1) / (t.columns.length+1)
// if showing vertical track bar, prevent text from showing over it
if t.vScroll = 1
maxX = t.x + t.width - t.skin.trackWidth
else
maxX = t.x + t.width
endif
toff = mod(floor(t.vOffset), 32)
setSpritePosition(t.bgSpr, t.x, t.y+t.skin.headHeight+toff)
// row selection
sy = t.y + t.skin.headheight + t.selectedRow*16 + t.voffset
setSpritePosition(t.selectSpr, t.x, sy)
// loop over rows, updating text positions and
for row = 0 to rowCount-1
x = t.x
y = t.y + t.skin.headheight + row*16 + t.voffset
for col = 0 to t.columns.length
id = row * (t.columns.length+1) + col
if col > 0 then x = x + t.columns[col-1].width
setTextPosition(t.cells[id], x+t.padding, y)
x2 = tmx_min(x + t.columns[col].width, maxX)
y2 = tmx_min(y + 16, t.y+t.height-1)
y1 = tmx_max(y, t.y+t.skin.headHeight)
if col = t.columns.length then x2 = tmx_max(x2, maxX)
setTextScissor(t.cells[id], x+t.padding, y1, x2-t.padding, y2)
next col
next row
// column headers
x = t.x
for col = 0 to t.columns.length
setTextPosition(t.columns[col].textId, x+t.padding, t.y+4)
x2 = tmx_min(x+t.columns[col].width, t.x+t.width)
if col = t.columns.length then x2 = tmx_max(x2, t.x+t.width)
setTextScissor(t.columns[col].textId, x+t.padding, t.y, x2-t.padding, t.y+t.skin.headHeight)
x = x + t.columns[col].width
next col
endfunction
function setVerticalThumb(t ref as Table, v as float)
rowCount = (t.cells.length+1) / (t.columns.length+1)
totalHeight = rowCount*16
viewportHeight# = t.height - t.skin.headHeight // visible area
thumbHeight = viewportHeight# * (viewportHeight# / totalHeight)
if thumbHeight < viewportHeight#
t.vy = v
if t.vy > viewportHeight# - thumbHeight then t.vy = viewportHeight# - thumbHeight
if t.vy < 0 then t.vy = 0
d# = viewportHeight# - thumbHeight
p# = t.vy / d#
t.voffset = -(totalHeight - viewportHeight#) * p#
endif
refactorTable(t)
endfunction
function addColumn(t ref as Table, colName as string)
c as TableColumn
c.textId = createText(colName)
setTextSize(c.textId, 16)
setTextColor(c.textId, 18,169,183,255)
c.width = 50
x = t.x
for i = 0 to t.columns.length
x = x + c.width
next i
setTextPosition(c.textId, x, t.y)
t.columns.insert(c)
endfunction
function tmx_min(n1, n2)
if n1 > n2 then exitfunction n2
endfunction n1
function tmx_max(n1, n2)
if n1 > n2 then exitfunction n1
endfunction n2
function addDataFromJSON(t ref as Table, json as string)
rowCount = (t.cells.length+1) / (t.columns.length+1)
offsetY = rowCount * 16
data as string[0,0]
data.fromJSON(json)
dl = (data.length+1)*(data[0].length+1)
startIndex = t.cells.length + 1
t.cells.length = t.cells.length + dl
for row = 0 to data.length
x = 0
w = 0
for j = 0 to data[row].length
id = row * (data[0].length+1) + j + startIndex
t.cells[id] = createText(data[row,j])
setTextSize(t.cells[id], 16)
setTextDepth(t.cells[id], 2)
y = t.y + row*16 + offsetY
if j > 0 then w = t.columns[j-1].width
x = j*50
setTextPosition(t.cells[id], x, y)
setTextScissor(t.cells[id], x, y, x+t.columns[j].width, y+16)
next j
next row
refactorTable(t)
endfunction
function insertRow(t ref as Table, arr as string[])
rowCount = (t.cells.length+1) / (t.columns.length+1)
offsetY = rowCount * 16
startIndex = t.cells.length + 1
t.cells.length = t.cells.length + (t.columns.length+1)
row = startIndex
x = 0
w = 0
for j = 0 to t.columns.length
id = startIndex + j
t.cells[id] = createText(arr[j])
setTextSize(t.cells[id], 16)
setTextColor(t.cells[id], 235,235,235,255)
setTextDepth(t.cells[id], 2)
y = t.y + row*16 + offsetY
if j > 0 then w = t.columns[j-1].width
x = j*50
setTextPosition(t.cells[id], x, y)
setTextScissor(t.cells[id], x, y, x+t.columns[j].width, y+16)
next j
refactorTable(t)
endfunction