Notifications (#3002)

This commit is contained in:
Totoo
2026-02-19 14:33:01 +01:00
committed by GitHub
parent 77f747aab2
commit 6498f5a1b2
27 changed files with 424 additions and 70 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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;
+4 -4
View File
@@ -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 *******************************************************/
+3 -1
View File
@@ -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;
@@ -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));
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -605,7 +605,7 @@ void pause_game() {
}
int main() {
std::srand(GenerateRandomSeed());
srand(GenerateRandomSeed());
Init();
ShowLevelMenu();
joystick.attach(&ReadJoystickForLevel, 0.3);
+15
View File
@@ -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{
+4 -12
View File
@@ -976,6 +976,8 @@ SystemView::SystemView(
navigation_view.push<SystemMenuView>();
add_child(&notification_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;
}
}
+3 -3
View File
@@ -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_;
};
+131
View File
@@ -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
+101
View File
@@ -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
+39
View File
@@ -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 = {
+30
View File
@@ -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__*/
+27 -10
View File
@@ -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 ****************************************************************/
+3
View File
@@ -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.
+30 -11
View File
@@ -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;
+1 -1
View File
@@ -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];