Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Author: Encreedem
- -- Link: http://www.computercraft.info/forums2/index.php?/topic/13944-161-graffiti-the-first-ide-for-guis/
- -- Notes to myself:
- -- TODO: The Progress Bars movePos and scalePos get messed up when it's facing up.
- -- TODO: Replace "get...Value()" with "objects.ObjectType.load()"
- local version = "1.6.2"
- -- >>> Variables
- -- Tables for users:
- local userFunctions = {}
- local userLists = {}
- local userInputs = {}
- local selectedItems = {}
- local selectedFiles = {}
- local toggleState = {}
- --Monitor
- local monitor = nil
- -- Text
- local text = {}
- text.back = " < "
- text.quit = " X "
- text.refresh = "Refresh"
- text.done = "Done"
- text.options = "Options"
- --text.fileSelector = "[...]"
- -- Colors
- local objectColors = {}
- -- Color Themes
- local colorThemes = {}
- colorThemes.Default = {
- background = colors.black;
- text = colors.white;
- Button = { default = colors.red; active = colors.lime; text = colors.white };
- ProgressBar = { low = colors.red; medium = colors.yellow; high = colors.lime; background = colors.gray; };
- Input = { default = colors.white; text = colors.black; active = colors.yellow; };
- List = { default = colors.blue; active = colors.lightBlue; };
- FileSelector = { default = colors.red; text = colors.white; dir = colors.lime; file = colors.white; active = colors.lime; };
- Editor = {
- new = colors.white;
- active = colors.lime;
- move = colors.magenta;
- scale = colors.pink;
- marker = colors.gray;
- editMarker = colors.lime;
- alignmentTrue = colors.lime;
- alignmentFalse = colors.red;
- };
- Container = {
- Panel = {
- border = colors.white;
- };
- ScrollView = {
- border = colors.white;
- scrollBackground = colors.lightGray;
- scrollForeground = colors.gray;
- };
- };
- }
- colorThemes["Windows CC"] = {
- background = colors.white;
- text = colors.black;
- Button = { default = colors.lightGray; active = colors.lightBlue; text = colors.black };
- ProgressBar = { low = colors.lime; medium = colors.lime; high = colors.lime; background = colors.lightGray; };
- Input = { default = colors.lightGray; text = colors.black; active = colors.white; };
- List = { default = colors.lightGray; active = colors.lightBlue; };
- FileSelector = { default = colors.gray; text = colors.black; dir = colors.blue; file = colors.black; active = colors.lightBlue; };
- Editor = {
- new = colors.lightBlue;
- active = colors.black;
- move = colors.magenta;
- scale = colors.pink;
- marker = colors.gray;
- editMarker = colors.lightBlue;
- alignmentTrue = colors.lime;
- alignmentFalse = colors.red;
- };
- Container = {
- Panel = {
- border = colors.lightGray;
- };
- ScrollView = {
- border = colors.lightGray;
- scrollBackground = colors.lightGray;
- scrollForeground = colors.black;
- };
- };
- }
- objectColors = colorThemes.Default
- -- Sizes
- local size = {}
- size.Button = { width = 10; height = 3; }
- size.ProgressBar = { length = 10; }
- size.Container = { width = 20; height = 10; }
- -- Files Info
- local root = "/" -- The path where the data-folder will be created by default.
- local dataFolderPath = nil
- local loadedFiles = {}
- local currentProject = "Default"
- -- API
- local initDone = false
- local isAPI = false -- Determines whether the program has been loaded as an API
- local variableValues = {}
- local progressBarValues = {}
- -- Editor Options
- local editMode = false
- local showEditorOptions = false
- local saveAfterQuit = true
- local editActions = { "Design", "Attributes", "Delete" }
- local lastWindow = "mainWindow"
- local editorFunctions = {}
- local rightClickActions = {"Attributes", "Delete" }
- -- tables
- local args = { ... }
- local sides = { "left", "top", "right", "bottom", "front", "back" }
- local objectTypes = { "Button", "Text", "Variable", "ProgressBar", "Input", "List", "Panel", "ScrollView" }
- local eventTypes = { "quit", "button_clicked", "button_toggled", "selection_changed", "text_changed" }
- local objects = {}
- local windows = {children = { mainWindow = { objType = "Window", children = {} } }}
- local windowBuffer = nil
- local defaultButtons = {}
- -- Other
- local quit = false
- local doLog = true -- Determines wheter a log file should be created or not.
- local maxX, maxY = 51, 19
- local out = term -- Output: either "term" or the monitor
- local outIsTerm = false
- local autoLoadObjects = true
- local changeButtonColor = true
- local currentWindow = "mainWindow"
- -- Settings
- local settings = {}
- settings.root = root
- settings.startupProject = currentProject
- settings.language = "en-US"
- settings.colorTheme = "Default"
- -- >> User Code: (This section is just for YOU! :D)
- -- user variables
- local randomValue= 50
- -- user functions
- --[[ How to make your own functions:
- Note: toggleState is only needed if the buttons'
- funcType-attribute is set to "toggle function"
- function userFunctions.<paramAttribute>(toggleState)
- your code here
- end
- ]]
- function userFunctions.test()
- sleep(1)
- end
- function userFunctions.setRandomValue()
- randomValue = math.random(100)
- end
- function userFunctions.refresh()
- drawWindow()
- end
- function userFunctions.toggleTest(toggleState)
- rs.setOutput("front", toggleState)
- end
- -- user lists
- userLists.testList = {
- "Testitem 1",
- "Testitem 2",
- "Testitem 3"
- }
- -- Define the value of a variable-object.
- function getVariableValue(object)
- assert(object)
- local objID = object.objID
- if (objID == "testVariable") then
- return "Variable";
- elseif (objID == "Time") then
- return textutils.formatTime(os.time(), true)
- else
- return variableValues[objID]
- end
- end
- -- Definie the value of a progressBar-object
- -- 0: empty; 100: full
- function getProgressBarValue(object)
- assert(object)
- local objID = object.objID
- if (objID == "testProgressBar") then
- return 87
- elseif (objID == "randomProgressBar") then
- return randomValue
- else
- return progressBarValues[objID]
- end
- end
- --[[ WARNING!
- Everything below this comment
- shouldn't be edited!
- If you do so and the program doesn't work anymore
- then it's your fault!
- ]]
- --[[ Displays the text, the content of a table
- or a star in the upper left corner until you press
- a key.]]
- function dBug(text)
- out.setCursorPos(1, 1)
- if (text == nil) then
- out.write("*")
- getKeyInput()
- out.setCursorPos(1, 1)
- out.write(" ")
- return
- elseif (type(text) == "table") then
- for key, value in pairs(text) do
- print(key .. ": " .. tostring(value))
- end
- else
- out.write(text)
- end
- getKeyInput()
- end
- -- Checks whether the table contains the given content-parameter.
- function tableContains(tbl, content)
- if (tbl and type(tbl) == "table") then
- for _, value in pairs(tbl) do
- if (value == content) then
- return true
- end
- end
- return false
- else
- return false
- end
- end
- -- >> Files
- local Files = {}
- -- Returns whether the filename ends with the
- -- given extension.
- function Files.endsWith(filename, extension)
- assert(filename)
- assert(extension)
- local sStart, sEnd = string.find(filename, extension, (#extension * -1))
- return (sStart ~= nil)
- end
- -- Serializes a normal or serialized table into a
- -- readable format which can be saved in a file.
- function Files.serialize(toSave)
- local saveText
- local serialized = ""
- local indentation = 0
- local indent = false
- if (type(toSave) == "string") then
- saveText = toSave
- elseif (type(toSave) == "table") then
- saveText = textutils.serialize(toSave)
- else
- error("Can't save variable of type .. " .. type(toSave) .. "!", 1)
- end
- for char in saveText:gmatch(".") do
- if (char == "{") then
- indentation = indentation + 1
- serialized = serialized .. char
- serialized = serialized .. "\n"
- indent = true
- elseif (char == ",") then
- serialized = serialized .. char .. "\n"
- indent = true
- elseif (char == "}") then
- indentation = indentation - 1
- serialized = serialized .. string.rep("\t", indentation)
- serialized = serialized .. char
- indent = true
- else
- if indent then
- serialized = serialized .. string.rep("\t", indentation)
- indent = false
- end
- serialized = serialized .. char
- end
- end
- serialized = serialized:gsub(" ", "<SPACE>")
- return serialized
- end
- -- Unserializes a string that has been serialized
- -- using the "Files.serialize" function into a
- -- table.
- function Files.unserialize(str)
- local saveText = str
- saveText = saveText:gsub("\n", "")
- saveText = saveText:gsub("\t", "")
- saveText = saveText:gsub(" ", "")
- saveText = saveText:gsub("<SPACE>", " ")
- saveText = textutils.unserialize(str)
- return saveText
- end
- -- Initializes the Graffiti data folder.
- function Files.init()
- if (fs.exists(fs.combine(root, ".GraffitiData"))) then
- dataFolderPath = fs.combine(root, ".GraffitiData")
- elseif (fs.exists(fs.combine(root, "GraffitiData"))) then
- dataFolderPath = fs.combine(root, "GraffitiData")
- else
- runSetup()
- end
- for key, value in pairs(Files) do
- if (type(value) == "table") then -- If it's an actual file type instead of a function.
- if (value.autoLoad and value.load) then
- Files.load(key)
- end
- end
- end
- end
- -- Saves all files with
- function Files.save()
- for _, file in pairs(Files) do
- if (type(file) == "table") then -- If it's an actual file type instead of a function.
- if (file.autoSave and file.save) then
- file.save()
- end
- end
- end
- end
- -- Loads the file and all of its required files.
- function Files.load(fileType)
- if not tableContains(loadedFiles, fileType) then
- local loadRequired = Files[fileType].loadRequired
- if (loadRequired) then
- for _, value in pairs(loadRequired) do
- Files.load(value)
- end
- end
- Files[fileType].load()
- table.insert(loadedFiles, fileType)
- end
- end
- function Files.saveTable(subDir, fileName, tbl)
- local fileHandle = Files.createDataFile(subDir, fileName, "w")
- for key, value in pairs(tbl) do
- fileHandle.writeLine(key .. "=" .. value)
- end
- if fileHandle then
- fileHandle.close()
- end
- end
- function Files.loadTable(subDir, fileName)
- local fileHandle = Files.getDataFileHandle(subDir, fileName, "r", false)
- local ret = {}
- if not fileHandle then
- return nil
- end
- repeat
- local line = fileHandle.readLine()
- if (line ~= nil and line ~= "" and string.find(line, "=")) then
- local key, value = splitAt(line, "=")
- ret[key] = value
- end
- until line == nil
- if fileHandle then
- fileHandle.close()
- end
- return ret
- end
- -- Removes the content of a file.
- function Files.clear(subDir, fileName)
- local fileHandle = Files.getDataFileHandle(subDir, fileName, "w", false)
- if fileHandle then
- fileHandle.close()
- end
- end
- -- Returns the handle of an existing file or null.
- -- The file will automatically be created if
- -- autoCreate is true.
- function Files.getDataFileHandle(subDir, filename, mode, autoCreate)
- assert(filename)
- assert(mode)
- local ret
- local path
- -- Create the path to the file if it doesn't exist.
- if subDir then
- local subDirPath
- if (type(subDir) == "string" and subDir ~= "") then
- subDirPath = fs.combine(dataFolderPath, subDir)
- path = fs.combine(subDirPath, filename)
- if (fs.exists(subDirPath)) then
- if (not fs.isDir(subDirPath)) then
- error("Can't create directory " .. subDir .. "! A file with that name exists!", 0)
- end
- else
- fs.makeDir(subDirPath)
- end
- elseif (type(subDir) == "table") then
- local subDirPath = dataFolderPath
- for _, dir in pairs(subDir) do
- subDirPath = fs.combine(subDirPath, dir)
- if (fs.exists(subDirPath)) then
- if (not fs.isDir(subDirPath)) then
- error("Can't create directory " .. subDir .. "! A file with that name exists!", 0)
- end
- else
- fs.makeDir(subDirPath)
- end
- end
- path = fs.combine(subDirPath, filename)
- end
- else
- path = fs.combine(dataFolderPath, filename)
- end
- if (fs.exists(path) and fs.isReadOnly(path)) then
- error("File " .. filePath .. " is readonly!", 0)
- end
- if (fs.exists(path) or mode == "w") then
- ret = fs.open(path, mode)
- elseif (autoCreate) then
- ret = fs.open(path, "w") or error("Can't open file " .. path, 2)
- end
- return ret
- end
- -- Creates or overrides a file and returns its handle.
- function Files.createDataFile(subDir, filename)
- return Files.getDataFileHandle(subDir, filename, "w")
- end
- -- >> Save File
- Files.Save = {}
- Files.Save.autoSave = true
- Files.Save.autoLoad = true
- Files.Save.loadRequired = { "Settings" }
- Files.Save.extension = ".window"
- -- Saves the content of the windows-table into the
- -- save file.
- function Files.Save.save()
- for name, window in pairs(windows.children) do
- if (type(window) == "table") then
- local saveString = Files.serialize(window)
- local fileHandle = Files.createDataFile({ "Save", currentProject }, name .. Files.Save.extension )
- fileHandle.write(saveString)
- fileHandle.close()
- end
- end
- end
- -- Loads the save file and puts the content into
- -- the windows-table
- function Files.Save.load()
- local path = fs.combine(dataFolderPath, "Save")
- path = fs.combine(path, currentProject)
- if not path or not fs.isDir(path) then
- return
- end
- for _, filename in pairs(fs.list(path)) do
- local filePath = fs.combine(path, filename)
- local windowName = string.sub(filename, 1, #filename - #Files.Save.extension)
- if (not fs.isDir(filePath) and Files.endsWith(filename, Files.Save.extension)) then
- local fileHandle = Files.getDataFileHandle({ "Save", currentProject }, filename, "r", false)
- if fileHandle then
- local loadString = fileHandle.readAll()
- loadString = Files.unserialize(loadString)
- if (loadString ~= nil and loadString ~= "") then
- windows.children[windowName] = loadString
- end
- fileHandle.close()
- end
- end
- end
- end
- -- >> Settings File
- Files.Settings = {}
- Files.Settings.autoSave = true
- Files.Settings.autoLoad = true
- Files.Settings.subDir = nil
- Files.Settings.name = "Graffiti.cfg"
- function Files.Settings.init()
- if not settings then
- return
- end
- for key, value in pairs(settings) do
- if (key == "startupProject") then
- currentProject = value
- elseif (key == "language") then
- settings.language = value
- elseif (key == "colorTheme" and colorThemes[value]) then
- objectColors = colorThemes[value]
- end
- end
- end
- -- Creates the settings file in the data-folder and fills it with default values.
- function Files.Settings.save()
- Files.saveTable(Files.Settings.subDir, Files.Settings.name, settings)
- end
- -- Loads the settings file.
- function Files.Settings.load()
- local tbl = Files.loadTable(Files.Settings.subDir, Files.Settings.name)
- if tbl then
- for key, value in pairs(tbl) do
- settings[key] = value
- end
- end
- Files.Settings.init()
- end
- -- >> Language File
- Files.Language = {
- autoSave = true;
- autoLoad = true;
- subDir = "Language";
- extension = ".lang";
- loadRequired = { "Settings" };
- }
- function Files.Language.save()
- local fileName = "Graffiti." .. settings.language .. Files.Language.extension
- Files.saveTable(Files.Language.subDir, fileName, text)
- end
- function Files.Language.load()
- local fileName = "Graffiti." .. settings.language .. Files.Language.extension
- local tbl = Files.loadTable(Files.Language.subDir, fileName)
- if tbl then
- for key, value in pairs(tbl) do
- text[key] = value
- end
- end
- end
- -- >> Log File
- Files.Log = {
- autoSave = false;
- autoLoad = false;
- subDir = "Log";
- name = "Graffiti.log";
- }
- -- Writes the text into a logfile.
- function log(text, logType)
- if not doLog then
- return
- end
- local logFileHandle = Files.getDataFileHandle(Files.Log.subDir, Files.Log.name, "a", true)
- if (type(text) == "table") then
- local delimiter = ""
- logFileHandle.write((logType or "INFO") .. ": ")
- for key, value in pairs(text) do
- logFileHandle.write(delimiter .. key .. "=" .. tostring(value))
- delimiter = ", "
- end
- logFileHandle.writeLine()
- else
- logFileHandle.writeLine((logType or "INFO") .. ": " .. tostring(text))
- end
- logFileHandle.close()
- end
- -- >>> Shortcut functions (for key inputs)
- -- Not yet implemented! WIP!
- -- TODO: Fix bug where the key event won't disappear from the queue.
- function callShortcut(key)
- --if (key and shortcut[key]) then
- -- shortcut[key]()
- --end
- end
- shortcut = {}
- -- S
- shortcut[31] = function()
- out.clear()
- out.setCursorPos(1, 1)
- print("Saving windows...")
- Files.Save.save()
- print("Windows saved!")
- print("Press any key to continue...")
- end
- -- Q
- shortcut[16] = function()
- quit = true
- end
- -- >>> Object helper functions
- function clearScreen()
- out.setBackgroundColor(objectColors.background)
- out.setTextColor(objectColors.text)
- out.clear()
- out.setCursorPos(1, 1)
- end
- -- Returns whether the button with the given name
- -- has been pressed.
- function defaultButtonPressed(name, x, y)
- assert(name)
- assert(x)
- assert(y)
- local window = getCurrentWindow()
- if (defaultButtons[name]) then
- local button = defaultButtons[name]
- if (x >= button.left and x <= button.right and y >= button.top and y <= button.bottom) then
- return (button.required() or (editMode and not showEditorOptions and currentWindow ~= "mainWindow"))
- end
- else
- return false
- end
- end
- -- Returns the window object that is currently
- -- displayed.
- function getCurrentWindow()
- if showEditorOptions then
- return editorWindows.children[currentWindow]
- else
- return windows.children[currentWindow]
- end
- end
- -- Used by the List and Selector objects to
- -- determine how wide the list should be.
- function getLongestString(strings)
- if (strings == nil or #strings == 0) then
- return 0
- end
- local ret = 0
- for key, value in pairs(strings) do
- length = string.len(value)
- if (length > ret) then
- ret = length
- end
- end
- return ret
- end
- -- Checks whether dir is a valid direction-string.
- function isValidDirection(dir)
- if (dir ~= nil and
- (dir == "left" or
- dir == "up" or
- dir == "right" or
- dir == "down")) then
- return true
- end
- return false
- end
- --[[ Returns a table containing tables for all
- files and directories in the given path:
- Returns: fileName, filePath, isDir
- ]]
- function getFileList(path)
- local ret = {}
- local dirList = {}
- local fileList = {}
- for file in fs.list(path) do
- if (fs.isDir(file)) then
- table.insert(dirList, dirIndex, file)
- dirIndex = dirIndex + 1
- else
- table.insert(fileList, fileIndex, file)
- fileIndex = fileIndex + 1
- end
- end
- for _, file in ipairs(dirList) do
- table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=true})
- end
- for _, file in ipairs(fileList) do
- table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=false})
- end
- return ret
- end
- -- Returns "horizontal" or "vertical" depending on
- -- the given direction.
- function getOrientation(direction)
- local orientation = nil
- if (direction == "left" or direction == "right") then
- orientation = "horizontal"
- elseif (direction == "up" or direction == "down") then
- orientation = "vertical"
- end
- return orientation
- end
- -- Checks whether the eventTypes table contains
- -- the given event type.
- function eventTypeExists(eventType)
- if (eventType == nil) then
- return true
- end
- for _, event in pairs(eventTypes) do
- if (event == eventType) then
- return true
- end
- end
- return false
- end
- -- Returns the size that a buffer needs to have
- -- to contain all children of a container.
- function getNecessaryBufferSize(children, minWidth, minHeight)
- log("getNecessaryBufferSize", "FUNC")
- local width, height = (minWidth or 0), (minHeight or 0)
- for _, child in pairs(children) do
- local right = child.width and child.x + child.width or child.x
- local bottom = child.height and child.y + child.height or child.y
- width = (right > width) and right or width
- height = (bottom > height) and bottom or height
- end
- return width, height
- end
- -- Returns a table containing the position and
- -- size that the scroll bar of a ScrollView object
- -- should have.
- function getScrollBarInfo(pos, containerSize, bufferSize)
- log("getScrollBarInfo", "FUNC")
- log("pos: " .. pos .. ", containerSize: " .. containerSize .. ", bufferSize: " .. bufferSize .. ".", "ATTR")
- local ret = {}
- local maxScrollBarHeight = containerSize - 4
- local scrollCount = bufferSize - containerSize + 1 -- How often can the user scroll?
- if (scrollCount < 0) then
- scrollCount = 0
- end
- local scrollBarSize = math.ceil(maxScrollBarHeight * (1 / (scrollCount + 1)))
- local scrollBarPos
- if (scrollCount == 0) then
- scrollBarPos = 0
- --elseif (pos == scrollCount) then
- --scrollBarPos = maxScrollBarHeight - scrollBarSize
- else
- scrollBarPos = math.floor((maxScrollBarHeight / (scrollCount + 1)) * pos)
- end
- ret.size = scrollBarSize
- ret.pos = scrollBarPos
- log("Scrollbar size: " .. scrollBarSize)
- log("Scrollbar pos: " .. scrollBarPos)
- return ret
- end
- -- >>> Path
- Path = {}
- -- Returns the container which is at the paths
- -- nest level.
- function Path:getContainerAt(nestLevel)
- local container = getCurrentWindow()
- for i = 1, nestLevel do
- if (container.children[self[i]] and container.children[self[i]].isContainer) then
- container = container.children[self[i]]
- else
- return nil -- NOTE: was "break" before
- end
- end
- return container
- end
- -- Returns the object which is represented by the
- -- path.
- function Path:getObject()
- local container = Path.getContainerAt(self, #self - 1)
- return container.children[self[#self]]
- end
- -- Returns the coordinates which are relative to
- -- the last container represented by the path.
- function Path:getRelativePos(x, y, checkFullPath)
- log("Path.getRelativePos", "FUNC")
- log("x: " .. x .. ", y: " .. y .. ", checkFullPath: " .. tostring(checkFullPath) .. ".", "ATTR")
- local container
- local limit = checkFullPath and #self or #self - 1
- for i = 1, limit do
- container = Path.getContainerAt(self, i)
- if container then
- x, y = objects.Container.getRelativePos(container, x, y)
- else
- break
- end
- end
- return x, y
- end
- --[[ Generates a table of keys representing the
- nesting of the containers that have been clicked.
- Returns: e.g. {containeID, subContainerID}]]
- Path.getContainerPath = function(x, y)
- local containerPath = {}
- local currentContainer = getCurrentWindow()
- local finished = false
- local nestLevel = 1
- while not finished do
- finished = true -- Set to true so that the loop stops when no container is found.
- for objectID, object in pairs(currentContainer.children) do
- if finished and object.isContainer then
- if (objects.Container.contentAreaClicked(object, x, y)) then
- containerPath[nestLevel] = objectID
- currentContainer = currentContainer.children[objectID]
- x, y = objects.Container.getRelativePos(currentContainer, x, y)
- finished = false
- end
- end
- end
- nestLevel = nestLevel + 1
- end
- return containerPath
- end
- -- >>> Buffer
- Buffer = {
- bufferTable = {};
- width = 0;
- height = 0;
- }
- function Buffer.newPixel(backCol, textCol, char, path)
- local ret = {}
- ret.background = backCol or objectColors.background
- ret.text = textCol or objectColors.text
- ret.char = (type(char) == "string" and char ~= "") and char or " "
- ret.path = path or nil
- if (backCol or textCol or char ~= " " or path) then
- ret.draw = true
- else
- ret.draw = false
- end
- return ret;
- end
- function Buffer:new(object)
- object = object or {}
- setmetatable(object, self)
- self.__index = self
- return object
- end
- function Buffer:init(width, height, path, backCol, textCol)
- self.width = width or error("Can't initialize buffer because the width didn't get specified!")
- self.height = height or error("Can't initialize buffer because the height didn't get specified!")
- self.bufferTable = {}
- for col = 1, width do
- self.bufferTable[col] = {}
- for row = 1, height do
- self.bufferTable[col][row] = self.newPixel(backCol, textCol, " ", path)
- end
- end
- end
- function Buffer:trim(left, top, right, bottom)
- left = (left < 0) and 0 or left
- top = (top < 0) and 0 or top
- right = (right < 0) and 0 or right
- bottom = (bottom < 0) and 0 or bottom
- local width = self.width - left - right
- local height = self.height - top - bottom
- local trimmed = Buffer:new()
- trimmed:init(width, height)
- for col = 1, width do
- for row = 1, height do
- trimmed:setPixel(col, row, self.bufferTable[left + col][top + row])
- end
- end
- return trimmed
- end
- function Buffer:draw(x, y)
- x = x or 1
- y = y or 1
- local currentPixel
- for col = 0, self.width - 1 do
- for row = 0, self.height - 1 do
- currentPixel = self.bufferTable[col+1][row+1]
- if (currentPixel.draw) then
- out.setCursorPos(x + col, y + row)
- out.setBackgroundColor(currentPixel.background)
- out.setTextColor(currentPixel.text)
- out.write(currentPixel.char)
- currentPixel.draw = false
- end
- end
- end
- end
- function Buffer:setBackgroundColor(color)
- for col = 1, width do
- for row = 1, height do
- self.bufferTable[col][row].background = color
- end
- end
- end
- function Buffer:setTextColor(color)
- for col = 1, width do
- for row = 1, height do
- self.bufferTable[col][row].text = color
- end
- end
- end
- function Buffer:setPixel(x, y, pixel)
- assert(x)
- assert(y)
- assert(pixel)
- if (x > 0 and x <= self.width and y > 0 and y <= self.height) then
- for key, value in pairs(pixel) do
- if value then
- self.bufferTable[x][y][key] = value
- end
- end
- end
- end
- function Buffer:addText(x, y, text)
- assert(x)
- assert(y)
- assert(text)
- for char in text:gmatch(".") do
- self:setPixel(x, y, { char=char })
- x = x + 1
- end
- end
- function Buffer:addBuffer(x, y, buffer)
- assert(x)
- assert(y)
- assert(buffer)
- for col = 1, buffer.width do
- for row = 1, buffer.height do
- self:setPixel(x + col - 1, y + row - 1, buffer.bufferTable[col][row])
- end
- end
- end
- -- Adds a border to the container buffer.
- function Buffer:makeBorder(path, color)
- local width, height = self.width, self.height
- self:addBuffer(1, 1, objects.Line.get("horizontal", width, color, path))
- self:addBuffer(1, height, objects.Line.get("horizontal", width, color, path))
- self:addBuffer(1, 1, objects.Line.get("vertical", height, color, path))
- self:addBuffer(width, 1, objects.Line.get("vertical", height, color, path))
- end
- -- >>> Objects
- objects = {}
- -- Returns a new object with its default attributes
- -- depending on the object type.
- -- Note: Don't call this function, use objects.create!
- function objects.new(objectType, x, y)
- log("objects.new", "FUNC")
- local object = {}
- local path = Path.getContainerPath(x, y)
- local relativeX, relativeY = Path.getRelativePos(path, x, y, true)
- object.objID = "new" .. objectType
- object.objType = objectType
- object.path = path
- object.x = relativeX
- object.y = relativeY
- object.absoluteX = x
- object.absoluteY = y
- local maxWidth = maxX - x
- local maxHeight = maxY - y
- if (objects[objectType] and objects[objectType].new) then
- objects[objectType].new(object, maxWidth, maxHeight)
- elseif (objects.Container[objectType]) then
- objects.Container.new(object, maxWidth, maxHeight)
- else
- objects["Unknown"].new(objectType)
- end
- return object
- end
- function objects.create(objType, x, y)
- local object = objects.new(objType, x, y)
- local container = Path.getContainerAt(object.path, #object.path)
- local key = objects.Container.getNextFreeKey(container)
- table.insert(object.path, key)
- table.insert(container.children, key, object)
- windowBuffer:addBuffer(x, y, objects.get(object))
- objects.draw(object)
- end
- function objects.getPosModifier(self)
- assert(self)
- local x, y = 0, 0
- if (#self.path > 1) then
- for nestLevel = 1, #self.path - 1 do
- local object = Path.getContainerAt(self.path, nestLevel)
- local modX, modY = objects.Container.getPosModifier(object, x, y)
- x, y = x + modX, y + modY
- end
- end
- return x, y
- end
- -- Returns the relative position of the object
- -- including the possible modifications of its
- -- containers.
- function objects.getRelativePos(self)
- assert(self)
- local x, y = self.x, self.y
- local modX, modY = objects.getPosModifier(self)
- return x + modX, y + modY
- end
- -- Returns the absolute position of the object
- -- including the possible modifications of its
- -- containers.
- function objects.getAbsolutePos(self)
- assert(self)
- local x, y = self.absoluteX, self.absoluteY
- local modX, modY = objects.getPosModifier(self)
- return x + modX, y + modY
- end
- -- Returns the buffer of the object.
- function objects.get(self, param)
- assert(self)
- log("objects.get", "FUNC")
- log(self.objType .. " ID: \"" .. self.objID .. "\", param: \"" .. tostring(param) .. "\"", "ATTR")
- local objType = self.objType or "Unknown"
- if (self.isContainer) then
- return objects.Container.get(self, param)
- elseif (objects[objType] and objects[objType].get) then
- return objects[objType].get(self, param)
- else
- error("Can't get buffer of object type \"" .. objType .. "\"!")
- end
- end
- --[[ Used to return the buffer of an object which
- shows the user where to click to interact with
- the object in a certain way.
- (e.g. scrolling the ScrollView)
- ]]
- function objects.addMarker(self, buffer)
- assert(self)
- log("objects.addMarker", "FUNC")
- log(self.objType .. " ID: \"" .. self.objID .. "\".", "ATTR")
- if (objects[self.objType] and objects[self.objType].addMarker) then
- objects[self.objType].addMarker(self, buffer)
- elseif (objects.Container[self.objType] and objects.Container[self.objType].addMarker) then
- objects.Container[self.objType].addMarker(self, buffer)
- end
- end
- function objects.remove(self)
- assert(self)
- log("objects.remove", "FUNC")
- log(self.objType .. " ID: \"" .. self.objID .. "\".")
- local path = self.path
- local objKey = path[#path]
- local container = Path.getContainerAt(self.path, #self.path - 1)
- container.children[objKey] = nil
- for i = #path - 2, 0, -1 do
- local parentContainer = Path.getContainerAt(self.path, i)
- parentContainer.children[path[i + 1]] = container
- container = parentContainer
- end
- windows.children[currentWindow] = container
- end
- -- Draws the object.
- function objects.draw(self, param, drawMarker)
- assert(self)
- log("objects.draw", "FUNC")
- log(self.objType .. " ID: " .. self.objID .. ", param: \"" .. tostring(param) .. "\"", "ATTR")
- local buffer = objects.get(self, param)
- if drawMarker then
- objects.addMarker(self, buffer)
- end
- if (#self.path > 1) then
- local container = getCurrentWindow()
- local x, y = self.absoluteX, self.absoluteY
- objects.Container.drawBuffer(container, buffer, self.path, x, y, x, y, 0)
- else
- buffer:draw(self.x, self.y)
- end
- drawDefaultButtons()
- end
- -- Returns the resulting event and a "params"-array.
- function objects.click(self, x, y)
- assert(self)
- log("objects.click", "FUNC")
- log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
- local objType = self.objType
- local event, params
- if objects[objType] and objects[objType].click then
- event, params = objects[objType].click(self, x, y)
- elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].click) then
- event, params = objects.Container[objType].click(self, x, y)
- end
- return event, params
- end
- -- Gets called when an object gets clicked in edit
- -- mode. Returns false if the object should be
- -- edited as usual (move, scale).
- function objects.editorClick(self, x, y)
- assert(self)
- assert(x)
- assert(y)
- log("objects.editorClick", "FUNC")
- log(self.objType .. " ID: \"" .. self.objID .. "\", x: " .. x .. ", y: " .. y .. ".", "ATTR")
- if (objects[self.objType] and objects[self.objType].editorClick) then
- return objects[self.objType].editorClick(self, x, y)
- elseif (objects.Container[self.objType] and objects.Container[self.objType].editorClick) then
- return objects.Container[self.objType].editorClick(self, x, y)
- else
- return false
- end
- end
- -- Moves the object.
- function objects.move(self, addX, addY)
- assert(self)
- log("objects.move", "FUNC")
- log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
- local objType = self.objType
- if (objects[objType] and objects[objType].move) then
- objects[objType].move(self, addX, addY)
- elseif (self.isContainer) then
- self.x = self.x + addX
- self.y = self.y + addY
- objects.Container.move(self, addX, addY)
- else
- self.x = self.x + addX
- self.y = self.y + addY
- self.absoluteX = self.absoluteX + addX
- self.absoluteY = self.absoluteY + addY
- end
- end
- -- Scales the object.
- function objects.scale(self, x, y)
- assert(self)
- log("objects.scale", "FUNC")
- log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
- log("Object pos: " .. self.x .. ", " .. self.y, "DEBUG")
- local objType = self.objType
- if (objects[objType] and objects[objType].scale) then
- objects[objType].scale(self, x, y)
- elseif (self.isContainer and objects.Container[objType].scale) then
- objects.Container[objType].scale(self, x, y)
- else
- local objX, objY = self.x, self.y
- if (x > objX and y >= objY) then
- self.width = x - objX + 1
- self.height = y - objY + 1
- end
- end
- end
- --[[ Returns the position of the pixel which shows
- the user where to click when he wants to move the
- object.]]
- function objects.getMovePos(self)
- local objType = self.objType
- if (objects[objType] and objects[objType].getMovePos) then
- return objects[objType].getMovePos(self)
- elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].getMovePos) then
- return objects.Container[objType].getMovePos(self)
- else
- return objects.getAbsolutePos(self)
- end
- end
- --[[ Returns the position of the pixel which shows
- the user where to click when he wants to scale the
- object.]]
- function objects.getScalePos(self)
- if not self.canScale then
- error("Tried to get scale pos of an unscalable object!")
- end
- local objType = self.objType
- if (objects[objType] and objects[objType].getScalePos) then
- return objects[objType].getScalePos(self)
- elseif (self.isContainer and objects.Container[objType].getScalePos) then
- objects.Container[objType].getScalePos(self)
- else
- local x, y = objects.getAbsolutePos(self)
- return x + self.width - 1, y + self.height - 1
- end
- end
- -- Returns the left, top, right and bottom coordinates of the object.
- function objects.getDimensions(self)
- local objType = self.objType
- if (objects[objType] and objects[objType].getDimensions) then
- return objects[objType].getDimensions(self)
- elseif (self.isContainer and objects.Container[objType] and objects.Container[objType].getDimensions) then
- return objects.Container[objType].getDimensions(self)
- else
- local left, top, right, bottom
- left, top = self.x, self.y
- right = left + self.width - 1
- bottom = top + self.height - 1
- return left, top, right, bottom
- end
- end
- -- >> Unknown
- objects.Unknown = {}
- objects.Unknown.new = function(objType)
- objType = objType and " \"" .. objType .. "\"" or ""
- error("Tried to create new object with unknown object ID" .. objType .. "!")
- end
- objects.Unknown.get = function(self)
- error("Tried to get the buffer of and unknown object!")
- end
- -- >> Button
- objects.Button = {}
- objects.Button.new = function(self, maxWidth, maxHeight)
- self.width = (maxWidth < size.Button.width) and maxWidth or size.Button.width
- self.height = (maxHeight < size.Button.height) and maxHeight or size.Button.height
- self.widthPercent = maxX / self.width
- self.heightPercent = maxY / self.height
- self.text = "Button"
- self.funcType = ""
- self.param = ""
- self.canScale = true
- self.canClick = true
- end
- objects.Button.get = function(self, buttonColor)
- local width = self.width
- local height = self.height
- local path = self.path
- local text = self.text
- local color
- if buttonColor then
- color = buttonColor
- elseif (toggleState[self.objID]) then
- color = objectColors.Button.active
- else
- color = objectColors.Button.default
- end
- local buffer = Buffer:new()
- local textCol = math.floor((width - string.len(text)) / 2) + 1
- local textRow = math.ceil(height / 2)
- buffer:init(width, height, path, color, objectColors.Button.text)
- buffer:addText(textCol, textRow, text)
- return buffer
- end
- -- Returns: "button_clicked" event, objID
- objects.Button.click = function(self, x, y)
- assert(self)
- local actionType = self.funcType
- local param = self.param
- if (actionType == "switch") then
- drawWindow(param)
- return nil, nil
- elseif (actionType == "function") then
- if changeButtonColor then
- objects.draw(self, objectColors.Button.active)
- end
- if userFunctions[param] then
- userFunctions[param](true)
- elseif editorFunctions[param] then
- editorFunctions[param](true)
- end
- if changeButtonColor then
- objects.draw(self, objectColors.Button.default)
- else
- changeButtonColor = true
- end
- return "button_clicked", {self.objID, true}
- elseif (actionType == "toggle function") then
- local state = not toggleState[self.objID]
- toggleState[self.objID] = state
- objects.draw(self)
- if userFunctions[param] then
- userFunctions[param](state)
- elseif editorFunctions[param] then
- editorFunctions[param](state)
- end
- return "button_toggled", {self.objID, state}
- else
- log("Unknown function type: \"" .. actionType .. "\"", "WARNING")
- end
- end
- -- >> Text
- objects.Text = {}
- objects.Text.new = function(self)
- self.text = "newText"
- self.width = #self.text
- self.height = 1
- end
- objects.Text.get = function(self)
- local width = #self.text
- local buffer = Buffer:new()
- buffer:init(width, 1, self.path)
- buffer:addText(1, 1, self.text)
- return buffer
- end
- objects.Text.draw = function(self)
- local x = self.x
- local y = self.y
- local text = self.text
- out.setCursorPos(x, y)
- out.write(text)
- end
- objects.Text.getDimensions = function(self)
- local left, top, right, bottom = self.x, self.y, 1, self.y
- right = left + string.len(self.text) - 1
- return left, top, right, bottom
- end
- -- >> Variable
- objects.Variable = {}
- objects.Variable.new = function(self)
- self.width = 1
- self.height = 1
- end
- objects.Variable.get = function(self, value)
- local buffer = Buffer:new()
- if value then
- buffer:init(string.len(value), 1, self.path)
- buffer:addText(1, 1, value)
- else
- buffer:init(1, 1, self.path)
- end
- return buffer
- end
- -- >> ProgressBar
- objects.ProgressBar = {}
- objects.ProgressBar.new = function(self, maxWidth)
- self.length = (maxWidth < size.ProgressBar.length) and maxWidth or size.ProgressBar.length
- self.lengthPercent = maxX / self.length
- self.width = self.length
- self.height = 1
- self.direction = "right"
- self.objID = "newProgressBar"
- self.canScale = true
- end
- objects.ProgressBar.get = function(self, value)
- local length = self.length
- local direction = (isValidDirection(self.direction)) and self.direction or "right"
- local orientation = getOrientation(direction) or error("Direction " .. direction .. " is invalid!")
- value = value or progressBarValues[self.objID]
- if (orientation == "horizontal") then
- width, height = length, 1
- else
- width, height = 1, length
- end
- local buffer = Buffer:new()
- buffer:init(width, height, self.path, objectColors.ProgressBar.background)
- if value then
- local color
- if (value < 33) then
- color = objectColors.ProgressBar.low
- elseif (value > 66) then
- color = objectColors.ProgressBar.high
- else
- color = objectColors.ProgressBar.medium
- end
- local filled = math.floor((length / 100) * value)
- local valueBuffer = objects.Line.get(getOrientation(direction), filled, color)
- local addX, addY = 1, 1
- if (direction == "left") then
- addX = width - filled
- elseif (direction == "up") then
- addY = height - filled
- end
- buffer:addBuffer(addX, addY, valueBuffer)
- end
- return buffer
- end
- objects.ProgressBar.getMovePos = function(self)
- local dir = self.direction
- local length = self.length
- local x, y = objects.getAbsolutePos(self)
- if (dir == "left") then
- return x + length - 1, y
- elseif (dir == "up") then
- return x, y + length - 1
- else
- return x, y
- end
- end
- objects.ProgressBar.getScalePos = function(self)
- local dir = self.direction
- local length = self.length
- local x, y = objects.getAbsolutePos(self)
- if (dir == "right") then
- return x + length - 1, y
- elseif (dir == "down") then
- return x, y + length - 1
- else
- return x, y
- end
- end
- -- Move and scale pos is wrong when the progress bar goes up.
- objects.ProgressBar.scale = function(self, x, y)
- local moveX, moveY = objects.getMovePos(self)
- local relMoveX, relMoveY = Path.getRelativePos(self.path, moveX, moveY)
- local length
- local newX, newY
- local direction
- local width, height
- if (x < relMoveX and y == relMoveY) then -- Clicked left of the progressBar.
- length = relMoveX - x + 1
- direction = "left"
- newX, newY = x, y
- width, height = length, 1
- elseif (x == relMoveX and y < relMoveY) then -- Clicked above the progressBar.
- length = relMoveY - y + 1
- direction = "up"
- newX, newY = x, y
- width, height = 1, length
- elseif (x > relMoveX and y == relMoveY) then -- Clicked right of the progressBar.
- length = x - relMoveX + 1
- direction = "right"
- newX, newY = relMoveX, relMoveY
- width, height = length, 1
- elseif (x == relMoveX and y > relMoveY) then -- Clicked below the progressBar.
- length = y - relMoveY + 1
- direction = "down"
- newX, newY = relMoveX, relMoveY
- width, height = 1, length
- else
- return
- end
- if (length > 2) then
- local xDiff, yDiff = newX - self.x, newY - self.y
- self.absoluteX, self.absolutey = self.absoluteX + xDiff, self.absoluteY + yDiff
- self.x, self.y = newX, newY
- self.direction = direction
- self.width, self.height = width, height
- self.length = length
- self.lengthPercent = length / maxX
- end
- end
- -- >> Input
- objects.Input = {}
- objects.Input.new = function(self)
- self.message = "Enter something."
- self.isPassword = false
- self.width = 2
- self.height = 1
- self.canClick = true
- end
- objects.Input.get = function(self)
- local userInput = userInputs[self.objID] or ""
- local width, height = 2 + string.len(userInput), 1
- local buffer = Buffer:new()
- buffer:init(width, height, self.path, objectColors.Input.default)
- if userInput ~= "" then
- buffer:addText(userInput)
- end
- return buffer
- end
- -- Returns: "text_changed" event, objID, text
- objects.Input.click = function(self)
- local x = self.x
- local y = self.y
- local objID = self.objID
- local message = self.message
- local isPassword = (self.isPassword == nil) and false or self.isPassword
- local maxLength = self.maxLength
- local existingInput = userInputs[objID]
- out.setBackgroundColor(objectColors.background)
- out.setCursorPos(x, y)
- if (existingInput ~= nil) then -- Clear the text on the input object.
- out.write(string.rep(" ", string.len(existingInput) + 2))
- else
- out.write(" ")
- end
- userInputs[objID] = nil
- out.setCursorPos(x, y)
- if not outIsTerm then
- -- make the input-object yellow
- out.setBackgroundColor(objectColors["Input"].active)
- out.write(" ")
- out.setBackgroundColor(objectColors.background)
- end
- if outIsTerm then
- out.setCursorPos(x + 1, y)
- end
- local userInput = readUserInput(message, isPassword)
- if (userInput ~= nil) then
- userInputs[objID] = userInput
- end
- out.setCursorPos(x, y)
- out.setBackgroundColor(objectColors.Input.default)
- out.setTextColor(objectColors.Input.text)
- out.write(" ")
- if (userInput ~= nil and userInput ~= "") then
- if isPassword then
- for i = 1, string.len(userInput) do
- out.write("*")
- end
- else
- out.write(userInput)
- end
- end
- out.write(" ")
- out.setBackgroundColor(objectColors.background)
- out.setTextColor(objectColors.text)
- return "text_changed", {self.objID, userInput}
- end
- -- >> List
- objects.List = {}
- objects.List.new = function(self)
- self.elements = userLists.testList
- self.objID = "testList"
- self.isMultiselect = false
- self.canClick = true
- end
- objects.List.get = function(self)
- local buffer = Buffer:new()
- local elements = self.elements or { "empty" }
- if (type(elements) == "string") then
- elements = { [1]=self.elements }
- end
- -- If there's a list which has this list's first element as its key.
- if (#elements == 1 and userLists[elements[1]]) then
- self.elements = userLists[self.elements[1]]
- end
- elements = self.elements
- local width = getLongestString(elements) + 2
- self.width = width
- local height = #elements
- self.height = height
- local objID = self.objID
- local isMultiselect = self.isMultiselect
- if not selectedItems[objID] then
- selectedItems[objID] = {}
- end
- buffer:init(width, height, self.path, objectColors.List.default)
- local line = 1
- for key, element in pairs(elements) do
- if (selectedItems[objID][key]) then
- buffer:addBuffer(1, line, objects.Line.get("horizontal", width, objectColors.List.active))
- end
- buffer:addText(2, line, elements[line])
- line = line + 1
- end
- return buffer
- end
- -- Returns: "selection_changed" event, objID, key, true or false
- objects.List.click = function(self, x, y)
- local objID = self.objID
- local isMultiselect = self.isMultiselect
- local itemSelected = selectedItems[objID][y]
- if (isMultiselect) then
- selectedItems[objID][y] = not itemSelected
- else
- selectedItems[objID] = {}
- selectedItems[objID][y] = true
- end
- objects.draw(self)
- return "selection_changed", {self.objID, y, selectedItems[objID][y]}
- end
- objects.List.getFirstSelectedKey = function(self)
- local objID = self.objID
- for key, value in pairs(selectedItems[objID]) do
- if (value == true) then
- return key
- end
- end
- return nil
- end
- -- >> File Selector
- objects.FileSelector = {}
- objects.FileSelector.new = function(self)
- self.width = string.len(text.fileSelector)
- self.height = 1
- self.isMultiselect = false
- self.canClick = true
- end
- objects.FileSelector.draw = function(self)
- local objectID = objectID
- local x = self.x
- local y = self.y
- local isMultiselect = self.isMultiselect
- out.setBackgroundColor(objectColors["FileSelector"].default)
- out.setTextColor(objectColors["FileSelector"].text)
- out.setCursorPos(x, y)
- out.write(text.fileSelector)
- if (selectedFiles[objectID] ~= nil) then
- out.setBackgroundColor(objectColors.background)
- out.setTextColor(objectColors.text)
- local files = selectedFiles[objectID]
- out.write(" ")
- if (type(files) == "table") then
- local sep = ""
- for _, fileName in pairs(files) do
- term.write(sep .. fileName)
- sep = ", "
- end
- else
- out.write(files)
- end
- end
- out.setBackgroundColor(objectColors.background)
- out.setTextColor(objectColors.text)
- end
- objects.FileSelector.click = function(self, x, y)
- error("Not yet implemented")
- -- TODO
- local finished = false
- local path = "/"
- local list = {}
- while not finished do
- clearScreen()
- out.setCursorPos(2, 1)
- out.write("Path: " .. path)
- list = getFileList(path)
- out.setTextColor(objectColors.FileSelector.text)
- end
- end
- -- >>> Containers
- objects.Container = {}
- objects.Container.getNextFreeKey = function(self)
- local nextKey = 1
- while self.children[nextKey] ~= nil do
- nextKey = nextKey + 1
- end
- return nextKey
- end
- -- Returns the area of the container which stores
- -- its children.
- objects.Container.getContentArea = function(self)
- assert(self)
- local objType = self.objType
- if (objects.Container[objType].getContentArea) then
- return objects.Container[objType].getContentArea(self)
- else
- local left = self.x + 1
- local top = self.y + 1
- local right = self.x + self.width - 2
- local bottom = self.y + self.height - 2
- return left, top, right, bottom
- end
- end
- -- Determines whether the container itself or its
- -- content area is at the given position.
- objects.Container.contentAreaClicked = function(self, x, y)
- local left, top, right, bottom = objects.Container.getContentArea(self)
- return (x >= left and x <= right and y >= top and y <= bottom)
- end
- -- Draws the buffer and trims it before that if
- -- necessary.
- objects.Container.drawBuffer = function(self, buffer, path, x, y, absoluteX, absoluteY, nestLevel)
- log("objects.Container.drawBuffer", "FUNC")
- local modX, modY = objects.Container.getPosModifier(self, x, y)
- x, y = x + modX, y + modY
- if (self.objType ~= "Window") then
- local containerLeft, containerTop, containerRight, containerBottom = 1, 1, self.width - 1, self.height - 1
- local objLeft, objTop, objRight, objBottom = x, y, x + buffer.width - 1, y + buffer.height - 1
- --log("Container dimensions: left: " .. containerLeft .. ", top: " .. containerTop .. ", right: " .. containerRight .. ", bottom: " ..containerBottom .. ".", "DEBUG")
- --log("Object dimensions: left: " .. objLeft .. ", top: " .. objTop .. ", right: " .. objRight .. ", bottom: " ..objBottom .. ".", "DEBUG")
- if (objLeft > self.width or
- objTop > self.height or
- objRight < 0 or
- objBottom < 0) then
- return
- elseif (objLeft < containerLeft or objTop < containerTop or objRight >= containerRight or objBottom >= containerBottom) then
- -- Object goes over the border. Trim it.
- local trimLeft = (containerLeft - objLeft) > 0 and containerLeft - objLeft or 0
- local trimTop = (containerTop - objTop) > 0 and containerTop - objTop or 0
- local trimRight = (objRight - containerRight + 1) > 0 and objRight - containerRight + 1 or 0
- local trimBottom = (objBottom - containerBottom + 1) > 0 and objBottom - containerBottom + 1 or 0
- buffer = buffer:trim(trimLeft, trimTop, trimRight, trimBottom)
- if (trimLeft > 0) then
- x = x + trimLeft
- absoluteX = absoluteX + trimLeft
- end
- if (trimTop > 0) then
- y = y + trimTop
- absoluteY = absoluteY + trimTop
- end
- end
- end
- if (nestLevel < #path - 1) then
- local container = Path.getContainerAt(path, nestLevel + 1)
- local relX, relY = objects.Container.getRelativePos(container, x, y)
- objects.Container.drawBuffer(container, buffer, path, relX, relY, absoluteX, absoluteY, nestLevel + 1)
- else
- buffer:draw(absoluteX + modX, absoluteY + modY)
- end
- end
- objects.Container.getRelativePos = function(self, x, y)
- local left, top, right, bottom = objects.Container.getContentArea(self)
- --log("objects.Container.getRelativePos", "FUNC")
- --log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
- --log("Left: " .. left .. ", top: " .. top .. ", right: " .. right .. ", bottom: " .. bottom .. ".", "DEBUG")
- retX = x - left + 1
- retY = y - top + 1
- return retX, retY
- end
- objects.Container.getParentsRelativePos = function(self, x, y)
- local left, top, right, bottom = objects.Container.getContentArea(self)
- return x + left - 1, y + top - 1
- end
- objects.Container.new = function(self, maxWidth, maxHeight)
- log("objects.Container.new", "FUNC")
- local defaultWidth, defaultHeight = size.Container.width, size.Container.height
- self.isContainer = true
- self.canScale = true
- self.children = {}
- self.width = (maxWidth < defaultWidth) and maxWidth or defaultWidth
- self.height = (maxHeight < defaultHeight) and maxHeight or defaultHeight
- log("New container dimensioins: " .. self.width .. " x " .. self.height .. ".")
- if (objects.Container[self.objType].new) then
- objects.Container[self.objType].new(self)
- end
- end
- objects.Container.get = function(self)
- log("Container dimensions: " .. (self.width or "unknown") .. " x " .. (self.height or "unknown") .. ".")
- local left, top, right, bottom = objects.Container.getContentArea(self)
- local minWidth, minHeight = right - left + 1, bottom - top + 1
- local width, height = getNecessaryBufferSize(self.children, minWidth, minHeight)
- local buffer = Buffer:new()
- buffer:init(width, height, {})
- for objectID, object in pairs(self.children) do
- local objectBuffer = objects.get(object)
- buffer:addBuffer(object.x, object.y, objectBuffer)
- end
- return objects.Container[self.objType].get(self, buffer)
- end
- objects.Container.getPosModifier = function(self, x, y)
- assert(self)
- log("objects.Container.getPosModifier", "FUNC")
- log("Object type: " .. tostring(self.objType) .. ", ID: " .. tostring(self.objID) .. ".", "INFO")
- log("Position: " .. x .. ", " .. y .. ".", "INFO")
- local objType = self.objType
- if (objects.Container[objType] and objects.Container[objType].getPosModifier) then
- local newX, newY = objects.Container[objType].getPosModifier(self, x, y)
- log("Modifier: " .. newX .. ", " .. newY .. ".", "INFO")
- return newX, newY
- else
- log("Position not modified.")
- return 0, 0
- end
- end
- objects.Container.move = function(self, addX, addY)
- self.absoluteX = self.absoluteX + addX
- self.absoluteY = self.absoluteY + addY
- for _, child in pairs(self.children) do
- if child.isContainer then
- objects.Container.move(child, addX, addY)
- else
- child.absoluteX = child.absoluteX + addX
- child.absoluteY = child.absoluteY + addY
- end
- end
- end
- -- >> Window
- objects.Container.Window = {}
- objects.Container.Window.new = function(windowName)
- local object = {}
- object.objType = "Window"
- object.parent = "mainWindow"
- object.children = {}
- object.width, object.height = maxX, maxY
- return object
- end
- objects.Container.Window.create = function(windowName)
- local object = objects.Container.Window.new(windowName)
- windows.children[windowName] = object
- end
- objects.Container.Window.get = function(self, contentBuffer)
- return contentBuffer
- end
- objects.Container.Window.getContentArea = function(self)
- return 1, 1, maxX, maxY
- end
- -- >> Panel
- objects.Container.Panel = {}
- objects.Container.Panel.get = function(self, contentBuffer)
- log("objects.Container.Panel.get", "FUNC")
- local buffer = Buffer:new()
- buffer:init(self.width, self.height, self.path, objectColors.Container.Panel.border)
- buffer:addBuffer(2, 2, contentBuffer)
- buffer:addBuffer(self.width, 1, objects.Line.get("vertical", self.height, objectColors.Container.Panel.border, self.path))
- buffer:addBuffer(1, self.height, objects.Line.get("horizontal", self.width, objectColors.Container.Panel.border, self.path))
- return buffer
- end
- -- >> ScrollView
- objects.Container.ScrollView = {}
- objects.Container.ScrollView.new = function(self)
- self.scrollX = 0
- self.scrollY = 0
- self.maxScrollX = 0
- self.maxScrollY = 0
- self.scrollXEnabled = false
- self.scrollYEnabled = true
- end
- objects.Container.ScrollView.get = function(self, contentBuffer)
- log("objects.Container.ScrollView.get", "FUNC")
- log("ScrollView (ID: " .. tostring(self.objID) .. ")", "INFO")
- local buffer = Buffer:new()
- buffer:init(self.width, self.height, self.path, objectColors.Container.ScrollView.border)
- buffer:addBuffer(2 - self.scrollX, 2 - self.scrollY, contentBuffer)
- buffer:makeBorder(self.path, objectColors.Container.ScrollView.border)
- --local edgePixel = { background=objectColors.Container.ScrollView.border, path=self.path }
- --buffer:setPixel(self.width, 1, edgePixel)
- --buffer:setPixel(self.width, self.height, edgePixel)
- --buffer:setPixel(1, self.height, edgePixel)
- if (self.scrollXEnabled) then
- self.maxScrollX = contentBuffer.width - self.width + 1
- buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Container.ScrollView.scrollBackground))
- if (self.width > 4) then
- local scrollBarInfo = getScrollBarInfo(self.scrollX, self.width, contentBuffer.width)
- buffer:addBuffer(scrollBarInfo.pos + 3, self.height, objects.Line.get("horizontal", scrollBarInfo.size, objectColors.Container.ScrollView.scrollForeground, self.path))
- end
- buffer:setPixel(2, self.height, { char="<", path=self.path })
- buffer:setPixel(self.width - 1, self.height, { char=">", path=self.path })
- else
- --buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Container.ScrollView.border, self.path))
- end
- if (self.scrollYEnabled) then
- self.maxScrollY = contentBuffer.height - self.height + 1
- buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Container.ScrollView.scrollBackground))
- if (self.height > 4) then
- local scrollBarInfo = getScrollBarInfo(self.scrollY, self.height, contentBuffer.height)
- buffer:addBuffer(self.width, scrollBarInfo.pos + 3, objects.Line.get("vertical", scrollBarInfo.size, objectColors.Container.ScrollView.scrollForeground, self.path))
- end
- buffer:setPixel(self.width, 2, { char="^", path=self.path })
- buffer:setPixel(self.width, self.height - 1, { char="V", path=self.path })
- else
- --buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Container.ScrollView.border, self.path))
- end
- return buffer
- end
- objects.Container.ScrollView.click = function(self, x, y)
- if (x == self.width and y == 2) then -- Up
- log("Up")
- if (self.scrollY > 0) then
- self.scrollY = self.scrollY - 1
- end
- elseif (x == self.width and y == self.height - 1) then -- Down
- log("Down")
- if (self.scrollY < self.maxScrollY) then
- self.scrollY = self.scrollY + 1
- end
- elseif (x == 2 and y == self.height) then -- Left
- log("Left")
- if (self.scrollX > 0) then
- self.scrollX = self.scrollX - 1
- end
- elseif (x == self.width - 1 and y == self.height) then -- Right
- log("Right")
- if (self.scrollX < self.maxScrollX) then
- self.scrollX = self.scrollX + 1
- end
- end
- windowBuffer:addBuffer(self.absoluteX, self.absoluteY, objects.get(self))
- objects.draw(self)
- end
- objects.Container.ScrollView.getPosModifier = function(self, x, y)
- return self.scrollX * -1, self.scrollY * -1
- end
- objects.Container.ScrollView.addMarker = function(self, buffer)
- if not (self.scrollXEnabled and self.scrollYEnabled) then
- if (not self.scrollXEnabled and self.width > 2) then
- buffer:addBuffer(2, self.height, objects.Line.get("horizontal", self.width - 2, objectColors.Editor.editMarker))
- end
- if (not self.scrollYEnabled and self.height > 2) then
- buffer:addBuffer(self.width, 2, objects.Line.get("vertical", self.height - 2, objectColors.Editor.editMarker))
- end
- end
- end
- objects.Container.ScrollView.editorClick = function(self, x, y)
- -- Check whether an arrow has been clicked.
- if (self.scrollYEnabled and x == self.width and y == 2) then -- Up
- log("Up")
- if (self.scrollY > 0) then
- self.scrollY = self.scrollY - 1
- end
- return true
- elseif (self.scrollYEnabled and x == self.width and y == self.height - 1) then -- Down
- log("Down")
- self.scrollY = self.scrollY + 1
- return true
- elseif (self.scrollXEnabled and x == 2 and y == self.height) then -- Left
- log("Left")
- if (self.scrollX > 0) then
- self.scrollX = self.scrollX - 1
- end
- return true
- elseif (self.scrollXEnabled and x == self.width - 1 and y == self.height) then -- Right
- log("Right")
- self.scrollX = self.scrollX + 1
- return true
- else -- Check whether the scrollBar has been clicked.
- if (x >= 1 and x <= self.width - 1 and y == self.height) then
- self.scrollXEnabled = not self.scrollXEnabled
- elseif (x == self.width and y > 1 and y < self.height - 1) then
- self.scrollYEnabled = not self.scrollYEnabled
- end
- end
- return false
- end
- -- >>> Objects that can't be added by the user.
- -- >> Line
- objects.Line = {}
- objects.Line.get = function(orientation, length, color, path)
- if (orientation ~= "horizontal" and orientation ~= "vertical") then
- orientation = getOrientation(orientation) or error("Orientation " .. orientation .. " is invalid!", 1)
- end
- assert(length)
- assert(color)
- local width, height
- if (orientation == "horizontal") then
- width, height = length, 1
- else
- width, height = 1, length
- end
- local buffer = Buffer:new()
- buffer:init(width, height, path, color)
- return buffer
- end
- -- >> Selector
- objects.Selector = {}
- objects.Selector.draw = function(x, y, elements)
- width = getLongestString(elements) + 2
- height = #elements + 2 -- Elements + up and down
- elementCount = #elements
- displayCount = elementCount
- enoughXSpace = true
- -- determine where the selector should actually be displayed
- if (width > maxX) then -- Not enough monitors horizontally?
- x = 1
- enoughXSpace = false
- elseif (maxX - x < width) then -- Not enough space to the right.
- if (x >= width) then -- Let's see if there is space to the left.
- x = x - width
- else -- No space? Check where you've got more space.
- if (maxX / 2) > x then -- More space to the left.
- x = maxX - width + 1
- enoughXSpace = false
- else -- More space to the right
- x = 1
- enoughXSpace = false
- end
- end
- else -- Enough space to the right.
- x = x + 1
- end
- if (height > maxY - y) then -- Not enough space from y to bottom.
- if ((maxY / 2) > y) then -- More space below y.
- if enoughXSpace then
- if (maxY < height) then -- Too big for the whole screen.
- y = 1
- displayCount = maxY - 2
- else -- Enough space next to x and not too high.
- y = maxY - height
- end
- else -- Can't display it next to the selected point.
- y = y + 1
- displayCount = maxY - y - 1
- end
- else -- More space above y.
- if enoughXSpace then
- if (y < height) then -- Not enough space from top to y.
- if (maxY < height) then -- Too big for the whole screen.
- y = 1
- displayCount = maxY - 2
- else -- Enough space next to x and not too high.
- y = 1
- end
- else -- Enough space from top to y.
- y = y - height + 1
- end
- else
- if (y < height) then -- Not enough space from top to y.
- if (maxY < height) then -- Too big for the whole screen.
- y = 1
- displayCount = maxY - 2
- else -- Not enough space next to x but not too high.
- y = 1
- displayCount = y - 4
- end
- else -- Enough space from top to y.
- y = y - height
- end
- end
- end
- end
- out.setBackgroundColor(objectColors.background)
- -- Read the user input.
- scroll = 1
- right = x + width - 1
- bottom = y + displayCount + 1
- finished = false
- while not finished do
- -- Display the actual selector.
- drawBox(x, y, width, height, objectColors.List.default)
- out.setBackgroundColor(objectColors["List"].default)
- middle = math.floor(width / 2)
- out.setCursorPos(x + middle, y)
- out.write("^")
- out.setCursorPos(x + middle, bottom)
- out.write("V")
- for i = 1, displayCount do
- out.setCursorPos(x, y + i)
- out.write(" " .. elements[i + scroll - 1] .. " ")
- end
- out.setBackgroundColor(objectColors.background)
- touchX, touchY, mouseButton = getCursorInput()
- if (touchX < x or touchX > right or touchY < y or touchY > bottom) then
- selectedItem = nil
- result = false
- finished = true
- else -- User touched the selector.
- if (touchY == y) then -- up
- if (scroll > 1) then -- Check if it makes sense to scroll up.
- scroll = scroll - 1
- end
- elseif (touchY == bottom) then -- down
- if (displayCount < elementCount) then
- if (scroll <= elementCount - displayCount) then
- scroll = scroll + 1
- end
- end
- else
- selectedItem = elements[touchY - y + scroll - 1]
- result = true
- finished = true
- end
- end
- end
- drawWindow(currentWindow)
- return result
- end
- -- >> API Functions
- -- API function: Sets the value of all variables
- -- with the given ID.
- function setVariableValue(variableID, newVar)
- variableValues[variableID] = newVar
- end
- -- API function: Sets the value of all progressBars
- -- with the given ID.
- function setProgressBarValue(objID, newVar)
- progressBarValues[objID] = newVar
- end
- -- >> User Input Functions
- -- Gets any input of the user
- -- (not from the environment)
- function getAnyInput()
- local finished = false
- local event = {}
- while not finished do
- finished = true
- os.sleep(0)
- input = {os.pullEvent()}
- event.eventType = input[1]
- if (event.eventType == "monitor_touch" and not outIsTerm) then
- event.eventType = "mouse"
- event.x = input[3]
- event.y = input[4]
- event.mouseButton = 1
- elseif (event.eventType == "mouse_click" and outIsTerm) then
- event.eventType = "mouse"
- event.x = input[3]
- event.y = input[4]
- event.mouseButton = input[2]
- elseif (event.eventType == "key") then
- event.key = input[2]
- else
- finished = false
- end
- end
- return event
- end
- -- Returns where the user clicked and which button
- -- he pressed (always 1 if it's a monitor).
- function getCursorInput()
- local finished = false
- while not finished do
- event, param, x, y = os.pullEvent()
- if (event == "monitor_touch" and not outIsTerm) then
- mouseButton = 1
- finished = true
- elseif (event == "mouse_click" and outIsTerm) then
- mouseButton = param
- finished = true
- end
- end
- return x, y, mouseButton
- end
- -- Waits until any key gets pressed.
- function getKeyInput()
- os.pullEvent("key")
- end
- function readUserInput(message, isPassword)
- if not outIsTerm then
- print(message)
- end
- if isPassword then
- ret = read("*")
- else
- ret = read()
- end
- return ret
- end
- -- >> Display Functions
- -- Has to be used instead of paintutils.drawpixel
- function drawPixel(x, y, color)
- out.setCursorPos(x, y)
- out.setBackgroundColor(color)
- out.write(" ")
- end
- function drawBox(x, y, width, height, color)
- out.setBackgroundColor(color)
- for row = 1, height do
- out.setCursorPos(x, y + row - 1)
- out.write(string.rep(" ", width))
- for col = x, width do
- if (windowBuffer[col] and windowBuffer[col][y + row - 1]) then
- windowBuffer[col][y + row - 1].draw = true
- end
- end
- end
- end
- -- Displays the text with red background colour.
- function drawSimpleButton(x, y, text)
- out.setCursorPos(x, y)
- out.setBackgroundColor(objectColors.Button.default)
- out.write(text)
- out.setBackgroundColor(objectColors.background)
- end
- -- Displays the default buttons.
- function drawDefaultButtons()
- local window = getCurrentWindow()
- local button
- if (window.showRefreshButton) then
- button = defaultButtons.refresh
- drawSimpleButton(button.left, button.top, button.text) -- Refresh
- end
- if (window.showBackButton and currentWindow ~= "mainWindow") then
- button = defaultButtons.back
- drawSimpleButton(button.left, button.top, button.text) -- Back
- end
- button = defaultButtons.quit
- drawSimpleButton(button.left, button.top, button.text) -- Quit
- button = defaultButtons.options
- if (button.required()) then
- drawSimpleButton(button.left, button.top, button.text) -- Options
- end
- end
- -- Loads the values of all variables and progressBars
- -- of the current window.
- function loadObjects()
- local window = getCurrentWindow()
- loadObjectsOf(window)
- end
- -- Loads all objects inside the container and its containers (recursive)
- function loadObjectsOf(container)
- for _, object in pairs(container.children) do
- local objectType = object.objType
- if (objectType == "Variable" or objectType == "ProgressBar") then
- local x = object.absoluteX
- local y = object.absoluteY
- local value = nil
- if (objectType == "Variable") then
- value = getVariableValue(object)
- elseif (objectType == "ProgressBar") then
- value = getProgressBarValue(object)
- end
- objects.draw(object, value)
- elseif (object.isContainer) then
- loadObjectsOf(object)
- end
- end
- end
- -- Displays all objects of the window with the
- -- ID windowID on the screen and changes the
- -- variable "currentWindow".
- function drawWindow(windowID)
- --clearScreen()
- if windowID then
- currentWindow = windowID
- else
- windowID = currentWindow
- end
- local windowObject = getCurrentWindow()
- windowBuffer = objects.Container.get(windowObject)
- windowBuffer:draw()
- if autoLoadObjects then
- loadObjects()
- end
- drawDefaultButtons()
- end
- -- >> Input Processing
- function pullEvent(requestedEvent)
- if not eventTypeExists(requestedEvent) then
- clearScreen()
- print("Event type " .. tostring(requestedEvent) .. " is invalid!")
- print()
- print("Available event types:")
- for _, event in pairs(eventTypes) do
- print(" " .. event)
- end
- error()
- end
- local finished = false
- local event, params
- while not finished do
- event, params = getInput()
- if event then
- if (requestedEvent ~= nil) then
- if (requestedEvent == event or event == "quit") then
- finished = true
- end
- else
- finished = true
- end
- end
- end
- return event, unpack(params)
- end
- function getInput()
- log("getInput", "FUNC")
- local finished = false
- local event, params
- local x, y, mouseButton = getCursorInput()
- log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".", "INFO")
- if (defaultButtonPressed("quit", x, y)) then
- log("Quit pressed")
- quit = true
- elseif (defaultButtonPressed("refresh", x, y)) then
- log("Refresh pressed")
- drawWindow()
- finished = true
- elseif (defaultButtonPressed("back", x, y)) then
- log("Back pressed")
- if (windows.children[currentWindow].parent ~= nil) then
- drawWindow(windows.children[currentWindow].parent)
- finished = true
- else
- drawWindow("mainWindow")
- finished = true
- end
- end
- if finished then
- return nil
- elseif quit then
- return "quit", { "Graffiti" } -- Used for the API
- end
- local param
- local path = windowBuffer.bufferTable[x][y].path
- log(path)
- if path and #path > 0 then
- local object = Path.getObject(path)
- local clickX, clickY = Path.getRelativePos(path, x, y)
- clickX, clickY = clickX - object.x + 1, clickY - object.y + 1
- log(object.objType .. " \"" .. object.objID .. "\" clicked at " .. clickX .. ", " .. clickY .. ".")
- event, params = objects.click(object, clickX, clickY)
- end
- return event, params
- end
- -- Shows the message on the computer for debugging.
- function debugMessage(message)
- if outIsTerm then
- error("Can't display a debug message on a computer!")
- end
- print(message)
- end
- -- Calls the "getInput" function until the user presses the quit-button.
- function main()
- drawWindow("mainWindow")
- while not quit do
- getInput()
- end
- end
- function splitAt(self, delimiter)
- delimiterPos = string.find(self, delimiter)
- left = string.sub(self, 1, delimiterPos - 1)
- right = string.sub(self, delimiterPos + #delimiter)
- return left, right
- end
- -- >>> Editor
- function generateWindowList()
- local ret = {}
- for key, value in pairs(windows.children) do
- table.insert(ret, key)
- end
- return ret
- end
- editorWindows = {
- children = {
- mainWindow = {
- objType = "Window",
- children = {
- [1] = { objType="Text", objID="ModeText", x=2, y=1, text="Mode:", path={1} };
- [2] = { objType="List", x=2, y=3, elements=editActions, objID="editActionList", isMultiselect=false, canClick=true, path={2} };
- [3] = { objType="Button", objID="btnLastWindow", x=2, y=7, width=13, height=3, text="Last Window", funcType="function", param="editLastWindow", canClick=true, canScale=true, path={3} };
- [4] = { objType="Button", objID="btnWindowList", x=17, y=7, width=13, height=3, text="Window List", funcType="function", param="loadWindowList", canClick=true, canScale=true, path={4} };
- };
- };
- windowListWindow = {
- objType = "Window",
- children = {
- [1] = { objType="List", objID="WindowList", x=2, y=2, elements=windowList, objID="windowList", isMultiselect=false, canClick=true, path={1} };
- [2] = { objType="Button", objID="SetParent", x=2, y=maxY-6, width=12, height=1, text="Set parent", funcType="function", param="setParent", canClick=true, canScale=true, path={2} };
- [3] = { objType="Button", objID="NewWindow", x=2, y=maxY-4, width=8, height=1, text="New", funcType="function", param="newWindow", canClick=true, canScale=true, path={3} };
- [4] = { objType="Button", objID="EditWindow", x=2, y=maxY-3, width=8, height=1, text="Edit", funcType="function", param="editWindow", canClick=true, canScale=true, path={4} };
- [5] = { objType="Button", objID="DeleteWindow", x=2, y=maxY-2, width=8, height=1, text="Delete", funcType="function", param="deleteWindow", canClick=true, canScale=true, path={5} };
- };
- };
- };
- }
- -- Used to give a List-object an array of all windows
- function editorFunctions.loadWindowList()
- windowList = generateWindowList()
- editorWindows.children.windowListWindow.children[1].elements = windowList
- changeButtonColor = false
- drawWindow("windowListWindow")
- end
- function editorFunctions.editLastWindow()
- if (lastWindow == nil) then
- lastWindow = "mainWindow"
- end
- showEditorOptions = false
- drawWindow(lastWindow)
- changeButtonColor = false
- end
- -- Let's the user define the parent-attribute of the current window.
- function editorFunctions.setParent()
- if (selectedItems.windowList == nil) then
- return
- end
- local list = editorWindows.children.windowListWindow.children[1]
- local selected = objects.List.getFirstSelectedKey(list)
- for i = 1, list.height do
- if (i ~= selected) then
- if (windowList[i] == windows.children[windowList[selected]].parent) then
- drawPixel(1, i + 1, colors.yellow)
- else
- drawPixel(1, i + 1, colors.lime)
- end
- end
- end
- local x, y, mouseButton = getCursorInput()
- local selectedParent = y - list.y + 1
- if (selectedParent >= 1 and selectedParent <= list.height) then -- Clicked inside the list.
- if (selectedParent ~= selected) then -- Selected parentWindow is not selected window.
- windows.children[windowList[selected]].parent = windowList[selectedParent]
- end
- end
- for i = 1, list.height do
- drawPixel(1, i + list.y - 1, colors.black)
- end
- end
- -- Creates a new window. The user has to enter the window name in the computer.
- function editorFunctions.newWindow()
- clearScreen()
- if not outIsTerm then
- out.setCursorPos(2, 2)
- out.write("Enter a window-name.")
- end
- out.setCursorPos(1, 1)
- message = "Pleas enter the name of the new window or nothing to cancel."
- userInput = readUserInput(message, false)
- while (userInput ~= nil and userInput ~= "" and windows.children[userInput] ~= nil) do
- message = "There is already a window with that name!"
- userInput = readUserInput(message, false)
- end
- if (userInput ~= nil and userInput ~= "") then
- objects.Container.Window.create(userInput)
- showEditorOptions = false
- drawWindow(userInput)
- lastWindow = userInput
- changeButtonColor = false
- end
- end
- -- Edits the window that has been selected in the "windowList"-list.
- function editorFunctions.editWindow()
- if selectedItems.windowList then
- local list = editorWindows.children.windowListWindow.children[1]
- local key = objects.List.getFirstSelectedKey(list)
- key = key or 1
- showEditorOptions = false
- lastWindow = windowList[key]
- drawWindow(lastWindow)
- changeButtonColor = false
- end
- end
- -- Deletes the window that has been selected in the "windowList"-list.
- function editorFunctions.deleteWindow()
- local list = editorWindows.children.windowListWindow.children[1]
- local key = objects.List.getFirstSelectedKey(list)
- if (key and windowList[key] ~= "mainWindow") then
- windows.children[windowList[key]] = nil
- showEditorOptions = true
- editorFunctions.loadWindowList()
- end
- end
- -- Shows lines marking the top left part of an
- -- object as well as well as pixels displaying
- -- the alignment of an object.
- function drawAlignmentLines(object, left, top, right, bottom)
- local color = objectColors.Editor.marker
- local moveX, moveY = objects.getMovePos(object)
- -- Draw the lines.
- objects["Line"].draw(left - 1, moveY, "left", left - 2, color) -- left
- objects["Line"].draw(moveX, top -1, "up", top - 2, color) -- up
- objects["Line"].draw(right + 1, moveY, "right", maxX - (right + 1), color) -- right
- objects["Line"].draw(moveX, bottom + 1, "down", maxY - (bottom + 1), color) -- down
- -- Display the alignment-pixels.
- horizontalAlignment = object.horizontalAlignment
- verticalAlignment = object.verticalAlignment
- if (horizontalAlignment == "left" or horizontalAlignment == "stretch") then -- left
- drawPixel(1, moveY, objectColors["Editor"].alignmentTrue)
- else
- drawPixel(1, moveY, objectColors["Editor"].alignmentFalse)
- end
- if (horizontalAlignment == "right" or horizontalAlignment == "stretch") then -- right
- drawPixel(maxX, moveY, objectColors["Editor"].alignmentTrue)
- else
- drawPixel(maxX, moveY, objectColors["Editor"].alignmentFalse)
- end
- if (verticalAlignment == "top" or verticalAlignment == "stretch") then -- top
- drawPixel(moveX, 1, objectColors["Editor"].alignmentTrue)
- else
- drawPixel(moveX, 1, objectColors["Editor"].alignmentFalse)
- end
- if (verticalAlignment == "bottom" or verticalAlignment == "stretch") then -- bottom
- drawPixel(moveX, maxY, objectColors["Editor"].alignmentTrue)
- else
- drawPixel(moveX, maxY, objectColors["Editor"].alignmentFalse)
- end
- out.setBackgroundColor(objectColors.background)
- end
- -- Returns the values of horizontalAlignment and
- -- verticalAlignment depending which sides are set
- -- to true.
- function getAlignment(left, top, right, bottom)
- local retHorizontal, retVertical = "left", "top"
- if right then
- if left then
- retHorizontal = "stretch"
- else
- retHorizontal = "right"
- end
- else
- retHorizontal = "left"
- end
- if bottom then
- if top then
- retVertical = "stretch"
- else
- retVertical = "bottom"
- end
- else
- retVertical = "top"
- end
- return retHorizontal, retVertical
- end
- -- Let's the user delete an object or change its attributes depending on the current edit-mode.
- function editObject(object)
- assert(object)
- log("editObject", "FUNC")
- log("Type: " .. object.objType .. ", ID" .. object.objID, "INFO")
- local objType = object.objType
- local modX, modY = objects.getPosModifier(object)
- local left, top, right, bottom = objects.getDimensions(object)
- left, top, right, bottom = left + modX, top + modY, right + modX, bottom + modY
- local actionsList = editorWindows.children.mainWindow.children[2]
- local action = editActions[objects.List.getFirstSelectedKey(actionsList)]
- if (action == "Delete") then
- objects.remove(object)
- elseif (action == "Attributes") then
- local objAttr = { }
- local includedAttributes = {
- text = true,
- param = true,
- objID = true,
- message = true,
- elements = true,
- message = true,
- funcType = true,
- isPassword = true,
- isMultiselect = true,
- scrollXEnabled = true,
- scrollYEnabled = true,
- }
- index = 1
- for key, value in pairs(object) do
- if (includedAttributes[key]) then
- table.insert(objAttr, index, key)
- index = index + 1
- end
- end
- out.clear()
- local yPos = 2
- top = yPos
- for attrKey, attrValue in ipairs(objAttr) do
- out.setCursorPos(2, yPos)
- out.write(attrValue .. ": ")
- out.write(object[attrValue])
- yPos = yPos + 1
- end
- out.setCursorPos(2, yPos + 1)
- out.setBackgroundColor(colors.red)
- out.write(text.done)
- out.setBackgroundColor(objectColors.background)
- bottom = yPos - 1
- finished = false
- while not finished do
- local x, y, mouseButton = getCursorInput()
- if y >= top and y <= bottom then
- local selectedAttr = objAttr[y - 1]
- if not outIsTerm then
- drawPixel(1, y, colors.yellow)
- end
- if (selectedAttr == "param" or
- selectedAttr == "objID" or
- selectedAttr == "message" or
- selectedAttr == "elements") then
- if outIsTerm then
- out.setCursorPos(1, y)
- out.clearLine(y)
- out.setCursorPos(2, y)
- out.write(selectedAttr .. ": ")
- end
- userInput = readUserInput("Please enter a value for the " .. selectedAttr .. ".", false)
- if (userInput ~= nil) then
- object[selectedAttr] = userInput
- end
- elseif (selectedAttr == "text") then
- if outIsTerm then
- out.setCursorPos(1, y)
- out.clearLine(y)
- out.setCursorPos(2, y)
- out.write(selectedAttr .. ": ")
- end
- userInput = readUserInput("Please enter a value for the " .. selectedAttr .. ".", false)
- if (userInput ~= nil) then
- object[selectedAttr] = userInput
- if (object.objType == "Text") then
- object.width = #userInput
- end
- end
- elseif (selectedAttr == "funcType") then -- Button attribute
- if (object.funcType == "switch") then
- object[selectedAttr] = "function"
- elseif (object.funcType == "function") then
- object[selectedAttr] = "toggle function"
- else
- object[selectedAttr] = "switch"
- end
- elseif (selectedAttr == "isPassword" or
- selectedAttr == "isMultiselect" or
- selectedAttr == "toggle" or
- selectedAttr == "scrollXEnabled" or
- selectedAttr == "scrollYEnabled") then
- object[selectedAttr] = not object[selectedAttr]
- end
- drawPixel(1, y, colors.black)
- if (not finished and selectedAttr ~= nil) then
- out.setCursorPos(2, y) -- I don't know if that's neccessary...
- for i = 2, maxX do
- out.write(" ")
- end
- out.setCursorPos(2, y)
- out.write(selectedAttr .. ": ")
- out.write(object[selectedAttr])
- end
- elseif (y == yPos + 1 and x >= 2 and x <= 1 + string.len(text.done)) then
- finished = true
- end
- end
- else -- Design mode
- local moveX, moveY = objects.getMovePos(object)
- local scaleX, scaleY
- objects.draw(object, nil, true) -- Draw the object with its markers.
- if object.canScale then
- scaleX, scaleY = objects.getScalePos(object)
- drawPixel(scaleX, scaleY, objectColors.Editor.scale)
- end
- drawPixel(moveX, moveY, objectColors.Editor.move)
- out.setBackgroundColor(objectColors.background)
- local x, y, mouseButton = getCursorInput()
- local relX, relY = Path.getRelativePos(object.path, x, y)
- if (relX >= left and relX <= right and relY >= top and relY <= bottom) then -- clicked inside the object
- if (x == moveX and y == moveY) then -- move object
- drawPixel(moveX, moveY, objectColors.Editor.active)
- x, y, mouseButton = getCursorInput()
- addX = x - moveX
- addY = y - moveY
- objects.move(object, addX, addY)
- elseif (object.canScale and x == scaleX and y == scaleY) then -- scale object
- drawPixel(scaleX, scaleY, objectColors.Editor.active)
- out.setBackgroundColor(objectColors.background)
- local x, y, mouseButton = getCursorInput()
- relX, relY = Path.getRelativePos(object.path, x, y)
- objects.scale(object, relX + (modX * -1), relY + (modY * -1))
- else
- relX, relY = relX - object.x + 1, relY - object.y + 1
- objects.editorClick(object, relX + modX, relY + modY)
- end
- end
- end
- out.setBackgroundColor(objectColors.background)
- drawWindow(currentWindow)
- end
- function markVariables(container)
- assert(container)
- for _, object in pairs(container.children) do
- if (object.isContainer) then
- markVariables(object)
- elseif (object.objType == "Variable") then
- drawPixel(object.absoluteX, object.absoluteY, objectColors.Editor.marker)
- out.setBackgroundColor(objectColors.background)
- end
- end
- end
- function markDefaultButtons()
- local window = getCurrentWindow()
- -- refresh button
- local refresh = defaultButtons.refresh
- out.setCursorPos(refresh.left, refresh.top)
- if (window.showRefreshButton) then
- out.setBackgroundColor(objectColors.Button.default)
- out.write(refresh.text)
- else
- out.setBackgroundColor(objectColors.Editor.marker)
- out.write(string.rep(" ", #refresh.text))
- end
- -- back button
- if (currentWindow ~= "mainWindow") then
- local back = defaultButtons.back
- out.setCursorPos(back.left, back.top)
- if (window.showBackButton) then
- out.setBackgroundColor(objectColors.Button.default)
- out.write(back.text)
- else
- out.setBackgroundColor(objectColors.Editor.marker)
- out.write(string.rep(" ", #back.text))
- end
- end
- out.setBackgroundColor(objectColors.background)
- end
- function getEditorInput()
- log("getEditorInput", "FUNC")
- local event
- local x, y, mouseButton
- if not showEditorOptions then
- markVariables(getCurrentWindow())
- markDefaultButtons()
- event = getAnyInput()
- if (event.eventType == "mouse") then
- x, y, mouseButton = event.x, event.y, event.mouseButton
- log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".")
- end
- end
- if (not showEditorOptions and event.eventType == "key") then
- callShortcut(event.key)
- elseif (showEditorOptions or defaultButtonPressed("options", x, y)) then
- showEditorOptions = true
- drawWindow("mainWindow")
- while showEditorOptions and not quit do
- getInput()
- end
- elseif (defaultButtonPressed("quit", x, y)) then
- quit = true
- elseif (defaultButtonPressed("refresh", x, y)) then
- windows.children[currentWindow].showRefreshButton = not windows.children[currentWindow].showRefreshButton
- elseif (defaultButtonPressed("back", x, y)) then
- windows.children[currentWindow].showBackButton = not windows.children[currentWindow].showBackButton
- else
- local container = getCurrentWindow()
- --log("Buffer table width: " .. #windowBuffer.bufferTable, "DEBUG")
- --log("Buffer table height: " .. #windowBuffer.bufferTable[1], "DEBUG")
- local path = windowBuffer.bufferTable[x][y].path
- if (path == nil or #path == 0) then -- No object touched. Draw selector for new object.
- drawPixel(x, y, objectColors.Editor.new)
- if (objects.Selector.draw(x, y, objectTypes)) then -- something has been selected
- objects.create(selectedItem, x, y)
- end
- else
- local object = Path.getObject(path)
- if (mouseButton == 1) then
- editObject(object)
- else
- if (objects.Selector.draw(x, y, rightClickActions)) then
- if (selectedItem == "Attributes") then
- lastItem = selectedItems.editActionList
- selectedItems.editActionList = { [2] = true }
- editObject(object)
- selectedItems.editActionList = lastItem
- elseif (selectedItem == "Delete") then
- objects.remove(object)
- drawWindow()
- end
- end
- end
- end
- end
- end
- -- Runs Graffiti in editMode.
- function windowEditor()
- editMode = true
- autoLoadObjects = false
- showEditorOptions = true
- while not quit do
- getEditorInput()
- end
- end
- -- >>> Screen size adaption
- function round(number)
- assert(number)
- comma = number % 1
- if comma < 0.5 then
- ret = math.floor(number)
- else
- ret = math.ceil(number)
- end
- return ret
- end
- function printInfo()
- print()
- print(version)
- print("Author: Encreedem")
- print()
- print("Param(s):")
- print("info - Shows some info about the program... but I guess you know that already.")
- print("edit - Starts the program in edit-mode.")
- print()
- print("Visit the CC-forums or my YouTube channel (Encreedem CP) for news and help.")
- end
- -- Gets called when Graffiti gets the argument "test"
- function testMethod()
- error("Nothing to test...", 2)
- end
- -- >>> initialization
- -- Runs the setup.
- function runSetup()
- dataFolderPath = fs.combine(root, "GraffitiData")
- fs.makeDir(dataFolderPath)
- -- TODO: Make an actual setup.
- end
- -- Initializes the default buttons.
- -- (Quit, Back, Refresh, Options)
- function initDefaultButtons()
- defaultButtons.quit = {
- text=text.quit,
- left=maxX - string.len(text.quit) + 1,
- top=1,
- right=maxX,
- bottom=1,
- required = function()
- return true
- end
- }
- defaultButtons.back = {
- text = text.back,
- left = 1,
- top = 1,
- right = string.len(text.back),
- bottom = 1,
- required = function()
- return getCurrentWindow().showBackButton
- end
- }
- defaultButtons.refresh = {
- text = text.refresh,
- left = maxX - string.len(text.refresh) + 1,
- top = maxY,
- right = maxX,
- bottom = maxY,
- required = function()
- return (getCurrentWindow().showRefreshButton or (editMode and not showEdtorOptions))
- end
- }
- defaultButtons.options = {
- text = text.options,
- left = 1,
- top = maxY,
- right = string.len(text.options),
- bottom = maxY,
- required=function()
- return (editMode and not showEditorOptions)
- end
- }
- end
- function initWindows()
- for _, window in pairs(windows.children) do
- window.width, window.height = maxX, maxY
- end
- end
- -- Tells the user that the monitor or computer
- -- doesn't support colors.
- function showColorWarning()
- out.clear()
- out.setCursorPos(2, 2)
- out.write("This computer/monitor does not support colors!")
- local state = 0
- local move = "I don't know this move!"
- local finished = false
- while not finished and not quit do
- out.setCursorPos(1, 4)
- out.clearLine()
- out.setCursorPos(2, 4)
- if (state == 0) then
- move = "<( \" <) <( \" <) <( \" <)"
- elseif (state == 1 or state == 3 or state == 5) then
- move = " (^\"^) (^\"^) (^\"^)"
- elseif (state == 2) then
- move = " (> \" )> (> \" )> (> \" )>"
- elseif (state == 4) then
- move = " (> \" )><( \" )><( \" <)"
- elseif (state == 6) then
- move = "<( \" <) (>\"<) (> \" )>"
- elseif (state == 7) then
- move = " (v''v) (v''v) (v''v)"
- else
- error("Unable to show you that you need an advanced computer/monitor in a fancy way!")
- end
- out.write(move)
- state = (state + 1) % 8
- os.sleep(0.25)
- end
- end
- -- Checks if the monitor on monitorSide exists and wraps it into "monitor".
- function getOutput()
- if (monitor == nil and outIsTerm == false) then
- local monitorFound = false
- for _, side in pairs(sides) do
- if (peripheral.getType(side) == "monitor") then
- monitor = peripheral.wrap(side)
- monitorFound = true
- out = monitor
- outIsTerm = false
- end
- end
- if not monitorFound then
- out = term
- outIsTerm = true
- end
- elseif outIsTerm then
- out = term
- else
- out = monitor
- end
- end
- function init()
- getOutput()
- maxX, maxY = out.getSize()
- if (maxX < 16 or maxY < 10) then -- smaller than 2x2
- print("Screen too small! You need at least 2x2 monitors!")
- return false
- elseif not out.isColor() then
- parallel.waitForAny(showColorWarning, getKeyInput)
- out.clear()
- out.setCursorPos(1, 1)
- return false
- end
- isAPI = (shell == nil)
- initDone = true
- return true
- end
- function checkArgs()
- doCall = main
- arg = args[1]
- if (arg ~= nil) then
- if (arg == "edit") then
- doCall = windowEditor
- elseif (arg == "info") then
- doCall = printInfo
- elseif (arg == "term") then
- outIsTerm = true
- elseif (arg == "test") then
- doCall = testMethod
- end
- end
- doCall()
- end
- if init() then
- Files.init()
- log("Graffiti initialized.")
- initWindows()
- initDefaultButtons()
- Files.clear(nil, Files.Settings.name)
- if not isAPI then
- checkArgs()
- -- Closing Program
- if editMode and saveAfterQuit then
- Files.save()
- end
- out.setTextColor(colors.white)
- out.setBackgroundColor(colors.black)
- out.clear()
- out.setCursorPos(1, 1)
- end
- else
- error("Graffiti Initialization failed!")
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement