Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- used to dump incoming events to the screen
- IS_DEBUG_MODE = false
- -- Animation timer data
- AnimTimerId = 0
- AppLastTickTime = 0
- -- The application animation timer tick rate
- AnimTimerTickRate = 10
- -- The interval of an application tick, in seconds
- AnimIntervalSecs = 1 / AnimTimerTickRate
- -- True or false if the current event is a timer/render tick
- IsAnimationTick = false
- -- A helper to print a debug message to the screen
- LastDebugMessage = "Debug Message Here"
- BLANK_EVENT = { type = nil, p1 = nil, p2 = nil, p3 = nil, p4 = nil, }
- COMPONENT_MOUSE_CAPTURE = nil
- APP_ROOT_OBJECTS = {}
- -- A base table template for a drawable component object
- DRAWABLE_TEMPLATE = {
- -- x,y are the x and y coords, relative to the parent
- -- w,h are the width and height of the object
- x = 0, y = 0, w = 0, h = 0,
- IsUpdating = false,
- OnAnimTick = function(this, time, delta)
- -- update object here. e contains the last event data,
- -- which may be the timer event
- end,
- RenderFunc = function(this, x, y)
- -- draw here. X and Y are absolute coordinates, accumulated
- -- during the recursive render process
- end,
- -- returns the handled state. True = don't go further
- -- These are the function definitions though, however, the handlers are stored in tables,
- -- OnPreviewMouseDown = function(this, time, delta, btn, x, y) return false end,
- -- OnMouseDown = function(this, time, delta, hit, btn, x, y) return false end,
- -- OnPreviewMouseDrag = function(this, time, delta, btn, x, y) return false end,
- -- OnMouseDrag = function(this, time, delta, btn, x, y) return false end,
- -- OnPreviewKeyPressed = function(this, time, delta, keyCode) return false end,
- -- OnKeyPressed = function(this, time, delta, keyCode) return false end,
- -- OnPreviewCharPressed = function(this, time, delta, ch) return false end,
- -- OnCharPressed = function(this, time, delta, ch) return false end,
- OnPreviewMouseDown = {},
- OnMouseDown = {},
- OnPreviewMouseDrag = {},
- OnMouseDrag = {},
- OnPreviewKeyPressed = {},
- OnKeyPressed = {},
- OnPreviewCharPressed = {},
- OnCharPressed = {},
- -- our parent object
- parent = nil,
- indexInParent = -1,
- IsInVisualTree = false,
- children = {}, -- a table of child drawable objects
- childrenToRemove = {}
- }
- --region Helper String Functions
- function string.jsub(str, startIndex, endIndex)
- if (endIndex == nil) then
- return string.sub(str, startIndex)
- end
- return string.sub(str, startIndex, endIndex - 1)
- end
- -- Checks if the given `str` starts with the given `value` (optionally start searching with the given start offset index. 1 by default). `string.startsWith("hello", "he", 1)` == `true`
- function string.startsWith(str, value, startIndex)
- if (startIndex == nil) then
- startIndex = 1
- end
- return string.jsub(str, startIndex, string.len(value) + startIndex) == value
- end
- -- Checks if the given `str` ends with the given value. string.endsWith("hello", "lo") == true
- function string.endsWith(str, value)
- local strLen = string.len(str)
- local valLen = string.len(value)
- return string.sub(str, strLen - valLen + 1, strLen) == value
- end
- function string.contains(str, value, startIndex)
- if (startIndex == nil) then
- startIndex = 1
- end
- return string.find(str, value) ~= nil
- end
- function string.indexOf(str, value, startIndex)
- if (startIndex == nil) then
- startIndex = 1
- end
- local s, e, c = string.find(str, value)
- if (s == nil) then
- return -1
- end
- return s
- end
- -- Clamps the given string to the given length. `string.len(str)` is smaller than or equal to `maxLen` then `str` is returned. Otherwise, a substring of `maxLen - 3` of characters + `"..."` is returned
- function string.clamp(str, maxLen)
- if (#str <= maxLen) then
- return str
- end
- return (string.sub(str, 1, maxLen - 3) .. "...")
- end
- --endregion
- utils = {
- centerOf = function(start, ending)
- if (start == ending) then
- return start
- elseif (start < ending) then
- return math.floor((ending - start) / 2)
- else
- return math.floor((start - ending) / 2)
- end
- end,
- centerForText = function(size, text)
- local strLen = #text
- if (strLen < 1) then
- return math.floor(size / 2)
- else
- return math.floor((size / 2) - (strLen / 2))
- end
- end,
- thatOrNull = function(nullable, nonNull)
- if (nullable == nil) then return nonNull else return nullable end
- end,
- byteToMegaByte = function(bytes) return bytes / 1048576 end,
- byteToKiloByte = function(bytes) return bytes / 1024 end,
- byteToGigaByte = function(bytes) return bytes / 1073741824 end
- }
- --region GUI Functions
- GDI = {
- Handle = term,
- -- returns a non null text colour
- nonNullTextColour = function(text_colour)
- if (text_colour == nil) then
- return colours.white
- end
- return text_colour
- end,
- -- returns a non null background colour
- nonNullBackgroundColour = function(background_colour)
- if (background_colour == nil) then
- return colours.black
- end
- return background_colour
- end,
- -- sets the monitor cursor pos
- cpos = function(x, y)
- GDI.Handle.setCursorPos(x, y)
- end,
- -- sets the monitor text colour
- textColour = function(colour)
- GDI.Handle.setTextColor(colour)
- end,
- -- sets the monitor background colour
- backgroundColour = function(colour)
- GDI.Handle.setBackgroundColor(colour)
- end,
- clearBackground = function(foreground, background)
- GDI.textColour(GDI.nonNullTextColour(foreground))
- GDI.backgroundColour(GDI.nonNullBackgroundColour(background))
- GDI.Handle.clear()
- end,
- clearMonitor = function()
- GDI.clearBackground(colours.white, colours.black)
- end,
- -- draws text on the monitor
- drawText = function(x, y, text, foreground, background)
- GDI.textColour(GDI.nonNullTextColour(foreground))
- GDI.backgroundColour(GDI.nonNullBackgroundColour(background))
- GDI.cpos(x, y)
- GDI.Handle.write(text)
- end,
- drawTextClamped = function(x, y, maxLength, text, foreground, background)
- GDI.drawText(x, y, string.clamp(text, maxLength), foreground, background)
- end,
- drawTextCenteredClamped = function(x, y, w, h, text, foreground, background)
- local theText = string.clamp(text, w)
- local finalX = x + math.floor((w / 2) - (#text / 2))
- local finalY = y + math.floor(h / 2)
- GDI.drawText(finalX, finalY, theText, foreground, background)
- end,
- drawLineH = function(x, y, w, background)
- GDI.drawText(x, y, string.rep(" ", w), background, background)
- end,
- drawLineV = function(x, y, h, background)
- GDI.textColour(GDI.nonNullTextColour(background))
- GDI.backgroundColour(GDI.nonNullBackgroundColour(background))
- for i = y, h do
- GDI.Handle.write(" ")
- GDI.cpos(x, i)
- end
- end,
- drawSquare = function(x, y, w, h, background)
- local line = y
- local endLine = y + h
- while (true) do
- GDI.drawLineH(x, line, w, background)
- line = line + 1
- if (line >= endLine) then
- return
- end
- end
- end,
- drawProgressBar = function(x, y, w, h, min, max, val, foreground, background)
- GDI.drawSquare(x, y, w, h, background)
- GDI.drawSquare(x, y, math.floor((val / (max - min)) * w), h, foreground)
- end,
- drawGroupBoxHeader = function(x, y, w, text, foreground, background)
- local strLen = string.len(text)
- if (strLen >= w) then
- GDI.drawText(x, y, string.sub(text, 1, w), foreground, background)
- elseif (strLen == (w - 1)) then
- GDI.drawText(x, y, text .. "-", foreground, background)
- elseif (strLen == (w - 2)) then
- GDI.drawText(x, y, "-" .. text .. "-", foreground, background)
- elseif (strLen == (w - 3)) then
- GDI.drawText(x, y, "-" .. text .. " -", foreground, background)
- else
- local leftOverSpace = strLen - w
- local halfLeftOver = leftOverSpace / 2
- if (leftOverSpace % 2 == 0) then
- -- even
- local glyphText = string.rep("-", halfLeftOver - 1)
- GDI.drawText(x, y, glyphText .. " " .. text .. " " .. glyphText)
- else
- -- odd
- local glyphTextA = string.rep("-", math.floor(halfLeftOver - 1))
- local glyphTextB = string.rep("-", math.ceil(halfLeftOver - 1))
- GDI.drawText(x, y, glyphTextA .. " " .. text .. " " .. glyphTextB)
- end
- end
- end,
- drawGroupBoxWall = function(x, y, h, foreground, background)
- GDI.textColour(GDI.nonNullTextColour(foreground))
- GDI.backgroundColour(GDI.nonNullBackgroundColour(background))
- for i = y, h do
- GDI.Handle.write("-")
- GDI.cpos(x, i)
- end
- end,
- drawGroupBoxFooter = function(x, y, w, foreground, background)
- GDI.drawText(x, y, string.rep("-", w), foreground, background)
- end,
- drawGroupBox = function(x, y, w, h, header, border_foreground, border_background, background)
- GDI.drawSquare(x, y, w, h, background)
- GDI.drawGroupBoxHeader(x, y, w, header, border_foreground, border_background)
- GDI.drawGroupBoxWall(x, y, h, border_foreground, border_background)
- GDI.drawGroupBoxWall(x + w, y, h, border_foreground, border_background)
- GDI.drawGroupBoxFooter(x, y + h, w, border_foreground, border_background)
- end,
- drawWindowTitle = function(x, y, w, title, foreground, background)
- if (background == nil) then
- background = colours.grey
- end
- local text = string.clamp(title, w - 5)
- GDI.drawText(x, y, text, foreground, background)
- local len = #text
- local endX = x + w
- GDI.drawLineH(x + len, y, w - len - 4, background)
- GDI.drawText(endX - 4, y, "-", colours.foreground, colours.lightGrey)
- GDI.drawText(endX - 3, y, "[]", colours.foreground, colours.lightGrey)
- GDI.drawText(endX - 1, y, "x", colours.foreground, colours.red)
- end,
- drawWindow = function(x, y, w, h, title, titleForeground, titleBackground, background)
- GDI.drawSquare(x, y, w, h, utils.thatOrNull(background, colours.white))
- GDI.drawWindowTitle(x, y, w, title, utils.thatOrNull(titleForeground, colours.lightGrey), utils.thatOrNull(titleBackground, colours.grey))
- end
- }
- --endregion
- app = {}
- function app.IsPointInArea(absX, absY, minX, minY, width, height)
- return absX >= minX and absX < (minX + width) and absY >= minY and absY < (minY + height)
- end
- function app.GetAbsolutePosition(theComponent)
- local x = theComponent.x
- local y = theComponent.y
- local parent = theComponent.parent
- while (parent ~= nil) do
- x = x + parent.x
- y = y + parent.y
- parent = parent.parent
- end
- return x, y
- end
- function app.IsMouseOverComponent(theComponent, absX, absY)
- local cX, cY = app.GetAbsolutePosition(theComponent)
- return absX >= cX and absX < (cX + theComponent.w) and absY >= cY and absY < (cY + theComponent.h)
- end
- function app.GetRelativeMousePos(component, mPosX, mPosY)
- local absX, absY = app.GetAbsolutePosition(component)
- return (mPosX - absX), (mPosY - absY)
- end
- function app.SetupTimer(nTime)
- AnimTimerId = os.startTimer(nTime)
- end
- function app.UpdateCachedIndices(children)
- for k, obj in pairs(children) do
- obj.indexInParent = k
- end
- end
- function app.InsertComponent(parent, child)
- if (child.IsInVisualTree == true) then
- error("Child already added to visual tree. It must be removed first")
- return
- end
- local targetTable
- if (parent == nil) then
- targetTable = APP_ROOT_OBJECTS
- child.parent = nil
- else
- if (parent.children == nil) then
- parent.children = {}
- end
- targetTable = parent.children
- child.parent = parent
- end
- child.indexInParent = #table
- table.insert(targetTable, child)
- child.IsInVisualTree = true
- app.UpdateCachedIndices(targetTable)
- return child
- end
- function app.RemoveFromParent(child)
- if (child.IsInVisualTree ~= true or child.indexInParent == -1) then
- return
- end
- local children
- if (child.parent == nil) then
- children = APP_ROOT_OBJECTS
- else
- children = child.parent.children
- if (children == nil) then
- return
- end
- end
- table.remove(children, child.indexInParent)
- app.UpdateCachedIndices(children)
- end
- function app.RenderComponent(component, x, y)
- if (component.RenderFunc ~= nil) then
- component.RenderFunc(component, x, y)
- end
- if (component.children ~= nil) then
- for k, v in pairs(component.children) do
- app.RenderComponent(v, x + v.x, y + v.y)
- end
- end
- end
- -- function app.CleanChildrenTableForScheduledRemoval(theTable)
- -- for i = #theTable, 1, -1 do
- -- if (theTable[i].isScheduledForRemoval) then
- -- table.remove(theTable, i)
- -- end
- -- end
- -- end
- function app.DoAnimationTick(component, time, delta)
- component.IsUpdating = true
- if (component.OnAnimTick ~= nil) then
- component.OnAnimTick(component, time, delta)
- end
- local children = component.children
- if (children ~= nil) then
- for k, comp in pairs(children) do
- if (comp.IsInVisualTree == true) then
- app.DoAnimationTick(comp, time, delta)
- end
- end
- end
- component.IsUpdating = false
- end
- core_component_button = {}
- --#region Button Component
- function core_component_button.DoAnimTick(this, time, delta)
- if (this.IsButtonPressed and this.IsToggleButton == false) then
- if (this.TimeToClearButtonPress ~= nil and time > this.TimeToClearButtonPress) then
- this.IsButtonPressed = false
- end
- end
- end
- function core_component_button.CoreOnMouseDown(this, time, delta, hit, btn, x, y)
- if (this.IsToggleButton) then
- if (this.IsButtonPressed) then
- this.IsButtonPressed = false
- else
- this.IsButtonPressed = true
- end
- else
- this.TimeToClearButtonPress = time + 0.6
- this.IsButtonPressed = true
- end
- LastDebugMessage = "Hit at: " .. x .. "," .. y
- end
- function core_component_button.OnDrawButton(this, x, y)
- local tColour
- if (this.IsButtonPressed == true) then
- tColour = this.PressedColour
- else
- tColour = this.BackgroundColour
- end
- GDI.drawSquare(x, y, this.w, this.h, tColour)
- GDI.drawTextCenteredClamped(x, y, this.w, this.h, this.ButtonText, this.ForegroundColour, tColour)
- end
- --#endregion
- function app.Component_CreateCore(x, y, w, h)
- local obj = {}
- obj.x = x
- obj.y = y
- obj.w = w
- obj.h = h
- obj.OnPreviewMouseDown = {}
- obj.OnMouseDown = {}
- obj.OnPreviewMouseDrag = {}
- obj.OnMouseDrag = {}
- obj.OnPreviewKeyPressed = {}
- obj.OnKeyPressed = {}
- obj.OnPreviewCharPressed = {}
- obj.OnCharPressed = {}
- return obj
- end
- function app.CreateButton(x, y, w, h, text, isToggleButton, onClicked)
- local btn = app.Component_CreateCore(x, y, w, h)
- btn.ButtonText = text
- btn.OnAnimTick = core_component_button.DoAnimTick
- btn.RenderFunc = core_component_button.OnDrawButton
- table.insert(btn.OnMouseDown, core_component_button.CoreOnMouseDown)
- if onClicked ~= nil then
- table.insert(btn.OnMouseDown, onClicked)
- end
- btn.IsButtonPressed = false
- if (isToggleButton == nil) then
- isToggleButton = false
- end
- btn.IsToggleButton = isToggleButton
- btn.ForegroundColour = colours.white
- btn.BackgroundColour = colours.lightGrey
- btn.PressedColour = colours.green
- return btn
- end
- --region Application Entry Point and message handling
- function app.DoTunnelMouseClick(component, time, delta, btn, absX, absY)
- if (app.IsMouseOverComponent(component, absX, absY) ~= true) then
- return nil
- end
- if (component.OnPreviewMouseDown ~= nil) then
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- for i, handler in pairs(component.OnPreviewMouseDown) do
- if (handler(component, time, delta, btn, relX, relY)) then
- return component
- end
- end
- end
- local items = component.children
- if (items == nil) then
- return component
- end
- for i = #items, 1, -1 do
- local hitObj = app.DoTunnelMouseClick(items[i], time, delta, btn, absX, absY)
- if (hitObj ~= nil) then
- return hitObj
- end
- end
- return component
- end
- function app.DoBubbleMouseClick(component, originalComponent, time, delta, btn, absX, absY)
- if (component.OnMouseDown ~= nil) then
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- for i, handler in pairs(component.OnMouseDown) do
- if (handler(component, time, delta, originalComponent, btn, relX, relY)) then
- return true
- end
- end
- end
- if (component.parent == nil) then
- return false
- end
- return app.DoBubbleMouseClick(component.parent, originalComponent, time, delta, btn, absX, absY)
- end
- function app.OnMouseClick(time, delta, btn, absX, absY)
- local hitComponent = nil
- for i = #APP_ROOT_OBJECTS, 1, -1 do
- hitComponent = app.DoTunnelMouseClick(APP_ROOT_OBJECTS[i], time, delta, btn, absX, absY)
- if (hitComponent ~= nil) then
- break
- end
- end
- if (hitComponent ~= nil) then
- app.DoBubbleMouseClick(hitComponent, hitComponent, time, delta, btn, absX, absY)
- end
- end
- -- TODO: implement these
- function app.OnMouseDrag(time, delta, btn, absX, absY)
- end
- function app.OnKeyPress(time, delta, keyCode)
- end
- function app.OnCharPress(time, delta, ch)
- end
- -- Application Tick. This may be called extremely frequently during events
- -- such as mouse dragging, key events, etc. But this is also called during the
- -- application timer event, which is classed as a render tick
- --
- -- The time parameter is the os time (seconds since the computer started)
- --
- -- The delta parameter is the time since the last tick. Usually this is the
- -- tick rate interval, but it may be exactly 1 server tick if something like
- -- a mouse drag event came in
- function OnApplicationMessage(time, delta, eventType, p1, p2, p3, p4, p5)
- if (IS_DEBUG_MODE == true) then
- term.clear()
- term.setCursorPos(1, 1)
- term.write("System Timer: " .. time)
- term.setCursorPos(1, 2)
- term.write(tostring(eventType))
- term.setCursorPos(1, 3)
- term.write(tostring(p1))
- term.setCursorPos(1, 4)
- term.write(tostring(p2))
- term.setCursorPos(1, 5)
- term.write(tostring(p3))
- term.setCursorPos(1, 6)
- term.write(tostring(p4))
- term.setCursorPos(1, 7)
- term.write(tostring(p5))
- return
- end
- if (IsAnimationTick) then
- -- tick animations
- for k, obj in pairs(APP_ROOT_OBJECTS) do
- app.DoAnimationTick(obj, time, delta)
- end
- -- render application
- GDI.clearMonitor()
- for k, obj in pairs(APP_ROOT_OBJECTS) do
- app.RenderComponent(obj, obj.x, obj.y)
- end
- GDI.drawText(1, 19, LastDebugMessage)
- else
- if (eventType == "mouse_click") then
- app.OnMouseClick(time, delta, p1, p2, p3)
- elseif (eventType == "mouse_drag") then
- app.OnMouseDrag(time, delta, p1, p2, p3)
- elseif (eventType == "key") then
- app.OnKeyPress(time, delta, p1)
- elseif (eventType == "char") then
- app.OnCharPress(time, delta, p1)
- end
- end
- end
- function AppMain()
- GDI.clearMonitor()
- -- load GUI components onto screen
- app.InsertComponent(nil, app.CreateButton(1, 1, 16, 3, "Copy Template", false))
- app.InsertComponent(nil, app.CreateButton(18, 4, 16, 3, "Paste Template", false))
- -- Application event/message loop. no need to modify this!!!
- app.SetupTimer(AnimIntervalSecs)
- while true do
- local eType, p1, p2, p3, p4, p5 = os.pullEventRaw()
- local osTime = os.clock()
- if (eType == "terminate") then
- GDI.clearMonitor()
- GDI.drawText(1, 1, "Application Terminated", colours.white, colours.red)
- term.setCursorPos(1, 2)
- break
- end
- if (eType == "timer" and p1 == AnimTimerId) then
- app.SetupTimer(AnimIntervalSecs)
- IsAnimationTick = true
- end
- OnApplicationMessage(osTime, osTime - AppLastTickTime, eType, p1, p2, p3, p4, p5)
- AppLastTickTime = osTime
- IsAnimationTick = false
- end
- end
- --endregion
- AppMain()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement