Hello all! I have been toiling away on a stopwatch for the last few weeks, and I finally feel like it's ready to be shared! This stopwatch is different from others that I've seen in that it uses a LUA script to manage all of the timing and meter displays. I designed the script to be as easy to use and expandable as possible.
Here is a short overview of how to use the script:
To use the stopwatch, certain measures and meters need to update ten times a second. By default, Rainmeter skins update once per second. To override this, insert the following into the [Rainmeter] section of your skin:
Code: Select all
[Rainmeter]
; To get certain meters to update ten times a second, set the update value in this section to 100 milliseconds.
Update=100
; This prevents meters other than those we want from updating excessively. Meters that we wish to update ten times a second will need 'UpdateDivider=1' set.
DefaultUpdateDivider=10
Code: Select all
[MeasureStopwatchScript]
Measure=Script
ScriptFile=#@#Scripts\Stopwatch.lua
; Defaults to 1 if unspecified. Setting this to 0 will return the times as MM:SS.T instead of HH:MM:SS.T
ShowHours=0
; Number of rows in the lap list
LapListHeight=8
; Defines the name of the time measure the stopwatch will use. Defaults to 'MeasureTime' if unspecified.
TimeMeasure=MeasureStopwatchTime
; Update ten times a second
UpdateDivider=1
; Enabling this measure is what starts the stopwatch
Disabled=1
[MeasureStopwatchTime]
Measure=Time
; Creates the tenths place out of a whole number
AverageSize=10
; Update ten times a second
UpdateDivider=1
Next, we have the main stopwatch display and lap time display:
Code: Select all
[MeterStopwatchMainDisplay]
Meter=String
MeterStyle=StyleString | StyleStringCenterAlign
FontSize=20
FontColor=#colorStopwatchMain#
Y=#contentMargin#
; Gets the current stopwatch time from the script
Text=[&MeasureStopwatchScript:GetTime()]
DynamicVariables=1
; Update ten times a second
UpdateDivider=1
[MeterStopwatchLapDisplay]
Meter=String
MeterStyle=StyleString | StyleStringCenterAlign
FontSize=15
Y=-3R
; Gets the current lap time from the script
Text=[&MeasureStopwatchScript:GetLapTime()]
DynamicVariables=1
; Update ten times a second
UpdateDivider=1
Next up, we have the scrolling detection. The scrolling works by using MouseScrollActions on an invisible image meter that covers the entire lap list. I have un-hidden the meter in the image below: Implementing this is fairly straightforward as well, thanks to the functions I put into the Stopwatch script. All you need to do is call the LapScrollUp() or LapScrollDown() functions when the corresponding scroll action is detected:
Code: Select all
[MeterStopwatchLapListScroll]
Meter=Image
; Set alpha value to > 0 if you wish to see the region where scrolling can occur
SolidColor=255,255,255,0
X=#contentMargin#
Y=#rowSpacing#R
W=#contentWidth#
; Set to the height of the lap list. Can either be a hard-coded value, or use the Y value of the bottom of the list as shown here.
H=([MeterStopwatchLap8Label:Y] + [MeterStopwatchLap8Label:H]) - [MeterStopwatchLapListScroll:Y]
DynamicVariables=1
; Tells the script to scroll the lap list down. Will automatically stop when the bottom of the list is reached.
MouseScrollDownAction=[!CommandMeasure MeasureStopwatchScript "LapScrollDown()"]
; Tells the script to scroll the lap list up. Will automatically stop when the top of the list is reached.
MouseScrollUpAction=[!CommandMeasure MeasureStopwatchScript "LapScrollUp()"]
; Prevents the mouse cursor from becoming a hand (a.k.a "click me!")
MouseActionCursor=0
Code: Select all
[MeterStopwatchLap1Label]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchLapLabel
Y=r
; Gets the lap number of the lap at the top of the list
Text=[&MeasureStopwatchScript:GetLap(1)]
[MeterStopwatchLap1LapTime]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchLapValue | StyleStringCenterAlign
; Gets the lap time of the lap at the top of the list
Text=[&MeasureStopwatchScript:GetLap(1, 'lap')]
[MeterStopwatchLap1Total]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchLapValue
; Gets the stopwatch time when the lap at the top of the list was created
Text=[&MeasureStopwatchScript:GetLap(1, 'total')]
The number argument in the GetLap() function DOES NOT refer to the lap number itself - it refers to the lap position in the list. For example, this first set is the top of the list, so it is lap 1. The next row down is the second row, hence lap 2. And so on.
Just including a number argument in GetLap() returns the lap number of the lap at the top of the list. Adding 'lap' or 'total' to the arguments returns the lap time or the total stopwatch time at the time the lap was made, respectively. Simply repeat this set of meters, incrementing the number by 1, for as long as you would like the list to be, and set the LapListHeight option on the script measure accordingly.
Last but not least, we have the controls! Here is the code with explanations baked in:
Code: Select all
[MeterStopwatchStartButton]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchButton
FontColor=#colorStopwatchStart#
FontSize=25
X=(#contentMargin# + 6)
Y=(#contentMargin# + 10)
Text=[\x45]
; Tell the other meters that the stopwatch is running, set the current time as the time the stopwatch will measure from, and enable the script measure so the stopwatch will start counting.
LeftMouseUpAction=[!SetVariable stopwatchStatus 1][!UpdateMeterGroup StopwatchMeters][!CommandMeasure MeasureStopwatchScript "deltaTime = [MeasureStopwatchTime:]"][!EnableMeasure MeasureStopwatchScript]
; Show only when the stopwatch is reset (optional)
Hidden=(#stopwatchStatus# <> 0)
ToolTipText=Start
[MeterStopwatchUnpauseButton]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchButton | MeterStopwatchStartButton
Y=r
; Tell the other meters that the stopwatch is running, command the script to unpause the stopwatch.
LeftMouseUpAction=[!SetVariable stopwatchStatus 1][!UpdateMeterGroup StopwatchMeters][!CommandMeasure MeasureStopwatchScript "paused = 0"]
; Show only when the stopwatch is paused (optional)
Hidden=(#stopwatchStatus# <> 2)
ToolTipText=Resume
[MeterStopwatchLapButton]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchButton
FontColor=#colorStopwatchLapButton#
X=5r
Y=5r
FontSize=16
Text=[\x7d]
; Command the script to create a lap using the current stopwatch and lap times
LeftMouseUpAction=[!CommandMeasure MeasureStopwatchScript "Lap()"]
; Show only when the stopwatch is running (optional)
Hidden=(#stopwatchStatus# <> 1)
ToolTipText=Lap
[MeterStopwatchPauseButton]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchButton
FontColor=#colorStopwatchPauseButton#
FontSize=25
X=(#contentMarginRight# - 4)
Y=(#contentMargin# + 10)
StringAlign=Right
Text=[\x60]
; Tell the other meters that the stopwatch is paused, command the measure to pause the stopwatch.
LeftMouseUpAction=[!SetVariable stopwatchStatus 2][!UpdateMeterGroup StopwatchMeters][!CommandMeasure MeasureStopwatchScript "paused = 1"]
; Show only when the stopwatch is running (optional)
Hidden=(#stopwatchStatus# <> 1)
ToolTipText=Pause
[MeterStopwatchResetButton]
Meter=String
MeterStyle=StyleString | StyleStringStopwatchButton
FontColor=#colorStopwatchResetButton#
FontSize=13
StringAlign=Right
X=-8r
Y=8r
Text=[\xe02a]
; Tell the other meters that the stopwatch is reset, disable the script measure to stop it from counting, command the script to reset all values to zero, update stopwatch meters.
LeftMouseUpAction=[!SetVariable stopwatchStatus 0][!DisableMeasure MeasureStopwatchScript][!CommandMeasure MeasureStopwatchScript "Reset()"][!UpdateMeterGroup StopwatchMeters]
; Show only when the stopwatch is paused or reset (optional)
Hidden=(#stopwatchStatus# = 1)
ToolTipText=Reset
For anyone interested, here is the contents of the script in its entirety:
Code: Select all
-- ----------------------------------------
-- Stopwatch.lua
-- v1.0.0
-- raiguard
-- ----------------------------------------
measureTime = 0
realTime = 0
deltaTime = 0
elapsedTime = 0
lapDeltaTime = 0
lapTime = 0
lapCount = 0
lapScroll = 0
laps = {}
lapListHeight = 0
paused = 0
debug = false
function Initialize()
measureTime = SKIN:GetMeasure(SELF:GetOption('TimeMeasure', 'MeasureTime'))
lapListHeight = tonumber(SELF:GetOption('LapListHeight', 5))
showHours = tonumber(SELF:GetOption('ShowHours', 1))
Reset()
end
function Update() --> Updates the stopwatch time and lap time ten times a second
realTime = measureTime:GetValue()
if paused == 1 then deltaTime = realTime - elapsedTime
else elapsedTime = (realTime - deltaTime) end
end
function Reset() --> Resets all stopwatch statistics to their starting point
realTime = 0
deltaTime = 0
elapsedTime = 0
lapDeltaTime = 0
lapTime = 0
lapCount = 0
lapScroll = 0
laps = {}
paused = 0
end
function GetTime() return FormatTimeString(elapsedTime) end --> Returns the current stopwatch time. Usage: Text=[&MeasureStopwatchScript:GetTime()]
function GetLapTime() return FormatTimeString(elapsedTime - lapDeltaTime) end --> Returns the current stopwatch lap time. Usage: Text=[&MeasureStopwatchScript:GetLapTime()]
function GetLap(lap, value) --> Returns the lap number, lap time, or stopwatch time for a specific lap.
if lapCount <= lap - 1 then return '-'
elseif value then return laps[lapScroll - (lap - 1)][value]
else return lapScroll - (lap - 1) end
-- USAGE:
-- Text=[&MeasureStopwatchScript:GetLap(1)] --> Returns the lap number of the highest lap on the list
-- Text=[&MeasureStopwatchScript:GetLap(1, 'lap')] --> Returns the lap's lap time
-- Text=[&MeasureStopwatchScript:GetLap(1, 'total')] --> Returns the total stopwatch time when that lap was made
end
function Lap() --> Takes the current stopwatch time and creates a new lap from it
if lapScroll == lapCount then lapScroll = lapScroll + 1 end
lapCount = lapCount + 1
table.insert(laps, lapCount, { lap = GetLapTime(), total = GetTime() })
lapDeltaTime = elapsedTime
LogHelper('Lap ' .. lapCount .. ' = ' .. laps[lapCount]['total'], 'Debug')
SKIN:Bang('!UpdateMeterGroup', 'LapMeters')
SKIN:Bang('!Redraw')
end
function LapScrollUp() --> Scrolls the lap list up. Will automatically stop if the top of the list is reached.
if lapScroll < lapCount then
lapScroll = lapScroll + 1
SKIN:Bang('!UpdateMeterGroup', 'LapMeters')
SKIN:Bang('!Redraw')
end
end
function LapScrollDown() --> Scrolls the lap list down. Will automatically stop if the bottom of the list is reached.
if lapScroll > lapListHeight then
lapScroll = lapScroll - 1
SKIN:Bang('!UpdateMeterGroup', 'LapMeters')
SKIN:Bang('!Redraw')
end
end
function FormatTimeString(time) --> Converts a raw timestamp value into a human-readable format.
local hours = tostring(math.floor((time / 3600) % 24)):gsub('(.+)', '0%1'):gsub('^%d(%d%d)$', '%1')
local minutes = tostring(math.floor((time / 60) % 60)):gsub('(.+)', '0%1'):gsub('^%d(%d%d)$', '%1')
local seconds = tostring(math.floor(time % 60)):gsub('(.+)', '0%1'):gsub('^%d(%d%d)$', '%1')
local tenths = round((time * 10) % 10)
if tenths == 10 then tenths = 0 end
if showHours == 1 then return hours .. ':' .. minutes .. ':' .. seconds .. '.' .. tenths
else return minutes .. ':' .. seconds .. '.' .. tenths end
end
function round(x) --> Rounds...
if x%2 ~= 0.5 then
return math.floor(x+0.5)
end
return x-0.5
end
-- function to make logging messages less cluttered
function LogHelper(message, type)
if type == nil then type = 'Debug' end
if debug == true then
SKIN:Bang("!Log", message, type)
elseif type ~= 'Debug' then
SKIN:Bang("!Log", message, type)
end
end