mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-08-15 09:56:00 -04:00
309 lines
No EOL
11 KiB
C++
309 lines
No EOL
11 KiB
C++
/*
|
|
* Copyright 2025 Mark Thompson
|
|
* copyleft Mr. Robot of F.Society
|
|
*
|
|
* This file is part of PortaPack.
|
|
*
|
|
* 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, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "ui_stopwatch.hpp"
|
|
#include "portapack.hpp"
|
|
#include "ch.h"
|
|
|
|
using namespace portapack;
|
|
|
|
namespace ui::external_app::stopwatch {
|
|
|
|
// clang-format off
|
|
// clang-format doesn't allow use as this way but it's not worth to have const var for this thing. too expensive
|
|
|
|
// minute
|
|
#define TOTAL_M1_POS {0 * 8 * 4, 3 * 16}
|
|
#define TOTAL_M2_POS {1 * 8 * 4, 3 * 16}
|
|
|
|
// sec
|
|
#define TOTAL_S1_POS {2 * 8 * 4 + 24, 3 * 16}
|
|
#define TOTAL_S2_POS {3 * 8 * 4 + 24, 3 * 16}
|
|
|
|
// ms
|
|
#define TOTAL_MS1_POS {4 * 8 * 4 + 7 * 8, 3 * 16 + 24}
|
|
#define TOTAL_MS2_POS {5 * 8 * 4 + 5 * 8, 3 * 16 + 24}
|
|
#define TOTAL_MS3_POS {6 * 8 * 4 + 3 * 8, 3 * 16 + 24}
|
|
|
|
#define LAP_Y 9 * 16
|
|
|
|
//lap min
|
|
#define LAP_M1_POS {0 * 8 * 4, LAP_Y}
|
|
#define LAP_M2_POS {1 * 8 * 4, LAP_Y}
|
|
|
|
//lap sec
|
|
#define LAP_S1_POS {2 * 8 * 4 + 24, LAP_Y}
|
|
#define LAP_S2_POS {3 * 8 * 4 + 24, LAP_Y}
|
|
|
|
//lap ms
|
|
#define LAP_MS1_POS {4 * 8 * 4 + 7 * 8, LAP_Y + 24}
|
|
#define LAP_MS2_POS {5 * 8 * 4 + 5 * 8, LAP_Y + 24}
|
|
#define LAP_MS3_POS {6 * 8 * 4 + 3 * 8, LAP_Y + 24}
|
|
// clang-format on
|
|
|
|
StopwatchView::StopwatchView(NavigationView& nav)
|
|
: painter{} {
|
|
add_children({
|
|
&labels,
|
|
&button_run_stop,
|
|
&button_reset_lap,
|
|
&button_done,
|
|
&options_ms_display_level,
|
|
});
|
|
|
|
button_run_stop.on_select = [this](Button&) {
|
|
if (running)
|
|
stop();
|
|
else
|
|
run();
|
|
};
|
|
|
|
button_reset_lap.on_select = [this](Button&) {
|
|
if (running)
|
|
lap();
|
|
else
|
|
reset();
|
|
};
|
|
|
|
button_done.on_select = [&nav](Button&) {
|
|
nav.pop();
|
|
};
|
|
|
|
options_ms_display_level.on_change = [&](size_t, ui::OptionsField::value_t) {
|
|
clean_ms_display(options_ms_display_level.selected_index());
|
|
};
|
|
|
|
options_ms_display_level.set_selected_index(0);
|
|
|
|
refresh_painting();
|
|
}
|
|
|
|
void StopwatchView::focus() {
|
|
button_run_stop.focus();
|
|
}
|
|
|
|
void StopwatchView::run() {
|
|
options_ms_display_level.hidden(true);
|
|
// ^ this won't take efferts if don't set_dirty, but needed to let user can't change during ticking
|
|
if (!paused) refresh_painting();
|
|
paused = false;
|
|
running = true;
|
|
start_time = chTimeNow() - previously_elapsed;
|
|
button_run_stop.set_text("STOP");
|
|
button_reset_lap.set_text("LAP");
|
|
}
|
|
|
|
void StopwatchView::stop() {
|
|
options_ms_display_level.hidden(false);
|
|
running = false;
|
|
paused = true;
|
|
end_time = chTimeNow();
|
|
previously_elapsed = end_time - start_time;
|
|
button_run_stop.set_text("START");
|
|
button_reset_lap.set_text("RESET");
|
|
}
|
|
|
|
void StopwatchView::reset() {
|
|
lap_time = end_time = start_time = previously_elapsed = 0;
|
|
for (uint8_t i = 0; i < 7; i++) {
|
|
refresh_painting();
|
|
}
|
|
refresh_painting();
|
|
set_dirty();
|
|
}
|
|
|
|
void StopwatchView::lap() {
|
|
lap_time = chTimeNow();
|
|
}
|
|
|
|
void StopwatchView::paint(Painter&) {
|
|
}
|
|
|
|
void StopwatchView::refresh_painting() {
|
|
// minute
|
|
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
|
|
// sec
|
|
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
|
|
// ms
|
|
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
|
|
// lap min
|
|
painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
|
|
// lap sec
|
|
painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
|
|
|
|
// lap ms
|
|
painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
}
|
|
|
|
/*when user seted it to display more, then display less later, this prevent the remain value exist on screen*/
|
|
void StopwatchView::clean_ms_display(uint8_t level) {
|
|
level++;
|
|
switch (level) {
|
|
case 0:
|
|
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
break;
|
|
case 1:
|
|
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
break;
|
|
case 2:
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void StopwatchView::resume_last() {
|
|
// minute
|
|
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, last_displayed[0], 4);
|
|
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, last_displayed[1], 4);
|
|
|
|
// sec
|
|
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, last_displayed[2], 4);
|
|
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, last_displayed[3], 4);
|
|
|
|
// ms
|
|
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, last_displayed[4], 2);
|
|
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, last_displayed[5], 2);
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, last_displayed[6], 2);
|
|
}
|
|
|
|
/* NB:
|
|
* Due to the flaw of dirty management, it's using work around, to reduce screen flickering:
|
|
*
|
|
* for example when xx:15:xxx turn to xx:16:xxx, it actually only paint 6, the 1 is old,
|
|
* but not dirty, so we can see without flikering.
|
|
*
|
|
* So with these work around, it won't show false info, but bare in mind that it could be, if you add more things.
|
|
*/
|
|
void StopwatchView::frame_sync() {
|
|
uint32_t elapsed_ticks = 0;
|
|
|
|
if (running) {
|
|
end_time = chTimeNow();
|
|
elapsed_ticks = end_time - start_time;
|
|
} else if (previously_elapsed > 0) {
|
|
elapsed_ticks = previously_elapsed;
|
|
}
|
|
|
|
constexpr uint32_t TICKS_PER_SECOND = CH_FREQUENCY;
|
|
constexpr uint32_t TICKS_PER_MINUTE = TICKS_PER_SECOND * 60;
|
|
|
|
uint32_t minutes = elapsed_ticks / TICKS_PER_MINUTE;
|
|
uint32_t seconds = (elapsed_ticks % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
|
|
uint32_t milliseconds = ((elapsed_ticks % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
|
|
|
|
// minute
|
|
if (last_displayed[0] != (minutes / 10) % 10)
|
|
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_red, '0' + (minutes / 10) % 10, 4);
|
|
if (last_displayed[1] != minutes % 10)
|
|
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_red, '0' + minutes % 10, 4);
|
|
|
|
// sec
|
|
if (last_displayed[2] != (seconds / 10) % 10)
|
|
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_green, '0' + (seconds / 10) % 10, 4);
|
|
if (last_displayed[3] != seconds % 10)
|
|
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_green, '0' + seconds % 10, 4);
|
|
|
|
// ms
|
|
/* v place holder to aligh logic*/
|
|
if ((true) && last_displayed[4] != (milliseconds / 100) % 10)
|
|
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 100) % 10, 2);
|
|
if ((options_ms_display_level.selected_index() >= 1) && last_displayed[5] != (milliseconds / 10) % 10)
|
|
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 10) % 10, 2);
|
|
if ((options_ms_display_level.selected_index() >= 2) && last_displayed[6] != milliseconds % 10)
|
|
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_yellow, '0' + milliseconds % 10, 2);
|
|
|
|
// min
|
|
last_displayed[0] = (minutes / 10) % 10;
|
|
last_displayed[1] = minutes % 10;
|
|
// sec
|
|
last_displayed[2] = (seconds / 10) % 10;
|
|
last_displayed[3] = seconds % 10;
|
|
// ms
|
|
last_displayed[4] = (milliseconds / 100) % 10;
|
|
last_displayed[5] = (milliseconds / 10) % 10;
|
|
last_displayed[6] = milliseconds % 10;
|
|
|
|
if (lap_time > 0) {
|
|
uint32_t lap_elapsed = lap_time - start_time;
|
|
|
|
uint32_t lap_minutes = lap_elapsed / TICKS_PER_MINUTE;
|
|
uint32_t lap_seconds = (lap_elapsed % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
|
|
uint32_t lap_milliseconds = ((lap_elapsed % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
|
|
|
|
// lap min
|
|
if (lap_last_displayed[0] == (lap_minutes / 10) % 10) {
|
|
painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, '0' + (lap_minutes / 10) % 10, 4);
|
|
}
|
|
if (lap_last_displayed[1] == lap_minutes % 10) {
|
|
painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, '0' + lap_minutes % 10, 4);
|
|
}
|
|
|
|
// lap sec
|
|
if (lap_last_displayed[2] == (lap_seconds / 10) % 10) {
|
|
painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, '0' + (lap_seconds / 10) % 10, 4);
|
|
}
|
|
if (lap_last_displayed[3] == lap_seconds % 10) {
|
|
painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, '0' + lap_seconds % 10, 4);
|
|
}
|
|
|
|
// lap ms
|
|
if (lap_last_displayed[4] == (lap_milliseconds / 100) % 10) {
|
|
painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 100) % 10, 2);
|
|
}
|
|
if (lap_last_displayed[5] == (lap_milliseconds / 10) % 10) {
|
|
painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 10) % 10, 2);
|
|
}
|
|
if (lap_last_displayed[6] == lap_milliseconds % 10) {
|
|
painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, '0' + lap_milliseconds % 10, 2);
|
|
}
|
|
|
|
// lp m
|
|
lap_last_displayed[0] = (lap_minutes / 10) % 10;
|
|
lap_last_displayed[1] = lap_minutes % 10;
|
|
|
|
// lp s
|
|
lap_last_displayed[2] = (lap_seconds / 10) % 10;
|
|
lap_last_displayed[3] = lap_seconds % 10;
|
|
|
|
// lp mss
|
|
lap_last_displayed[4] = (lap_milliseconds / 100) % 10;
|
|
lap_last_displayed[5] = (lap_milliseconds / 10) % 10;
|
|
lap_last_displayed[6] = lap_milliseconds % 10;
|
|
}
|
|
}
|
|
|
|
} // namespace ui::external_app::stopwatch
|