Posted: 8th Mar 2024 18:42
Made this because.... well just because. While this loads the table data from a json string, it's simple enough to build it however you want. Because drawing commands by default are drawn last over sprites and text, I opted to use a sprite for the background and window header. You can move the window by dragging the header, resize the window (drag lower right corner) and columns, and scroll using the mouse or dragging the scrollbar. Though as I write this I just remembered it only does vertical scrolling, I forgot to implement horizontal....
I designed it to make it somewhat easy to change themes of the window.




+ Code Snippet
SetErrorMode(2)
SetWindowSize( 1024, 768, 0 )
SetVirtualResolution( 1024, 768 )
SetSyncRate( 60, 0 )
UseNewDefaultFonts( 1 )




Type DataTable
	x as float
	y as float
	width as float
	height as float
	cols as integer
	rows as integer
	data as integer[-1,-1]
	voffset as float
EndType




Type TableColumn
	width  as integer
	sort   as integer
	textId as integer
	name as string
EndType



c = makeColor(255,0,0)

jj$ = '[["Hester Randolph","pink","summer","swifty","046c55ed-dfe0-4d08-8461-ed8d44ac4100"],["Vilma Sears","red","spring","swifty","8f4df59b-3bc9-4502-9c80-50674c64d10e"],["Lynn Morris","pink","spring","rap","723bdf6c-c34c-4527-8001-40ef4fee5c3e"],["Clark Hale","blue","spring","folk","72d3c74b-c4ca-40fc-8136-0ec93328e836"],["Suzanne Cleveland","red","spring","swifty","75761958-ff38-45ae-80e9-8da2eb676518"],["Jones Rollins","blue","Fall","swifty","4be68612-d026-463f-88dc-4a51ddacf737"],["Kinney Collier","turquoise","Fall","rap","318fdcb4-0fa8-4a84-bc67-38917d4edfd5"],["Mona Snow","brown","spring","blues/jazz","9c7fc70b-87b7-4ea6-8df4-def1f4e28d54"]]'



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
	titleHeight as integer  // height of title bar
	trackWidth  as integer  // width of vertical scroll bar track
	trackColor  as integer 
	thumbColor	as integer
EndType


defaultSkin as Table_Skin
defaultSkin.bgColor 	= 0xAA4b433b // ABGR
defaultSkin.borderColor = 0xFF6a5d51
defaultSkin.headHeight  = 24
defaultSkin.titleHeight = 20
defaultSkin.trackWidth  = 14
defaultSkin.trackColor  = makeColor(38,48,53)
defaultSkin.thumbColor  = 0xFF6a5d51

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
	titleSpr	as integer	// window title bar sprite
EndType


t.dragging = 0
t.moveTable = 0
t.resizeTable = 0
t.vscrolling = 0

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, "Color")
addColumn(t, "Season")
addColumn(t, "Music Genre")
addColumn(t, "More Stuff")

addDataFromJSON(t, jj$)
addDataFromJSON(t, jj$)



t.bgSpr = createSprite(0)
setSpriteColor(t.bgSpr, 59,67,76,255)
setSpriteSize(t.bgSpr, t.width, t.height)
setSpritePosition(t.bgSpr, t.x, t.y)

t.titleSpr = createSprite(0)
setSpriteSize(t.titleSpr, t.width, t.skin.titleHeight)
setSpriteColor(t.titleSpr, 81,93,106,255)
setSpritePosition(t.titleSpr, t.x, t.y)


data as string[0,0]
data.fromJSON(jj$)



do


	
	
	updateTable(t)



    Sync()
loop



function updateTable(t ref as Table)
	
	rowCount = (t.cells.length+1) / (t.columns.length+1)
	totalHeight = rowCount*16
	viewportHeight# = t.height - t.skin.headHeight - t.skin.titleHeight
	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()
		mx = getRawMouseX()
		my = getRawMouseY()
		thumbY = t.y+t.skin.titleHeight+t.skin.headheight + t.vy
		// titlebar
		if mx > t.x and mx < t.x+t.width and my > t.y and my < t.y+t.skin.titleHeight
			t.moveTable = 1
			t.ox = mx - t.x
			t.oy = my - t.y
		// 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
	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)
		setSpriteSize(t.titleSpr, t.width, t.skin.titleHeight)
		refactorTable(t)
	elseif t.vscrolling = 1
		setVerticalThumb(t, getRawMouseY() - (t.y+t.skin.titleHeight+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)
		setSpritePosition(t.titleSpr, 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+t.skin.titleHeight and getRawMouseY() < t.y+t.height
				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



/**
* 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.titleHeight+t.skin.headHeight+1
	rowCount = (t.cells.length+1) / (t.columns.length+1)
	totalHeight = rowCount*16
	viewportHeight# = t.height - t.skin.headHeight - t.skin.titleHeight
	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, y+t.vy, x+t.skin.trackWidth, 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.titleHeight+t.skin.headHeight, t.x+t.width, t.y+t.skin.titleHeight+t.skin.headHeight, t.skin.borderColor, t.skin.borderColor)
	// title bar
	drawLine(t.x, t.y+t.skin.titleHeight, t.x+t.width, t.y+t.skin.titleHeight, 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+t.skin.titleHeight+1, x, t.y+t.skin.titleHeight+(t.height-t.skin.titleHeight), 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
	


	// loop over rows, updating text positions and
	for row = 0 to rowCount-1
		x = t.x
		y = t.y + t.skin.titleHeight + 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.titleHeight+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+t.skin.titleHeight+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+t.skin.titleHeight, x2-t.padding, t.y+t.skin.titleHeight+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 - t.skin.titleHeight  // 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)
	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

Posted: 8th Mar 2024 22:56
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
+ Code Snippet
// 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
+ Code Snippet
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