Something went wrong. Try again.
A modern, network-enabled music player platform built on Rockbox technology. rockboxd.tsiry-sandratraina.com
rust deno navidrome airplay libadwaita zig mpris snapcast mpd rockbox audio subsonic
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333--[[ __________ __ ___. Open \______ \ ____ ____ | | _\_ |__ _______ ___ Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ \/ \/ \/ \/ \/
Port of Stopwatch to Lua for touchscreen targets. Original copyright: Copyright (C) 2004 Mike Holden
Copyright (C) 2009 by Maurus Cuelenaere
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
]]--
require "actions"require "buttons"
STOPWATCH_FILE = rb.PLUGIN_APPS_DATA_DIR .. "/stopwatch.dat"
local LapsView = { lapTimes = {}, timer = { counting = false, prevTotal = 0, startAt = 0, current = 0 }, vp = { x = 80, y = 0, width = rb.LCD_WIDTH - 80, height = rb.LCD_HEIGHT, font = rb.FONT_UI, fg_pattern = rb.lcd_get_foreground() }, scroll = { prevY = 0, cursorPos = 0 }}
function LapsView:init() local _, _, h = rb.font_getstringsize("", self.vp.font)
self.vp.maxLaps = self.vp.height / h self.vp.lapHeight = h
self:loadState()end
function LapsView:display() rb.set_viewport(self.vp) rb.clear_viewport()
local nrOfLaps = math.min(self.vp.maxLaps, #self.lapTimes) rb.lcd_puts_scroll(0, 0, ticksToString(self.timer.current))
for i=1, nrOfLaps do local idx = #self.lapTimes - self.scroll.cursorPos - i + 1 if self.lapTimes[idx] ~= nil then rb.lcd_puts_scroll(0, i, ticksToString(self.lapTimes, idx)) end end
rb.set_viewport(nil)end
function LapsView:checkForScroll(btn, x, y) if x > self.vp.x and x < self.vp.x + self.vp.width and y > self.vp.y and y < self.vp.y + self.vp.height then
if bit.band(btn, rb.buttons.BUTTON_REL) == rb.buttons.BUTTON_REL then self.scroll.prevY = 0 else if #self.lapTimes > self.vp.maxLaps and self.scroll.prevY ~= 0 then self.scroll.cursorPos = self.scroll.cursorPos - (y - self.scroll.prevY) / self.vp.lapHeight
local maxLaps = math.min(self.vp.maxLaps, #self.lapTimes) if self.scroll.cursorPos < 0 then self.scroll.cursorPos = 0 elseif self.scroll.cursorPos >= maxLaps then self.scroll.cursorPos = maxLaps end end
self.scroll.prevY = y end
return true else return false endend
function LapsView:incTimer() if self.timer.counting then self.timer.current = self.timer.prevTotal + rb.current_tick() - self.timer.startAt else self.timer.current = self.timer.prevTotal endend
function LapsView:startTimer() self.timer.startAt = rb.current_tick() self.timer.currentLap = self.timer.prevTotal self.timer.counting = trueend
function LapsView:stopTimer() self.timer.prevTotal = self.timer.prevTotal + rb.current_tick() - self.timer.startAt self.timer.counting = falseend
function LapsView:newLap() table.insert(self.lapTimes, self.timer.current)end
function LapsView:resetTimer() self.lapTimes = {} self.timer.counting = false self.timer.current, self.timer.prevTotal, self.timer.startAt = 0, 0, 0 self.scroll.cursorPos = 0end
function LapsView:saveState() local fd = assert(io.open(STOPWATCH_FILE, "w"))
for _, v in ipairs({"current", "startAt", "prevTotal", "counting"}) do assert(fd:write(tostring(self.timer[v]) .. "\n")) end for _, v in ipairs(self.lapTimes) do assert(fd:write(tostring(v) .. "\n")) end
fd:close()end
function LapsView:loadState() local fd = io.open(STOPWATCH_FILE, "r") if fd == nil then return end
for _, v in ipairs({"current", "startAt", "prevTotal"}) do self.timer[v] = tonumber(fd:read("*line")) end self.timer.counting = toboolean(fd:read("*line"))
local line = fd:read("*line") while line do table.insert(self.lapTimes, tonumber(line)) line = fd:read("*line") end
fd:close()end
local Button = { x = 0, y = 0, width = 80, height = 50, label = ""}
function Button:new(o) local o = o or {}
if o.label then local _, w, h = rb.font_getstringsize(o.label, LapsView.vp.font) o.width = math.max(5 * w / 4,o.width) o.height = 3 * h / 2 end
setmetatable(o, self) self.__index = self return oend
function Button:draw() local _, w, h = rb.font_getstringsize(self.label, LapsView.vp.font) local x, y = (2 * self.x + self.width - w) / 2, (2 * self.y + self.height - h) / 2
rb.lcd_drawrect(self.x, self.y, self.width, self.height) rb.lcd_putsxy(x, y, self.label)end
function Button:isPressed(x, y) return x > self.x and x < self.x + self.width and y > self.y and y < self.y + self.heightend
-- Helper functionfunction ticksToString(laps, lap) local ticks = type(laps) == "table" and laps[lap] or laps lap = lap or 0
local hours = ticks / (rb.HZ * 3600) ticks = ticks - (rb.HZ * hours * 3600) local minutes = ticks / (rb.HZ * 60) ticks = ticks - (rb.HZ * minutes * 60) local seconds = ticks / rb.HZ ticks = ticks - (rb.HZ * seconds) local cs = ticks
if (lap == 0) then return string.format("%2d:%02d:%02d.%02d", hours, minutes, seconds, cs) else if (lap > 1) then local last_ticks = laps[lap] - laps[lap-1] local last_hours = last_ticks / (rb.HZ * 3600) last_ticks = last_ticks - (rb.HZ * last_hours * 3600) local last_minutes = last_ticks / (rb.HZ * 60) last_ticks = last_ticks - (rb.HZ * last_minutes * 60) local last_seconds = last_ticks / rb.HZ last_ticks = last_ticks - (rb.HZ * last_seconds) local last_cs = last_ticks
return string.format("%2d %2d:%02d:%02d.%02d [%2d:%02d:%02d.%02d]", lap, hours, minutes, seconds, cs, last_hours, last_minutes, last_seconds, last_cs) else return string.format("%2d %2d:%02d:%02d.%02d", lap, hours, minutes, seconds, cs) end endend
-- Helper functionfunction toboolean(v) return v == "true"end
function arrangeButtons(btns) local totalWidth, totalHeight, maxWidth, maxHeight, vp = 0, 0, 0, 0 local width, row = 0, 0 local items, num_rows
for i, btn in pairs(btns) do maxHeight = math.max(maxHeight, btn.height) totalWidth = totalWidth + btn.width items = i end
for _, btn in pairs(btns) do btn.height = maxHeight end
num_rows = totalWidth / rb.LCD_WIDTH
for _, btn in pairs(btns) do btn.x = width btn.y = rb.LCD_HEIGHT - ((num_rows - row) * maxHeight) width = width + btn.width if (width > rb.LCD_WIDTH - 5) then -- 5 is rounding margin width = 0 row = row+1 end end vp = { x = 0, y = 0, width = rb.LCD_WIDTH, height = rb.LCD_HEIGHT - num_rows*maxHeight - 2 }
for k, v in pairs(vp) do LapsView.vp[k] = v endend
rb.touchscreen_mode(rb.TOUCHSCREEN_POINT)
LapsView:init()
local third = rb.LCD_WIDTH/3
local btns = { Button:new({name = "startTimer", label = "Start", width = third}), Button:new({name = "stopTimer", label = "Stop", width = third}), Button:new({name = "resetTimer", label = "Reset", width = rb.LCD_WIDTH-third*2}), -- correct rounding error Button:new({name = "newLap", label = "New Lap", width = third*2}), Button:new({name = "exitApp", label = "Quit", width = rb.LCD_WIDTH-third*2}) -- correct rounding error }
arrangeButtons(btns)
for _, btn in pairs(btns) do btn:draw()end
repeat LapsView:incTimer()
local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
if (action == rb.actions.ACTION_TOUCHSCREEN) then local btn, x, y = rb.action_get_touchscreen_press()
if LapsView:checkForScroll(btn, x, y) then -- Don't do anything elseif btn == rb.buttons.BUTTON_REL then for _, btn in pairs(btns) do local name = btn.name if (btn:isPressed(x, y)) then if name == "exitApp" then action = rb.actions.ACTION_STD_CANCEL else LapsView[name](LapsView) end end end end end
LapsView:display() rb.lcd_update() rb.sleep(rb.HZ/50)until action == rb.actions.ACTION_STD_CANCEL
LapsView:saveState()