A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9
10 Port of Stopwatch to Lua for touchscreen targets.
11 Original copyright: Copyright (C) 2004 Mike Holden
12
13 Copyright (C) 2009 by Maurus Cuelenaere
14
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.
19
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
22
23]]--
24
25require "actions"
26require "buttons"
27
28STOPWATCH_FILE = rb.PLUGIN_APPS_DATA_DIR .. "/stopwatch.dat"
29
30
31local LapsView = {
32 lapTimes = {},
33 timer = {
34 counting = false,
35 prevTotal = 0,
36 startAt = 0,
37 current = 0
38 },
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()
46 },
47 scroll = {
48 prevY = 0,
49 cursorPos = 0
50 }
51}
52
53function LapsView:init()
54 local _, _, h = rb.font_getstringsize("", self.vp.font)
55
56 self.vp.maxLaps = self.vp.height / h
57 self.vp.lapHeight = h
58
59 self:loadState()
60end
61
62function LapsView:display()
63 rb.set_viewport(self.vp)
64 rb.clear_viewport()
65
66 local nrOfLaps = math.min(self.vp.maxLaps, #self.lapTimes)
67 rb.lcd_puts_scroll(0, 0, ticksToString(self.timer.current))
68
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
75
76 rb.set_viewport(nil)
77end
78
79function 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
82
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
89
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
97
98 self.scroll.prevY = y
99 end
100
101 return true
102 else
103 return false
104 end
105end
106
107function 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
113 end
114end
115
116function LapsView:startTimer()
117 self.timer.startAt = rb.current_tick()
118 self.timer.currentLap = self.timer.prevTotal
119 self.timer.counting = true
120end
121
122function LapsView:stopTimer()
123 self.timer.prevTotal = self.timer.prevTotal + rb.current_tick()
124 - self.timer.startAt
125 self.timer.counting = false
126end
127
128function LapsView:newLap()
129 table.insert(self.lapTimes, self.timer.current)
130end
131
132function 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
137end
138
139function LapsView:saveState()
140 local fd = assert(io.open(STOPWATCH_FILE, "w"))
141
142 for _, v in ipairs({"current", "startAt", "prevTotal", "counting"}) do
143 assert(fd:write(tostring(self.timer[v]) .. "\n"))
144 end
145 for _, v in ipairs(self.lapTimes) do
146 assert(fd:write(tostring(v) .. "\n"))
147 end
148
149 fd:close()
150end
151
152function LapsView:loadState()
153 local fd = io.open(STOPWATCH_FILE, "r")
154 if fd == nil then return end
155
156 for _, v in ipairs({"current", "startAt", "prevTotal"}) do
157 self.timer[v] = tonumber(fd:read("*line"))
158 end
159 self.timer.counting = toboolean(fd:read("*line"))
160
161 local line = fd:read("*line")
162 while line do
163 table.insert(self.lapTimes, tonumber(line))
164 line = fd:read("*line")
165 end
166
167 fd:close()
168end
169
170local Button = {
171 x = 0,
172 y = 0,
173 width = 80,
174 height = 50,
175 label = ""
176}
177
178function Button:new(o)
179 local o = o or {}
180
181 if o.label then
182 local _, w, h = rb.font_getstringsize(o.label, LapsView.vp.font)
183 o.width = math.max(5 * w / 4,o.width)
184 o.height = 3 * h / 2
185 end
186
187 setmetatable(o, self)
188 self.__index = self
189 return o
190end
191
192function 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
195
196 rb.lcd_drawrect(self.x, self.y, self.width, self.height)
197 rb.lcd_putsxy(x, y, self.label)
198end
199
200function 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
203end
204
205-- Helper function
206function ticksToString(laps, lap)
207 local ticks = type(laps) == "table" and laps[lap] or laps
208 lap = lap or 0
209
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
217
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
230
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)
236 end
237 end
238end
239
240-- Helper function
241function toboolean(v)
242 return v == "true"
243end
244
245function arrangeButtons(btns)
246 local totalWidth, totalHeight, maxWidth, maxHeight, vp = 0, 0, 0, 0
247 local width, row = 0, 0
248 local items, num_rows
249
250 for i, btn in pairs(btns) do
251 maxHeight = math.max(maxHeight, btn.height)
252 totalWidth = totalWidth + btn.width
253 items = i
254 end
255
256 for _, btn in pairs(btns) do
257 btn.height = maxHeight
258 end
259
260 num_rows = totalWidth / rb.LCD_WIDTH
261
262 for _, btn in pairs(btns) do
263 btn.x = width
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
267 width = 0
268 row = row+1
269 end
270 end
271 vp = {
272 x = 0,
273 y = 0,
274 width = rb.LCD_WIDTH,
275 height = rb.LCD_HEIGHT - num_rows*maxHeight - 2
276 }
277
278 for k, v in pairs(vp) do
279 LapsView.vp[k] = v
280 end
281end
282
283rb.touchscreen_mode(rb.TOUCHSCREEN_POINT)
284
285LapsView:init()
286
287local third = rb.LCD_WIDTH/3
288
289local btns = {
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
295 }
296
297arrangeButtons(btns)
298
299for _, btn in pairs(btns) do
300 btn:draw()
301end
302
303repeat
304 LapsView:incTimer()
305
306 local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
307
308 if (action == rb.actions.ACTION_TOUCHSCREEN) then
309 local btn, x, y = rb.action_get_touchscreen_press()
310
311 if LapsView:checkForScroll(btn, x, y) then
312 -- Don't do anything
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
319 else
320 LapsView[name](LapsView)
321 end
322 end
323 end
324 end
325 end
326
327 LapsView:display()
328 rb.lcd_update()
329 rb.sleep(rb.HZ/50)
330until action == rb.actions.ACTION_STD_CANCEL
331
332LapsView:saveState()
333