mirror of
				https://github.com/LDDestroier/CC/
				synced 2025-10-30 23:12:59 +00:00 
			
		
		
		
	 ca865a5e9b
			
		
	
	ca865a5e9b
	
	
	
		
			
			CraftOS-PC crashes if you attempt to cancel a timer or alarm that isn't running. Although, this is really a bug with COS-PC and not with Workspace...
		
			
				
	
	
		
			1571 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1571 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- Workspaces for ComputerCraft
 | |
| -- by LDDestroier
 | |
| 
 | |
| local tArg = {...}
 | |
| 
 | |
| local instances = {}
 | |
| local configPath = ".workspace_config"
 | |
| local useConfig = true		-- if false, will not create or use the config file
 | |
| 
 | |
| local config = {
 | |
| 	workspaceMoveSpeed = 0.15,
 | |
| 	defaultProgram = "rom/programs/shell.lua",
 | |
| 	timesRan = 0,
 | |
| 	useDefaultProgramWhenStarting = true,
 | |
| 	doPauseClockAndTime = true,
 | |
| 	skipAcrossEmptyWorkspaces = true,
 | |
| 	showInactiveFrame = true,
 | |
| 	doTrippyVoid = false,
 | |
| 	flipTheFuckOut = false,
 | |
| 	WSmap = {
 | |
| 		{true,true,true},
 | |
| 		{true,true,true},
 | |
| 		{true,true,true},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| local scr_x, scr_y = term.getSize()
 | |
| 
 | |
| -- values determined after every new/removed workspace
 | |
| local gridWidth, gridHeight, gridMinX, gridMinY
 | |
| 
 | |
| -- used by argument parser
 | |
| local argList, argErrors
 | |
| 
 | |
| local getMapSize = function()
 | |
| 	local xmax, xmin, ymax, ymin = -math.huge, math.huge, -math.huge, math.huge
 | |
| 	local isRowEmpty
 | |
| 	for y, v in pairs(config.WSmap) do
 | |
| 		isRowEmpty = true
 | |
| 		for x, vv in pairs(v) do
 | |
| 			if vv then
 | |
| 				xmin = math.min(xmin, x)
 | |
| 				xmax = math.max(xmax, x)
 | |
| 				isRowEmpty = false
 | |
| 			end
 | |
| 		end
 | |
| 		if not isRowEmpty then
 | |
| 			ymin = math.min(ymin, y)
 | |
| 			ymax = math.max(ymax, y)
 | |
| 		end
 | |
| 	end
 | |
| 	return xmax, ymax, xmin, ymin
 | |
| end
 | |
| 
 | |
| local readFile = function(path)
 | |
| 	local file = fs.open(path, "r")
 | |
| 	local contents = file.readAll()
 | |
| 	file.close()
 | |
| 	return contents
 | |
| end
 | |
| 
 | |
| local saveConfig = function()
 | |
| 	if useConfig then
 | |
| 		local file = fs.open(configPath, "w")
 | |
| 		file.write( textutils.serialize(config) )
 | |
| 		file.close()
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local loadConfig = function()
 | |
| 	if useConfig and fs.exists(configPath) then
 | |
| 		local contents = readFile(configPath)
 | |
| 		local newConfig = textutils.unserialize(contents)
 | |
| 		for k,v in pairs(newConfig) do
 | |
| 			config[k] = v
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local cwrite = function(text, y, terminal)
 | |
| 	terminal = terminal or term.current()
 | |
| 	local cx, cy = terminal.getCursorPos()
 | |
| 	local sx, sy = terminal.getSize()
 | |
| 	terminal.setCursorPos(sx / 2 - #text / 2, y or (sy / 2))
 | |
| 	terminal.write(text)
 | |
| end
 | |
| 
 | |
| local displayHelp = function(doCenter)
 | |
| 	local w = doCenter and cwrite or function(txt) print(txt) end
 | |
| 	w("CTRL+SHIFT+ARROW to switch workspace.   ",	-3 + scr_y / 2)
 | |
| 	w("CTRL+SHIFT+TAB+ARROW to swap.           ",	-2 + scr_y / 2)
 | |
| 	w("CTRL+SHIFT+[WASD] to create a workspace ",	-1 + scr_y / 2)
 | |
| 	w(" up/left/down/right respectively.       ",	 0 + scr_y / 2)
 | |
| 	w("CTRL+SHIFT+P to pause a workspace.      ",	 1 + scr_y / 2)
 | |
| 	w("CTRL+SHIFT+Q to delete a workspace.     ",	 2 + scr_y / 2)
 | |
| 	w("Terminate an inactive workspace to exit.",	 3 + scr_y / 2)
 | |
| end
 | |
| 
 | |
| local function interpretArgs(tInput, tArgs)
 | |
| 	local output = {}
 | |
| 	local errors = {}
 | |
| 	local usedEntries = {}
 | |
| 	for aName, aType in pairs(tArgs) do
 | |
| 		output[aName] = false
 | |
| 		for i = 1, #tInput do
 | |
| 			if not usedEntries[i] then
 | |
| 				if tInput[i] == aName and not output[aName] then
 | |
| 					if aType then
 | |
| 						usedEntries[i] = true
 | |
| 						if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then
 | |
| 							usedEntries[i+1] = true
 | |
| 							if aType == "number" then
 | |
| 								output[aName] = tonumber(tInput[i+1])
 | |
| 							else
 | |
| 								output[aName] = tInput[i+1]
 | |
| 							end
 | |
| 						else
 | |
| 							output[aName] = nil
 | |
| 							errors[1] = errors[1] and (errors[1] + 1) or 1
 | |
| 							errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1])
 | |
| 						end
 | |
| 					else
 | |
| 						usedEntries[i] = true
 | |
| 						output[aName] = true
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	for i = 1, #tInput do
 | |
| 		if not usedEntries[i] then
 | |
| 			output[#output+1] = tInput[i]
 | |
| 		end
 | |
| 	end
 | |
| 	return output, errors
 | |
| end
 | |
| 
 | |
| local argData = {
 | |
| 	["--help"] = false,
 | |
| 	["-h"] = false,
 | |
| 	["--config"] = false,
 | |
| 	["-c"] = false,
 | |
| 	["--noconfig"] = false,
 | |
| }
 | |
| 
 | |
| argList, argErrors = interpretArgs({...}, argData)
 | |
| 
 | |
| if argList["--help"] or argList["-h"] then
 | |
| 	displayHelp(false)
 | |
| 	write("\n")
 | |
| 	return
 | |
| elseif argList["--config"] or argList["-c"] then
 | |
| 	shell.run("rom/programs/edit.lua", configPath)
 | |
| 	return
 | |
| end
 | |
| 
 | |
| if argList["--noconfig"] then
 | |
| 	useConfig = false
 | |
| end
 | |
| 
 | |
| loadConfig()
 | |
| saveConfig()
 | |
| 
 | |
| -- lists all keys currently pressed
 | |
| local keysDown = {}
 | |
| 
 | |
| -- amount of time (seconds) until workspace indicator disappears
 | |
| local workspaceIndicatorDuration = 0.6
 | |
| 
 | |
| -- if held down while moving workspace, will swap positions
 | |
| local swapKey = keys.tab
 | |
| 
 | |
| local windowWidth = scr_x
 | |
| local windowHeight = scr_y
 | |
| local doDrawWorkspaceIndicator = false
 | |
| 
 | |
| local scroll = {0,0}		-- change this value when scrolling
 | |
| local realScroll = {0,0}	-- this value changes depending on scroll for smoothness purposes
 | |
| local focus = {}			-- currently focused instance, declared when loading from config
 | |
| 
 | |
| local isRunning = true
 | |
| 
 | |
| -- start up lddterm (I'm starting to think I should've used window API)
 | |
| local lddterm = {
 | |
| 	FULL_IMAGE = false,
 | |
| 	OLD_IMAGE = false,
 | |
| }
 | |
| lddterm.alwaysRender = false		-- renders after any and all screen-changing functions.
 | |
| lddterm.useColors = true			-- normal computers do not allow color, but this variable doesn't do anything yet
 | |
| lddterm.baseTerm = term.current()	-- will draw to this terminal
 | |
| lddterm.transformation = nil		-- will modify the current buffer as an NFT image before rendering
 | |
| lddterm.cursorTransformation = nil	-- will modify the cursor position
 | |
| lddterm.drawFunction = nil			-- will draw using this function instead of basic NFT drawing
 | |
| lddterm.adjustX = 0					-- moves entire screen X
 | |
| lddterm.adjustY = 0					-- moves entire screen Y
 | |
| lddterm.selectedWindow = 1			-- determines which window controls the cursor
 | |
| lddterm.windows = {}				-- internal list of all lddterm windows
 | |
| lddterm.nativePalettes = {			-- native palette colors
 | |
| 	[ 1 ] = {
 | |
| 		0.94117647409439,
 | |
| 		0.94117647409439,
 | |
| 		0.94117647409439,
 | |
| 	},
 | |
| 	[ 2 ] = {
 | |
| 		0.94901961088181,
 | |
| 		0.69803923368454,
 | |
| 		0.20000000298023,
 | |
| 	},
 | |
| 	[ 4 ] = {
 | |
| 		0.89803922176361,
 | |
| 		0.49803921580315,
 | |
| 		0.84705883264542,
 | |
| 	},
 | |
| 	[ 8 ] = {
 | |
| 		0.60000002384186,
 | |
| 		0.69803923368454,
 | |
| 		0.94901961088181,
 | |
| 	},
 | |
| 	[ 16 ] = {
 | |
| 		0.87058824300766,
 | |
| 		0.87058824300766,
 | |
| 		0.42352941632271,
 | |
| 	},
 | |
| 	[ 32 ] = {
 | |
| 		0.49803921580315,
 | |
| 		0.80000001192093,
 | |
| 		0.098039217293262,
 | |
| 	},
 | |
| 	[ 64 ] = {
 | |
| 		0.94901961088181,
 | |
| 		0.69803923368454,
 | |
| 		0.80000001192093,
 | |
| 	},
 | |
| 	[ 128 ] = {
 | |
| 		0.29803922772408,
 | |
| 		0.29803922772408,
 | |
| 		0.29803922772408,
 | |
| 	},
 | |
| 	[ 256 ] = {
 | |
| 		0.60000002384186,
 | |
| 		0.60000002384186,
 | |
| 		0.60000002384186,
 | |
| 	},
 | |
| 	[ 512 ] = {
 | |
| 		0.29803922772408,
 | |
| 		0.60000002384186,
 | |
| 		0.69803923368454,
 | |
| 	},
 | |
| 	[ 1024 ] = {
 | |
| 		0.69803923368454,
 | |
| 		0.40000000596046,
 | |
| 		0.89803922176361,
 | |
| 	},
 | |
| 	[ 2048 ] = {
 | |
| 		0.20000000298023,
 | |
| 		0.40000000596046,
 | |
| 		0.80000001192093,
 | |
| 	},
 | |
| 	[ 4096 ] = {
 | |
| 		0.49803921580315,
 | |
| 		0.40000000596046,
 | |
| 		0.29803922772408,
 | |
| 	},
 | |
| 	[ 8192 ] = {
 | |
| 		0.34117648005486,
 | |
| 		0.65098041296005,
 | |
| 		0.30588236451149,
 | |
| 	},
 | |
| 	[ 16384 ] = {
 | |
| 		0.80000001192093,
 | |
| 		0.29803922772408,
 | |
| 		0.29803922772408,
 | |
| 	},
 | |
| 	[ 32768 ] = {
 | |
| 		0.066666670143604,
 | |
| 		0.066666670143604,
 | |
| 		0.066666670143604,
 | |
| 	}
 | |
| }
 | |
| -- backdropColors used for the void outside of windows, if using rainbow void
 | |
| local backdropColors = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}
 | |
| 
 | |
| local copyTable
 | |
| copyTable = function(tbl)
 | |
| 	local output = {}
 | |
| 	for k,v in pairs(tbl) do
 | |
| 		if type(v) == "table" then
 | |
| 			output[k] = copyTable(v)
 | |
| 		else
 | |
| 			output[k] = v
 | |
| 		end
 | |
| 	end
 | |
| 	return output
 | |
| end
 | |
| 
 | |
| -- draws one of three things:
 | |
| --  1. workspace grid indicator
 | |
| --  2. "PAUSED" screen
 | |
| --  3. "UNPAUSED" screen
 | |
| local drawWorkspaceIndicator = function(terminal, wType)
 | |
| 	gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 	terminal = terminal or term.current()
 | |
| 	if wType == 1 then
 | |
| 		for y = gridMinY - 1, gridHeight + 1 do
 | |
| 			for x = gridMinX - 1, gridWidth + 1 do
 | |
| 				terminal.setCursorPos((x - gridMinX) + scr_x / 2 - (gridWidth - gridMinX) / 2, (y - gridMinY) + math.ceil(scr_y / 2) - (gridHeight - gridMinY) / 2)
 | |
| 				if instances[y] then
 | |
| 					if instances[y][x] then
 | |
| 						if focus[1] == x and focus[2] == y then
 | |
| 							terminal.blit(" ", "8", "8")
 | |
| 						elseif instances[y][x].active then
 | |
| 							terminal.blit(" ", "7", "7")
 | |
| 						else
 | |
| 							terminal.blit(" ", "0", "f")
 | |
| 						end
 | |
| 					else
 | |
| 						terminal.blit(" ", "0", "0")
 | |
| 					end
 | |
| 				else
 | |
| 					terminal.blit(" ", "0", "0")
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	elseif wType == 2 then
 | |
| 		local msg = "PAUSED"
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 - 1)
 | |
| 		terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2)
 | |
| 		terminal.blit(" " .. msg .. " ", ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 + 1)
 | |
| 		terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 	elseif wType == 3 then
 | |
| 		local msg = "UNPAUSED"
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 - 1)
 | |
| 		terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2)
 | |
| 		terminal.blit(" " .. msg .. " ", ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 		terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 + 1)
 | |
| 		terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2))
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- converts blit colors to colors api, and back
 | |
| local to_colors, to_blit = {
 | |
| 	[' '] = 0,
 | |
| 	['0'] = 1,
 | |
| 	['1'] = 2,
 | |
| 	['2'] = 4,
 | |
| 	['3'] = 8,
 | |
| 	['4'] = 16,
 | |
| 	['5'] = 32,
 | |
| 	['6'] = 64,
 | |
| 	['7'] = 128,
 | |
| 	['8'] = 256,
 | |
| 	['9'] = 512,
 | |
| 	['a'] = 1024,
 | |
| 	['b'] = 2048,
 | |
| 	['c'] = 4096,
 | |
| 	['d'] = 8192,
 | |
| 	['e'] = 16384,
 | |
| 	['f'] = 32768,
 | |
| }, {}
 | |
| for k,v in pairs(to_colors) do
 | |
| 	to_blit[v] = k
 | |
| end
 | |
| 
 | |
| -- separates string into table based on divider
 | |
| local explode = function(div, str, replstr, includeDiv)
 | |
| 	if (div == '') then
 | |
| 		return false
 | |
| 	end
 | |
| 	local pos, arr = 0, {}
 | |
| 	for st, sp in function() return string.find(str, div, pos, false) end do
 | |
| 		table.insert(arr, string.sub(replstr or str, pos, st - 1 + (includeDiv and #div or 0)))
 | |
| 		pos = sp + 1
 | |
| 	end
 | |
| 	table.insert(arr, string.sub(replstr or str, pos))
 | |
| 	return arr
 | |
| end
 | |
| 
 | |
| -- determines the size of the terminal before rendering always
 | |
| local determineScreenSize = function()
 | |
| 	scr_x, scr_y = lddterm.baseTerm.getSize()
 | |
| 	lddterm.screenWidth = scr_x
 | |
| 	lddterm.screenHeight = scr_y
 | |
| end
 | |
| 
 | |
| determineScreenSize()
 | |
| 
 | |
| -- takes two or more windows and checks if the first of them overlap the other(s)
 | |
| lddterm.checkWindowOverlap = function(window, ...)
 | |
| 	if #lddterm.windows < 2 then
 | |
| 		return false
 | |
| 	end
 | |
| 	local list, win = {...}
 | |
| 	for i = 1, #list do
 | |
| 		win = list[i]
 | |
| 		if win ~= window then
 | |
| 
 | |
| 			if (
 | |
| 				window.x < win.x + win.width and
 | |
| 				win.x < window.x + window.width and
 | |
| 				window.y < win.y + win.height and
 | |
| 				win.y < window.y + window.height
 | |
| 			) then
 | |
| 				return true
 | |
| 			end
 | |
| 
 | |
| 		end
 | |
| 	end
 | |
| 	return false
 | |
| end
 | |
| 
 | |
| local fixCursorPos = function()
 | |
| 	local cx, cy
 | |
| 	if lddterm.windows[lddterm.selectedWindow] then
 | |
| 		if lddterm.cursorTransformation then
 | |
| 			cx, cy = lddterm.cursorTransformation(
 | |
| 				lddterm.windows[lddterm.selectedWindow].cursor[1],
 | |
| 				lddterm.windows[lddterm.selectedWindow].cursor[2]
 | |
| 			)
 | |
| 			lddterm.baseTerm.setCursorPos(
 | |
| 				cx + lddterm.windows[lddterm.selectedWindow].x - 1,
 | |
| 				cy + lddterm.windows[lddterm.selectedWindow].y - 1
 | |
| 			)
 | |
| 		else
 | |
| 			lddterm.baseTerm.setCursorPos(
 | |
| 				-1 + lddterm.windows[lddterm.selectedWindow].cursor[1] + lddterm.windows[lddterm.selectedWindow].x,
 | |
| 				lddterm.windows[lddterm.selectedWindow].cursor[2] + lddterm.windows[lddterm.selectedWindow].y - 1
 | |
| 			)
 | |
| 		end
 | |
| 		lddterm.baseTerm.setCursorBlink(lddterm.windows[lddterm.selectedWindow].blink)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- renders the screen with optional transformation function
 | |
| lddterm.render = function(transformation, drawFunction, forceDraw)
 | |
| 	-- determine new screen size and change lddterm screen to fit
 | |
| 	old_scr_x, old_scr_y = scr_x, scr_y
 | |
| 	determineScreenSize()
 | |
| 	if old_scr_x ~= scr_x or old_scr_y ~= scr_y then
 | |
| 		lddterm.baseTerm.clear()
 | |
| 	end
 | |
| 	lddterm.OLD_IMAGE = lddterm.FULL_IMAGE or {{},{},{}}
 | |
| 	lddterm.FULL_IMAGE = lddterm.screenshot()
 | |
| 	if type(transformation) == "function" then
 | |
| 		lddterm.FULL_IMAGE = transformation(lddterm.FULL_IMAGE)
 | |
| 	end
 | |
| 	if drawFunction then
 | |
| 		drawFunction(lddterm.FULL_IMAGE, lddterm.baseTerm)
 | |
| 	else
 | |
| 		for y = 1, #lddterm.FULL_IMAGE[1] do
 | |
| 			if forceDraw or (lddterm.FULL_IMAGE[1][y] ~= lddterm.OLD_IMAGE[1][y]) or (lddterm.FULL_IMAGE[2][y] ~= lddterm.OLD_IMAGE[2][y]) or (lddterm.FULL_IMAGE[3][y] ~= lddterm.OLD_IMAGE[3][y]) then
 | |
| 				lddterm.baseTerm.setCursorPos(1 + lddterm.adjustX, y + lddterm.adjustY)
 | |
| 				lddterm.baseTerm.blit(lddterm.FULL_IMAGE[1][y], lddterm.FULL_IMAGE[2][y], lddterm.FULL_IMAGE[3][y])
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	if doDrawWorkspaceIndicator then
 | |
| 		drawWorkspaceIndicator(nil, doDrawWorkspaceIndicator)
 | |
| 	end
 | |
| 	fixCursorPos()
 | |
| end
 | |
| 
 | |
| -- sets term palette to that of instance (x, y)'s
 | |
| local correctPalette = function(x, y)
 | |
| 	local exists = false
 | |
| 	if instances[y] then
 | |
| 		if instances[y][x] then
 | |
| 			for i = 0, 15 do
 | |
| 				lddterm.baseTerm.setPaletteColor(2^i, table.unpack(instances[y][x].window.palette[2^i]))
 | |
| 			end
 | |
| 			exists = true
 | |
| 		end
 | |
| 	end
 | |
| 	if not exists then
 | |
| 		for i = 0, 15 do
 | |
| 			lddterm.baseTerm.setPaletteColor(2^i, table.unpack(lddterm.nativePalettes[2^i]))
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| lddterm.newWindow = function(width, height, x, y, meta)
 | |
| 	meta = meta or {}
 | |
| 	local window = {
 | |
| 		width = math.floor(width),
 | |
| 		height = math.floor(height),
 | |
| 		blink = true,
 | |
| 		cursor = meta.cursor or {1, 1},
 | |
| 		colors = meta.colors or {"0", "f"},
 | |
| 		clearChar = meta.clearChar or " ",
 | |
| 		visible = meta.visible or true,
 | |
| 		x = math.floor(x) or 1,
 | |
| 		y = math.floor(y) or 1,
 | |
| 		buffer = {{},{},{}},
 | |
| 		palette = {}
 | |
| 	}
 | |
| 	for y = 1, height do
 | |
| 		window.buffer[1][y] = {}
 | |
| 		window.buffer[2][y] = {}
 | |
| 		window.buffer[3][y] = {}
 | |
| 		for x = 1, width do
 | |
| 			window.buffer[1][y][x] = window.clearChar
 | |
| 			window.buffer[2][y][x] = window.colors[1]
 | |
| 			window.buffer[3][y][x] = window.colors[2]
 | |
| 		end
 | |
| 	end
 | |
| 	window.palette = copyTable(lddterm.nativePalettes)
 | |
| 
 | |
| 	window.handle = {}
 | |
| 	window.handle.setCursorPos = function(x, y)
 | |
| 		window.cursor = {x, y}
 | |
| 		fixCursorPos()
 | |
| 	end
 | |
| 	window.handle.getCursorPos = function()
 | |
| 		return window.cursor[1], window.cursor[2]
 | |
| 	end
 | |
| 	window.handle.setCursorBlink = function(blink)
 | |
| 		window.blink = blink or false
 | |
| 	end
 | |
| 	window.handle.getCursorBlink = function()
 | |
| 		return window.blink
 | |
| 	end
 | |
| 	window.handle.scroll = function(amount)
 | |
| 		if amount > 0 then
 | |
| 			for i = 1, amount do
 | |
| 				for c = 1, 3 do
 | |
| 					table.remove(window.buffer[c], 1)
 | |
| 					window.buffer[c][window.height] = {}
 | |
| 					for xx = 1, width do
 | |
| 						window.buffer[c][window.height][xx] = (
 | |
| 							c == 1 and window.clearChar or
 | |
| 							c == 2 and window.colors[1] or
 | |
| 							c == 3 and window.colors[2]
 | |
| 						)
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		elseif amount < 0 then
 | |
| 			for i = 1, -amount do
 | |
| 				for c = 1, 3 do
 | |
| 					window.buffer[c][window.height] = nil
 | |
| 					table.insert(window.buffer[c], 1, {})
 | |
| 					for xx = 1, width do
 | |
| 						window.buffer[c][1][xx] = (
 | |
| 							c == 1 and window.clearChar or
 | |
| 							c == 2 and window.colors[1] or
 | |
| 							c == 3 and window.colors[2]
 | |
| 						)
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		if lddterm.alwaysRender then
 | |
| 			lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.write = function(text)
 | |
| 		assert(text ~= nil, "expected string 'text'")
 | |
| 		text = tostring(text)
 | |
| 		local cx = math.floor(window.cursor[1])
 | |
| 		local cy = math.floor(window.cursor[2])
 | |
| 		for i = 1, #text do
 | |
| 			if cx >= 1 and cx <= window.width and cy >= 1 and cy <= window.height then
 | |
| 				window.buffer[1][cy][cx] = text:sub(i,i)
 | |
| 				window.buffer[2][cy][cx] = window.colors[1]
 | |
| 				window.buffer[3][cy][cx] = window.colors[2]
 | |
| 			end
 | |
| 			cx = math.min(cx + 1, window.width + 1)
 | |
| 		end
 | |
| 		window.cursor = {cx, cy}
 | |
| 		if lddterm.alwaysRender then
 | |
| 			lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.blit = function(char, textCol, backCol)
 | |
| 		if type(char) == "number" then
 | |
| 			char = tostring(char)
 | |
| 		end
 | |
| 		if type(textCol) == "number" then
 | |
| 			textCol = tostring(textCol)
 | |
| 		end
 | |
| 		if type(backCol) == "number" then
 | |
| 			backCol = tostring(backCol)
 | |
| 		end
 | |
| 		assert(char ~= nil, "expected string 'char'")
 | |
| 		local cx = math.floor(window.cursor[1])
 | |
| 		local cy = math.floor(window.cursor[2])
 | |
| 		for i = 1, #char do
 | |
| 			if cx >= 1 and cx <= window.width and cy >= 1 and cy <= window.height then
 | |
| 				window.buffer[1][cy][cx] = char:sub(i,i)
 | |
| 				window.buffer[2][cy][cx] = textCol:sub(i,i)
 | |
| 				window.buffer[3][cy][cx] = backCol:sub(i,i)
 | |
| 			end
 | |
| 			cx = cx + 1
 | |
| 		end
 | |
| 		window.cursor = {cx, cy}
 | |
| 		if lddterm.alwaysRender then
 | |
| 			lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.clear = function(char)
 | |
| 		local cx = 1
 | |
| 		char = type(char) == "string" and char or " "
 | |
| 		for y = 1, window.height do
 | |
| 			for x = 1, window.width do
 | |
| 				if char then
 | |
| 					cx = (x % #char) + 1
 | |
| 				end
 | |
| 				window.buffer[1][y][x] = char and char:sub(cx, cx) or window.clearChar
 | |
| 				window.buffer[2][y][x] = window.colors[1]
 | |
| 				window.buffer[3][y][x] = window.colors[2]
 | |
| 			end
 | |
| 		end
 | |
| 		if lddterm.alwaysRender then
 | |
| 			lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.clearLine = function(cy, char)
 | |
| 		cy = math.floor(cy or window.cursor[2])
 | |
| 		char = type(char) == "string" and char or " "
 | |
| 		local cx = 1
 | |
| 		if window.buffer[1][cy or window.cursor[2]] then
 | |
| 			for x = 1, window.width do
 | |
| 				if char then
 | |
| 					cx = (x % #char) + 1
 | |
| 				end
 | |
| 				window.buffer[1][cy or window.cursor[2]][x] = char and char:sub(cx, cx) or window.clearChar
 | |
| 				window.buffer[2][cy or window.cursor[2]][x] = window.colors[1]
 | |
| 				window.buffer[3][cy or window.cursor[2]][x] = window.colors[2]
 | |
| 			end
 | |
| 			if lddterm.alwaysRender then
 | |
| 				lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.getSize = function()
 | |
| 		return window.width, window.height
 | |
| 	end
 | |
| 	window.handle.isColor = function()
 | |
| 		return lddterm.useColors
 | |
| 	end
 | |
| 	window.handle.isColour = window.handle.isColor
 | |
| 	window.handle.setTextColor = function(color)
 | |
| 		if to_blit[color] then
 | |
| 			window.colors[1] = to_blit[color]
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.setTextColour = window.handle.setTextColor
 | |
| 	window.handle.setBackgroundColor = function(color)
 | |
| 		if to_blit[color] then
 | |
| 			window.colors[2] = to_blit[color]
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.setBackgroundColour = window.handle.setBackgroundColor
 | |
| 	window.handle.getTextColor = function()
 | |
| 		return to_colors[window.colors[1]] or colors.white
 | |
| 	end
 | |
| 	window.handle.getTextColour = window.handle.getTextColor
 | |
| 	window.handle.getBackgroundColor = function()
 | |
| 		return to_colors[window.colors[2]] or colors.black
 | |
| 	end
 | |
| 	window.handle.getBackgroundColour = window.handle.getBackgroundColor
 | |
| 	window.handle.reposition = function(x, y)
 | |
| 		window.x = math.floor(x or window.x)
 | |
| 		window.y = math.floor(y or window.y)
 | |
| 		if lddterm.alwaysRender then
 | |
| 			lddterm.render(lddterm.transformation, lddterm.drawFunction, true)
 | |
| 		end
 | |
| 	end
 | |
| 	window.handle.setPaletteColor = function(slot, r, g, b)
 | |
| 		assert(type(slot) == "number", "bad argument #1 to 'setPaletteColor' (expected number, got " .. type(slot) .. ")")
 | |
| 		assert(to_blit[slot], "Invalid color (got " .. tostring(slot) .. ")")
 | |
| 		if g then	-- individual color values
 | |
| 			assert(type(r) == "number", "bad argument #2 to 'setPaletteColor' (expected number, got " .. type(r) .. ")")
 | |
| 			assert(type(g) == "number", "bad argument #3 to 'setPaletteColor' (expected number, got " .. type(g) .. ")")
 | |
| 			assert(type(b) == "number", "bad argument #4 to 'setPaletteColor' (expected number, got " .. type(b) .. ")")
 | |
| 			window.palette[slot] = {
 | |
| 				math.min(1, math.max(0, r)),
 | |
| 				math.min(1, math.max(0, g)),
 | |
| 				math.min(1, math.max(0, b)),
 | |
| 			}
 | |
| 		else	-- using HEX
 | |
| 			assert(type(r) == "number", "bad argument #2 to 'setPaletteColor' (expected number, got " .. type(r) .. ")")
 | |
| 			window.palette[slot] = {colors.unpackRGB(r)}
 | |
| 		end
 | |
| 		correctPalette(window.x, window.y)
 | |
| 	end
 | |
| 	window.handle.setPaletteColour = window.handle.setPaletteColor
 | |
| 	window.handle.getPaletteColor = function(slot)
 | |
| 		assert(type(slot) == "number", "bad argument #1 to 'setPaletteColor' (expected number, got " .. type(slot) .. ")")
 | |
| 		assert(to_blit[slot], "Invalid color (got " .. tostring(slot) .. ")")
 | |
| 		return table.unpack(window.palette[slot])
 | |
| 	end
 | |
| 	window.handle.getPaletteColour = window.handle.getPaletteColor
 | |
| 	window.handle.getPosition = function()
 | |
| 		return window.x, window.y
 | |
| 	end
 | |
| 	window.handle.restoreCursor = function()
 | |
| 		lddterm.baseTerm.setCursorPos(
 | |
| 			-1 + window.cursor[1] + window.x,
 | |
| 			window.cursor[2] + window.y - 1
 | |
| 		)
 | |
| 	end
 | |
| 	window.handle.setVisible = function(visible)
 | |
| 		window.visible = visible or false
 | |
| 	end
 | |
| 
 | |
| 	window.handle.redraw = lddterm.render
 | |
| --	window.handle.current = window.handle
 | |
| 
 | |
| 	window.layer = #lddterm.windows + 1
 | |
| 	lddterm.windows[window.layer] = window
 | |
| 
 | |
| 	return window, window.layer
 | |
| end
 | |
| 
 | |
| lddterm.setLayer = function(window, _layer)
 | |
| 	local layer = math.max(1, math.min(#lddterm.windows, _layer))
 | |
| 
 | |
| 	local win = window
 | |
| 	table.remove(lddterm.windows, win.layer)
 | |
| 	table.insert(lddterm.windows, layer, win)
 | |
| 
 | |
| 	if lddterm.alwaysRender then
 | |
| 		lddterm.render(lddterm.transformation, lddterm.drawFunction)
 | |
| 	end
 | |
| 	return true
 | |
| end
 | |
| 
 | |
| local old_scr_x, old_scr_y
 | |
| 
 | |
| -- gets screenshot of whole lddterm desktop, OR a single window
 | |
| lddterm.screenshot = function(window)
 | |
| 	local output = {{},{},{}}
 | |
| 	local line
 | |
| 	if window then
 | |
| 		for y = 1, #window.buffer do
 | |
| 			line = {"","",""}
 | |
| 			for x = 1, #window.buffer do
 | |
| 				line = {
 | |
| 					line[1] .. window.buffer[1][y][x],
 | |
| 					line[2] .. window.buffer[2][y][x],
 | |
| 					line[3] .. window.buffer[3][y][x]
 | |
| 				}
 | |
| 			end
 | |
| 			output[1][y] = line[1]
 | |
| 			output[2][y] = line[2]
 | |
| 			output[3][y] = line[3]
 | |
| 		end
 | |
| 	else
 | |
| 		for y = 1, scr_y do
 | |
| 			line = {"","",""}
 | |
| 			for x = 1, scr_x do
 | |
| 
 | |
| 				lt, lb = t, b
 | |
| 				if config.doTrippyVoid then
 | |
| 					c = string.char(math.random(128, 159))
 | |
| 					t = backdropColors[1 + math.floor((y - realScroll[2] * scr_y) % #backdropColors)]
 | |
| 					b = backdropColors[1 + math.floor((x - realScroll[1] * scr_x) % #backdropColors)]
 | |
| 				else
 | |
| 					c = string.char( math.max(128, math.random(-5000, 159)) )
 | |
| 					t = ({"7", "8"})[math.random(1, 2)]
 | |
| 					b = "f"
 | |
| 				end
 | |
| 				for l, v in pairs(lddterm.windows) do
 | |
| 					if lddterm.windows[l] then
 | |
| 						if lddterm.windows[l].visible then
 | |
| 							sx = 1 + x - lddterm.windows[l].x
 | |
| 							sy = 1 + y - lddterm.windows[l].y
 | |
| 							if lddterm.windows[l].buffer[1][sy] then
 | |
| 								if lddterm.windows[l].buffer[1][sy][sx] then
 | |
| 									c = lddterm.windows[l].buffer[1][sy][sx] or c
 | |
| 									t = lddterm.windows[l].buffer[2][sy][sx] or t
 | |
| 									b = lddterm.windows[l].buffer[3][sy][sx] or b
 | |
| 									break
 | |
| 								end
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				line = {
 | |
| 					line[1] .. c,
 | |
| 					line[2] .. t,
 | |
| 					line[3] .. b
 | |
| 				}
 | |
| 			end
 | |
| 			output[1][y] = line[1]
 | |
| 			output[2][y] = line[2]
 | |
| 			output[3][y] = line[3]
 | |
| 		end
 | |
| 	end
 | |
| 	return output
 | |
| end
 | |
| 
 | |
| local newInstance = function(x, y, program, initialStart)
 | |
| 	x, y = math.floor(x), math.floor(y)
 | |
| 	if instances[y] then
 | |
| 		if instances[y][x] then
 | |
| 			return
 | |
| 		end
 | |
| 	end
 | |
| 	gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 	for yy = gridMinY, y do
 | |
| 		instances[yy] = instances[yy] or {}
 | |
| 	end
 | |
| 	instances[y] = instances[y] or {}
 | |
| 	for xx = gridMinX, x do
 | |
| 		instances[y][xx] = instances[y][xx] or false
 | |
| 	end
 | |
| 	local window = lddterm.newWindow(windowWidth, windowHeight, 1, 1)
 | |
| 
 | |
| 	local instance = {
 | |
| 		x = x,
 | |
| 		y = y,
 | |
| 		active = initialStart,
 | |
| 		program = program or config.defaultProgram,
 | |
| 		window = window,
 | |
| 		timer = {},
 | |
| 		clockMod = 0,
 | |
| 		lastClock = 0,
 | |
| 		timeMod = 0,
 | |
| 		lastTime = 0,
 | |
| 		extraEvents = {},
 | |
| 		paused = false
 | |
| 	}
 | |
| 
 | |
| 	local func = function()
 | |
| 		term.redirect(window.handle)
 | |
| 
 | |
| 		local runProgram = function()
 | |
| 			instance.paused = false
 | |
| 			term.setCursorBlink(false)
 | |
| 			if not instance.program or type(instance.program) == "string" then
 | |
| 				setfenv(function() pcall(shell.run, instance.program) end, instance.env)()
 | |
| 			elseif type(instance.program) == "function" then
 | |
| 				pcall(function() load(instance.program, nil, nil, instance.env) end)
 | |
| 			end
 | |
| 			instance.extraEvents = {}
 | |
| 			instance.timer = {}
 | |
| 			instance.clockMod = 0
 | |
| 			instance.lastClock = 0
 | |
| 			instance.timeMod = 0
 | |
| 			instance.lastTime = 0
 | |
| 		end
 | |
| 
 | |
| 		local cx, cy
 | |
| 
 | |
| 		local drawInactiveScreen = function()
 | |
| 			term.setTextColor(colors.white)
 | |
| 			term.setBackgroundColor(colors.black)
 | |
| 			term.clear()
 | |
| 			term.setCursorBlink(false)
 | |
| 
 | |
| 			if config.showInactiveFrame then
 | |
| 				if (instance.y + instance.x) % 2 == 0 then
 | |
| 					term.setTextColor(colors.lightGray)
 | |
| 				else
 | |
| 					term.setTextColor(colors.gray)
 | |
| 				end
 | |
| 				for y = 1, scr_y do
 | |
| 					for x = 1, scr_x do
 | |
| 						if y == 1 or y == scr_y then
 | |
| 							if x <= 3 or x > scr_x - 3 then
 | |
| 								term.setCursorPos(x, y)
 | |
| 								term.write("\127")
 | |
| 							end
 | |
| 						elseif y <= 3 or y > scr_y - 3 then
 | |
| 							if x == 1 or x == scr_x then
 | |
| 								term.setCursorPos(x, y)
 | |
| 								term.write("\127")
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				term.setTextColor(colors.white)
 | |
| 			end
 | |
| 
 | |
| 			cwrite("This workspace is inactive.", 0 + scr_y / 2)
 | |
| 			cwrite("Press SPACE to start the workspace.", 1 + scr_y / 2)
 | |
| 			cwrite("(" .. tostring(instance.x) .. ", " .. tostring(instance.y) .. ")", 3 + scr_y / 2)
 | |
| 		end
 | |
| 
 | |
| 		local evt
 | |
| 		while true do
 | |
| 
 | |
| 			if initialStart then
 | |
| 				runProgram()
 | |
| 			end
 | |
| 
 | |
| 			instance.active = false
 | |
| 			instance.paused = false
 | |
| 			if config.useDefaultProgramWhenStarting then
 | |
| 				instance.program = config.defaultProgram
 | |
| 			end
 | |
| 
 | |
| 			drawInactiveScreen()
 | |
| 
 | |
| 			coroutine.yield()
 | |
| 
 | |
| 			window.palette = copyTable(lddterm.nativePalettes)
 | |
| 			correctPalette(window.x, window.y)
 | |
| 
 | |
| 			repeat
 | |
| 				evt = {os.pullEventRaw()}
 | |
| 				if evt[1] == "workspace_swap" then
 | |
| 					drawInactiveScreen()
 | |
| 				end
 | |
| 			until (evt[1] == "key" and evt[2] == keys.space) or evt[1] == "terminate"
 | |
| 			sleep(0)
 | |
| 			if evt[1] == "terminate" then
 | |
| 				isRunning = false
 | |
| 				return
 | |
| 			end
 | |
| 
 | |
| 			term.setCursorPos(1,1)
 | |
| 			term.clear()
 | |
| 			term.setCursorBlink(true)
 | |
| 
 | |
| 			instance.active = true
 | |
| 
 | |
| 			if not initialStart then
 | |
| 				runProgram()
 | |
| 			end
 | |
| 
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	instances[y][x] = instance
 | |
| 
 | |
| 	instances[y][x].env = {}
 | |
| 	setmetatable(instances[y][x].env, {__index = _ENV})
 | |
| 
 | |
| 	instances[y][x].co = coroutine.create(func)
 | |
| end
 | |
| 
 | |
| -- prevents wiseassed-ness
 | |
| config.workspaceMoveSpeed = math.min(math.max(config.workspaceMoveSpeed, 0.001), 1)
 | |
| 
 | |
| local tickDownInstanceTimers = function(x, y)
 | |
| 	timersToDelete = {}
 | |
| 	for id, duration in pairs(instances[y][x].timer) do
 | |
| 		if duration <= 0.05 then
 | |
| 			instances[y][x].extraEvents[#instances[y][x].extraEvents + 1] = {"timer", id}
 | |
| 			timersToDelete[#timersToDelete + 1] = id
 | |
| 		else
 | |
| 			instances[y][x].timer[id] = duration - 0.05
 | |
| 		end
 | |
| 	end
 | |
| 	for i = 1, #timersToDelete do
 | |
| 		instances[y][x].timer[timersToDelete[i]] = nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local scrollWindows = function(doScrollWindows, tickDownTimers)
 | |
| 	local changed = false
 | |
| 	local timersToDelete = {}
 | |
| 	local xrand, yrand = 0, 0
 | |
| 	if config.flipTheFuckOut then
 | |
| 		xrand, yrand = math.random(-5, 5) / 60, math.random(-5, 5) / 60
 | |
| 	end
 | |
| 	if doScrollWindows then
 | |
| 		if realScroll[1] < scroll[1] + xrand then
 | |
| 			realScroll[1] = math.min(realScroll[1] + config.workspaceMoveSpeed, scroll[1] + xrand)
 | |
| 			changed = true
 | |
| 		elseif realScroll[1] > scroll[1] + xrand then
 | |
| 			realScroll[1] = math.max(realScroll[1] - config.workspaceMoveSpeed, scroll[1] + xrand)
 | |
| 			changed = true
 | |
| 		end
 | |
| 		if realScroll[2] < scroll[2] + yrand then
 | |
| 			realScroll[2] = math.min(realScroll[2] + config.workspaceMoveSpeed, scroll[2] + yrand)
 | |
| 			changed = true
 | |
| 		elseif realScroll[2] > scroll[2] + yrand then
 | |
| 			realScroll[2] = math.max(realScroll[2] - config.workspaceMoveSpeed, scroll[2] + yrand)
 | |
| 			changed = true
 | |
| 		end
 | |
| 	end
 | |
| 	gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 	for y = gridMinY, gridHeight do
 | |
| 		if instances[y] then
 | |
| 			for x = gridMinX, gridWidth do
 | |
| 				if instances[y][x] then
 | |
| 
 | |
| 					instances[y][x].window.x = math.floor(1 + (x + realScroll[1] - 1) * scr_x)
 | |
| 					instances[y][x].window.y = math.floor(1 + (y + realScroll[2] - 1) * scr_y)
 | |
| 					if not instances[y][x].paused then
 | |
| 						tickDownInstanceTimers(x, y)
 | |
| 					end
 | |
| 
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	return changed
 | |
| end
 | |
| 
 | |
| local swapInstances = function(xmod, ymod)
 | |
| 	if not instances[focus[2]][focus[1]].active then
 | |
| 		table.insert(instances[focus[2]][focus[1]].extraEvents, {"workspace_swap"})
 | |
| 	end
 | |
| 	if not instances[focus[2] + ymod][focus[1] + xmod].active then
 | |
| 		table.insert(instances[focus[2] + ymod][focus[1] + xmod].extraEvents, {"workspace_swap"})
 | |
| 	end
 | |
| 
 | |
| 	instances[focus[2]][focus[1]], instances[focus[2] + ymod][focus[1] + xmod] = instances[focus[2] + ymod][focus[1] + xmod], instances[focus[2]][focus[1]]
 | |
| 	instances[focus[2]][focus[1]].x, instances[focus[2] + ymod][focus[1] + xmod].x = instances[focus[2] + ymod][focus[1] + xmod].x, instances[focus[2]][focus[1]].x
 | |
| 	instances[focus[2]][focus[1]].y, instances[focus[2] + ymod][focus[1] + xmod].y = instances[focus[2] + ymod][focus[1] + xmod].y, instances[focus[2]][focus[1]].y
 | |
| end
 | |
| 
 | |
| local addWorkspace = function(xmod, ymod)
 | |
| 	config.WSmap[focus[2] + ymod] = config.WSmap[focus[2] + ymod] or {}
 | |
| 	if not config.WSmap[focus[2] + ymod][focus[1] + xmod] then
 | |
| 		config.WSmap[focus[2] + ymod][focus[1] + xmod] = true
 | |
| 		newInstance(focus[1] + xmod, focus[2] + ymod, config.defaultProgram, false)
 | |
| 		saveConfig()
 | |
| 		gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local removeWorkspace = function(xmod, ymod)
 | |
| 	if config.WSmap[focus[2] + ymod][focus[1] + xmod] then
 | |
| 		local good = false
 | |
| 
 | |
| 		for m = 1, math.max(gridHeight - gridMinY + 1, gridWidth - gridMinX + 1) do
 | |
| 			for y = -1, 1 do
 | |
| 				for x = -1, 1 do
 | |
| 					if math.abs(x) + math.abs(y) == 1 then
 | |
| 						if instances[focus[2] + y * m] then
 | |
| 							if instances[focus[2] + y * m][focus[1] + x * m] then
 | |
| 								good = true
 | |
| 								break
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				if good then
 | |
| 					break
 | |
| 				end
 | |
| 			end
 | |
| 			if good then
 | |
| 				break
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		if good then
 | |
| 			lddterm.windows[instances[focus[2] + ymod][focus[1] + xmod].window.layer] = nil
 | |
| 			config.WSmap[focus[2] + ymod][focus[1] + xmod] = nil
 | |
| 			instances[focus[2] + ymod][focus[1] + xmod] = nil
 | |
| 			local isRowEmpty
 | |
| 			local remList = {}
 | |
| 			for y, v in pairs(config.WSmap) do
 | |
| 				isRowEmpty = true
 | |
| 				for x, vv in pairs(v) do
 | |
| 					if vv then
 | |
| 						isRowEmpty = false
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				if isRowEmpty then
 | |
| 					remList[#remList + 1] = y
 | |
| 				end
 | |
| 			end
 | |
| 			for i = 1, #remList do
 | |
| 				config.WSmap[remList[i]] = nil
 | |
| 			end
 | |
| 			saveConfig()
 | |
| 			gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 		end
 | |
| 	else
 | |
| --		print("There's no such workspace.")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local inputEvt = {
 | |
| 	key = true,
 | |
| 	key_up = true,
 | |
| 	char = true,
 | |
| 	mouse_click = true,
 | |
| 	mouse_scroll = true,
 | |
| 	mouse_drag = true,
 | |
| 	mouse_up = true,
 | |
| 	paste = true,
 | |
| 	terminate = true
 | |
| }
 | |
| 
 | |
| -- what a mess! this needs serious rewriting with if statements
 | |
| 
 | |
| --local checkIfCanRun = function(evt, x, y)
 | |
| --	if evt then
 | |
| --		return (
 | |
| --			justStarted or (
 | |
| --				(not instances[y][x].paused) and (
 | |
| --					not instances[y][x].eventFilter or
 | |
| --					instances[y][x].eventFilter == evt[1] or
 | |
| --					evt[1] == "terminate"
 | |
| --				) and (
 | |
| --					(not inputEvt[evt[1]]) and
 | |
| --					instances[y][x].active or (
 | |
| --						x == focus[1] and
 | |
| --						y == focus[2]
 | |
| --					) or (
 | |
| --						x == focus[1] and
 | |
| --						y == focus[2]
 | |
| --					) and (
 | |
| --						evt[1] == "terminate"
 | |
| --					) or evt[1] == "workspace_swap"
 | |
| --				)
 | |
| --			)
 | |
| --		)
 | |
| --	else
 | |
| --		return false
 | |
| --	end
 | |
| --end
 | |
| 
 | |
| 
 | |
| local checkIfCanRun = function(evt, x, y)
 | |
| 	if not instances[y] then
 | |
| 		return false
 | |
| 	elseif not instances[y][x] then
 | |
| 		return false
 | |
| 	end
 | |
| 
 | |
| 	local instance = instances[y][x]
 | |
| 	local focused = (focus[1] == x and focus[2] == y)
 | |
| 
 | |
| 	if evt then
 | |
| 		if justStarted then
 | |
| 			return true
 | |
| 		else
 | |
| 			if instance.paused then
 | |
| 				return false
 | |
| 			else
 | |
| 
 | |
| 				if evt[1] == "workspace_swap" then
 | |
| 					return true
 | |
| 				elseif evt[1] == "terminate" and focused then
 | |
| 					return true
 | |
| 				else
 | |
| 					if instance.active then
 | |
| 						if focused then
 | |
| 							return true
 | |
| 						elseif inputEvt[evt[1]] then
 | |
| 							return false
 | |
| 						else
 | |
| 							return true
 | |
| 						end
 | |
| 					else
 | |
| 						return focused
 | |
| 					end
 | |
| 				end
 | |
| 
 | |
| 			end
 | |
| 		end
 | |
| 	else
 | |
| 		return false
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local oldFuncReplace = {os = {}, term = {}}	-- used when replacing certain os functions per-instance
 | |
| 
 | |
| local setInstanceSpecificFunctions = function(x, y)
 | |
| 	os.startTimer = function(duration)
 | |
| 		if type(duration) == "number" then
 | |
| 			local t
 | |
| 			while true do
 | |
| 				t = math.random(1, 2^30)
 | |
| 				if not instances[y][x].timer[t] then
 | |
| 					instances[y][x].timer[t] = math.floor(duration * 20) / 20
 | |
| 					return t
 | |
| 				end
 | |
| 			end
 | |
| 		else
 | |
| 			error("bad argument #1 (number expected, got " .. type(duration) .. ")", 2)
 | |
| 		end
 | |
| 	end
 | |
| 	os.cancelTimer = function(id)
 | |
| 		if type(id) == "number" then
 | |
| 			instances[y][x].timer[id] = nil
 | |
| 		else
 | |
| 			error("bad argument #1 (number expected, got " .. type(id) .. ")", 2)
 | |
| 		end
 | |
| 	end
 | |
| 	if config.doPauseClockAndTime then
 | |
| 		os.clock = function()
 | |
| 			return oldFuncReplace.os.clock() + instances[y][x].clockMod
 | |
| 		end
 | |
| 		os.time = function()
 | |
| 			return oldFuncReplace.os.time() + instances[y][x].timeMod
 | |
| 		end
 | |
| 	end
 | |
| 	os.queueEvent = function(evt, ...)
 | |
| 		if type(evt) == "string" then
 | |
| 			instances[y][x].extraEvents[#instances[y][x].extraEvents + 1] = {evt, ...}
 | |
| 		else
 | |
| 			error("bad argument #1 (number expected, got " .. type(evt) .. ")", 2)
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local resumeInstance = function(evt, x, y)
 | |
| 	setInstanceSpecificFunctions(x, y)
 | |
| 	previousTerm = term.redirect(instances[y][x].window.handle)
 | |
| 
 | |
| 	if not (evt[1] == "resume_instance" and evt[2] == x and evt[3] == y) then
 | |
| 		if checkIfCanRun(evt, x, y) and not (banTimerEvent and evt[1] == "timer") then
 | |
| 			cSuccess, instances[y][x].eventFilter = coroutine.resume(instances[y][x].co, table.unpack(evt))
 | |
| 		end
 | |
| 
 | |
| 		if #instances[y][x].extraEvents ~= 0 and not instances[y][x].paused then
 | |
| 			if checkIfCanRun(instances[y][x].extraEvents[1], x, y) then
 | |
| 				cSuccess, instances[y][x].eventFilter = coroutine.resume(instances[y][x].co, table.unpack(instances[y][x].extraEvents[1]))
 | |
| 			end
 | |
| 			table.remove(instances[y][x].extraEvents, 1)
 | |
| 		end
 | |
| 
 | |
| 		if checkIfCanRun(instances[y][x].extraEvents[1], x, y) then
 | |
| 			oldFuncReplace.os.queueEvent("resume_instance", x, y, instances[y][x].extraEvents[1])
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	term.redirect(previousTerm)
 | |
| 
 | |
| 	os.startTimer = oldFuncReplace.os.startTimer
 | |
| 	os.cancelTimer = oldFuncReplace.os.cancelTimer
 | |
| 	if config.doPauseClockAndTime then
 | |
| 		os.clock = oldFuncReplace.os.clock
 | |
| 		os.time = oldFuncReplace.os.time
 | |
| 	end
 | |
| 	os.queueEvent = oldFuncReplace.os.queueEvent
 | |
| end
 | |
| 
 | |
| local main = function()
 | |
| 	local enteringCommand
 | |
| 	local justStarted = true
 | |
| 	local tID, wID = 0, 0
 | |
| 	local pCounter, program = 0
 | |
| 
 | |
| 	for y, v in pairs(config.WSmap) do
 | |
| 		for x, vv in pairs(v) do
 | |
| 			if vv then
 | |
| 				pCounter = pCounter + 1
 | |
| 				program = (argList[pCounter] and fs.exists(argList[pCounter])) and argList[pCounter]
 | |
| 				if not program then
 | |
| 					program = (argList[pCounter] and fs.exists(argList[pCounter] .. ".lua")) and (argList[pCounter] .. ".lua")
 | |
| 				end
 | |
| 				newInstance(
 | |
| 					x, y,
 | |
| 					program or config.defaultProgram,
 | |
| 					program and true or (pCounter == 1)
 | |
| 				)
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 	focus[2] = gridMinY
 | |
| 	for x = gridMinX, gridWidth do
 | |
| 		if instances[focus[2]][x] then
 | |
| 			focus[1] = x
 | |
| 			realScroll = {-x + 1, -gridMinY + 1}
 | |
| 			scroll = {-x + 1, -gridMinY + 1}
 | |
| 			break
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	scrollWindows(true, false)
 | |
| 
 | |
| 	term.clear()
 | |
| 	if useConfig and config.timesRan <= 0 then
 | |
| 		displayHelp(true)
 | |
| 		sleep(0.1)
 | |
| 		os.pullEvent("key")
 | |
| 
 | |
| 		os.queueEvent("mouse_click", 0, 0, 0)
 | |
| 	end
 | |
| 
 | |
| 	config.timesRan = config.timesRan + 1
 | |
| 	saveConfig()
 | |
| 
 | |
| 	local previousTerm, cSuccess
 | |
| 
 | |
| 	-- timer for instance timers and window scrolling
 | |
| 	tID = os.startTimer(0.05)
 | |
| 
 | |
| 	-- if true, timer events won't be accepted by instances (unless it's an extraEvent)
 | |
| 	local banTimerEvent, evt
 | |
| 	local doRedraw = false			-- redraw screen after resuming every instance
 | |
| 	local doForceRedraw = false	-- redraw screen without checking for changes in screen
 | |
| 	local doTick = true					-- check for key inputs and whatnot
 | |
| 
 | |
| 	local checkIfExtraEvents = function()
 | |
| 		for y = gridMinY, gridHeight do
 | |
| 			if instances[y] then
 | |
| 				for x = gridMinX, gridWidth do
 | |
| 					if instances[y][x] then
 | |
| 						if #instances[y][x].extraEvents ~= 0 then
 | |
| 							return true
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		return false
 | |
| 	end
 | |
| 
 | |
| 	while isRunning do
 | |
| 		gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 		doRedraw = false
 | |
| 		doForceRedraw = false
 | |
| 		doTick = true
 | |
| 
 | |
| 		evt = {os.pullEventRaw()}
 | |
| 
 | |
| 		enteringCommand = false
 | |
| 		if evt[1] == "key" then
 | |
| 			keysDown[evt[2]] = true
 | |
| 		elseif evt[1] == "key_up" then
 | |
| 			keysDown[evt[2]] = nil
 | |
| 		elseif evt[1] == "timer" then
 | |
| 			if evt[2] == wID then
 | |
| 				enteringCommand = true
 | |
| 				doDrawWorkspaceIndicator = false
 | |
| 				banTimerEvent = true
 | |
| 				doRedraw = true
 | |
| 				doForceRedraw = true
 | |
| 			else
 | |
| 				if evt[2] == tID then
 | |
| 					doRedraw = true
 | |
| 					banTimerEvent = true
 | |
| 					tID = os.startTimer(0.05)
 | |
| 					scrollWindows(true, true)
 | |
| 				else
 | |
| 					banTimerEvent = false
 | |
| 					scrollWindows(false, true)
 | |
| 				end
 | |
| 			end
 | |
| 		elseif evt[1] == "resume_instance" then
 | |
| 
 | |
| 			resumeInstance(evt[4], evt[2], evt[3])
 | |
| 
 | |
| 			doTick = false
 | |
| 		end
 | |
| 
 | |
| 		if doTick and ((keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl]) and (keysDown[keys.leftShift] or keysDown[keys.rightShift])) then
 | |
| 			if evt[1] == "key" then
 | |
| 				if evt[2] == keys.p then
 | |
| 					if instances[focus[2]][focus[1]].active then
 | |
| 						instances[focus[2]][focus[1]].paused = not instances[focus[2]][focus[1]].paused
 | |
| 						enteringCommand = true
 | |
| 						doDrawWorkspaceIndicator = instances[focus[2]][focus[1]].paused and 2 or 3
 | |
| 						
 | |
| 						wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 						if config.doPauseClockAndTime then
 | |
| 							if instances[focus[2]][focus[1]].paused then
 | |
| 								instances[focus[2]][focus[1]].lastClock = os.clock() + instances[focus[2]][focus[1]].clockMod
 | |
| 								instances[focus[2]][focus[1]].lastTime = os.time() + instances[focus[2]][focus[1]].timeMod
 | |
| 							else
 | |
| 								instances[focus[2]][focus[1]].clockMod = instances[focus[2]][focus[1]].lastClock - os.clock()
 | |
| 								instances[focus[2]][focus[1]].timeMod = instances[focus[2]][focus[1]].lastTime - os.time()
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				elseif evt[2] == keys.o then
 | |
| 					loadConfig()
 | |
| 				end
 | |
| 			end
 | |
| 			if keysDown[keys.left] then
 | |
| 				for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (focus[1] - gridMinX + 1) do
 | |
| 					if instances[focus[2]][focus[1] - i] then
 | |
| 						if keysDown[swapKey] then
 | |
| 							swapInstances(-i, 0)
 | |
| 						end
 | |
| 						focus[1] = focus[1] - i
 | |
| 						scroll[1] = scroll[1] + i
 | |
| 						keysDown[keys.left] = false
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				correctPalette(focus[1], focus[2])
 | |
| 				enteringCommand = true
 | |
| 			end
 | |
| 			if keysDown[keys.right] then
 | |
| 				for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (gridWidth - focus[1]) do
 | |
| 					if instances[focus[2]][focus[1] + i] then
 | |
| 						if keysDown[swapKey] then
 | |
| 							swapInstances(i, 0)
 | |
| 						end
 | |
| 						focus[1] = focus[1] + i
 | |
| 						scroll[1] = scroll[1] - i
 | |
| 						keysDown[keys.right] = false
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				correctPalette(focus[1], focus[2])
 | |
| 				enteringCommand = true
 | |
| 			end
 | |
| 			if keysDown[keys.up] then
 | |
| 				for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (focus[2] - gridMinY + 1) do
 | |
| 					if instances[focus[2] - i] then
 | |
| 						if instances[focus[2] - i][focus[1]] then
 | |
| 							if keysDown[swapKey] then
 | |
| 								swapInstances(0, -i)
 | |
| 							end
 | |
| 							focus[2] = focus[2] - i
 | |
| 							scroll[2] = scroll[2] + i
 | |
| 							keysDown[keys.up] = false
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				correctPalette(focus[1], focus[2])
 | |
| 				enteringCommand = true
 | |
| 			end
 | |
| 			if keysDown[keys.down] then
 | |
| 				for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (gridHeight - focus[2]) do
 | |
| 					if instances[focus[2] + i] then
 | |
| 						if instances[focus[2] + i][focus[1]] then
 | |
| 							if keysDown[swapKey] then
 | |
| 								swapInstances(0, i)
 | |
| 							end
 | |
| 							focus[2] = focus[2] + i
 | |
| 							scroll[2] = scroll[2] - i
 | |
| 							keysDown[keys.down] = false
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				correctPalette(focus[1], focus[2])
 | |
| 				enteringCommand = true
 | |
| 			end
 | |
| 			if keysDown[keys.w] then
 | |
| 				addWorkspace(0, -1)
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				keysDown[keys.w] = false
 | |
| 				gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 			end
 | |
| 			if keysDown[keys.s] then
 | |
| 				addWorkspace(0, 1)
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				keysDown[keys.s] = false
 | |
| 				gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 			end
 | |
| 			if keysDown[keys.a] then
 | |
| 				addWorkspace(-1, 0)
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				keysDown[keys.a] = false
 | |
| 				gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 			end
 | |
| 			if keysDown[keys.d] then
 | |
| 				addWorkspace(1, 0)
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				keysDown[keys.d] = false
 | |
| 				gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 			end
 | |
| 			if keysDown[keys.q] then
 | |
| 				doDrawWorkspaceIndicator = 1
 | |
| 				
 | |
| 				wID = os.startTimer(workspaceIndicatorDuration)
 | |
| 				keysDown[keys.q] = false
 | |
| 				local good = false
 | |
| 				for m = 1, math.max(gridHeight - gridMinY + 1, gridWidth - gridMinX + 1) do
 | |
| 					for y = -1, 1 do
 | |
| 						for x = -1, 1 do
 | |
| 							if math.abs(x) + math.abs(y) == 1 then
 | |
| 								if instances[focus[2] + y * m] then
 | |
| 									if instances[focus[2] + y * m][focus[1] + x * m] then
 | |
| 										removeWorkspace(0, 0)
 | |
| 										focus = {
 | |
| 											focus[1] + x * m,
 | |
| 											focus[2] + y * m
 | |
| 										}
 | |
| 										scroll = {
 | |
| 											scroll[1] - x * m,
 | |
| 											scroll[2] - y * m
 | |
| 										}
 | |
| 										good = true
 | |
| 										break
 | |
| 									end
 | |
| 								end
 | |
| 							end
 | |
| 						end
 | |
| 						if good then
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 					if good then
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				correctPalette(focus[1], focus[2])
 | |
| 				gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		if doTick and (not enteringCommand) then
 | |
| 
 | |
| 			oldFuncReplace.os.startTimer = os.startTimer
 | |
| 			oldFuncReplace.os.cancelTimer = os.cancelTimer
 | |
| 			if config.doPauseClockAndTime then
 | |
| 				oldFuncReplace.os.clock = os.clock
 | |
| 				oldFuncReplace.os.time = os.time
 | |
| 			end
 | |
| 			oldFuncReplace.os.queueEvent = os.queueEvent
 | |
| 
 | |
| 			for y = gridMinY, gridHeight do
 | |
| 				if instances[y] then
 | |
| 					for x = gridMinX, gridWidth do
 | |
| 						if instances[y][x] then
 | |
| 
 | |
| 							resumeInstance(evt, x, y)
 | |
| 
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 
 | |
| 		end
 | |
| 
 | |
| 		if doRedraw then
 | |
| 			lddterm.render(nil, nil, doForceRedraw)
 | |
| 		end
 | |
| 
 | |
| 		lddterm.selectedWindow = instances[focus[2]][focus[1]].window.layer
 | |
| 		justStarted = false
 | |
| 
 | |
| 	end
 | |
| end
 | |
| 
 | |
| if _G.currentlyRunningWorkspace then
 | |
| 	print("Workspace is already running.")
 | |
| 	return
 | |
| else
 | |
| 	_G.currentlyRunningWorkspace = true
 | |
| end
 | |
| 
 | |
| _G.instances = instances
 | |
| 
 | |
| local result, message = pcall(main)
 | |
| 
 | |
| _G.currentlyRunningWorkspace = false
 | |
| 
 | |
| term.clear()
 | |
| term.setCursorPos(1,1)
 | |
| if result then
 | |
| 	print("Thanks for using Workspace!")
 | |
| else
 | |
| 	print("There was an error, and Workspace had to stop.")
 | |
| 	print("The error goes as follows:\n")
 | |
| 	print(message)
 | |
| end
 |