Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Workspaces for ComputerCraft
- -- by LDDestroier
- local tArg = {...}
- local instances = {}
- local configPath = ".workspace_config"
- 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},
- }
- }
- -- 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()
- local file = fs.open(configPath, "w")
- file.write( textutils.serialize(config) )
- file.close()
- end
- local loadConfig = function()
- if 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
- 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 scr_x, scr_y = term.getSize()
- 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
- 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
- -- start up lddterm (I'm starting to think I should've used window API)
- local lddterm = {}
- 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
- -- 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"}
- -- 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)
- -- 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
- local image = lddterm.screenshot()
- if type(transformation) == "function" then
- image = transformation(image)
- end
- if drawFunction then
- drawFunction(image, lddterm.baseTerm)
- else
- for y = 1, #image[1] do
- lddterm.baseTerm.setCursorPos(1 + lddterm.adjustX, y + lddterm.adjustY)
- lddterm.baseTerm.blit(image[1][y], image[2][y], image[3][y])
- end
- end
- if doDrawWorkspaceIndicator then
- drawWorkspaceIndicator(nil, doDrawWorkspaceIndicator)
- end
- fixCursorPos()
- 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 = {{},{},{}},
- }
- 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.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)
- end
- end
- window.handle.setPaletteColor = function(...)
- return lddterm.baseTerm.setPaletteColor(...)
- end
- window.handle.setPaletteColour = window.handle.setPaletteColor
- window.handle.getPaletteColor = function(...)
- return lddterm.baseTerm.getPaletteColor(...)
- 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 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()
- 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 displayHelp = function()
- cwrite("CTRL+SHIFT+ARROW to switch workspace. ", -3 + scr_y / 2)
- cwrite("CTRL+SHIFT+TAB+ARROW to swap. ", -2 + scr_y / 2)
- cwrite("CTRL+SHIFT+[WASD] to create a workspace ", -1 + scr_y / 2)
- cwrite(" up/left/down/right respectively. ", 0 + scr_y / 2)
- cwrite("CTRL+SHIFT+P to pause a workspace. ", 1 + scr_y / 2)
- cwrite("CTRL+SHIFT+Q to delete a workspace. ", 2 + scr_y / 2)
- cwrite("Terminate an inactive workspace to exit.", 3 + scr_y / 2)
- 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
- }
- local checkIfCanRun = function(evt, x, y)
- 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"
- )
- )
- )
- end
- local main = function()
- local enteringCommand
- local justStarted = true
- local tID, wID = 0, 0
- local pCounter, program = 0
- local oldFuncReplace = {os = {}, term = {}} -- used when replacing certain os functions per-instance
- 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 config.timesRan <= 0 then
- displayHelp()
- sleep(0.1)
- os.pullEvent("key")
- os.queueEvent("mouse_click", 0, 0, 0)
- end
- config.timesRan = config.timesRan + 1
- saveConfig()
- local previousTerm, cSuccess
- 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
- -- 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
- 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
- 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
- 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
- end
- if (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
- os.cancelTimer(wID)
- 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
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- 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
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- 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
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- 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
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- enteringCommand = true
- end
- if keysDown[keys.w] then
- addWorkspace(0, -1)
- doDrawWorkspaceIndicator = 1
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- keysDown[keys.w] = false
- gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
- end
- if keysDown[keys.s] then
- addWorkspace(0, 1)
- doDrawWorkspaceIndicator = 1
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- keysDown[keys.s] = false
- gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
- end
- if keysDown[keys.a] then
- addWorkspace(-1, 0)
- doDrawWorkspaceIndicator = 1
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- keysDown[keys.a] = false
- gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
- end
- if keysDown[keys.d] then
- addWorkspace(1, 0)
- doDrawWorkspaceIndicator = 1
- os.cancelTimer(wID)
- wID = os.startTimer(workspaceIndicatorDuration)
- keysDown[keys.d] = false
- gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
- end
- if keysDown[keys.q] then
- doDrawWorkspaceIndicator = 1
- os.cancelTimer(wID)
- 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
- gridWidth, gridHeight, gridMinX, gridMinY = getMapSize()
- end
- end
- if 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
- setInstanceSpecificFunctions(x, y)
- previousTerm = term.redirect(instances[y][x].window.handle)
- if justStarted or (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
- for i = 1, #instances[y][x].extraEvents do
- if checkIfCanRun(instances[y][x].extraEvents[i], x, y) then
- cSuccess, instances[y][x].eventFilter = coroutine.resume(instances[y][x].co, table.unpack(instances[y][x].extraEvents[i]))
- else
- break
- end
- end
- instances[y][x].extraEvents = {}
- end
- term.redirect(previousTerm)
- end
- end
- end
- end
- 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
- if doRedraw then
- lddterm.render()
- end
- lddterm.selectedWindow = instances[focus[2]][focus[1]].window.layer
- justStarted = false
- end
- 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
- }
- argList, argErrors = interpretArgs({...}, argData)
- if argList["--help"] or argList["-h"] then
- displayHelp()
- write("\n")
- return
- elseif argList["--config"] or argList["-c"] then
- shell.run("rom/programs/edit.lua", configPath)
- return
- 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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement