Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Animation timer data
- AppTimerId = 0
- AppLastEventTime = 0
- -- The application animation timer tick rate
- AnimTimerTickRate = 10
- -- The interval of an application tick, in seconds
- AppTimerInterval = 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, }
- FOCUSED_COMPONENT = nil
- -- True when a component was focused during a mouse click bubble event
- HAS_SWITCHED_FOCUS_DURING_BUBBLE_CLICK = false
- 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, e) return false end, -- args = { btn, x, y }
- -- OnMouseDown = function(this, time, delta, e) return false end, -- args = { hit, btn, x, y }
- -- OnMouseDrag = function(this, time, delta, e) return false end, -- args = { btn, x, y }
- -- OnKeyPressed = function(this, time, delta, e) return false end, -- args = { key }
- -- OnCharPressed = function(this, time, delta, e) return false end, -- args = { ch }
- OnPreviewMouseDown = {},
- OnMouseDown = {},
- OnMouseDrag = {},
- OnKeyPressed = {},
- OnCharPressed = {},
- -- focus info
- IsFocusable = false,
- IsFocused = false,
- CanProcessDragWhenFocused = false,
- -- 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 = nil,
- HandleWidth = 0,
- HandleHeight = 0,
- -- 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,
- SetRenderTarget = function (target)
- HANDLE = target
- GDI.clearMonitor()
- local w,h = target.getSize()
- GDI.HandleWidth = w
- GDI.HandleHeight = h
- end
- }
- app = {}
- -- the base component object
- CComponent = {}
- function CComponent.new()
- end
- -- a button object
- CButton = {}
- CButton.__index = CButton
- setmetatable(CButton, CComponent)
- -- a text block object
- CTextBlock = {}
- setmetatable(CTextBlock, CComponent)
- -- a component that contains a collection of page objects,
- -- with a next/prev button to switch between pages
- CPageViewer = {}
- setmetatable(CPageViewer, CComponent)
- -- a page in a page viewer, which presents some content
- CPage = {}
- setmetatable(CPage, CComponent)
- --endregion
- function CComponent:GetAbsolutePosition()
- local nextComponent = self
- local x,y = 0,0
- while (nextComponent ~= nil) do
- x = x + nextComponent.x
- y = y + nextComponent.y
- nextComponent = nextComponent.parent
- end
- return x, y
- end
- function CComponent:IsMouseOverComponent(absX, absY)
- local cX, cY = app.GetAbsolutePosition(self)
- return absX >= cX and absX < (cX + self.w) and absY >= cY and absY < (cY + self.h)
- end
- function CComponent:GetRelativeMousePos(mPosX, mPosY)
- local absX, absY = app.GetAbsolutePosition(self)
- return (mPosX - absX), (mPosY - absY)
- end
- function app.SetupTimer(nTime)
- AppTimerId = os.startTimer(nTime)
- end
- function app.UpdateCachedIndices(children)
- for k, obj in pairs(children) do
- obj.indexInParent = k
- end
- end
- function CComponent:InsertComponent(child)
- if (child.IsInVisualTree == true) then
- error("Child already added to visual tree. It must be removed first")
- return
- end
- if (self.children == nil) then
- self.children = {}
- end
- child.parent = self
- child.indexInParent = #table
- table.insert(self.children, child)
- child.IsInVisualTree = true
- app.UpdateCachedIndices(self.children)
- return child
- end
- function CComponent:RemoveFromParent()
- if (self.IsInVisualTree ~= true or self.indexInParent == -1) then
- return
- end
- if (self.parent == nil) then
- return
- end
- local children = self.parent.children
- if (children == nil) then
- return
- end
- table.remove(children, self.indexInParent)
- app.UpdateCachedIndices(children)
- end
- function app.SwitchFocusTo(component)
- if (FOCUSED_COMPONENT ~= nil) then
- FOCUSED_COMPONENT.IsFocused = false
- end
- FOCUSED_COMPONENT = component
- if (component ~= nil) then
- component.IsFocused = true
- end
- end
- function app.RenderComponent(component, x, y)
- if (component.RenderFunc ~= nil) then
- component.RenderFunc(component, x, y)
- end
- if (component.children ~= nil) then
- app.RenderComponents(component.children, x, y)
- end
- end
- function app.RenderComponents(components, x, y)
- for k, v in pairs(components) do
- app.RenderComponent(v, x + v.x, y + v.y)
- 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
- --#region Button Component
- function CButton:DoAnimTick(self, time, delta)
- if (self.IsButtonPressed and self.IsToggleButton == false) then
- if (self.TimeToClearButtonPress ~= nil and time > self.TimeToClearButtonPress) then
- self.IsButtonPressed = false
- end
- end
- end
- function CButton:CoreOnMouseDown(self, time, delta, e)
- if (self.IsToggleButton) then
- if (self.IsButtonPressed) then
- self.IsButtonPressed = false
- else
- self.IsButtonPressed = true
- end
- else
- self.TimeToClearButtonPress = time + 0.5
- self.IsButtonPressed = true
- end
- LastDebugMessage = "Hit at: " .. e.x .. "," .. e.y
- end
- function CButton:OnDraw(self, x, y)
- local tColour
- if (self.IsButtonPressed == true) then
- tColour = self.PressedColour
- else
- tColour = self.BackgroundColour
- end
- GDI.drawSquare(x, y, self.w, self.h, tColour)
- GDI.drawTextCenteredClamped(x, y, self.w, self.h, self.ButtonText, self.ForegroundColour, tColour)
- end
- --#endregion
- --#region Button Component
- function CTextBlock:DoAnimTick(self, time, delta)
- end
- function CTextBlock:CoreOnMouseDown(self, time, delta, e)
- end
- function CTextBlock:OnDraw(self, x, y)
- GDI.drawTextCenteredClamped(x, y, self.w, self.h, self.Text, self.ForegroundColour, self.BackgroundColour)
- 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.OnMouseDrag = {}
- obj.OnKeyPressed = {}
- obj.OnCharPressed = {}
- return obj
- end
- function app.CreateTextBlock(x, y, w, h, text, fgColour, bgColour)
- local tb = app.Component_CreateCore(x, y, w, h)
- tb.Text = text
- tb.OnAnimTick = core_component_textBlock.DoAnimTick
- tb.RenderFunc = core_component_textBlock.OnDraw
- table.insert(tb.OnMouseDown, core_component_textBlock.CoreOnMouseDown)
- tb.ForegroundColour = fgColour
- tb.BackgroundColour = bgColour
- return tb
- 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 = CButton.DoAnimTick
- btn.RenderFunc = CButton.OnDraw
- table.insert(btn.OnMouseDown, CButton.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 and #component.OnPreviewMouseDown > 0) then
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- local args = {btn=btn,x=relX,y=relY}
- for i, handler in pairs(component.OnPreviewMouseDown) do
- if (handler(component, time, delta, args)) then
- return component
- end
- end
- end
- local items = component.children
- if (items ~= nil) then
- 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
- end
- return component
- end
- function app.DoBubbleMouseClick(component, originalComponent, time, delta, btn, absX, absY)
- if (component.OnMouseDown ~= nil and #component.OnMouseDown > 0) then
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- local args = {btn=btn,hit=originalComponent,x=relX,y=relY}
- for i, handler in pairs(component.OnMouseDown) do
- if (handler(component, time, delta, args)) then
- return
- end
- end
- end
- if (component.parent ~= nil) then
- app.DoBubbleMouseClick(component.parent, originalComponent, time, delta, btn, absX, absY)
- end
- 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
- -- just in case
- HAS_SWITCHED_FOCUS_DURING_BUBBLE_CLICK = true
- if (hitComponent ~= nil) then
- local finalComponent = app.DoBubbleMouseClick(hitComponent, hitComponent, time, delta, btn, absX, absY)
- if (finalComponent == nil) then
- if (hitComponent.IsFocusable == true) then
- app.SwitchFocusTo(hitComponent)
- else
- app.SwitchFocusTo(nil)
- end
- elseif (finalComponent.IsFocusable and finalComponent.IsFocused ~= true) then
- app.SwitchFocusTo(finalComponent)
- else
- app.SwitchFocusTo(nil)
- end
- else
- app.SwitchFocusTo(nil)
- end
- HAS_SWITCHED_FOCUS_DURING_BUBBLE_CLICK = false
- end
- -- TODO: implement these
- function app.OnMouseDrag(time, delta, btn, absX, absY)
- local component = FOCUSED_COMPONENT
- if (component == nil or component.CanProcessDragWhenFocused ~= true or #component.OnMouseDrag < 1) then
- return
- end
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- local args = {btn=btn,x=relX,y=relY}
- for i, handler in pairs(component.OnMouseDrag) do
- if (handler(component, time, delta, args)) then
- return
- end
- end
- end
- function app.OnKeyPress(time, delta, keyCode)
- local component = FOCUSED_COMPONENT
- if (component == nil or #component.OnKeyPressed < 1) then
- return
- end
- local relX, relY = app.GetRelativeMousePos(component, absX, absY)
- local args = {key=keyCode}
- for i, handler in pairs(component.OnKeyPressed) do
- if (handler(component, time, delta, args)) then
- return
- end
- end
- 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
- local function OnApplicationMessage(time, delta, eventType, p1, p2, p3, p4, p5)
- if (eventType == "timer" and p1 == AnimTimerId) then
- app.SetupTimer(AnimIntervalSecs)
- -- tick animations
- for k, obj in pairs(APP_ROOT_OBJECTS) do
- app.DoAnimationTick(obj, time, delta)
- end
- -- render application
- GDI.clearMonitor()
- app.RenderComponents(APP_ROOT_OBJECTS, 0, 0)
- 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
- local function AppMain()
- GDI.SetRenderTarget(term)
- -- 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))
- app.InsertComponent(nil, app.CreateTextBlock(1, 7, 16, 3, "eooeoe"))
- -- 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
- OnApplicationMessage(osTime, osTime - AppLastTickTime, eType, p1, p2, p3, p4, p5)
- AppLastEventTime = osTime
- IsAnimationTick = false
- end
- end
- --endregion
- AppMain()
- -- class MyButton {
- -- public:
- -- int id;
- -- char* text;
- --
- -- void setText(char* text) {
- -- this->text = text;
- -- if (text == nullptr) {
- -- this->id = -1;
- -- }
- -- }
- -- }
- --
- -- MyButton buttons[5];
- --
- -- void handleButton(MyButton* btn) {
- -- btn.id++;
- -- }
- --
- -- void main() {
- -- MyButton btn;
- -- btn.id = 24;
- -- handleButton(&btn);
- -- }
- --
- -- void onInputEvent(char* msg, void* p0, void* p1) {
- -- buttons[*(int*)p0].id = *(int*)p1
- -- buttons[*(int*)p0].setText((char*)"ok!")
- -- }
- -- local MyButton = {}
- -- MyButton.__index = MyButton
- --
- -- function MyButton.new()
- -- local self = setmetatable({}, MyButton)
- -- self.id = 0
- -- self.text = nil
- -- return self
- -- end
- --
- -- function MyButton:setText(text)
- -- self.text = text
- -- if text == nil then
- -- self.id = -1
- -- end
- -- end
- --
- -- local buttons = {}
- -- for i = 1, 5 do
- -- buttons[i] = MyButton.new()
- -- end
- --
- -- function handleButton(btn)
- -- btn.id = btn.id + 1
- -- end
- --
- -- local function onInputEvent(msg, p0, p1)
- -- buttons[tonumber(p0)].id = tonumber(p1)
- -- buttons[tonumber(p0)]:setText("ok!")
- -- end
- --
- -- local btn = MyButton.new()
- -- btn.id = 24
- -- handleButton(btn)
- --
- -- onInputEvent(nil, 1, 2)
Advertisement
Add Comment
Please, Sign In to add comment