Initial push

This commit is contained in:
Charles Click 2024-06-16 10:59:07 -04:00
commit 2c1de57645
Signed by: roryejinn
GPG Key ID: EDECB89DEDB998C0
19 changed files with 8637 additions and 0 deletions

10
mfile Normal file
View File

@ -0,0 +1,10 @@
{
"package": "Avalon",
"title": "Avalon UI",
"description": "An extensible template for Mudlet UIs",
"version": "0.1",
"author": "Charles Click",
"icon": "",
"dependencies": "",
"outputFile": false
}

0
readme.md Normal file
View File

View File

@ -0,0 +1,9 @@
[
{
"isActive": "yes",
"isFolder": "no",
"name": "lock UI",
"regex": "^klock$",
"script": ""
}
]

View File

@ -0,0 +1,186 @@
avalon = avalon or {
bIsSetup = false,
bEnableUserCss = false,
containers = {},
elements = {},
alignment = {}
}
function buildUI()
-- Setup Individual Displays
if not avalon.bIsSetup then
avalon.containers.top = Adjustable.Container:new({name = "avalonContainersTop", y="0%", height="10%", autoLoad=false})
avalon.containers.left = Adjustable.Container:new({name = "avalonContainersLeft", x="0%", y="0%", height="100%", width="20%", autoLoad=false})
avalon.containers.right = Adjustable.Container:new({name = "avalonContainersRight", x="-20%", y="0%", height="100%", width="20%", autoLoad=false})
avalon.containers.bottom = Adjustable.Container:new({name = "avalonContainersBottom", y="-10%", height="10%", autoLoad=false})
avalon.containers.top:attachToBorder("top")
avalon.containers.left:attachToBorder("left")
avalon.containers.right:attachToBorder("right")
avalon.containers.bottom:attachToBorder("bottom")
avalon.containers.top:connectToBorder("left")
avalon.containers.top:connectToBorder("right")
avalon.containers.bottom:connectToBorder("left")
avalon.containers.bottom:connectToBorder("right")
-- Setup Top Bar
-- The Top Bar is just a single hbox
avalon.alignment.topmain = Geyser.HBox:new(
{
name = "avalonAlignmentTopMain",
width = "100%",
height = "85%"
},
avalon.containers.top
)
-- Setup Bottom Bar
-- The Bottom Bar is a vbox that contains a top hbox that holds three gauges and a bottom hbox for whatever content you want
avalon.alignment.bottommain = Geyser.VBox:new(
{
name = "avalonAlignmentBottomMain",
width = "95%",
height = "100%"
},
avalon.containers.bottom
)
avalon.alignment.bottomgauges = Geyser.HBox:new(
{
name = "avalonAlignmentBottomgauges",
width = "100%",
height = "50%"
},
avalon.alignment.bottommain
)
avalon.alignment.bottominfo = Geyser.HBox:new(
{
name="avalonAlignmentBottominfo",
width = "100%",
height = "-50%"
},
avalon.alignment.bottommain
)
-- Setup Left Bar
-- The Left bar consists of a vbox with four internal vboxes for information display
-- These are named as:
-- leftmain - Primary vbox that the other vboxes attach to
-- lefttop - The top most vbox inside leftmain
-- leftmidtop - The first of two vboxes between the top and bottom. This is the one below lefttop
-- leftmidbottom - The second of two vboxes between the top and bottom. This is the one above leftbottom and below leftmidtop
-- leftbottom - The bottom most vbox inside leftmain
avalon.alignment.leftmain = Geyser.VBox:new(
{
name = "avalonAlignmentLeftmain",
width = "95%",
height = "100%"
},
avalon.containers.left
)
avalon.alignment.lefttop = Geyser.VBox:new(
{
name = "avalonAlignmentLefttop",
width = "100%",
height = "25%"
},
avalon.alignment.leftmain
)
avalon.alignment.leftmidtop = Geyser.VBox:new(
{
name = "avalonAlignmentLeftmidtop",
width = "100%",
height = "25%"
},
avalon.alignment.leftmain
)
avalon.alignment.leftmidbottom = Geyser.VBox:new(
{
name = "avalonAlignmentLeftmidbottom",
width = "100%",
height = "25%"
},
avalon.alignment.leftmain
)
avalon.alignment.leftbottom = Geyser.VBox:new(
{
name = "avalonAlignmentLeftbottom",
width = "100%",
height = "25%"
},
avalon.alignment.leftmain
)
-- Setup Right Bar
-- The Right Bar consists of vbox that contains a mapper in the top right and chat in the bottom right
-- The chat utilizes Demonnic's great ECMO https://github.com/demonnic/MDK
-- rightmain - Primary VB ox that other VBoxes attach to
-- rightmap - The Mapper
-- rightchat - The EMCO Chat window
avalon.alignment.rightmain = Geyser.VBox:new(
{
name = "avalonAlignmentRightmain",
width = "100%",
height = "100%"
},
avalon.containers.right
)
avalon.alignment.rightmap = Geyser.Container:new(
{
name = "avalonAlignmentRightmap",
width = "100%",
height = "60%"
},
avalon.alignment.rightmain
)
avalon.alignment.rightchat = Geyser.Container:new(
{
name = "avalonAlignmentRightchat",
width = "100%",
height = "40%"
},
avalon.alignment.rightmain
)
testLabel(avalon.alignment.topmain, "Top Main")
testLabel(avalon.alignment.bottomgauges, "Bottom Gauges")
testLabel(avalon.alignment.bottominfo, "Bottom Info")
testLabel(avalon.alignment.lefttop, "Left Top")
testLabel(avalon.alignment.leftmidtop, "Left Mid Top")
testLabel(avalon.alignment.leftmidbottom, "Left Mid Bottom")
testLabel(avalon.alignment.leftbottom, "Left Bottom")
testLabel(avalon.alignment.rightmap, "Right Map")
testLabel(avalon.alignment.rightchat, "Right Chat")
avalon.bIsSetup = true
end
end
function testLabel(container, text)
local newLabel = Geyser.Label:new(
{
name = text,
width = "100%",
height = "100%",
fgColor = "black",
message = "<center>" .. text .. "</center>"
},
container
)
newLabel:setColor(0,255,0,150)
table.insert(avalon.elements, newLabel)
end

View File

@ -0,0 +1 @@
-- End User CSS Functions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
--- Figlet
-- A module to read figlet fonts and produce figlet ascii art from text
-- @module figlet
-- @copyright 2010,2011 Nick Gammon
-- @copyright 2022 Damian Monogue
local Figlet = {}
--[[
Based on figlet.
FIGlet Copyright 1991, 1993, 1994 Glenn Chappell and Ian Chai
FIGlet Copyright 1996, 1997 John Cowan
Portions written by Paul Burton
Internet: <ianchai@usa.net>
FIGlet, along with the various FIGlet fonts and documentation, is
copyrighted under the provisions of the Artistic License (as listed
in the file "artistic.license" which is included in this package.
--]]
--[[
Latin-1 codes for German letters, respectively:
LATIN CAPITAL LETTER A WITH DIAERESIS = A-umlaut
LATIN CAPITAL LETTER O WITH DIAERESIS = O-umlaut
LATIN CAPITAL LETTER U WITH DIAERESIS = U-umlaut
LATIN SMALL LETTER A WITH DIAERESIS = a-umlaut
LATIN SMALL LETTER O WITH DIAERESIS = o-umlaut
LATIN SMALL LETTER U WITH DIAERESIS = u-umlaut
LATIN SMALL LETTER SHARP S = ess-zed
--]]
local deutsch = {196, 214, 220, 228, 246, 252, 223}
local fcharlist = {}
local magic, hardblank, charheight, maxlen, smush, cmtlines, ffright2left, smush2
local function readfontchar(fontfile, theord)
local t = {}
fcharlist[theord] = t
-- read each character line
--[[
eg.
__ __ @
| \/ |@
| \ / |@
| |\/| |@
| | | |@
|_| |_|@
@
@@
--]]
for i = 1, charheight do
local line = assert(fontfile:read("*l"), "Not enough character lines for character " .. theord)
local line = string.gsub(line, "%s+$", "") -- remove trailing spaces
assert(line ~= "", "Unexpected empty line")
-- find the last character (eg. @)
local endchar = line:sub(-1) -- last character
-- trim one or more of the last character from the end
while line:sub(-1) == endchar do
line = line:sub(1, #line - 1)
end -- while line ends with endchar
table.insert(t, line)
end -- for each line
end -- readfontchar
--- Reads a figlet font file (.flf) into memory and readies it for use by the next figlet
-- These files are cached in memory so that future calls to load a font just read from there.
-- @param filename the full path to the file to read the font from
function Figlet.readfont(filename)
local fontfile = assert(io.open(filename, "r"))
local s
fcharlist = {}
-- header line
s = assert(fontfile:read("*l"), "Empty FIGlet file")
-- eg. flf2a$ 8 6 59 15 10 0 24463 153
-- magic charheight maxlen smush cmtlines ffright2left smush2 ??
-- configuration line
magic, hardblank, charheight, maxlen, smush, cmtlines, ffright2left, smush2 = string.match(s,
"^(flf2).(.) (%d+) %d+ (%d+) (%-?%d+) (%d+) ?(%d*) ?(%d*) ?(%-?%d*)")
assert(magic, "Not a FIGlet 2 font file")
-- convert to numbers
charheight = tonumber(charheight)
maxlen = tonumber(maxlen)
smush = tonumber(smush)
cmtlines = tonumber(cmtlines)
-- sanity check
if charheight < 1 then
charheight = 1
end -- if
-- skip comment lines
for i = 1, cmtlines do
assert(fontfile:read("*l"), "Not enough comment lines")
end -- for
-- get characters space to tilde
for theord = string.byte(' '), string.byte('~') do
readfontchar(fontfile, theord)
end -- for
-- get 7 German characters
for theord = 1, 7 do
readfontchar(fontfile, deutsch[theord])
end -- for
-- get extra ones like:
-- 0x0395 GREEK CAPITAL LETTER EPSILON
-- 246 LATIN SMALL LETTER O WITH DIAERESIS
repeat
local extra = fontfile:read("*l")
if not extra then
break
end -- if eof
local negative, theord = string.match(extra, "^(%-?)0[xX](%x+)")
if theord then
theord = tonumber(theord, 16)
if negative == "-" then
theord = -theord
end -- if negative
else
theord = string.match(extra, "^%d+")
assert(theord, "Unexpected line:" .. extra)
theord = tonumber(theord)
end -- if
readfontchar(fontfile, theord)
until false
fontfile:close()
-- remove leading/trailing spaces
for k, v in pairs(fcharlist) do
-- first see if all lines have a leading space or a trailing space
local leading_space = true
local trailing_space = true
for _, line in ipairs(v) do
if line:sub(1, 1) ~= " " then
leading_space = false
end -- if
if line:sub(-1, -1) ~= " " then
trailing_space = false
end -- if
end -- for each line
-- now remove them if necessary
for i, line in ipairs(v) do
if leading_space then
v[i] = line:sub(2)
end -- removing leading space
if trailing_space then
v[i] = line:sub(1, -2)
end -- removing trailing space
end -- for each line
end -- for each character
end -- readfont
-- add one character to output lines
local function addchar(which, output, kern, smush)
local c = fcharlist[string.byte(which)]
if not c then
return
end -- if doesn't exist
for i = 1, charheight do
if smush and output[i] ~= "" and which ~= " " then
local lhc = output[i]:sub(-1)
local rhc = c[i]:sub(1, 1)
output[i] = output[i]:sub(1, -2) -- remove last character
if rhc ~= " " then
output[i] = output[i] .. rhc
else
output[i] = output[i] .. lhc
end
output[i] = output[i] .. c[i]:sub(2)
else
output[i] = output[i] .. c[i]
end -- if
if not (kern or smush) or which == " " then
output[i] = output[i] .. " "
end -- if
end -- for
end -- addchar
--- Returns a table of lines representing a string as figlet
-- @tparam string s the text to make into a figlet
-- @tparam boolean kern should we reduce spacing
-- @tparam boolean smush causes the letters to share edges, condensing it even further
function Figlet.ascii_art(s, kern, smush)
assert(fcharlist)
assert(charheight > 0)
-- make table of output lines
local output = {}
for i = 1, charheight do
output[i] = ""
end -- for
for i = 1, #s do
local c = s:sub(i, i)
if c >= " " and c < "\127" then
addchar(c, output, kern, smush)
end -- if in range
end -- for
-- fix up blank character so we can do a string.gsub on it
local fixedblank = string.gsub(hardblank, "[%%%]%^%-$().[*+?]", "%%%1")
for i, line in ipairs(output) do
output[i] = string.gsub(line, fixedblank, " ")
end -- for
return output
end -- function ascii_art
--- Returns the figlet as a string, rather than a table
-- @tparam string str the string the make into a figlet
-- @tparam boolean kern should we reduce the space between letters?
-- @tparam boolean smush should the letters share edges, further condensing the output?
-- @see ascii_art
function Figlet.getString(str, kern, smush)
local tbl = Figlet.ascii_art(str, kern, smush)
return table.concat(tbl, "\n")
end
--- Returns a figlet as a string, with kern set to true.
-- @tparam string str The string to turn into a figlet
-- @see getString
function Figlet.getKern(str)
return Figlet.getString(str, true)
end
--- Returns a figlet as a string, with smush set to true.
-- @tparam string str The string to turn into a figlet
-- @see getString
function Figlet.getSmush(str)
return Figlet.getString(str, true, true)
end
return Figlet

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,447 @@
local ftext = require("MDK.ftext")
describe("ftext:", function()
describe("ftext.fText:", function()
local fText = ftext.fText
it("Should properly center text", function()
local expected = " some text "
local actual = fText("some text", {width = 20})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should properly pad left aligned text", function()
local expected = "some text "
local actual = fText("some text", {width = 20, alignment = "left"})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should properly pad right aligned text", function()
local expected = " some text"
local actual = fText("some text", {width = 20, alignment = "right"})
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should wrap lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = fText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(line:len(), 10)
end
options.width = 15
actual = fText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(line:len(), 15)
end
end)
describe("non-space spacer character:", function()
local str = "some text"
local options = {width = "20", alignment = "left", spacer = "="}
it("Should work with left align", function()
local expected = "some text =========="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with right align", function()
local expected = "========== some text"
options.alignment = "right"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with center align", function()
local expected = ("==== some text =====")
options.alignment = "center"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
end)
describe("nogap option:", function()
local str = "some text"
local options = {width = "20", alignment = "left", spacer = "=", nogap = true}
it("Should work with left align", function()
local expected = "some text==========="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with right align", function()
local expected = "===========some text"
options.alignment = "right"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should work with center align", function()
local expected = "=====some text======"
options.alignment = "center"
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
end)
describe("cap functionality", function()
local str = "some text"
local options = {width = 20, spacer = "=", cap = "|"}
it("Should place the spacer outside the cap by default", function()
local expected = "===| some text |===="
local actual = fText(str, options)
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should place it inside the cap if inside option is true", function()
local expected = "|=== some text ====|"
options.inside = true
local actual = fText(str, options)
options.inside = nil
assert.equals(expected, actual)
assert.equals(20, actual:len())
end)
it("Should mirror certain characters with their opposites", function()
local expected = "===[ some text ]===="
options.mirror = true
options.cap = "["
local actual = fText(str, options)
assert.equals(expected, actual)
options.inside = true
expected = "[=== some text ====]"
actual = fText(str, options)
assert.equals(expected, actual)
options.inside = nil
options.cap = "<"
expected = "===< some text >===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "{"
expected = "==={ some text }===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "("
expected = "===( some text )===="
actual = fText(str, options)
assert.equals(expected, actual)
options.cap = "|"
expected = "===| some text |===="
actual = fText(str, options)
assert.equals(expected, actual)
end)
end)
end)
describe("ftext.cfText", function()
local cfText = ftext.cfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "<purple>",
spacerColor = "<green>",
textColor = "<red>",
}
it("Should handle cecho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "<purple>[<reset><green>===<reset><red> some text <reset><green>====<reset><purple>]<reset>"
local actual = cfText(str, options)
local actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "<green>===<reset><purple>[<reset><red> some text <reset><purple>]<reset><green>====<reset>"
options.inside = false
actual = cfText(str, options)
actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap cecho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = cfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(cecho2string(line):len(), 10)
end
options.width = 15
actual = cfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(cecho2string(line):len(), 15)
end
end)
end)
describe("ftext.dfText", function()
local dfText = ftext.dfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "<160,32,240>",
spacerColor = "<0,255,0>",
textColor = "<255,0,0>",
}
it("Should handle decho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "<160,32,240>[<r><0,255,0>===<r><255,0,0> some text <r><0,255,0>====<r><160,32,240>]<r>"
local actual = dfText(str, options)
local actualStripped = decho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "<0,255,0>===<r><160,32,240>[<r><255,0,0> some text <r><160,32,240>]<r><0,255,0>====<r>"
options.inside = false
actual = dfText(str, options)
actualStripped = decho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap decho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = dfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(decho2string(line):len(), 10)
end
options.width = 15
actual = dfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(decho2string(line):len(), 15)
end
end)
end)
describe("ftext.hfText", function()
local hfText = ftext.hfText
local str = "some text"
local options = {
width = 20,
spacer = "=",
cap = "[",
inside = true,
mirror = true,
capColor = "#a020f0",
spacerColor = "#00ff00",
textColor = "#ff0000",
}
it("Should handle hecho colored text", function()
local expectedStripped = "[=== some text ====]"
local expected = "#a020f0[#r#00ff00===#r#ff0000 some text #r#00ff00====#r#a020f0]#r"
local actual = hfText(str, options)
local actualStripped = hecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
expectedStripped = "===[ some text ]===="
expected = "#00ff00===#r#a020f0[#r#ff0000 some text #r#a020f0]#r#00ff00====#r"
options.inside = false
actual = hfText(str, options)
actualStripped = hecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should wrap hecho lines to the correct length", function()
local str = "This is a test of the emergency broadcast system. This is only a test"
local options = {width = 10, alignment = "centered"}
local actual = hfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(hecho2string(line):len(), 10)
end
options.width = 15
actual = hfText(str, options)
for _, line in ipairs(actual:split("\n")) do
assert.equals(hecho2string(line):len(), 15)
end
end)
end)
describe("ftext.TextFormatter", function()
local tf = ftext.TextFormatter
local str = "some text"
local formatter
before_each(function()
formatter = tf:new({width = 20})
end)
it("Should let you change width using :setWidth", function()
formatter:setWidth(80)
local expected =
"<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
assert.equals(80, cecho2string(actual):len())
end)
it("Should format for cecho by default", function()
local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local expectedStripped = " some text "
local actual = formatter:format(str)
local actualStripped = cecho2string(actual)
assert.equals(expected, actual)
assert.equals(expectedStripped, actualStripped)
assert.equals(20, actualStripped:len())
end)
it("Should produce the same line as cfText given the same options", function()
local expected = ftext.cfText(str, formatter.options)
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should let you change type using :setType", function()
formatter:setType("h")
local expected = ftext.hfText(str, formatter.options)
local actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setType("d")
expected = ftext.dfText(str, formatter.options)
actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setType("")
expected = ftext.fText(str, formatter.options)
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should default to word wrapping, and let you change it with :setWrap", function()
formatter:setWidth(10)
local expected =
"<white><reset><white> <reset><white> some <reset><white> <reset><white><reset>\n<white><reset><white> <reset><white> text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
expected = "<white><reset><white><reset><white> some text <reset><white><reset><white><reset>"
formatter:setWrap(false)
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the cap using :setCap", function()
formatter:setCap('|')
local expected = "<white>|<reset><white> <reset><white> some text <reset><white> <reset><white>|<reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the capColor using :setCapColor", function()
formatter:setCapColor('<red>')
local expected = "<red><reset><white> <reset><white> some text <reset><white> <reset><red><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the spacer color using :setSpacerColor", function()
formatter:setSpacerColor("<red>")
local expected = "<white><reset><red> <reset><white> some text <reset><red> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the text color using :setTextColor", function()
formatter:setTextColor("<red>")
local expected = "<white><reset><white> <reset><red> some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the spacer using :setSpacer", function()
formatter:setSpacer("=")
-- local expected = "<white><reset><white> <reset><white> some text <reset><white> <reset><white><reset>"
local expected = "<white><reset><white>====<reset><white> some text <reset><white>=====<reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to set the alignment using :setAlignment", function()
formatter:setAlignment("left")
local expected = "<white><reset><white><reset><white>some text <reset><white> <reset><white><reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
formatter:setAlignment("right")
expected = "<white><reset><white> <reset><white> some text<reset><white><reset><white><reset>"
actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the 'inside' option using :setInside", function()
formatter:setInside(false)
local expected = "<white> <reset><white><reset><white> some text <reset><white><reset><white> <reset>"
local actual = formatter:format(str)
assert.equals(expected, actual)
end)
it("Should allow you to change the mirror option using :setMirror", function()
formatter:setCap('<')
formatter:setMirror(true)
local expected = "<white><<reset><white> <reset><white> some text <reset><white> <reset><white>><reset>"
local actual = formatter:format(str)
assert.equal(expected, actual)
end)
end)
describe("ftext.TableMaker", function()
local TableMaker = ftext.TableMaker
local tm
before_each(function()
tm = TableMaker:new()
tm:addColumn({name = "col1", width = 15, textColor = "<red>"})
tm:addColumn({name = "col2", width = 15, textColor = "<blue>"})
tm:addColumn({name = "col3", width = 15, textColor = "<green>"})
tm:addRow({"some text", "more text", "other text"})
tm:addRow({"little text", "bigger text", "text"})
end)
it("Should assemble a formatted table given default options", function()
local expected = [[<white>*************************************************<reset>
<white>*<reset><white><reset><white> <reset><red> col1 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> col2 <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> col3 <reset><white> <reset><white><reset><white>*<reset>
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
<white>*<reset><white><reset><white> <reset><red> some text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> other text <reset><white> <reset><white><reset><white>*<reset>
<white>*<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>|<reset><white>---------------<reset><white>*<reset>
<white>*<reset><white><reset><white> <reset><red> little text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><blue> bigger text <reset><white> <reset><white><reset><white>|<reset><white><reset><white> <reset><green> text <reset><white> <reset><white><reset><white>*<reset>
<white>*************************************************<reset>
]]
local actual = tm:assemble()
assert.equals(expected, actual)
end)
it("TableMaker:getCell should return the text and formatter for a specific cell", function()
local expectedText = "more text"
local expectedFormatter = tm.columns[2]
local actualText, actualFormatter = tm:getCell(1, 2)
assert.equals(expectedText, actualText)
assert.equals(expectedFormatter, actualFormatter)
local expectedFormatted = "<white><reset><white> <reset><blue> more text <reset><white> <reset><white><reset>"
local actualFormatted = actualFormatter:format(actualText)
assert.equals(expectedFormatted, actualFormatted)
end)
end)
end)

View File

@ -0,0 +1,461 @@
--- MiniConsole with logging capabilities
-- @classmod LoggingConsole
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2020 Damian Monogue
-- @license MIT, see LICENSE.lua
local homedir = getMudletHomeDir():gsub("\\", "/")
local pathOfThisFile = (...):match("(.-)[^%.]+$")
local dt = require(pathOfThisFile .. "demontools")
local exists, htmlHeader, htmlHeaderPattern = dt.exists, dt.htmlHeader, dt.htmlHeaderPattern
local LoggingConsole = {log = true, logFormat = "h", path = "|h/log/consoleLogs/|y/|m/|d/", fileName = "|n.|e"}
--- Creates and returns a new LoggingConsole.
-- @param cons table of constraints. Includes all the valid Geyser.MiniConsole constraints, plus
-- <table class="tg">
-- <thead>
-- <tr>
-- <th>option name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- </thead>
-- <tbody>
-- <tr>
-- <td class="tg-1">log</td>
-- <td class="tg-1">Should the miniconsole be logging?</td>
-- <td class="tg-1">true</td>
-- </tr>
-- <tr>
-- <td class="tg-2">logFormat</td>
-- <td class="tg-2">"h" for html, "t" for plaintext, "l" for log (with ansi)</td>
-- <td class="tg-2">h</td>
-- </tr>
-- <tr>
-- <td class="tg-1">path</td>
-- <td class="tg-1">The path the file lives in. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)</td>
-- <td class="tg-1">"|h/log/consoleLogs/|y/|m/|d/"</td>
-- </tr>
-- <tr>
-- <td class="tg-2">fileName</td>
-- <td class="tg-2">The name of the log file. It is templated, same as path above</td>
-- <td class="tg-2">"|n.|e"</td>
-- </tr>
-- </tbody>
-- </table>
-- @param container the container for the console
-- @usage
-- local LoggingConsole = require("MDK.loggingconsole")
-- myLoggingConsole = LoggingConsole:new({
-- name = "my logging console",
-- x = 0,
-- y = 0,
-- height = 200,
-- width = 400,
-- }) -- just like making a miniconsole, really
function LoggingConsole:new(cons, container)
cons = cons or {}
local consType = type(cons)
assert(consType == "table", "LoggingConsole:new(cons, container): cons must be a valid table of constraints. Got: " .. consType)
local me = Geyser.MiniConsole:new(cons, container)
setmetatable(me, self)
self.__index = self
return me
end
--- Returns the file extension of the logfile this console will log to
function LoggingConsole:getExtension()
local extension = "log"
if table.contains({"h", "html"}, self.logFormat) then
extension = "html"
end
return extension
end
--- Returns a string with all templated items replaced
---@tparam string str The templated string to transform
---@local
function LoggingConsole:transformTemplate(str)
local ttbl = getTime()
local year = ttbl.year
local month = string.format("%02d", ttbl.month)
local day = string.format("%02d", ttbl.day)
local name = self.name
local extension = self:getExtension()
str = str:gsub("|h", homedir)
str = str:gsub("|y", year)
str = str:gsub("|m", month)
str = str:gsub("|d", day)
str = str:gsub("|n", name)
str = str:gsub("|e", extension)
return str
end
--- Returns the path to the logfile for this console
function LoggingConsole:getPath()
local path = self:transformTemplate(self.path)
if not path:ends("/") then
path = path .. "/"
end
return path
end
--- Sets the path to use for the log file.
-- @param path the path to put the log file in. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)
function LoggingConsole:setPath(path)
self.path = path
end
--- Returns the filename for the logfile for this console
function LoggingConsole:getFileName()
local fileName = self:transformTemplate(self.fileName)
fileName = fileName:gsub("[<>:'\"/\\?*]", "_")
return fileName
end
--- Sets the fileName to use for the log file.
-- @param fileName the fileName to use for the logfile. It is templated.<br>|h is replaced by the profile homedir.<br>|y by 4 digit year.<br>|m by 2 digit month<br>|d by 2 digit day<br>|n by the name constraint<br>|e by the file extension (html for h logType, log for others)
function LoggingConsole:setFileName(fileName)
self.fileName = fileName
end
--- Returns the pull path and filename for the logfile for this console
function LoggingConsole:getFullFilename()
local path = self:getPath()
local fileName = self:getFileName()
local fullPath = path .. fileName
fullPath = fullPath:gsub("|", "_")
return fullPath
end
--- Turns logging for this console on
function LoggingConsole:enableLogging()
self.log = true
end
--- Turns logging for this console off
function LoggingConsole:disableLogging()
self.log = false
end
--- Creates the path for the logfile for this console if necessary
---@local
function LoggingConsole:createPathIfNotExists()
local path = self:transformTemplate(self.path)
if not path:ends("/") then
path = path .. "/"
end
if not exists(path) then
local ok, err = dt.mkdir_p(path)
if not ok then
assert(false, "Could not create directory for log files:" .. path .. "\n Reason was: " .. err)
end
end
return true
end
--- Handles actually writing to the log file
---@local
function LoggingConsole:writeToLog(str)
local fileName = self:getFullFilename()
self:createPathIfNotExists()
if self:getExtension() == "html" then
if not io.exists(fileName) then
str = htmlHeader .. str
end
str = str
end
local file, err = io.open(fileName, "a")
if not file then
echo(err .. "\n")
return
end
file:write(str)
file:close()
end
local parent = Geyser.MiniConsole
--- Handler function which does the lifting for c/d/h/echo and appendBuffer to provide the logfile writing functionality
---@param str the string to echo. Use "" for appends
---@param etype the type of echo. Valid are "c", "d", "h", "e", and "a"
---@param log Allows you to override the default behaviour defined by the .log property. Pass true to definitely log, false to skip logging.
---@local
function LoggingConsole:xEcho(str, etype, log)
if log == nil then
log = self.log
end
local logStr
local logType = self.logFormat
if logType:find("h") then
logType = "h"
elseif logType ~= "t" then
logType = "l"
end
if etype == "d" then -- decho
if logType == "h" then
logStr = dt.decho2html(str)
elseif logType == "t" then
logStr = dt.decho2string(str)
else
logStr = dt.decho2ansi(str)
end
parent.decho(self, str)
elseif etype == "c" then -- cecho
if logType == "h" then
logStr = dt.cecho2html(str)
elseif logType == "t" then
logStr = dt.cecho2string(str)
else
logStr = dt.cecho2ansi(str)
end
parent.cecho(self, str)
elseif etype == "h" then -- hecho
if logType == "h" then
logStr = dt.hecho2html(str)
elseif logType == "t" then
logStr = dt.hecho2string(str)
else
logStr = dt.hecho2ansi(str)
end
parent.hecho(self, str)
elseif etype == "a" then -- append
str = dt.append2decho()
str = str .. "\n"
if logType == "h" then
logStr = dt.decho2html(str)
elseif logType == "t" then
logStr = dt.decho2string(str)
else
logStr = dt.decho2ansi(str)
end
parent.appendBuffer(self)
elseif etype == "e" then -- echo
if logType == "h" then
logStr = dt.decho2html(str)
else
logStr = str
end
parent.echo(self, str)
end
if log then
self:writeToLog(logStr)
end
end
--- Does the actual lifting of echoing links/popups
-- @local
function LoggingConsole:xEchoLink(text, lType, command, hint, useFormat, log)
if log == nil then
log = self.log
end
local logStr = ""
if lType:starts("c") then
if self.logFormat == "h" then
logStr = dt.cecho2html(text)
elseif self.logFormat == "l" then
logStr = dt.cecho2ansi(text)
elseif self.logFormat == "t" then
logStr = dt.cecho2string(text)
end
if lType:ends("p") then
parent.cechoPopup(self, text, command, hint, useFormat)
else
parent.cechoLink(self, text, command, hint, useFormat)
end
elseif lType:starts("d") then
if self.logFormat == "h" then
logStr = dt.decho2html(text)
elseif self.logFormat == "l" then
logStr = dt.decho2ansi(text)
elseif self.logFormat == "t" then
logStr = dt.decho2string(text)
end
if lType:ends("p") then
parent.dechoPopup(self, text, command, hint, useFormat)
else
parent.dechoLink(self, text, command, hint, useFormat)
end
elseif lType:starts("h") then
if self.logFormat == "h" then
logStr = dt.hecho2html(text)
elseif self.logFormat == "l" then
logStr = dt.hecho2ansi(text)
elseif self.logFormat == "t" then
logStr = dt.hecho2string(text)
end
if lType:ends("p") then
parent.hechoPopup(self, text, command, hint, useFormat)
else
parent.hechoLink(self, text, command, hint, useFormat)
end
elseif lType:starts("e") then
logStr = text
if lType:ends("p") then
parent.echoPopup(self, text, command, hint, useFormat)
else
parent.echoLink(self, text, command, hint, useFormat)
end
end
if log then
self:writeToLog(logStr)
end
end
--- cechoLink for LoggingConsole
-- @param text the text to use for the link
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
-- @param hint A tooltip which is displayed when the mouse is over the link
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:cechoLink(text, command, hint, log)
self:xEchoLink(text, "c", command, hint, true, log)
end
--- dechoLink for LoggingConsole
-- @param text the text to use for the link
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
-- @param hint A tooltip which is displayed when the mouse is over the link
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:dechoLink(text, command, hint, log)
self:xEchoLink(text, "d", command, hint, true, log)
end
--- hechoLink for LoggingConsole
-- @param text the text to use for the link
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
-- @param hint A tooltip which is displayed when the mouse is over the link
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:hechoLink(text, command, hint, log)
self:xEchoLink(text, "h", command, hint, true, log)
end
--- echoLink for LoggingConsole
-- @param text the text to use for the link
-- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]]
-- @param hint A tooltip which is displayed when the mouse is over the link
-- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline.
-- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep") -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log)
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", false, false) -- same as above, but forces it not to log regardless of self.log setting
-- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console.
function LoggingConsole:echoLink(text, command, hint, useCurrentFormat, log)
self:xEchoLink(text, "e", command, hint, useCurrentFormat, log)
end
--- cechoPopup for LoggingConsole
-- @param text the text to use for the link
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:cechoPopup(text, commands, hints, log)
self:xEchoLink(text, "cp", commands, hints, true, log)
end
--- dechoPopup for LoggingConsole
-- @param text the text to use for the link
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:dechoPopup(text, commands, hints, log)
self:xEchoLink(text, "dp", commands, hints, true, log)
end
--- hechoPopup for LoggingConsole
-- @param text the text to use for the link
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
-- @param log Should we log this line? Defaults to self.log if not passed.
function LoggingConsole:hechoPopup(text, commands, hints, log)
self:xEchoLink(text, "hp", commands, hints, true, log)
end
--- echoPopup for LoggingConsole
-- @param text the text to use for the link
-- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]}
-- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}}
-- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline.
-- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}) -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log)
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, false, false) -- same as above, but forces it not to log regardless of self.log setting
-- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console.
function LoggingConsole:echoPopup(text, commands, hints, useCurrentFormat, log)
self:xEchoLink(text, "ep", commands, hints, useCurrentFormat, log)
end
--- Append copy()ed text to the console
-- @param log should we log this?
function LoggingConsole:appendBuffer(log)
self:xEcho("", "a", log)
end
--- Append copy()ed text to the console
-- @param log should we log this?
function LoggingConsole:append(log)
self:xEcho("", "a", log)
end
--- echo's a string to the console.
-- @param str the string to echo
-- @param log should this be logged? Used to override the .log constraint
function LoggingConsole:echo(str, log)
self:xEcho(str, "e", log)
end
--- hecho's a string to the console.
-- @param str the string to hecho
-- @param log should this be logged? Used to override the .log constraint
function LoggingConsole:hecho(str, log)
self:xEcho(str, "h", log)
end
--- decho's a string to the console.
-- @param str the string to decho
-- @param log should this be logged? Used to override the .log constraint
function LoggingConsole:decho(str, log)
self:xEcho(str, "d", log)
end
--- cecho's a string to the console.
-- @param str the string to cecho
-- @param log should this be logged? Used to override the .log constraint
function LoggingConsole:cecho(str, log)
self:xEcho(str, "c", log)
end
--- Replays the last X lines from the console's log file, if it exists
-- @param numberOfLines The number of lines to replay from the end of the file
function LoggingConsole:replay(numberOfLines)
local fileName = self:getFullFilename()
if not exists(fileName) then
return
end
local file = io.open(fileName, "r")
local lines = file:read("*a")
if self:getExtension() == "html" then
for _, line in ipairs(htmlHeaderPattern:split("\n")) do
if line ~= "" then
lines = lines:gsub(line .. "\n", "")
end
end
lines = dt.html2decho(lines)
else
lines = ansi2decho(lines)
end
local linesTbl = lines:split("\n")
local result
if #linesTbl <= numberOfLines then
result = lines
else
result = ""
local start = #linesTbl - numberOfLines
for index, str in ipairs(linesTbl) do
if index >= start then
result = string.format("%s\n%s", result, str)
end
end
end
self:decho(result, false)
end
setmetatable(LoggingConsole, parent)
return LoggingConsole

View File

@ -0,0 +1,456 @@
--- Loginator creates an object which allows you to log things to file at
-- various severity levels, with the ability to only log items above a specific
-- severity to file.
-- @classmod Loginator
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2021 Damian Monogue
-- @license MIT, see LICENSE.lua
local Loginator = {
format = "h",
name = "logname",
fileNameTemplate = "|p/log/Loginator/|y-|M-|d-|n.|e",
entryTemplate = "|y-|M-|d |h:|m:|s.|x [|c|l|r] |t",
level = "warn",
bgColor = "black",
fontSize = 12,
fgColor = "white",
}
local levelColors = {error = "red", warn = "DarkOrange", info = "ForestGreen", debug = "ansi_yellow"}
local loggerLevels = {error = 1, warn = 2, info = 3, debug = 4}
local function exists(path)
local ok, err, code = os.rename(path, path)
if not ok and code == 13 then
return true
end
return ok, err
end
local function isWindows()
return package.config:sub(1, 1) == [[\]]
end
local function mkdir_p(path)
path = path:gsub("\\", "/")
local pathTbl = path:split("/")
local cwd = "/"
if isWindows() then
cwd = ""
end
for index, dirName in ipairs(pathTbl) do
if index == 1 then
cwd = cwd .. dirName
else
cwd = cwd .. "/" .. dirName
cwd = cwd:gsub("//", "/")
end
if not table.contains({"/", "C:"}, cwd) and not exists(cwd) then
local ok, err = lfs.mkdir(cwd)
if not ok then
return ok, err
end
end
end
return true
end
local htmlHeaderTemplate = [=[ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
<style type="text/css">
body {
background-color: |b;
color: |c;
font-family: 'Droid Sans Mono';
white-space: pre;
font-size: |fpx;
}
</style>
</head>
<body><span>
]=]
--- Creates a new Loginator object
--@tparam table options table of options for the logger
-- <table class="tg">
-- <thead>
-- <tr>
-- <th>option name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- </thead>
-- <tbody>
-- <tr>
-- <td class="tg-1">format</td>
-- <td class="tg-1">What format to log in? "h" for html, "a" for ansi, anything else for plaintext.</td>
-- <td class="tg-1">"h"</td>
-- </tr>
-- <tr>
-- <td class="tg-2">name</td>
-- <td class="tg-2">What is the name of the logger? Will replace |n in templates</td>
-- <td class="tg-2">logname</td>
-- </tr>
-- <tr>
-- <td class="tg-1">level</td>
-- <td class="tg-1">What level should the logger operate at? This will control what level the log function defaults to, as well as what logs will actually be written<br>
-- Only items of an equal or higher severity to this will be written to the log file.</td>
-- <td class="tg-1">"info"</td>
-- </tr>
-- <tr>
-- <td class="tg-2">bgColor</td>
-- <td class="tg-2">What background color to use for html logs</td>
-- <td class="tg-2">"black"</td>
-- </tr>
-- <tr>
-- <td class="tg-1">fgColor</td>
-- <td class="tg-1">What color to use for the main text in html logs</td>
-- <td class="tg-1">"white"</td>
-- </tr>
-- <tr>
-- <td class="tg-2">fontSize</td>
-- <td class="tg-2">What font size to use in html logs</td>
-- <td class="tg-2">12</td>
-- </tr>
-- <tr>
-- <td class="tg-1">levelColors</td>
-- <td class="tg-1">Table with the log level as the key, and the color which corresponds to it as the value</td>
-- <td class="tg-1">{ error = "red", warn = "DarkOrange", info = "ForestGreen", debug = "ansi_yellow" }</td>
-- </tr>
-- <tr>
-- <td class="tg-2">fileNameTemplate</td>
-- <td class="tg-2">A template which will be transformed into the full filename, with path. See template options below for replacements</td>
-- <td class="tg-2">"|p/log/Loginator/|y-|M-|d-|n.|e"</td>
-- </tr>
-- <tr>
-- <td class="tg-1">entryTemplate</td>
-- <td class="tg-1">The template which controls the look of each log entry. See template options below for replacements</td>
-- <td class="tg-1">"|y-|M-|d |h:|m:|s.|x [|c|l|r] |t"</td>
-- </tr>
-- </tbody>
-- </table><br>
-- Table of template options
-- <table class="tg">
-- <thead>
-- <tr>
-- <th>template code</th>
-- <th>what it is replaced with</th>
-- <th>example</th>
-- </tr>
-- </thead>
-- <tbody>
-- <tr>
-- <td class="tg-1">|y</td>
-- <td class="tg-1">the year in 4 digits</td>
-- <td class="tg-1">2021</td>
-- </tr>
-- <tr>
-- <td class="tg-2">|p</td>
-- <td class="tg-2">getMudletHomeDir()</td>
-- <td class="tg-2">/home/demonnic/.config/mudlet/profiles/testprofile</td>
-- </tr>
-- <tr>
-- <td class="tg-1">|M</td>
-- <td class="tg-1">Month as 2 digits</td>
-- <td class="tg-1">05</td>
-- </tr>
-- <tr>
-- <td class="tg-2">|d</td>
-- <td class="tg-2">day, as 2 digits</td>
-- <td class="tg-2">23</td>
-- </tr>
-- <tr>
-- <td class="tg-1">|h</td>
-- <td class="tg-1">hour in 24hr time format, 2 digits</td>
-- <td class="tg-1">03</td>
-- </tr>
-- <tr>
-- <td class="tg-2">|m</td>
-- <td class="tg-2">minute as 2 digits</td>
-- <td class="tg-2">42</td>
-- </tr>
-- <tr>
-- <td class="tg-1">|s</td>
-- <td class="tg-1">seconds as 2 digits</td>
-- <td class="tg-1">34</td>
-- </tr>
-- <tr>
-- <td class="tg-2">|x</td>
-- <td class="tg-2">milliseconds as 3 digits</td>
-- <td class="tg-2">194</td>
-- </tr>
-- <tr>
-- <td class="tg-1">|e</td>
-- <td class="tg-1">Filename extension expected. "html" for html format, "log" for everything else</td>
-- <td class="tg-1">html</td>
-- </tr>
-- <tr>
-- <td class="tg-2">|l</td>
-- <td class="tg-2">The logging level of the entry, in ALLCAPS</td>
-- <td class="tg-2">WARN</td>
-- </tr>
-- <tr>
-- <td class="tg-1">|c</td>
-- <td class="tg-1">The color which corresponds with the logging level. Set via the levelColors table in the options. Example not included.</td>
-- <td class="tg-1"></td>
-- </tr>
-- <tr>
-- <td class="tg-2">|r</td>
-- <td class="tg-2">Reset back to standard color. Used to close |c. Example not included</td>
-- <td class="tg-2"></td>
-- </tr>
-- <tr>
-- <td class="tg-1">|n</td>
-- <td class="tg-1">The name of the logger, set via the options when you have Loginator create it.</td>
-- <td class="tg-1">CoolPackageLog</td>
-- </tr>
--</tbody>
--</table>
--@return newly created logger object
function Loginator:new(options)
options = options or {}
local optionsType = type(options)
if optionsType ~= "table" then
return nil, f "Loginator:new(options) options as table expected, got {optionsType}"
end
local me = table.deepcopy(options)
me.levelColors = me.levelColors or {}
local lcType = type(me.levelColors)
if lcType ~= "table" then
return nil, f "Loginator:new(options) provided options.levelColors must be a table, but you provided a {lcType}"
end
for lvl,clr in pairs(levelColors) do
me.levelColors[lvl] = me.levelColors[lvl] or clr
end
setmetatable(me, self)
self.__index = self
return me
end
---@local
function Loginator:processTemplate(str, level)
local lvl = level or self.level
local timeTable = getTime()
for what, with in pairs({
["|y"] = function()
return timeTable.year
end,
["|p"] = getMudletHomeDir,
["|M"] = function()
return string.format("%02d", timeTable.month)
end,
["|d"] = function()
return string.format("%02d", timeTable.day)
end,
["|h"] = function()
return string.format("%02d", timeTable.hour)
end,
["|m"] = function()
return string.format("%02d", timeTable.min)
end,
["|s"] = function()
return string.format("%02d", timeTable.sec)
end,
["|x"] = function()
return string.format("%03d", timeTable.msec)
end,
["|e"] = function()
return (self.format:starts("h") and "html" or "log")
end,
["|l"] = function()
return lvl:upper()
end,
["|c"] = function()
return self:getColor(lvl)
end,
["|r"] = function()
return self:getReset()
end,
["|n"] = function()
return self.name
end,
}) do
if str:find(what) then
str = str:gsub(what, with())
end
end
return str
end
--- Set the color to associate with a logging level post-creation
--@param color The color to set for the level, as a string. Can be any valid color string for cecho, decho, or hecho.
--@param level The level to set the color for. Must be one of 'error', 'warn', 'info', or 'debug'
--@return true if the color is updated, or nil+error if it could not be updated for some reason.
function Loginator:setColorForLevel(color, level)
if not color then
return nil, "You must provide a color to set"
end
if not level then
return nil, "You must provide a level to set the color for"
end
if not loggerLevels[level] then
return nil, "Invalid level. Valid levels are 'error', 'warn', 'info', or 'debug'"
end
if not Geyser.Color.parse(color) then
return nil, "You must provide a color which can be parsed by Geyser.Color.parse. Examples are 'blue' (cecho), '<128,0,0>' (decho), '#aa3388' (hecho), or {128,0,0} (table of r,g,b values)"
end
self.levelColors[level] = color
return true
end
---@local
function Loginator:getColor(level)
if self.format == "t" then
return ""
end
local r, g, b = Geyser.Color.parse((self.levelColors[level] or {128, 128, 128}))
if self.format == "h" then
return string.format("<span style='color: rgb(%d,%d,%d);'>", r, g, b)
elseif self.format == "a" then
return string.format("\27[38:2::%d:%d:%dm", r, g, b)
end
return ""
end
---@local
function Loginator:getReset()
if self.format == "t" then
return ""
elseif self.format == "h" then
return "</span>"
elseif self.format == "a" then
return "\27[39;49m"
end
return ""
end
--- Returns the full path and filename to the logfile
function Loginator:getFullFilename()
return self:processTemplate(self.fileNameTemplate)
end
--- Write an error level message to the logfile. Error level messages are always written.
--@param msg the message to log
--@return true if msg written, nil+error if error
function Loginator:error(msg)
return self:log(msg, "error")
end
--- Write a warn level message to the logfile.
-- Msg is only written if the logger level is <= warn
-- From most to least severe the levels are:
-- error > warn > info > debug
--@param msg the message to log
--@return true if msg written, false if skipped due to level, nil+error if error
function Loginator:warn(msg)
return self:log(msg, "warn")
end
--- Write an info level message to the logfile.
-- Msg is only written if the logger level is <= info
-- From most to least severe the levels are:
-- error > warn > info > debug
--@param msg the message to log
--@return true if msg written, false if skipped due to level, nil+error if error
function Loginator:info(msg)
return self:log(msg, "info")
end
--- Write a debug level message to the logfile.
-- Msg is only written if the logger level is debug
-- From most to least severe the levels are:
-- error > warn > info > debug
--@param msg the message to log
--@return true if msg written, false if skipped due to level, nil+error if error
function Loginator:debug(msg)
return self:log(msg, "debug")
end
--- Write a message to the log file and optionally specify the level
--@param msg the message to log
--@param level the level to log the message at. Defaults to the level of the logger itself if not provided.
--@return true if msg written, false if skipped due to level, nil+error if error
function Loginator:log(msg, level)
level = level or self.level
local levelNumber = loggerLevels[level]
if not levelNumber then
return nil, f"Unknown logging level: {level}. Valid levels are 'error', 'warn', 'info', and 'debug'"
end
local displayLevelNumber = loggerLevels[self.level]
if levelNumber > displayLevelNumber then
return false
end
local filename = self:getFullFilename()
local filteredMsg = self:processTemplate(self.entryTemplate, level):gsub("|t", msg)
local ok, err = self:createPathIfNotExists(filename)
if err then
debugc(err)
return ok, err
end
if self.format == "h" and not io.exists(filename) then
filteredMsg = self:getHtmlHeader() .. filteredMsg
end
local file, err = io.open(filename, "a")
if not file then
err = string.format("Logger %s failed to open %s because: %s\n", self.name, filename, err)
debugc(err)
return nil, err
end
file:write(filteredMsg .. "\n")
file:close()
return true
end
--- Uses openUrl() to request your OS open the logfile in the appropriate application. Usually your web browser for html and text editor for all others.
function Loginator:open()
openUrl(self:getFullFilename())
end
--- Uses openUrl() to request your OS open the directory the logfile resides in. This allows for easier browsing if you have more than one file.
function Loginator:openDir()
openUrl(self:getPath())
end
--- Returns the path to the log file (directory in which the file resides) as a string
--@param filename optional filename to return the path of. If not supplied, with use the logger's current filename
function Loginator:getPath(filename)
filename = filename or self:getFullFilename()
filename = filename:gsub([[\]], "/")
local filenameTable = filename:split("/")
filenameTable[#filenameTable] = nil
local path = table.concat(filenameTable, "/")
return path
end
---@local
function Loginator:createPathIfNotExists(filename)
if exists(filename) then
return false
end
filename = filename:gsub([[\]], "/")
local path = self:getPath(filename)
if exists(path) then
return false
end
local ok, err = mkdir_p(path)
if not ok then
err = string.format("Could not create directory for log files: %s\n Reason was: %s", path, err)
return nil, err
end
return true
end
---@local
function Loginator:getHtmlHeader()
local header = htmlHeaderTemplate
header = header:gsub("|b", self.bgColor)
header = header:gsub("|c", self.fgColor)
header = header:gsub("|f", self.fontSize)
return header
end
return Loginator

View File

@ -0,0 +1,255 @@
--- Self Updating Gauge, extends <a href="https://www.mudlet.org/geyser/files/geyser/GeyserGauge.html">Geyser.Gauge</a>
-- @classmod SUG
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2020 Damian Monogue
-- @license MIT, see LICENSE.lua
local SUG = {
name = "SelfUpdatingGaugeClass",
active = true,
updateTime = 333,
currentVariable = "",
maxVariable = "",
defaultCurrent = 50,
defaultMax = 100,
textTemplate = " |c/|m |p%",
strict = true,
}
-- Internal function, used to turn a string variable name into a value
local function getValueAt(accessString)
local ok, err = pcall(loadstring("return " .. tostring(accessString)))
if ok then return err end
return nil, err
end
-- ========== End section copied from demontools.lua
--- Creates a new Self Updating Gauge.
-- @tparam table cons table of options which control the Gauge's behaviour. In addition to all valid contraints for Geyser.Gauge, SUG adds:
-- <br>
-- <table class="tg">
-- <tr>
-- <th>name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- <tr>
-- <td class="tg-1">active</td>
-- <td class="tg-1">boolean, if true starts the timer updating</td>
-- <td class="tg-1">true</td>
-- </tr>
-- <tr>
-- <td class="tg-2">updateTime</td>
-- <td class="tg-2">How often should the gauge autoupdate? Milliseconds. 0 to disable the timer but still allow event updates</td>
-- <td class="tg-2">333</td>
-- </tr>
-- <tr>
-- <td class="tg-1">currentVariable</td>
-- <td class="tg-1">What variable will hold the 'current' value of the gauge? Pass the name as a string, IE "currentHP" or "gmcp.Char.Vitals.hp"</td>
-- <td class="tg-1">""</td>
-- </tr>
-- <tr>
-- <td class="tg-2">maxVariable</td>
-- <td class="tg-2">What variable will hold the 'current' value of the gauge? Pass the name as a string, IE "maxHP" or "gmcp.Char.Vitals.maxhp"</td>
-- <td class="tg-2">""</td>
-- </tr>
-- <tr>
-- <td class="tg-1">textTemplate</td>
-- <td class="tg-1">Template to use for the text on the gauge. "|c" replaced with current value, "|m" replaced with max value, "|p" replaced with the % full the gauge should be</td>
-- <td class="tg-1">" |c/|m |p%"</td>
-- </tr>
-- <tr>
-- <td class="tg-2">defaultCurrent</td>
-- <td class="tg-2">What value to use if the currentVariable points to nil or something which cannot be made a number?</td>
-- <td class="tg-2">50</td>
-- </tr>
-- <tr>
-- <td class="tg-1">defaultMax</td>
-- <td class="tg-1">What value to use if the maxVariable points to nil or something which cannot be made a number?</td>
-- <td class="tg-1">100</td>
-- </tr>
-- <tr>
-- <td class="tg-2">updateEvent</td>
-- <td class="tg-2">The name of an event to listen for to perform an update. Can be run alongside or instead of the timer updates. Empty string to turn off</td>
-- <td class="tg-2">""</td>
-- </tr>
-- <tr>
-- <td class="tg-1">updateHook</td>
-- <td class="tg-1">A function which is run each time the gauge updates. Should take 3 arguments, the gauge itself, current value, and max value. You can return new current and max values to be used, for example `return 34, 120` would cause the gauge to use 34 for current and 120 for max regardless of what the variables it reads say.</td>
-- <td class="tg-1"></td>
-- </tr>
-- </table>
-- @param container The Geyser container for this gauge
-- @usage
-- local SUG = require("MDK.sug") --the following will watch "gmcp.Char.Vitals.hp" and "gmcp.Char.Vitals.maxhp" and update itself every 333 milliseconds
-- myGauge = SUG:new({
-- name = "myGauge",
-- currentVariable = "gmcp.Char.Vitals.hp", --if this is nil, it will use the defaultCurrent of 50
-- maxVariable = "gmcp.Char.Vitals.maxhp", --if this is nil, it will use the defaultMax of 100.
-- height = 50,
-- })
function SUG:new(cons, container)
local funcName = "SUG:new(cons, container)"
cons = cons or {}
local consType = type(cons)
assert(consType == "table", string.format("%s: cons as table expected, got %s", funcName, consType))
local me = SUG.parent:new(cons, container)
setmetatable(me, self)
self.__index = self
-- apply any styling requested
if me.cssFront then
if not me.cssBack then
me.cssBack = me.cssFront .. "background-color: black;"
end
me:setStyleSheet(me.cssFront, me.cssBack, me.cssText)
end
if me.active then
me:start()
end
me:update()
return me
end
--- Set how often to update the gauge on a timer
-- @tparam number time time in milliseconds. 0 to disable the timer
function SUG:setUpdateTime(time)
if type(time) ~= "number" then
debugc("SUG:setUpdateTime(time) time as number expected, got " .. type(time))
return
end
self.updateTime = time
if self.active then self:start() end
end
--- Set the event to listen for to update the gauge
-- @tparam string event the name of the event to listen for, use "" to disable events without stopping any existing timers
function SUG:setUpdateEvent(event)
if type(event) ~= string then
debugc("SUG:setUpdateEvent(event) event name as string expected, got " .. type(event))
return
end
self.updateEvent = event
if self.active then self:start() end
end
--- Set the name of the variable the Self Updating Gauge watches for the 'current' value of the gauge
-- @tparam string variableName The name of the variable to get the current value for the gauge. For instance "currentHP", "gmcp.Char.Vitals.hp" etc
function SUG:setCurrentVariable(variableName)
local nameType = type(variableName)
local funcName = "SUG:setCurrentVariable(variableName)"
assert(nameType == "string", string.format("%s: variableName as string expected, got: %s", funcName, nameType))
local val = getValueAt(variableName)
local valType = type(tonumber(val))
assert(valType == "number",
string.format("%s: variableName must point to a variable which is a number or coercable into one. %s points to a %s", funcName, variableName,
type(val)))
self.currentVariable = variableName
self:update()
end
--- Set the name of the variable the Self Updating Gauge watches for the 'max' value of the gauge
-- @tparam string variableName The name of the variable to get the max value for the gauge. For instance "maxHP", "gmcp.Char.Vitals.maxhp" etc. Set to "" to only check the current value
function SUG:setMaxVariable(variableName)
if variableName == "" then
self.maxVariable = variableName
self:update()
return
end
local nameType = type(variableName)
local funcName = "SUG:setMaxVariable(variableName)"
assert(nameType == "string", string.format("%s: variableName as string expected, got: %s", funcName, nameType))
local val = getValueAt(variableName)
local valType = type(tonumber(val))
assert(valType == "number",
string.format("%s: variableName must point to a variable which is a number or coercable into one. %s points to a %s", funcName, variableName,
type(val)))
self.maxVariable = variableName
self:update()
end
--- Set the template for the Self Updating Gauge to set the text with. "|c" is replaced by the current value, "|m" is replaced by the max value, and "|p" is replaced by the percentage current/max
-- @tparam string template The template to use for the text on the gauge. If the max value is 200 and current is 68, then |c will be replace by 68, |m replaced by 200, and |p replaced by 34.
function SUG:setTextTemplate(template)
local templateType = type(template)
local funcName = "SUG:setTextTemplate(template)"
assert(templateType == "string", string.format("%s: template as string expected, got %s", funcName, templateType))
self.textTemplate = template
self:update()
end
--- Set the updateHook function which is run just prior to the gauge updating
-- @tparam function func The function which will be called when the gauge updates. It should take 3 arguments, the gauge itself, the current value, and the max value. If you wish to override the current or max values used for the gauge, you can return new current and max values, like `return newCurrent newMax`
function SUG:setUpdateHook(func)
local funcType = type(func)
if funcType ~= "function" then
return nil, "setUpdateHook only takes functions, no strings or anything like that. You passed in: " .. funcType
end
self.updateHook = func
end
--- Stops the Self Updating Gauge from updating
function SUG:stop()
self.active = false
if self.timer then
killTimer(self.timer)
self.timer = nil
end
if self.eventHandler then
killAnonymousEventHandler(self.eventHandler)
self.eventHandler = nil
end
end
--- Starts the Self Updating Gauge updating. If it is already updating, it will restart it.
function SUG:start()
self:stop()
self.active = true
local update = function() self:update() end
if self.updateTime > 0 then
self.timer = tempTimer(self.updateTime / 1000, update, true)
end
local updateEvent = self.updateEvent
if updateEvent and updateEvent ~= "" and updateEvent ~= "*" then
self.eventHandler = registerAnonymousEventHandler(self.updateEvent, update)
end
end
--- Reads the values from currentVariable and maxVariable, and updates the gauge's value and text.
function SUG:update()
local current = getValueAt(self.currentVariable)
local max = getValueAt(self.maxVariable)
current = tonumber(current)
max = tonumber(max)
if current == nil then
current = self.defaultCurrent
debugc(string.format(
"Self Updating Gauge named %s is trying to update with an invalid current value. Using the defaultCurrent instead. currentVariable: '%s' maxVariable: '%s'",
self.name, self.currentVariable, self.maxVariable))
end
if max == nil then
max = self.defaultMax
if self.maxVariable ~= "" then
debugc(string.format(
"Self Updating Gauge named %s is trying to update with an invalid max value. Using the defaultCurrent instead. currentVariable: '%s' maxVariable: '%s'",
self.name, self.currentVariable, self.maxVariable))
end
end
if self.updateHook and type(self.updateHook) == "function" then
local ok, newcur, newmax = pcall(self.updateHook, self, current, max)
if ok and newcur then
current = newcur
max = newmax and newmax or self.defaultMax
end
end
local text = self.textTemplate
local percent = math.floor((current / max * 100) + 0.5)
text = text:gsub("|c", current)
text = text:gsub("|m", max)
text = text:gsub("|p", percent)
self:setValue(current, max, text)
end
SUG.parent = Geyser.Gauge
setmetatable(SUG, Geyser.Gauge)
return SUG

View File

@ -0,0 +1,335 @@
--- Creates a text based gauge, for use in miniconsoles and the like.
-- @classmod TextGauge
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2020 Damian Monogue
-- @copyright 2021 Damian Monogue
-- @license MIT, see LICENSE.lua
local TextGauge = {width = 24, fillCharacter = ":", emptyCharacter = "-", showPercent = true, showPercentSymbol = true, format = "c", value = 50}
--- Creates a new TextGauge.
-- @tparam[opt] table options The table of options you would like the TextGauge to start with.
-- <br><br>Table of new options
-- <table class="tg">
-- <thead>
-- <tr>
-- <th>option name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- </thead>
-- <tbody>
-- <tr>
-- <td class="tg-1">width</td>
-- <td class="tg-1">How many characters wide to make the gauge</td>
-- <td class="tg-1">24</td>
-- </tr>
-- <tr>
-- <td class="tg-2">fillCharacter</td>
-- <td class="tg-2">What character to use for the 'full' part of the gauge</td>
-- <td class="tg-2">:</td>
-- </tr>
-- <tr>
-- <td class="tg-1">overflowCharacter</td>
-- <td class="tg-1">What character to use for >100% part of the gauge</td>
-- <td class="tg-1">if not set, it uses whatever you set fillCharacter to</td>
-- </tr>
-- <tr>
-- <td class="tg-2">emptyCharacter</td>
-- <td class="tg-2">What character to use for the 'empty' part of the gauge</td>
-- <td class="tg-2">-</td>
-- </tr>
-- <tr>
-- <td class="tg-1">showPercentSymbol</td>
-- <td class="tg-1">Should we show the % sign itself?</td>
-- <td class="tg-1">true</td>
-- </tr>
-- <tr>
-- <td class="tg-2">showPercent</td>
-- <td class="tg-2">Should we show what % of the gauge is filled?</td>
-- <td class="tg-2">true</td>
-- </tr>
-- <tr>
-- <td class="tg-1">value</td>
-- <td class="tg-1">How much of the gauge should be filled</td>
-- <td class="tg-1">50</td>
-- </tr>
-- <tr>
-- <td class="tg-2">format</td>
-- <td class="tg-2">What type of color formatting to use? 'c' for cecho, 'd' for decho, 'h' for hecho</td>
-- <td class="tg-2">c</td>
-- </tr>
-- <tr>
-- <td class="tg-1">fillColor</td>
-- <td class="tg-1">What color to make the full part of the bar?</td>
-- <td class="tg-1">"DarkOrange" or equivalent for your format type</td>
-- </tr>
-- <tr>
-- <td class="tg-2">emptyColor</td>
-- <td class="tg-2">what color to use for the empty part of the bar?</td>
-- <td class="tg-2">"white" or format appropriate equivalent</td>
-- </tr>
-- <tr>
-- <td class="tg-1">percentColor</td>
-- <td class="tg-1">What color to print the percentage numvers in, if shown?</td>
-- <td class="tg-1">"white" or fortmat appropriate equivalent</td>
-- </tr>
-- <tr>
-- <td class="tg-2">percentSymbolColor</td>
-- <td class="tg-2">What color to make the % if shown?</td>
-- <td class="tg-2">If not set, uses what percentColor is set to.</td>
-- </tr>
-- <tr>
-- <td class="tg-1">overflowColor</td>
-- <td class="tg-1">What color to make the >100% portion of the bar?</td>
-- <td class="tg-1">If not set, will use the same color as fillColor</td>
-- </tr>
-- </tbody>
-- </table>
-- @usage
-- local TextGauge = require("MDK.textgauge")
-- myTextGauge = TextGauge:new()
-- gaugeText = myTextGauge:setValue(382, 830)
function TextGauge:new(options)
options = options or {}
local optionsType = type(options)
assert(optionsType == "table" or optionsType == "nil", "TextGauge:new(options): options expected as table, got " .. optionsType)
local me = table.deepcopy(options)
setmetatable(me, self)
self.__index = self
me:setDefaultColors()
return me
end
--- Sets the width in characters of the gauge
-- @tparam number width number of characters wide to make the gauge
function TextGauge:setWidth(width)
local widthType = type(width)
assert(widthType == "number", string.format("TextGauge:setWidth(width): width as number expected, got %s", widthType))
self.width = width
end
function TextGauge:setFormat(format)
self.format = self:getColorType(format)
self:setDefaultColors()
end
--- Sets the character to use for the 'full' part of the gauge
-- @tparam string character the character to use.
function TextGauge:setFillCharacter(character)
assert(character ~= nil, "TextGauge:setFillCharacter(character): character required, got nil")
assert(utf8.len(character) == 1, "TextGauge:setFillCharacter(character): character must be a single character")
self.fillCharacter = character
end
--- Sets the character to use for the 'overflow' (>100%) part of the gauge
-- @tparam string character the character to use.
function TextGauge:setOverflowCharacter(character)
assert(character ~= nil, "TextGauge:setOverflowCharacter(character): character required, got nil")
assert(utf8.len(character) == 1, "TextGauge:setOverflowCharacter(character): character must be a single character")
self.overflowCharacter = character
end
--- Sets the character to use for the 'full' part of the gauge
-- @tparam string character the character to use.
function TextGauge:setEmptyCharacter(character)
assert(character ~= nil, "TextGauge:setEmptyCharacter(character): character required, got nil")
assert(utf8.len(character) == 1, "TextGauge:setEmptyCharacter(character): character must be a single character")
self.emptyCharacter = character
end
--- Sets the fill color for the gauge.
-- @tparam string color the color to use for the full portion of the gauge. Will be run through Geyser.Golor
function TextGauge:setFillColor(color)
assert(color ~= nil, "TextGauge:setFillColor(color): color required, got nil")
self.fillColor = color
end
--- Sets the overflow color for the gauge.
-- @tparam string color the color to use for the full portion of the gauge. Will be run through Geyser.Golor
function TextGauge:setOverflowColor(color)
assert(color ~= nil, "TextGauge:setOverflowColor(color): color required, got nil")
self.overflowColor = color
end
--- Sets the empty color for the gauge.
-- @tparam string color the color to use for the empty portion of the gauge. Will be run through Geyser.Golor
function TextGauge:setEmptyColor(color)
assert(color ~= nil, "TextGauge:setEmptyColor(color): color required, got nil")
self.emptyColor = color
end
--- Sets the fill color for the gauge.
-- @tparam string color the color to use for the numeric value. Will be run through Geyser.Golor
function TextGauge:setPercentColor(color)
assert(color ~= nil, "TextGauge:setPercentColor(color): color required, got nil")
self.percentColor = color
end
--- Sets the fill color for the gauge.
-- @tparam string color the color to use for the numeric value. Will be run through Geyser.Golor
function TextGauge:setPercentSymbolColor(color)
assert(color ~= nil, "TextGauge:setPercentSymbolColor(color): color required, got nil")
self.percentSymbolColor = color
end
--- Enables reversing the fill direction (right to left instead of the usual left to right)
function TextGauge:enableReverse()
self.reverse = true
end
--- Disables reversing the fill direction (go back to the usual left to right)
function TextGauge:disableReverse()
self.reverse = false
end
--- Enables showing the percent value of the gauge
function TextGauge:enableShowPercent()
self.showPercent = true
end
--- Disables showing the percent value of the gauge
function TextGauge:disableShowPercent()
self.showPercent = false
end
--- Enables showing the percent symbol (appears after the value)
function TextGauge:enableShowPercentSymbol()
self.showPercentSymbol = true
end
--- Enables showing the percent symbol (appears after the value)
function TextGauge:disableShowPercentSymbol()
self.showPercentSymbol = false
end
function TextGauge:getColorType(format)
format = format or self.format
local dec = {"d", "decimal", "dec", "decho"}
local hex = {"h", "hexidecimal", "hex", "hecho"}
local col = {"c", "color", "colour", "col", "name", "cecho"}
if table.contains(col, format) then
return "c"
elseif table.contains(dec, format) then
return "d"
elseif table.contains(hex, format) then
return "h"
else
return ""
end
end
-- internal function, used at instantiation to ensure some colors are set
function TextGauge:setDefaultColors()
local colorType = self:getColorType()
if colorType == "c" then
self.percentColor = self.percentColor or "white"
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
self.fillColor = self.fillColor or "DarkOrange"
self.emptyColor = self.emptyColor or "white"
self.resetColor = "<reset>"
elseif colorType == "d" then
self.percentColor = self.percentColor or "<255,255,255>"
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
self.fillColor = self.fillColor or "<255,140,0>"
self.emptyColor = self.emptyColor or "<255,255,255>"
self.resetColor = "<r>"
elseif colorType == "h" then
self.percentColor = self.percentColor or "#ffffff"
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
self.fillColor = self.fillColor or "#ff8c00"
self.emptyColor = self.emptyColor or "#ffffff"
self.resetColor = "#r"
else
self.percentColor = self.percentColor or ""
self.percentSymbolColor = self.percentSymbolColor or self.percentColor
self.fillColor = self.fillColor or ""
self.emptyColor = self.emptyColor or ""
self.resetColor = ""
end
self.overflowColor = self.overflowColor or self.fillColor
end
-- Internal function used to route Geyser.Color based on internally stored format
function TextGauge:getColor(color)
local colorType = self:getColorType()
if colorType == "c" then
return string.format("<%s>", color) -- pass the color back in <> for cecho
elseif colorType == "d" then
return Geyser.Color.hdec(color) -- return it in decho format
elseif colorType == "h" then
return Geyser.Color.hex(color) -- return it in hex format
else
return "" -- return an empty string for noncolored output
end
end
--- Used to set the gauge's value and return the string representation of the gauge
-- @tparam[opt] number current current value. If no value is passed it will use the stored value. Defaults to 50 to prevent errors.
-- @tparam[opt] number max maximum value. If not passed, the internally stored one will be used. Defaults to 100 so that it can be used with single values as a percent
-- @usage myGauge:setValue(55) -- sets the gauge to 55% full
-- @usage myGauge:setValue(2345, 2780) -- will figure out what the percentage fill is based on the given current/max values
function TextGauge:setValue(current, max)
current = current or self.value
assert(type(current) == "number", "TextGauge:setValue(current,max) current as number expected, got " .. type(current))
assert(max == nil or type(max) == "number", "TextGauge:setValue(current, max) option max as number expected, got " .. type(max))
if current < 0 then
current = 0
end
max = max or 100
local value = math.floor(current / max * 100)
self.value = value
local width = self.width
local percentString = ""
local percentSymbolString = ""
local fillCharacter = self.fillCharacter
local overflowCharacter = self.overflowCharacter or fillCharacter
local emptyCharacter = self.emptyCharacter
local fillColor = self:getColor(self.fillColor)
local overflowColor = self:getColor(self.overflowColor)
local emptyColor = self:getColor(self.emptyColor)
local percentColor = self:getColor(self.percentColor)
local percentSymbolColor = self:getColor(self.percentSymbolColor)
local resetColor = self.resetColor
if self.showPercent then
percentString = string.format("%s%02d%s", percentColor, value, resetColor)
width = width - 2
end
if self.showPercentSymbol then
percentSymbolString = string.format("%s%s%s", percentSymbolColor, "%", resetColor)
width = width - 1
end
local perc = value / 100
local overflow = perc - 1
if overflow < 0 then
overflow = 0
end
if overflow > 1 then
perc = 2
overflow = 1
end
local overflowWidth = math.floor(overflow * width)
local fillWidth = math.floor((perc - overflow) * width)
local emptyWidth = width - fillWidth
fillWidth = fillWidth - overflowWidth
if value >= 100 and self.showPercent then
fillWidth = fillWidth - 1
end
if value >= 200 and self.showPercent then
overflowWidth = overflowWidth - 1
end
local result = ""
if self.reverse then
result = string.format("%s%s%s%s%s%s%s%s%s%s%s", emptyColor, string.rep(emptyCharacter, emptyWidth), resetColor,fillColor, string.rep(fillCharacter, fillWidth), resetColor, overflowColor, string.rep(overflowCharacter, overflowWidth), resetColor, percentString, percentSymbolString, resetColor)
else
result = string.format("%s%s%s%s%s%s%s%s%s%s%s", overflowColor, string.rep(overflowCharacter, overflowWidth), fillColor,
string.rep(fillCharacter, fillWidth), resetColor, emptyColor, string.rep(emptyCharacter, emptyWidth), resetColor,
percentString, percentSymbolString, resetColor)
end
return result
end
--- Synonym for setValue
function TextGauge:print(...)
self:setValue(...)
end
return TextGauge

View File

@ -0,0 +1,529 @@
--- Animated countdown timer, extends <a href="https://www.mudlet.org/geyser/files/geyser/GeyserGauge.html">Geyser.Gauge</a>
-- @classmod TimerGauge
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2020 Damian Monogue
-- @license MIT, see LICENSE.lua
local TimerGauge = {
name = "TimerGaugeClass",
active = true,
showTime = true,
prefix = "",
timeFormat = "S.t",
suffix = "",
updateTime = "10",
autoHide = true,
autoShow = true,
manageContainer = false,
}
function TimerGauge:setStyleSheet(cssFront, cssBack, cssText)
cssFront = cssFront or self.cssFront
cssBack = cssBack or self.cssBack
cssBack = cssBack or self.cssFront .. "background-color: black;"
cssText = cssText or self.cssText
if cssFront then
self.front:setStyleSheet(cssFront)
end
if cssBack then
self.back:setStyleSheet(cssBack)
end
if cssText then
self.text:setStyleSheet(cssText)
end
-- self.gauge:setStyleSheet(cssFront, cssBack, cssText)
self.cssFront = cssFront
self.cssBack = cssBack
self.cssText = cssText
end
--- Shows the TimerGauge. If the manageContainer property is true, then will add it back to its container
function TimerGauge:show2()
if self.manageContainer and self.savedContainer then
self.savedContainer:add(self)
self.savedContainer = nil
end
self:show()
end
--- Hides the TimerGauge. If manageContainer property is true, then it will remove it from its container and if the container is an HBox or VBox it will initiate size/position management
function TimerGauge:hide2()
if self.manageContainer and self.container.name ~= Geyser.name then
self.savedContainer = self.container
Geyser:add(self)
self.savedContainer:remove(self)
if self.savedContainer.type == "VBox" or self.savedContainer.type == "HBox" then
if self.savedContainer.organize then
self.savedContainer:organize()
else
self.savedContainer:reposition()
end
end
end
self:hide()
end
--- Starts the timergauge. Works whether the timer is stopped or not. Does not start a timer which is already at 0
-- @tparam[opt] boolean show override the autoShow property. True will always show, false will never show.
-- @usage myTimerGauge:start() --starts the timer, will show or not based on autoShow property
-- myTimerGauge:start(false) --starts the timer, will not change hidden status, regardless of autoShow property
-- myTimerGauge:start(true) --starts the timer, will show it regardless of autoShow property
function TimerGauge:start(show)
if show == nil then
show = self.autoShow
end
self.active = true
if self.timer then
killTimer(self.timer)
self.timer = nil
end
startStopWatch(self.stopWatchName)
self:update()
self.timer = tempTimer(self.updateTime / 1000, function()
self:update()
end, true)
if show then
self:show2()
end
end
--- Stops the timergauge. Works whether the timer is started or not.
-- @tparam[opt] boolean hide override the autoHide property. True will always hide, false will never hide.
-- @usage myTimerGauge:stop() --stops the timer, will hide or not based on autoHide property
-- myTimerGauge:stop(false) --stops the timer, will not change hidden status, regardless of autoHide property
-- myTimerGauge:stop(true) --stops the timer, will hide it regardless of autoHide property
function TimerGauge:stop(hide)
if hide == nil then
hide = self.autoHide
end
self.active = false
if self.timer then
killTimer(self.timer)
self.timer = nil
end
stopStopWatch(self.stopWatchName)
if hide then
self:hide2()
end
end
--- Alias for stop.
-- @tparam[opt] boolean hide override the autoHide property. True will always hide, false will never hide.
function TimerGauge:pause(hide)
self:stop(hide)
end
--- Resets the time on the timergauge to its original value. Does not alter the running state of the timer
function TimerGauge:reset()
resetStopWatch(self.stopWatchName)
adjustStopWatch(self.stopWatchName, self.time * -1)
self:update()
end
--- Resets and starts the timergauge.
-- @tparam[opt] boolean show override the autoShow property. true will always show, false will never show
-- @usage myTimerGauge:restart() --restarts the timer, will show or not based on autoShow property
-- myTimerGauge:restart(false) --restarts the timer, will not change hidden status, regardless of autoShow property
-- myTimerGauge:restart(true) --restarts the timer, will show it regardless of autoShow property
function TimerGauge:restart(show)
self:reset()
self:start(show)
end
--- Get the amount of time remaining on the timer, in seconds
-- @tparam string format Format string for how to return the time. If not provided defaults to self.timeFormat(which defaults to "S.t").<br>
-- If "" is passed will return "" as the time. See below table for formatting codes<br>
-- <table class="tg">
-- <tr>
-- <th>format code</th>
-- <th>what it is replaced with</th>
-- </tr>
-- <tr>
-- <td class="tg-1">S</td>
-- <td class="tg-1">Time left in seconds, unbroken down. Does not include milliseconds.<br>
-- IE a timer with 2 minutes left it would replace S with 120
-- </td>
-- </tr>
-- <tr>
-- <td class="tg-2">dd</td>
-- <td class="tg-2">Days, with 1 leading 0 (0, 01, 02-...)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">d</td>
-- <td class="tg-1">Days, with no leading 0 (1,2,3-...)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">hh</td>
-- <td class="tg-2">hours, with leading 0 (00-24)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">h</td>
-- <td class="tg-1">hours, without leading 0 (0-24)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">MM</td>
-- <td class="tg-2">minutes, with a leading 0 (00-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">M</td>
-- <td class="tg-1">minutes, no leading 0 (0-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">ss</td>
-- <td class="tg-2">seconds, with leading 0 (00-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">s</td>
-- <td class="tg-1">seconds, no leading 0 (0-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">t</td>
-- <td class="tg-2">tenths of a second<br>
-- timer with 12.345 seconds left, t would<br>
-- br replaced by 3.
-- </td>
-- </tr>
-- <tr>
-- <td class="tg-1">mm</td>
-- <td class="tg-1">milliseconds with leadings 0s (000-999)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">m</td>
-- <td class="tg-2">milliseconds with no leading 0s (0-999)</td>
-- </tr>
-- </table><br>
-- @usage myTimerGauge:getTime() --returns the time using myTimerGauge.format
-- myTimerGauge:getTime("hh:MM:ss") --returns the time as hours, minutes, and seconds, with leading 0s (01:23:04)
-- myTimerGauge:getTime("S.mm") --returns the time as the total number of seconds, including milliseconds (114.004)
function TimerGauge:getTime(format)
format = format or self.timeFormat
local time = getStopWatchTime(self.stopWatchName)
local timerTable = getStopWatchBrokenDownTime(self.stopWatchName)
if time > 0 then
self:stop(self.autoHide)
resetStopWatch(self.stopWatchName)
time = getStopWatchTime(self.stopWatchName)
timerTable = getStopWatchBrokenDownTime(self.stopWatchName)
self.active = false
end
if format == "" then
return format
end
local totalSeconds = string.split(math.abs(time), "%.")[1]
local tenths = string.sub(string.format("%03d", timerTable.milliSeconds), 1, 1)
format = format:gsub("S", totalSeconds)
format = format:gsub("t", tenths)
format = format:gsub("mm", string.format("%03d", timerTable.milliSeconds))
format = format:gsub("m", timerTable.milliSeconds)
format = format:gsub("MM", string.format("%02d", timerTable.minutes))
format = format:gsub("M", timerTable.minutes)
format = format:gsub("dd", string.format("%02d", timerTable.days))
format = format:gsub("d", timerTable.days)
format = format:gsub("ss", string.format("%02d", timerTable.seconds))
format = format:gsub("s", timerTable.seconds)
format = format:gsub("hh", string.format("%02d", timerTable.hours))
format = format:gsub("h", timerTable.hours)
return format
end
-- Execute the timer's hook, if there is one. Internal function
function TimerGauge:executeHook()
local hook = self.hook
if not hook then
return
end
local hooktype = type(hook)
if hooktype == "string" then
local f, e = loadstring("return " .. hook)
if not f then
f, e = loadstring(hook)
end
if not f then
debugc(string.format("TimerGauge encountered an error while executing the hook for TimerGauge with name: %s error: %s", self.name, tostring(e)))
return
end
hook = f
end
hooktype = type(hook)
if hooktype ~= "function" then
debugc(string.format(
"TimerGauge with name: %s was given a hook which is neither a function nor a string which can be made into one. Provided type was %s",
self.name, hooktype))
return
end
local worked, err = pcall(hook)
if not worked then
debugc(string.format("TimerGauge named %s encountered the following error while executing its hook: %s", self.name, err))
end
end
--- Sets the timer's remaining time to 0, stops it, and executes the hook if one exists.
-- @tparam[opt] boolean skipHook use true to have it set the timer to 0 and stop, but not execute the hook.
-- @usage myTimerGauge:finish() --executes the hook if it has one
-- myTimerGauge:finish(false) --will not execute the hook
function TimerGauge:finish(skipHook)
resetStopWatch(self.stopWatchName)
self:update(skipHook)
end
-- Internal function, no ldoc
-- Updates the gauge based on time remaining.
-- @tparam[opt] boolean skipHook use true if you do not want to execute the hook if the timer is at 0.
function TimerGauge:update(skipHook)
local time = self.showTime and self:getTime(self.timeFormat) or ""
local current = tonumber(self:getTime("S.mm"))
local suffix = self.suffix or ""
local prefix = self.prefix or ""
local text = string.format("%s%s%s", prefix, time, suffix)
self:setValue(current, self.time, text)
if current == 0 then
if self.timer then
killTimer(self.timer)
self.timer = nil
end
if not skipHook then
self:executeHook()
end
end
end
--- Sets the amount of time the timer will run for. Make sure to call :reset() or :restart()
-- if you want to cause the timer to run for that amount of time. If you set it to a time lower
-- than the time left on the timer currently, it will reset the current time, otherwise it is left alone
-- @tparam number time how long in seconds the timer should run for
-- @usage myTimerGauge:setTime(50) -- sets myTimerGauge's max time to 50.
function TimerGauge:setTime(time)
local timetype = type(time)
if timetype ~= "number" then
local err = string.format("TimerGauge:setTime(time): time as number expected, got %s", timetype)
debugc(err)
return nil, err
end
time = math.abs(time)
if time == 0 then
local err = "TimerGauge:setTime(time): you cannot pass in 0 as the max time for the timer"
debugc(err)
return nil, err
end
local currentTime = tonumber(self:getTime("S.t"))
self.time = time
if time < currentTime then
self:reset()
else
self:update(currentTime == 0)
end
end
--- Changes the time between gauge updates.
-- @tparam number updateTime amount of time in milliseconds between gauge updates. Must be a positive number.
function TimerGauge:setUpdateTime(updateTime)
local updateTimeType = type(updateTime)
assert(updateTimeType == "number",
string.format("TimerGauge:setUpdateTime(updateTime): name: %s updateTime as number expected, got %s", self.name, updateTimeType))
assert(updateTime > 0,
string.format("TimerGauge:setUpdateTime(updateTime): name: %s updateTime must be a positive number. You gave %d", self.name, updateTime))
self.updateTime = updateTime
if self.timer then
killTimer(self.timer)
self.timer = nil
end
if self.active then
self.timer = tempTimer(updateTime / 1000, function()
self:update()
end, true)
end
end
TimerGauge.parent = Geyser.Gauge
setmetatable(TimerGauge, Geyser.Gauge)
--- Creates a new TimerGauge instance.
-- @tparam table cons a table of options (or constraints) for how the TimerGauge will behave. Valid options include:
-- <br>
-- <table class="tg">
-- <tr>
-- <th>name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- <tr>
-- <td class="tg-1">time</td>
-- <td class="tg-1">how long the timer should run for</td>
-- <td class="tg-1"></td>
-- </tr>
-- <tr>
-- <td class="tg-2">active</td>
-- <td class="tg-2">whether the timer should run or not</td>
-- <td class="tg-2">true</td>
-- </tr>
-- <tr>
-- <td class="tg-1">showTime</td>
-- <td class="tg-1">should we show the time remaining on the gauge?</td>
-- <td class="tg-1">true</td>
-- </tr>
-- <tr>
-- <td class="tg-2">prefix</td>
-- <td class="tg-2">text you want shown before the time.</td>
-- <td class="tg-2">""</td>
-- </tr>
-- <tr>
-- <td class="tg-1">suffix</td>
-- <td class="tg-1">text you want shown after the time.</td>
-- <td class="tg-1">""</td>
-- </tr>
-- <tr>
-- <td class="tg-2">timerCaption</td>
-- <td class="tg-2">Alias for suffix. Deprecated and may be remove in the future</td>
-- <td class="tg-2"/>
-- </tr>
-- <tr>
-- <td class="tg-1">updateTime</td>
-- <td class="tg-1">number of milliseconds between gauge updates.</td>
-- <td class="tg-1">10</td>
-- </tr>
-- <tr>
-- <td class="tg-2">autoHide</td>
-- <td class="tg-2">should the timer :hide() itself when it runs out/you stop it?</td>
-- <td class="tg-2">true</td>
-- </tr>
-- <tr>
-- <td class="tg-1">autoShow</td>
-- <td class="tg-1">should the timer :show() itself when you start it?</td>
-- <td class="tg-1">true</td>
-- </tr>
-- <tr>
-- <td class="tg-2">manageContainer</td>
-- <td class="tg-2">should the timer remove itself from its container when you call <br>:hide() and add itself back when you call :show()?</td>
-- <td class="tg-2">false</td>
-- </tr>
-- <tr>
-- <td class="tg-1">timeFormat</td>
-- <td class="tg-1">how should the time be displayed/returned if you call :getTime()? <br>See table below for more information</td>
-- <td class="tg-1">"S.t"</td>
-- </tr>
-- </table>
-- <br>Table of time format options
-- <table class="tg">
-- <tr>
-- <th>format code</th>
-- <th>what it is replaced with</th>
-- </tr>
-- <tr>
-- <td class="tg-1">S</td>
-- <td class="tg-1">Time left in seconds, unbroken down. Does not include milliseconds.<br>
-- IE a timer with 2 minutes left it would replace S with 120
-- </td>
-- </tr>
-- <tr>
-- <td class="tg-2">dd</td>
-- <td class="tg-2">Days, with 1 leading 0 (0, 01, 02-...)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">d</td>
-- <td class="tg-1">Days, with no leading 0 (1,2,3-...)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">hh</td>
-- <td class="tg-2">hours, with leading 0 (00-24)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">h</td>
-- <td class="tg-1">hours, without leading 0 (0-24)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">MM</td>
-- <td class="tg-2">minutes, with a leading 0 (00-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">M</td>
-- <td class="tg-1">minutes, no leading 0 (0-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">ss</td>
-- <td class="tg-2">seconds, with leading 0 (00-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-1">s</td>
-- <td class="tg-1">seconds, no leading 0 (0-59)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">t</td>
-- <td class="tg-2">tenths of a second<br>
-- timer with 12.345 seconds left, t would<br>
-- br replaced by 3.
-- </td>
-- </tr>
-- <tr>
-- <td class="tg-1">mm</td>
-- <td class="tg-1">milliseconds with leadings 0s (000-999)</td>
-- </tr>
-- <tr>
-- <td class="tg-2">m</td>
-- <td class="tg-2">milliseconds with no leading 0s (0-999)</td>
-- </tr>
-- </table><br>
-- @param parent The Geyser parent for this TimerGauge
-- @usage
-- local TimerGauge = require("MDK.timergauge")
-- myTimerGauge = TimerGauge:new({
-- name = "testGauge",
-- x = 100,
-- y = 100,
-- height = 40,
-- width = 200,
-- time = 10
-- })
function TimerGauge:new(cons, parent)
-- type checking and error handling
local consType = type(cons)
if consType ~= "table" then
local err = string.format("TimerGauge:new(options, parent): options must be provided as a table, received: %s", consType)
debugc(err)
return nil, err
end
local timetype = type(cons.time)
local time = tonumber(cons.time)
if not time then
local err = string.format(
"TimerGauge:new(options, parent): options table must include a time entry, which must be a number. We received: %s which is type: %s",
cons.time or tostring(cons.time), timetype)
debugc(err)
return nil, err
end
cons.time = math.abs(time)
if cons.time == 0 then
local err = "TimerGauge:new(options, parent): time entry in options table must be non-0"
debugc(err)
return nil, err
end
if cons.timerCaption and (not cons.suffix) then
cons.suffix = cons.timerCaption
end
cons.type = cons.type or "timergauge"
-- call parent constructor
local me = self.parent:new(cons, parent)
-- add TimerGauge as the metatable/index
setmetatable(me, self)
self.__index = self
-- apply any styling requested
if me.cssFront then
if not me.cssBack then
me.cssBack = me.cssFront .. "background-color: black;"
end
me:setStyleSheet(me.cssFront, me.cssBack, me.cssText)
end
-- create and reset the driving stopwatch
me.stopWatchName = me.name .. "_timergauge"
createStopWatch(me.stopWatchName)
me:reset()
-- start it up?
if me.active then
me:start()
end
me:update()
return me
end
return TimerGauge

View File

@ -0,0 +1 @@
-- These functions create the windows

View File

@ -0,0 +1,114 @@
-- Setup Bottom Bar
-- The Bottom Bar is a vbox that contains a top hbox that holds three gauges and a bottom hbox for whatever content you want
avalon.containers.alignment.bottommain = Geyser.VBox:new(
{
name = "avalonContainersAlignmentBottomMain",
width = "95%",
height = "100%"
},
avalon.containers.bottom
)
avalon.containers.alignment.bottomgauges = Geyser.HBox:new(
{
name = "avalonContainersAlignmentBottomgauges",
width = "100%",
height = "50%"
},
avalon.containers.alignment.bottommain
)
avalon.containers.alignment.bottominfo = Geyser.HBox:new(
{
name="avalonContainersAlignmentBottominfo",
width = "100%",
height = "-50%"
},
avalon.containers.alignment.bottommain
)
-- Setup Left Bar
-- The Left bar consists of a vbox with four internal vboxes for information display
-- These are named as:
-- leftmain - Primary vbox that the other vboxes attach to
-- lefttop - The top most vbox inside leftmain
-- leftmidtop - The first of two vboxes between the top and bottom. This is the one below lefttop
-- leftmidbottom - The second of two vboxes between the top and bottom. This is the one above leftbottom and below leftmidtop
-- leftbottom - The bottom most vbox inside leftmain
avalon.containers.alignment.leftmain = Geyser.VBox:new(
{
name = "avalonContainersAlignmentLeftmain",
width = "95%",
height = "100%"
},
avalon.containers.left
)
avalon.containers.alignment.lefttop = Geyser.VBox:new(
{
name = "avalonContainersAlignmentLefttop",
width = "100%",
height = "25%"
},
avalon.containers.alignment.leftmain
)
avalon.containers.alignment.leftmidtop = Geyser.VBox:new(
{
name = "avalonContainersAlignmentLeftmidtop",
width = "100%",
height = "25%"
},
avalon.containers.alignment.leftmain
)
avalon.containers.alignment.leftmidbottom = Geyser.VBox:new(
{
name = "avalonContainersAlignmentLeftmidbottom",
width = "100%",
height = "25%"
},
avalon.containers.alignment.leftmain
)
avalon.containers.alignment.leftbottom = Geyser.VBox:new(
{
name = "avalonContainersAlignmentLeftbottom",
width = "100%",
height = "25%"
},
avalon.containers.alignment.leftmain
)
-- Setup Right Bar
-- The Right Bar consists of vbox that contains a mapper in the top right and chat in the bottom right
-- The chat utilizes Demonnic's great ECMO https://github.com/demonnic/MDK
-- rightmain - Primary VBox that other VBoxes attach to
-- rightmap - The Mapper
-- rightchat - The EMCO Chat window
avalon.containers.alignment.rightmain = Geyser.VBox:new(
{
name = "avalonContainersAlignmentRightmain",
width = "100%",
height = "100%"
},
avalon.containers.right
)
avalon.containers.alignment.rightmap = Geyser.Container:new(
{
name = "avalonContainersAlignmentRightmap",
width = "100%",
height = "60%"
},
avalon.containers.alignment.rightmain
)
avalon.containers.alignment.rightchat = Geyser.Container:new(
{
name = "avalonContainersAlignmentRightchat",
width = "100%",
height = "40%"
},
avalon.containers.alignment.rightmain
)

View File

@ -0,0 +1,8 @@
[
{
"isActive": "yes",
"isFolder": "no",
"name": "KaiUI init",
"script": ""
}
]

View File

@ -0,0 +1,22 @@
[
{
"name": "Initialize",
"isActive": "yes",
"isFolder": "no",
"multiline": "no",
"multielineDelta": "0",
"matchall": "no",
"filter": "no",
"fireLength": "0",
"highlight": "no",
"highlightFG": "#ff0000",
"highlightBG": "#ffff00",
"patterns": [
{
"pattern": "Rapture Runtime Environment v2.4.5 -- (c) 2017 -- Iron Realms Entertainment",
"type": "substring"
}
],
"script": ""
}
]