portapack-mayhem/firmware/application/external/stopwatch/ui_stopwatch.cpp
sommermorgentraum 0ce6ea8318
stopwatch opt (#2578)
* stopwatch opt

* comments

* format

* fxi ms display when user tune display level
2025-03-21 10:34:10 +08:00

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