mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-07-26 10:38:52 +00:00
Notifications (#3002)
This commit is contained in:
@@ -258,6 +258,7 @@ set(CPPSRC
|
||||
ook_file.cpp
|
||||
ui_baseband_stats_view.cpp
|
||||
ui_navigation.cpp
|
||||
ui_notifications.cpp
|
||||
ui_record_view.cpp
|
||||
ui_sd_card_status_view.cpp
|
||||
ui/ui_alphanum.cpp
|
||||
|
||||
@@ -225,7 +225,7 @@ void BLETxView::send_packet() {
|
||||
uint8_t min = 0x00;
|
||||
uint8_t max = 0x0F;
|
||||
|
||||
hexDigit = min + std::rand() % (max - min + 1);
|
||||
hexDigit = min + rand() % (max - min + 1);
|
||||
} break;
|
||||
default:
|
||||
hexDigit = 0;
|
||||
|
||||
@@ -2640,7 +2640,7 @@ void DebugPmemView::update() {
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
@@ -2650,10 +2650,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
nav_.pop();
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, rand());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
pen_color = std::rand();
|
||||
pen_color = rand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -2676,7 +2676,7 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
|
||||
void DebugScreenTest::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||
painter.draw_string({10 * 8, screen_height / 2}, *Theme::getInstance()->bg_darkest, "Use Stylus");
|
||||
pen_color = std::rand();
|
||||
pen_color = rand();
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
@@ -256,7 +256,9 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
if (!w->hidden()) {
|
||||
// To achieve reverse depth ordering (last object drawn is
|
||||
// considered "top"), descend first.
|
||||
for (const auto child : w->children()) {
|
||||
auto& children = w->children();
|
||||
for (auto it = children.rbegin(); it != children.rend(); ++it) { // reverse, bc the lastly added will be "top" if overlaps
|
||||
const auto& child = *it;
|
||||
const auto touched_widget = touch_widget(child, event);
|
||||
if (touched_widget) {
|
||||
return touched_widget;
|
||||
|
||||
+1
-1
@@ -339,7 +339,7 @@ void AdultToysView::randomizeMac() {
|
||||
}
|
||||
|
||||
void AdultToysView::randomChn() {
|
||||
channel_number = 37 + std::rand() % (39 - 37 + 1);
|
||||
channel_number = 37 + rand() % (39 - 37 + 1);
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ void BlackjackView::paint(Painter& painter) {
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
init_deck();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ void BLESpamView::reset() {
|
||||
}
|
||||
|
||||
void BLESpamView::randomChn() {
|
||||
channel_number = 37 + std::rand() % (39 - 37 + 1);
|
||||
channel_number = 37 + rand() % (39 - 37 + 1);
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -655,7 +655,7 @@ void BreakoutView::paint(Painter& painter) {
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ void DinoGameView::paint(Painter& painter) {
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -204,11 +204,11 @@ void spawn_entity(uint8_t type, uint8_t x, uint8_t y) {
|
||||
|
||||
void spawn_random_entity(uint8_t type) {
|
||||
if (num_entities >= MAX_ENTITIES) return;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
uint8_t spawn_x, spawn_y;
|
||||
do {
|
||||
spawn_x = std::rand() % LEVEL_WIDTH;
|
||||
spawn_y = std::rand() % LEVEL_HEIGHT;
|
||||
spawn_x = rand() % LEVEL_WIDTH;
|
||||
spawn_y = rand() % LEVEL_HEIGHT;
|
||||
} while (get_block_at(spawn_x, spawn_y) == 0xF ||
|
||||
(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y));
|
||||
|
||||
@@ -227,9 +227,9 @@ void remove_entity(uint8_t index) {
|
||||
bool spawned = false;
|
||||
|
||||
while (!spawned && attempts < 50) {
|
||||
std::srand(LPC_RTC->CTIME0 + attempts);
|
||||
uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
|
||||
uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
|
||||
srand(LPC_RTC->CTIME0 + attempts);
|
||||
uint8_t spawn_x = rand() % LEVEL_WIDTH;
|
||||
uint8_t spawn_y = rand() % LEVEL_HEIGHT;
|
||||
|
||||
int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
|
||||
int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
|
||||
@@ -270,9 +270,9 @@ void initialize_level() {
|
||||
uint8_t attempts = 0;
|
||||
|
||||
while (initial_enemies < 2 && num_entities < MAX_ENTITIES && attempts < 50) {
|
||||
std::srand(LPC_RTC->CTIME0 + attempts);
|
||||
int8_t offset_x = (std::rand() % 11) - 5;
|
||||
int8_t offset_y = (std::rand() % 11) - 5;
|
||||
srand(LPC_RTC->CTIME0 + attempts);
|
||||
int8_t offset_x = (rand() % 11) - 5;
|
||||
int8_t offset_y = (rand() % 11) - 5;
|
||||
|
||||
if (abs(offset_x) < 3 && abs(offset_y) < 3) {
|
||||
attempts++;
|
||||
@@ -307,10 +307,10 @@ void initialize_level() {
|
||||
|
||||
attempts = 0;
|
||||
while (num_entities < MIN_ENTITIES && attempts < 100) {
|
||||
std::srand(LPC_RTC->CTIME0 + attempts + 100);
|
||||
srand(LPC_RTC->CTIME0 + attempts + 100);
|
||||
|
||||
uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
|
||||
uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
|
||||
uint8_t spawn_x = rand() % LEVEL_WIDTH;
|
||||
uint8_t spawn_y = rand() % LEVEL_HEIGHT;
|
||||
|
||||
int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
|
||||
int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
|
||||
@@ -1045,7 +1045,7 @@ void DoomView::on_show() {
|
||||
void DoomView::paint(Painter& painter) {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
scene = 0;
|
||||
up = down = left = right = fired = false;
|
||||
jogging = view_height = 0;
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ Game2048View::Game2048View(NavigationView& nav)
|
||||
|
||||
void Game2048View::on_show() {
|
||||
if (!initialized) {
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
reset_game();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@@ -239,10 +239,10 @@ void RandomPasswordView::on_freqchg(int64_t freq) {
|
||||
}
|
||||
|
||||
void RandomPasswordView::set_random_freq() {
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
// this is only for seed to visit random freq, the radio is still real random
|
||||
|
||||
auto random_freq = 100000000 + (std::rand() % 900000000); // 100mhz to 1ghz
|
||||
auto random_freq = 100000000 + (rand() % 900000000); // 100mhz to 1ghz
|
||||
receiver_model.set_target_frequency(random_freq);
|
||||
field_frequency.set_value(random_freq);
|
||||
}
|
||||
@@ -297,12 +297,12 @@ void RandomPasswordView::new_password() {
|
||||
/// roll worker
|
||||
for (int i = 0; i < password_length * 2; i += 2) {
|
||||
unsigned int seed = seeds_deque[i];
|
||||
std::srand(seed);
|
||||
srand(seed);
|
||||
uint8_t rollnum = (uint8_t)(seeds_deque[i + 1] % 128);
|
||||
uint8_t nu = 0;
|
||||
for (uint8_t o = 0; o < rollnum; ++o) nu = std::rand();
|
||||
for (uint8_t o = 0; o < rollnum; ++o) nu = rand();
|
||||
nu++;
|
||||
char c = charset[std::rand() % charset.length()];
|
||||
char c = charset[rand() % charset.length()];
|
||||
initial_password += c;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ void SnakeView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -605,7 +605,7 @@ void pause_game() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::srand(GenerateRandomSeed());
|
||||
srand(GenerateRandomSeed());
|
||||
Init();
|
||||
ShowLevelMenu();
|
||||
joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
|
||||
@@ -145,6 +145,21 @@ Continuous (Fox-oring)
|
||||
rffc507x::RFFC507x first_if;
|
||||
ui::SystemView* system_view_ptr;
|
||||
|
||||
static uint32_t random_seed_state = 123456789;
|
||||
extern "C" int rand(void) {
|
||||
random_seed_state ^= random_seed_state << 13;
|
||||
random_seed_state ^= random_seed_state >> 17;
|
||||
random_seed_state ^= random_seed_state << 5;
|
||||
return (int)(random_seed_state & 0x7FFFFFFF);
|
||||
}
|
||||
extern "C" void srand(unsigned int seed) {
|
||||
if (seed == 0) {
|
||||
random_seed_state = 123456789;
|
||||
} else {
|
||||
random_seed_state = seed;
|
||||
}
|
||||
}
|
||||
|
||||
static void event_loop() {
|
||||
static ui::Context context;
|
||||
static ui::SystemView system_view{
|
||||
|
||||
@@ -976,6 +976,8 @@ SystemView::SystemView(
|
||||
|
||||
navigation_view.push<SystemMenuView>();
|
||||
|
||||
add_child(¬ification_view);
|
||||
|
||||
if (pmem::config_splash()) {
|
||||
navigation_view.push<SplashScreenView>();
|
||||
}
|
||||
@@ -1061,20 +1063,10 @@ SplashScreenView::SplashScreenView(NavigationView& nav)
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t SplashScreenView::myrand(uint32_t* state) {
|
||||
uint32_t x = *state;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
*state = x;
|
||||
return x;
|
||||
}
|
||||
|
||||
void SplashScreenView::get_random_splash_file(std::filesystem::path& path) {
|
||||
path = u"";
|
||||
|
||||
uint32_t rng_state = LPC_RTC->CTIME0;
|
||||
if (rng_state == 0) rng_state = 0xABBACAFE;
|
||||
srand(LPC_RTC->CTIME0);
|
||||
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
@@ -1096,7 +1088,7 @@ void SplashScreenView::get_random_splash_file(std::filesystem::path& path) {
|
||||
(ext[3] == 'P' || ext[3] == 'p')) {
|
||||
valid_count++;
|
||||
// Reservoir Sampling:
|
||||
if (((rng_state = myrand(&rng_state)) % valid_count) == 0) {
|
||||
if ((rand() % valid_count) == 0) {
|
||||
path = splash_dir / fno.fname;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#include "ui_audio.hpp"
|
||||
#include "ui_sd_card_status_view.hpp"
|
||||
#include "ui_dfu_menu.hpp"
|
||||
|
||||
#include "ui_notifications.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ui_bmpview.hpp"
|
||||
#include "ff.h"
|
||||
@@ -366,7 +366,6 @@ class SplashScreenView : public View {
|
||||
Button button_done{
|
||||
{screen_width, 0, 1, 1},
|
||||
""};
|
||||
uint32_t myrand(uint32_t* state);
|
||||
void get_random_splash_file(std::filesystem::path& path);
|
||||
};
|
||||
|
||||
@@ -447,11 +446,12 @@ class SystemView : public View {
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
NavigationView navigation_view{};
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
NotificationView notification_view{navigation_view};
|
||||
DfuMenu overlay{navigation_view};
|
||||
DfuMenu2 overlay2{navigation_view};
|
||||
NavigationView navigation_view{};
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "ui_notifications.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace ui {
|
||||
|
||||
NotificationEntryView::NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler)
|
||||
: entry_(entry), notifhandler_(notifhandler) {
|
||||
add_children({&background, &border, &title_text, &message_text, &close_button});
|
||||
border.set_outline(true);
|
||||
if (entry_.icon != NOTIF_ICON_NONE) {
|
||||
add_child(&icon_image);
|
||||
icon_image.set_parent_rect({UI_POS_X(0) + 2, UI_POS_Y(1), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)});
|
||||
message_text.set_parent_rect({UI_POS_X(2) + 2, UI_POS_Y(1), UI_POS_WIDTH_REMAINING(6) - 2, UI_POS_HEIGHT(2)});
|
||||
if (entry_.icon == NOTIF_ICON_MESSAGE) {
|
||||
icon_image.set_bitmap(&bitmap_icon_pocsag);
|
||||
}
|
||||
|
||||
} else {
|
||||
message_text.set_parent_rect({UI_POS_X(1), UI_POS_Y(1), UI_POS_WIDTH_REMAINING(5) - 1, UI_POS_HEIGHT(2)});
|
||||
}
|
||||
title_text.set_style(Theme::getInstance()->bg_dark);
|
||||
title_text.set(entry_.title);
|
||||
message_text.set_style(Theme::getInstance()->bg_darkest);
|
||||
message_text.set(entry_.message);
|
||||
|
||||
close_button.on_select = [this](Button&) {
|
||||
if (notifhandler_) {
|
||||
notifhandler_->remove_notification(entry_.id);
|
||||
}
|
||||
};
|
||||
|
||||
message_text.on_select = [this](Text&) {
|
||||
if (!entry_.source_app.empty() && notifhandler_) {
|
||||
notifhandler_->open_notification(entry_.source_app);
|
||||
// notifhandler_->remove_notification(entry_.id);
|
||||
}
|
||||
};
|
||||
title_text.on_select = message_text.on_select;
|
||||
}
|
||||
|
||||
NotificationView::NotificationView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
this->on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
NotificationView::~NotificationView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
}
|
||||
|
||||
void NotificationView::on_tick_second() {
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < notification_views_.size();) {
|
||||
if (notification_views_[i]->entry()->increase_time(1000)) {
|
||||
notification_views_.erase(notification_views_.begin() + i);
|
||||
changed = true;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
rearrange_notifications();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationView::rearrange_notifications() {
|
||||
size_t count = children().size();
|
||||
while (children().size() > 0) {
|
||||
remove_child(children().back());
|
||||
}
|
||||
|
||||
const int entry_height = UI_POS_HEIGHT(3) + 1;
|
||||
int index = 0;
|
||||
|
||||
for (auto& view_ptr : notification_views_) {
|
||||
view_ptr->set_parent_rect({UI_POS_X(0),
|
||||
(Coord)(UI_POS_Y(0) + (index * entry_height)),
|
||||
UI_POS_MAXWIDTH,
|
||||
entry_height});
|
||||
add_child(view_ptr.get());
|
||||
index++;
|
||||
}
|
||||
|
||||
set_parent_rect({UI_POS_X(0), UI_POS_Y(0), UI_POS_MAXWIDTH, (Coord)(notification_views_.size() * entry_height)});
|
||||
set_dirty();
|
||||
if (count != children().size()) {
|
||||
// refresh screen!!!
|
||||
system_view_ptr->set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationView::add_notification(NotificationEntry& entry) {
|
||||
if (notification_views_.size() >= max_notifications) {
|
||||
notification_views_.erase(notification_views_.begin());
|
||||
}
|
||||
|
||||
entry.id = ++curr_not_id;
|
||||
notification_views_.push_back(std::make_unique<NotificationEntryView>(entry, this));
|
||||
|
||||
rearrange_notifications();
|
||||
}
|
||||
|
||||
void NotificationView::remove_notification(uint16_t id) {
|
||||
bool found = false;
|
||||
size_t found_index = 0;
|
||||
|
||||
for (size_t i = 0; i < notification_views_.size(); i++) {
|
||||
if (notification_views_[i]->id() == id) {
|
||||
found = true;
|
||||
found_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
notification_views_.erase(notification_views_.begin() + found_index);
|
||||
rearrange_notifications();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationView::open_notification(std::string app_name) {
|
||||
if (app_name.empty()) return;
|
||||
nav_.StartAppByName(app_name.c_str());
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class NotificationView;
|
||||
class NavigationView;
|
||||
|
||||
enum notification_icon_t : uint8_t {
|
||||
NOTIF_ICON_NONE = 0,
|
||||
NOTIF_ICON_MESSAGE
|
||||
};
|
||||
|
||||
class NotificationEntry {
|
||||
public:
|
||||
std::string source_app{};
|
||||
std::string title{};
|
||||
std::string message{};
|
||||
notification_icon_t icon = NOTIF_ICON_NONE;
|
||||
uint16_t timeout = 10000;
|
||||
uint16_t id = 0;
|
||||
|
||||
NotificationEntry() = default;
|
||||
NotificationEntry(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000, uint16_t id = 0)
|
||||
: source_app(source_app), title(title), message(message), icon(icon), timeout(timeout), id(id) {}
|
||||
|
||||
static NotificationEntry build(const std::string& source_app, const std::string& title, const std::string& message, notification_icon_t icon = NOTIF_ICON_NONE, uint16_t timeout = 10000) {
|
||||
return NotificationEntry{source_app, title, message, icon, timeout, 0};
|
||||
}
|
||||
|
||||
bool increase_time(uint16_t delta) {
|
||||
current_time += delta;
|
||||
return (current_time >= timeout);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t current_time = 0;
|
||||
};
|
||||
|
||||
class NotificationEntryView : public View {
|
||||
public:
|
||||
NotificationEntryView(const NotificationEntry& entry, NotificationView* notifhandler);
|
||||
|
||||
NotificationEntryView(const NotificationEntryView&) = delete;
|
||||
NotificationEntryView& operator=(const NotificationEntryView&) = delete;
|
||||
|
||||
ui::Rectangle background{{1, 1, UI_POS_MAXWIDTH - 2, UI_POS_HEIGHT(3) - 2}, Theme::getInstance()->bg_darkest->background};
|
||||
ui::Rectangle border{{0, 0, UI_POS_MAXWIDTH, UI_POS_HEIGHT(3) + 1}, Theme::getInstance()->bg_light->background};
|
||||
ui::Text title_text{{UI_POS_X(0) + 1, UI_POS_Y(0) + 1, UI_POS_WIDTH_REMAINING(4) - 3, UI_POS_HEIGHT(1)}, ""};
|
||||
ui::Text message_text{{UI_POS_X(1), UI_POS_Y(1), UI_POS_WIDTH_REMAINING(5) - 1, UI_POS_HEIGHT(2)}, ""};
|
||||
ui::Image icon_image{}; // 16*16 bitmap
|
||||
ui::Button close_button{{UI_POS_X_RIGHT(4) - 2, UI_POS_Y(0) + 1, UI_POS_WIDTH(4), UI_POS_HEIGHT(2)}, "X"};
|
||||
|
||||
uint16_t id() const { return entry_.id; }
|
||||
NotificationEntry* entry() { return &entry_; }
|
||||
|
||||
private:
|
||||
NotificationEntry entry_;
|
||||
NotificationView* notifhandler_;
|
||||
};
|
||||
|
||||
class NotificationView : public View {
|
||||
public:
|
||||
NotificationView(NavigationView& nav);
|
||||
~NotificationView();
|
||||
|
||||
void add_notification(NotificationEntry& entry);
|
||||
void remove_notification(uint16_t id);
|
||||
void open_notification(std::string app_name);
|
||||
|
||||
private:
|
||||
void on_tick_second();
|
||||
void rearrange_notifications();
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
// Changed to unique_ptr to handle non-copyable Views safely
|
||||
std::vector<std::unique_ptr<NotificationEntryView>> notification_views_{};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
uint16_t curr_not_id = 0;
|
||||
constexpr static uint8_t max_notifications = 4;
|
||||
|
||||
MessageHandlerRegistration message_handler_notifs{
|
||||
Message::ID::NotificationData, [this](Message* const p) {
|
||||
const auto message = static_cast<const NotificationDataMessage*>(p);
|
||||
NotificationEntry entry = NotificationEntry::build(message->source_app, message->title, message->message, static_cast<notification_icon_t>(message->icon), message->timeout);
|
||||
this->add_notification(entry);
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
@@ -1418,6 +1418,44 @@ static void cmd_getdevtype(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
chprintf(chp, res.c_str());
|
||||
}
|
||||
|
||||
static void cmd_notification(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: notif <iconindex> [appname]\r\n";
|
||||
if (argc < 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
int iconindex = atoi(argv[0]);
|
||||
std::string appname = (argc > 1) ? argv[1] : "";
|
||||
chprintf(chp, "Send title, and a <CR>\r\n");
|
||||
std::string title{};
|
||||
uint8_t msg[1]{0};
|
||||
do {
|
||||
size_t bytes_read = chSequentialStreamRead(chp, &msg[0], 1);
|
||||
if (bytes_read != 1)
|
||||
break;
|
||||
if (msg[0] == '\r') // end of title
|
||||
break;
|
||||
if (msg[0] == '\n') continue;
|
||||
title += (char)msg[0];
|
||||
} while (title.size() < 48);
|
||||
std::string message{};
|
||||
chprintf(chp, "Send message, and a <CR>\r\n");
|
||||
do {
|
||||
size_t bytes_read = chSequentialStreamRead(chp, &msg[0], 1);
|
||||
if (bytes_read != 1)
|
||||
break;
|
||||
if (msg[0] == '\r') // end of message
|
||||
break;
|
||||
if (msg[0] == '\n') continue;
|
||||
message += (char)msg[0];
|
||||
} while (message.size() < 298);
|
||||
|
||||
NotificationDataMessage msgn{appname.c_str(), title.c_str(), message.c_str(), (uint8_t)iconindex};
|
||||
EventDispatcher::send_message(msgn);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -1456,6 +1494,7 @@ static const ShellCommand commands[] = {
|
||||
{"getres", cmd_getres},
|
||||
{"getflash", cmd_getflash},
|
||||
{"getdevtype", cmd_getdevtype},
|
||||
{"notif", cmd_notification},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
|
||||
@@ -154,6 +154,7 @@ class Message {
|
||||
MorseTXkey = 96,
|
||||
StreamTXConfiguration = 97,
|
||||
RTTYData = 98,
|
||||
NotificationData = 99,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -1793,4 +1794,33 @@ class RTTYDataMessage : public Message {
|
||||
static constexpr uint16_t max_len = 490;
|
||||
};
|
||||
|
||||
class NotificationDataMessage : public Message {
|
||||
public:
|
||||
constexpr NotificationDataMessage(const char* source_app, const char* title, const char* message, uint8_t icon = 0, uint16_t timeout = 10000)
|
||||
: Message{ID::NotificationData},
|
||||
icon(icon),
|
||||
timeout(timeout) {
|
||||
if (source_app) {
|
||||
size_t len = std::min(strlen(source_app), (size_t)19);
|
||||
memcpy(this->source_app, source_app, len);
|
||||
this->source_app[len] = '\0';
|
||||
}
|
||||
if (title) {
|
||||
size_t len = std::min(strlen(title), (size_t)49);
|
||||
memcpy(this->title, title, len);
|
||||
this->title[len] = '\0';
|
||||
}
|
||||
if (message) {
|
||||
size_t len = std::min(strlen(message), (size_t)299);
|
||||
memcpy(this->message, message, len);
|
||||
this->message[len] = '\0';
|
||||
}
|
||||
}
|
||||
char source_app[20]{0}; // source application name, null-terminated, max 19 chars + null
|
||||
char title[50]{0}; // title, null-terminated, max 49 chars + null
|
||||
char message[300]{0}; // message, null-terminated, max 299 chars + null
|
||||
uint8_t icon = 0;
|
||||
uint16_t timeout = 10000;
|
||||
};
|
||||
|
||||
#endif /*__MESSAGE_H__*/
|
||||
|
||||
@@ -397,18 +397,35 @@ void Text::getWidgetName(std::string& result) {
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
auto max_len = (unsigned)rect.width() / s.font.char_width();
|
||||
auto text_view = std::string_view{text};
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
const int char_width = s.font.char_width();
|
||||
const int line_height = s.font.line_height();
|
||||
if (char_width == 0 || line_height == 0) return;
|
||||
const size_t chars_per_line = rect.width() / char_width;
|
||||
if (chars_per_line == 0) return;
|
||||
size_t lines_capacity = rect.height() / line_height;
|
||||
if (lines_capacity == 0) lines_capacity = 1; // at least one line, even if it overflows vertically
|
||||
auto text_view = std::string_view{text};
|
||||
size_t current_offset = 0;
|
||||
for (size_t line_idx = 0; line_idx < lines_capacity; ++line_idx) {
|
||||
if (current_offset >= text_view.length()) break;
|
||||
size_t chunk_len = std::min(chars_per_line, text_view.length() - current_offset);
|
||||
painter.draw_string(
|
||||
rect.location() + Point(0, line_idx * line_height),
|
||||
s,
|
||||
text_view.substr(current_offset, chunk_len));
|
||||
current_offset += chunk_len;
|
||||
}
|
||||
}
|
||||
|
||||
if (text_view.length() > max_len)
|
||||
text_view = text_view.substr(0, max_len);
|
||||
|
||||
painter.draw_string(
|
||||
rect.location(),
|
||||
s,
|
||||
text_view);
|
||||
bool Text::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start) {
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Labels ****************************************************************/
|
||||
|
||||
@@ -209,6 +209,7 @@ class Rectangle : public Widget {
|
||||
|
||||
class Text : public Widget {
|
||||
public:
|
||||
std::function<void(Text&)> on_select{};
|
||||
Text()
|
||||
: text{""} {
|
||||
}
|
||||
@@ -223,6 +224,8 @@ class Text : public Widget {
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
protected:
|
||||
// NB: Don't truncate this string. The UI will only render
|
||||
// as many characters as will fit in its rectange.
|
||||
|
||||
@@ -386,7 +386,9 @@ void Text::set(std::string_view value) {
|
||||
text = std::string{value};
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
std::string Text::get() {
|
||||
return text;
|
||||
}
|
||||
void Text::getAccessibilityText(std::string& result) {
|
||||
result = text;
|
||||
}
|
||||
@@ -396,18 +398,35 @@ void Text::getWidgetName(std::string& result) {
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
auto max_len = (unsigned)rect.width() / s.font.char_width();
|
||||
auto text_view = std::string_view{text};
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
const int char_width = s.font.char_width();
|
||||
const int line_height = s.font.line_height();
|
||||
if (char_width == 0 || line_height == 0) return;
|
||||
const size_t chars_per_line = rect.width() / char_width;
|
||||
if (chars_per_line == 0) return;
|
||||
size_t lines_capacity = rect.height() / line_height;
|
||||
if (lines_capacity == 0) lines_capacity = 1; // at least one line, even if it overflows vertically
|
||||
auto text_view = std::string_view{text};
|
||||
size_t current_offset = 0;
|
||||
for (size_t line_idx = 0; line_idx < lines_capacity; ++line_idx) {
|
||||
if (current_offset >= text_view.length()) break;
|
||||
size_t chunk_len = std::min(chars_per_line, text_view.length() - current_offset);
|
||||
painter.draw_string(
|
||||
rect.location() + Point(0, line_idx * line_height),
|
||||
s,
|
||||
text_view.substr(current_offset, chunk_len));
|
||||
current_offset += chunk_len;
|
||||
}
|
||||
}
|
||||
|
||||
if (text_view.length() > max_len)
|
||||
text_view = text_view.substr(0, max_len);
|
||||
|
||||
painter.draw_string(
|
||||
rect.location(),
|
||||
s,
|
||||
text_view);
|
||||
bool Text::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start) {
|
||||
if (on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Labels ****************************************************************/
|
||||
|
||||
@@ -210,6 +210,7 @@ class Rectangle : public Widget {
|
||||
|
||||
class Text : public Widget {
|
||||
public:
|
||||
std::function<void(Text&)> on_select{};
|
||||
Text()
|
||||
: text{""} {
|
||||
}
|
||||
@@ -218,11 +219,14 @@ class Text : public Widget {
|
||||
Text(Rect parent_rect);
|
||||
|
||||
void set(std::string_view value);
|
||||
std::string get();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
protected:
|
||||
// NB: Don't truncate this string. The UI will only render
|
||||
// as many characters as will fit in its rectange.
|
||||
|
||||
@@ -125,7 +125,7 @@ class DigitalRain {
|
||||
|
||||
public:
|
||||
DigitalRain() {
|
||||
std::srand(0);
|
||||
srand(0);
|
||||
WIDTH = screen_width;
|
||||
HEIGHT = screen_height + 5;
|
||||
COLS = WIDTH / CHAR_WIDTH;
|
||||
|
||||
@@ -6996,7 +6996,7 @@ int Context::run() {
|
||||
} else if (p->order_by.compare("name", true) == 0) {
|
||||
std::sort(testArray.begin(), testArray.end(), nameOrderComparator);
|
||||
} else if (p->order_by.compare("rand", true) == 0) {
|
||||
std::srand(p->rand_seed);
|
||||
srand(p->rand_seed);
|
||||
|
||||
// random_shuffle implementation
|
||||
const auto first = &testArray[0];
|
||||
|
||||
Reference in New Issue
Block a user