3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 Port of Stopwatch to Lua for touchscreen targets.
11 Original copyright: Copyright (C) 2004 Mike Holden
13 Copyright (C) 2009 by Maurus Cuelenaere
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
28 STOPWATCH_FILE
= rb
.PLUGIN_APPS_DATA_DIR
.. "/stopwatch.dat"
42 width
= rb
.LCD_WIDTH
- 80,
43 height
= rb
.LCD_HEIGHT
,
45 fg_pattern
= rb
.lcd_get_foreground()
53 function LapsView
:init()
54 local _
, _
, h
= rb
.font_getstringsize("", self
.vp
.font
)
56 self
.vp
.maxLaps
= self
.vp
.height
/ h
62 function LapsView
:display()
63 rb
.set_viewport(self
.vp
)
66 local nrOfLaps
= math
.min(self
.vp
.maxLaps
, #self
.lapTimes
)
67 rb
.lcd_puts_scroll(0, 0, ticksToString(self
.timer
.current
))
70 local idx
= #self
.lapTimes
- self
.scroll
.cursorPos
- i
+ 1
71 if self
.lapTimes
[idx
] ~= nil then
72 rb
.lcd_puts_scroll(0, i
, ticksToString(self
.lapTimes
, idx
))
79 function LapsView
:checkForScroll(btn
, x
, y
)
80 if x
> self
.vp
.x
and x
< self
.vp
.x
+ self
.vp
.width
and
81 y
> self
.vp
.y
and y
< self
.vp
.y
+ self
.vp
.height
then
83 if bit
.band(btn
, rb
.buttons
.BUTTON_REL
) == rb
.buttons
.BUTTON_REL
then
86 if #self
.lapTimes
> self
.vp
.maxLaps
and self
.scroll
.prevY
~= 0 then
87 self
.scroll
.cursorPos
= self
.scroll
.cursorPos
-
88 (y
- self
.scroll
.prevY
) / self
.vp
.lapHeight
90 local maxLaps
= math
.min(self
.vp
.maxLaps
, #self
.lapTimes
)
91 if self
.scroll
.cursorPos
< 0 then
92 self
.scroll
.cursorPos
= 0
93 elseif self
.scroll
.cursorPos
>= maxLaps
then
94 self
.scroll
.cursorPos
= maxLaps
107 function LapsView
:incTimer()
108 if self
.timer
.counting
then
109 self
.timer
.current
= self
.timer
.prevTotal
+ rb
.current_tick()
112 self
.timer
.current
= self
.timer
.prevTotal
116 function LapsView
:startTimer()
117 self
.timer
.startAt
= rb
.current_tick()
118 self
.timer
.currentLap
= self
.timer
.prevTotal
119 self
.timer
.counting
= true
122 function LapsView
:stopTimer()
123 self
.timer
.prevTotal
= self
.timer
.prevTotal
+ rb
.current_tick()
125 self
.timer
.counting
= false
128 function LapsView
:newLap()
129 table.insert(self
.lapTimes
, self
.timer
.current
)
132 function LapsView
:resetTimer()
134 self
.timer
.counting
= false
135 self
.timer
.current
, self
.timer
.prevTotal
, self
.timer
.startAt
= 0, 0, 0
136 self
.scroll
.cursorPos
= 0
139 function LapsView
:saveState()
140 local fd
= assert(io
.open(STOPWATCH_FILE
, "w"))
142 for _
, v
in ipairs({"current", "startAt", "prevTotal", "counting"}) do
143 assert(fd
:write(tostring(self
.timer
[v
]) .. "\n"))
145 for _
, v
in ipairs(self
.lapTimes
) do
146 assert(fd
:write(tostring(v
) .. "\n"))
152 function LapsView
:loadState()
153 local fd
= io
.open(STOPWATCH_FILE
, "r")
154 if fd
== nil then return end
156 for _
, v
in ipairs({"current", "startAt", "prevTotal"}) do
157 self
.timer
[v
] = tonumber(fd
:read("*line"))
159 self
.timer
.counting
= toboolean(fd
:read("*line"))
161 local line
= fd
:read("*line")
163 table.insert(self
.lapTimes
, tonumber(line
))
164 line
= fd
:read("*line")
178 function Button
:new(o
)
182 local _
, w
, h
= rb
.font_getstringsize(o
.label
, LapsView
.vp
.font
)
183 o
.width
= math
.max(5 * w
/ 4,o
.width
)
187 setmetatable(o
, self
)
192 function Button
:draw()
193 local _
, w
, h
= rb
.font_getstringsize(self
.label
, LapsView
.vp
.font
)
194 local x
, y
= (2 * self
.x
+ self
.width
- w
) / 2, (2 * self
.y
+ self
.height
- h
) / 2
196 rb
.lcd_drawrect(self
.x
, self
.y
, self
.width
, self
.height
)
197 rb
.lcd_putsxy(x
, y
, self
.label
)
200 function Button
:isPressed(x
, y
)
201 return x
> self
.x
and x
< self
.x
+ self
.width
and
202 y
> self
.y
and y
< self
.y
+ self
.height
206 function ticksToString(laps
, lap
)
207 local ticks
= type(laps
) == "table" and laps
[lap
] or laps
210 local hours
= ticks
/ (rb
.HZ
* 3600)
211 ticks
= ticks
- (rb
.HZ
* hours
* 3600)
212 local minutes
= ticks
/ (rb
.HZ
* 60)
213 ticks
= ticks
- (rb
.HZ
* minutes
* 60)
214 local seconds
= ticks
/ rb
.HZ
215 ticks
= ticks
- (rb
.HZ
* seconds
)
219 return string.format("%2d:%02d:%02d.%02d", hours
, minutes
, seconds
, cs
)
222 local last_ticks
= laps
[lap
] - laps
[lap
-1]
223 local last_hours
= last_ticks
/ (rb
.HZ
* 3600)
224 last_ticks
= last_ticks
- (rb
.HZ
* last_hours
* 3600)
225 local last_minutes
= last_ticks
/ (rb
.HZ
* 60)
226 last_ticks
= last_ticks
- (rb
.HZ
* last_minutes
* 60)
227 local last_seconds
= last_ticks
/ rb
.HZ
228 last_ticks
= last_ticks
- (rb
.HZ
* last_seconds
)
229 local last_cs
= last_ticks
231 return string.format("%2d %2d:%02d:%02d.%02d [%2d:%02d:%02d.%02d]",
232 lap
, hours
, minutes
, seconds
, cs
, last_hours
,
233 last_minutes
, last_seconds
, last_cs
)
235 return string.format("%2d %2d:%02d:%02d.%02d", lap
, hours
, minutes
, seconds
, cs
)
241 function toboolean(v
)
245 function arrangeButtons(btns
)
246 local totalWidth
, totalHeight
, maxWidth
, maxHeight
, vp
= 0, 0, 0, 0
247 local width
, row
= 0, 0
248 local items
, num_rows
250 for i
, btn
in pairs(btns
) do
251 maxHeight
= math
.max(maxHeight
, btn
.height
)
252 totalWidth
= totalWidth
+ btn
.width
256 for _
, btn
in pairs(btns
) do
257 btn
.height
= maxHeight
260 num_rows
= totalWidth
/ rb
.LCD_WIDTH
262 for _
, btn
in pairs(btns
) do
264 btn
.y
= rb
.LCD_HEIGHT
- ((num_rows
- row
) * maxHeight
)
265 width
= width
+ btn
.width
266 if (width
> rb
.LCD_WIDTH
- 5) then -- 5 is rounding margin
274 width
= rb
.LCD_WIDTH
,
275 height
= rb
.LCD_HEIGHT
- num_rows
*maxHeight
- 2
278 for k
, v
in pairs(vp
) do
283 rb
.touchscreen_set_mode(rb
.TOUCHSCREEN_POINT
)
287 local third
= rb
.LCD_WIDTH
/3
290 Button
:new({name
= "startTimer", label
= "Start", width
= third
}),
291 Button
:new({name
= "stopTimer", label
= "Stop", width
= third
}),
292 Button
:new({name
= "resetTimer", label
= "Reset", width
= rb
.LCD_WIDTH
-third
*2}), -- correct rounding error
293 Button
:new({name
= "newLap", label
= "New Lap", width
= third
*2}),
294 Button
:new({name
= "exitApp", label
= "Quit", width
= rb
.LCD_WIDTH
-third
*2}) -- correct rounding error
299 for _
, btn
in pairs(btns
) do
306 local action
= rb
.get_action(rb
.contexts
.CONTEXT_STD
, 0)
308 if (action
== rb
.actions
.ACTION_TOUCHSCREEN
) then
309 local btn
, x
, y
= rb
.action_get_touchscreen_press()
311 if LapsView
:checkForScroll(btn
, x
, y
) then
313 elseif btn
== rb
.buttons
.BUTTON_REL
then
314 for _
, btn
in pairs(btns
) do
315 local name
= btn
.name
316 if (btn
:isPressed(x
, y
)) then
317 if name
== "exitApp" then
318 action
= rb
.actions
.ACTION_STD_CANCEL
320 LapsView
[name
](LapsView
)
330 until action
== rb
.actions
.ACTION_STD_CANCEL