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
= "/.rockbox/rocks/apps/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
)
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 for _
, btn
in pairs(btns
) do
248 totalWidth
= totalWidth
+ btn
.width
249 totalHeight
= totalHeight
+ btn
.height
250 maxHeight
= math
.max(maxHeight
, btn
.height
)
251 maxWidth
= math
.max(maxWidth
, btn
.width
)
254 if totalWidth
<= rb
.LCD_WIDTH
then
256 for _
, btn
in pairs(btns
) do
257 btn
.y
= rb
.LCD_HEIGHT
- maxHeight
260 temp
= temp
+ btn
.width
266 width
= rb
.LCD_WIDTH
,
267 height
= rb
.LCD_HEIGHT
- maxHeight
269 elseif totalHeight
<= rb
.LCD_HEIGHT
then
271 for _
, btn
in pairs(btns
) do
272 btn
.x
= rb
.LCD_WIDTH
- maxWidth
275 temp
= temp
+ btn
.height
281 width
= rb
.LCD_WIDTH
- maxWidth
,
282 height
= rb
.LCD_HEIGHT
285 error("Can't arrange the buttons according to your screen's resolution!")
288 for k
, v
in pairs(vp
) do
293 rb
.touchscreen_set_mode(rb
.TOUCHSCREEN_POINT
)
298 Button
:new({name
= "startTimer", label
= "Start"}),
299 Button
:new({name
= "stopTimer", label
= "Stop"}),
300 Button
:new({name
= "newLap", label
= "New Lap"}),
301 Button
:new({name
= "resetTimer", label
= "Reset"}),
302 Button
:new({name
= "exitApp", label
= "Quit"})
307 for _
, btn
in pairs(btns
) do
314 local action
= rb
.get_action(rb
.contexts
.CONTEXT_STD
, 0)
316 if (action
== rb
.actions
.ACTION_TOUCHSCREEN
) then
317 local btn
, x
, y
= rb
.action_get_touchscreen_press()
319 if LapsView
:checkForScroll(btn
, x
, y
) then
321 elseif btn
== rb
.buttons
.BUTTON_REL
then
322 for _
, btn
in pairs(btns
) do
323 local name
= btn
.name
324 if (btn
:isPressed(x
, y
)) then
325 if name
== "exitApp" then
326 action
= rb
.actions
.ACTION_STD_CANCEL
328 LapsView
[name
](LapsView
)
338 until action
== rb
.actions
.ACTION_STD_CANCEL