Remove plugin-specific defines from the pdbox makefile, and add them to m_pd.h, which...
[kugel-rb.git] / apps / plugins / stopwatch.lua
blobc8fac3c0006404947d83312158d03ebdc1d19e4d
1 --[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
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.
23 ]]--
25 require "actions"
26 require "buttons"
28 STOPWATCH_FILE = "/.rockbox/rocks/apps/stopwatch.dat"
31 local LapsView = {
32 lapTimes = {},
33 timer = {
34 counting = false,
35 prevTotal = 0,
36 startAt = 0,
37 current = 0
39 vp = {
40 x = 80,
41 y = 0,
42 width = rb.LCD_WIDTH - 80,
43 height = rb.LCD_HEIGHT,
44 font = rb.FONT_UI,
45 fg_pattern = rb.lcd_get_foreground()
47 scroll = {
48 prevY = 0,
49 cursorPos = 0
53 function LapsView:init()
54 local _, _, h = rb.font_getstringsize("", self.vp.font)
56 self.vp.maxLaps = self.vp.height / h
57 self.vp.lapHeight = h
59 self:loadState()
60 end
62 function LapsView:display()
63 rb.set_viewport(self.vp)
64 rb.clear_viewport()
66 local nrOfLaps = math.min(self.vp.maxLaps, #self.lapTimes)
67 rb.lcd_puts_scroll(0, 0, ticksToString(self.timer.current))
69 for i=1, nrOfLaps do
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))
73 end
74 end
76 rb.set_viewport(nil)
77 end
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
84 self.scroll.prevY = 0
85 else
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
95 end
96 end
98 self.scroll.prevY = y
99 end
101 return true
102 else
103 return false
107 function LapsView:incTimer()
108 if self.timer.counting then
109 self.timer.current = self.timer.prevTotal + rb.current_tick()
110 - self.timer.startAt
111 else
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()
124 - self.timer.startAt
125 self.timer.counting = false
128 function LapsView:newLap()
129 table.insert(self.lapTimes, self.timer.current)
132 function LapsView:resetTimer()
133 self.lapTimes = {}
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"))
149 fd:close()
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")
162 while line do
163 table.insert(self.lapTimes, tonumber(line))
164 line = fd:read("*line")
167 fd:close()
170 local Button = {
171 x = 0,
172 y = 0,
173 width = 80,
174 height = 50,
175 label = ""
178 function Button:new(o)
179 local o = o or {}
181 if o.label then
182 local _, w, h = rb.font_getstringsize(o.label, LapsView.vp.font)
183 o.width = 5 * w / 4
184 o.height = 3 * h / 2
187 setmetatable(o, self)
188 self.__index = self
189 return o
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
205 -- Helper function
206 function ticksToString(laps, lap)
207 local ticks = type(laps) == "table" and laps[lap] or laps
208 lap = lap or 0
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)
216 local cs = ticks
218 if (lap == 0) then
219 return string.format("%2d:%02d:%02d.%02d", hours, minutes, seconds, cs)
220 else
221 if (lap > 1) then
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)
234 else
235 return string.format("%2d %2d:%02d:%02d.%02d", lap, hours, minutes, seconds, cs)
240 -- Helper function
241 function toboolean(v)
242 return v == "true"
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
255 local temp = 0
256 for _, btn in pairs(btns) do
257 btn.y = rb.LCD_HEIGHT - maxHeight
258 btn.x = temp
260 temp = temp + btn.width
263 vp = {
264 x = 0,
265 y = 0,
266 width = rb.LCD_WIDTH,
267 height = rb.LCD_HEIGHT - maxHeight
269 elseif totalHeight <= rb.LCD_HEIGHT then
270 local temp = 0
271 for _, btn in pairs(btns) do
272 btn.x = rb.LCD_WIDTH - maxWidth
273 btn.y = temp
275 temp = temp + btn.height
278 vp = {
279 x = 0,
280 y = 0,
281 width = rb.LCD_WIDTH - maxWidth,
282 height = rb.LCD_HEIGHT
284 else
285 error("Can't arrange the buttons according to your screen's resolution!")
288 for k, v in pairs(vp) do
289 LapsView.vp[k] = v
293 rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
295 LapsView:init()
297 local btns = {
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"})
305 arrangeButtons(btns)
307 for _, btn in pairs(btns) do
308 btn:draw()
311 repeat
312 LapsView:incTimer()
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
320 -- Don't do anything
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
327 else
328 LapsView[name](LapsView)
335 LapsView:display()
336 rb.lcd_update()
337 rb.sleep(rb.HZ/50)
338 until action == rb.actions.ACTION_STD_CANCEL
340 LapsView:saveState()