lotj-mudlet-ui/src/resources/MDK/timergauge.lua
Charles Click 954dc3c607 * Add Demonic MDK.
* Inject autostudy plugin into the UI directly.
* Add a TLC automator for study.
[SKIP CI] Do not run CI for this.
2024-10-09 23:12:21 +00:00

530 lines
17 KiB
Lua
Executable File

--- 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