Add Notepad app (#1010)

* easier 'now', start adding text editor

* Adding scrolling to notepad

* Better scrolling

* Better scrolling, off-by-1 bugs

* MVP fit and finish

* Add tiny font and use in Notepad

* Font tweaking, tiny font cursor

* Fix warning

* Format changed files

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
This commit is contained in:
Kyle Reed 2023-05-19 13:39:35 -07:00 committed by GitHub
parent 693d7864e4
commit 02811b9967
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1316 additions and 56 deletions

View File

@ -210,6 +210,7 @@ set(CPPSRC
ui/ui_alphanum.cpp ui/ui_alphanum.cpp
ui/ui_audio.cpp ui/ui_audio.cpp
ui/ui_channel.cpp ui/ui_channel.cpp
ui/ui_font_fixed_5x8.cpp
ui/ui_font_fixed_8x16.cpp ui/ui_font_fixed_8x16.cpp
ui/ui_geomap.cpp ui/ui_geomap.cpp
ui/ui_qrcode.cpp ui/ui_qrcode.cpp
@ -259,6 +260,7 @@ set(CPPSRC
apps/ui_sonde.cpp apps/ui_sonde.cpp
apps/ui_sstvtx.cpp apps/ui_sstvtx.cpp
# apps/ui_test.cpp # apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp apps/ui_tone_search.cpp
apps/ui_touch_calibration.cpp apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp apps/ui_touchtunes.cpp

View File

@ -44,7 +44,7 @@
{"1750k", 1750000}, \ {"1750k", 1750000}, \
{"2000k", 2000000}, \ {"2000k", 2000000}, \
{"2500k", 2500000}, \ {"2500k", 2500000}, \
{ "2750k", 2750000 } // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC) {"2750k", 2750000}, // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC)
namespace ui { namespace ui {

View File

@ -76,9 +76,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
} }
void ADSBLogger::log_str(std::string& logline) { void ADSBLogger::log_str(std::string& logline) {
rtc::RTC datetime; log_file.write_entry(logline);
rtcGetTime(&RTCD1, &datetime);
log_file.write_entry(datetime, logline);
} }
// Aircraft Details // Aircraft Details

View File

@ -34,10 +34,7 @@ using namespace portapack;
using namespace modems; using namespace modems;
void AFSKLogger::log_raw_data(const std::string& data) { void AFSKLogger::log_raw_data(const std::string& data) {
rtc::RTC datetime; log_file.write_entry(data);
rtcGetTime(&RTCD1, &datetime);
log_file.write_entry(datetime, data);
} }
namespace ui { namespace ui {

View File

@ -31,10 +31,7 @@
using namespace portapack; using namespace portapack;
void APRSLogger::log_raw_data(const std::string& data) { void APRSLogger::log_raw_data(const std::string& data) {
rtc::RTC datetime; log_file.write_entry(data);
rtcGetTime(&RTCD1, &datetime);
log_file.write_entry(datetime, data);
} }
namespace ui { namespace ui {

View File

@ -45,22 +45,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
return ::truncate(path.string(), max_length); return ::truncate(path.string(), max_length);
} }
// Gets a human readable file size string.
std::string get_pretty_size(uint32_t file_size) {
static const std::string suffix[5] = {"B", "kB", "MB", "GB", "??"};
size_t suffix_index = 0;
while (file_size >= 1024) {
file_size /= 1024;
suffix_index++;
}
if (suffix_index > 4)
suffix_index = 4;
return to_string_dec_uint(file_size) + suffix[suffix_index];
}
// Case insensitive path equality on underlying "native" string. // Case insensitive path equality on underlying "native" string.
bool iequal( bool iequal(
const fs::path& lhs, const fs::path& lhs,
@ -272,25 +256,27 @@ void FileManBaseView::refresh_list() {
auto entry_name = truncate(entry.path, 20); auto entry_name = truncate(entry.path, 20);
if (entry.is_directory) { if (entry.is_directory) {
menu_view.add_item({entry_name, menu_view.add_item(
ui::Color::yellow(), {entry_name,
&bitmap_icon_dir, ui::Color::yellow(),
[this](KeyEvent key) { &bitmap_icon_dir,
if (on_select_entry) [this](KeyEvent key) {
on_select_entry(key); if (on_select_entry)
}}); on_select_entry(key);
}});
} else { } else {
const auto& assoc = get_assoc(entry.path.extension()); const auto& assoc = get_assoc(entry.path.extension());
auto size_str = get_pretty_size(entry.size); auto size_str = to_string_file_size(entry.size);
menu_view.add_item({entry_name + std::string(21 - entry_name.length(), ' ') + size_str, menu_view.add_item(
assoc.color, {entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
assoc.icon, assoc.color,
[this](KeyEvent key) { assoc.icon,
if (on_select_entry) [this](KeyEvent key) {
on_select_entry(key); if (on_select_entry)
}}); on_select_entry(key);
}});
} }
// HACK: Should page menu items instead of limiting the number. // HACK: Should page menu items instead of limiting the number.

View File

@ -118,7 +118,7 @@ bool MorseView::start_tx() {
update_tx_duration(); update_tx_duration();
if (!symbol_count) { if (!symbol_count) {
nav_.display_modal("Error", "Message too long,\nmust be < 256 symbols.", INFO, nullptr); nav_.display_modal("Error", "Message too long,\nmust be < 256 symbols.");
return false; return false;
} }

View File

@ -0,0 +1,322 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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_fileman.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace {
template <typename T>
T mid(const T& val1, const T& val2, const T& val3) {
return std::max(val1, std::min(val2, val3));
}
} // namespace
namespace ui {
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{&button_open,
&text_position});
set_focusable(true);
// log_.append("LOGS/NOTEPAD.TXT");
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
};
};
}
void TextEditorView::on_focus() {
refresh_ui();
}
void TextEditorView::paint(Painter& painter) {
auto first_line = paint_state_.first_line;
auto first_col = paint_state_.first_col;
if (!paint_state_.has_file)
return;
if (cursor_.line < first_line)
first_line = cursor_.line;
else if (cursor_.line >= first_line + max_line)
first_line = cursor_.line - max_line + 1;
if (cursor_.col < first_col)
first_col = cursor_.col;
if (cursor_.col >= first_col + max_col)
first_col = cursor_.col - max_col + 1;
if (first_line != paint_state_.first_line ||
first_col != paint_state_.first_col) {
paint_state_.first_line = first_line;
paint_state_.first_col = first_col;
paint_state_.redraw_text = true;
}
if (paint_state_.redraw_text) {
paint_text(painter, first_line, first_col);
paint_state_.redraw_text = false;
}
paint_cursor(painter);
}
bool TextEditorView::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
if (key == KeyEvent::Left)
delta_col = -1;
else if (key == KeyEvent::Right)
delta_col = 1;
else if (key == KeyEvent::Up)
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
/* else if (key == KeyEvent::Select)
; // TODO: Edit/Menu */
// Always allow cursor direction to be updated.
cursor_.dir = delta_col != 0 ? ScrollDirection::Horizontal : ScrollDirection::Vertical;
auto updated = apply_scrolling_constraints(delta_line, delta_col);
if (updated)
refresh_ui();
return updated;
}
bool TextEditorView::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
updated = apply_scrolling_constraints(0, delta);
else
updated = apply_scrolling_constraints(delta, 0);
if (updated)
refresh_ui();
return updated;
}
bool TextEditorView::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
int32_t new_line = cursor_.line + delta_line;
int32_t new_col = cursor_.col + delta_col;
auto new_line_length = info_.line_length(new_line);
if (new_col < 0)
--new_line;
else if (new_col > new_line_length && delta_line == 0) {
// Only want to wrap if moving horizontally.
new_col = 0;
++new_line;
}
if (new_line < 0 || (uint32_t)new_line >= info_.line_count())
return false;
new_line_length = info_.line_length(new_line);
// TODO: don't wrap with encoder?
// Wrap or clamp column.
if (new_line_length == 0)
new_col = 0;
else if (new_col > new_line_length || new_col < 0)
new_col = new_line_length;
cursor_.line = new_line;
cursor_.col = new_col;
return true;
}
void TextEditorView::refresh_ui() {
if (paint_state_.has_file) {
text_position.set(
to_string_dec_uint(cursor_.col + 1) + ":" +
to_string_dec_uint(cursor_.line + 1) + "/" +
to_string_dec_uint(info_.line_count()) +
(info_.truncated ? "*" : "") +
" Size: " +
to_string_file_size(info_.size));
focus();
} else {
button_open.focus();
}
set_dirty();
}
void TextEditorView::refresh_file_info() {
constexpr size_t buffer_size = 128;
char buffer[buffer_size];
uint32_t base_offset = 0;
file_.seek(0);
info_.newlines.clear();
info_.line_ending = LineEnding::LF;
info_.size = file_.size();
info_.truncated = false;
while (true) {
auto result = file_.read(buffer, buffer_size);
if (result.is_error())
break; // TODO: report error?
// TODO: CRLF state machine for cross block.
for (uint32_t i = 0; i < result.value(); ++i) {
switch (buffer[i]) {
case '\n':
info_.newlines.push_back(base_offset + i);
}
}
base_offset += result.value();
// Fake a newline at the end for consistency.
// Could check if there already is a trailing newline, but it doesn't hurt.
if (result.value() < buffer_size) {
info_.newlines.push_back(base_offset);
break;
}
// HACK HACK: only show first 1000 lines for now.
if (info_.newlines.size() >= 1000) {
info_.truncated = true;
break;
}
}
refresh_ui();
}
void TextEditorView::open_file(const fs::path& path) {
// TODO: need a temp backing file for edits.
auto result = file_.open(path);
paint_state_.has_file = !result.is_valid(); /* Has an error. */
if (paint_state_.has_file) {
refresh_file_info();
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
} else {
nav_.display_modal("Read Error", "Cannot open file:\n" + result.value().what());
paint_state_.has_file = false;
}
paint_state_.redraw_text = true;
refresh_ui();
}
std::string TextEditorView::read(uint32_t offset, uint32_t length) {
if (offset >= info_.size)
return {"[BAD OFFSET]"};
std::string buffer(length + 1, '\0');
file_.seek(offset);
auto result = file_.read(&buffer[0], length);
if (result.is_ok())
/* resize? */;
else
return result.error().what();
return buffer;
}
void TextEditorView::paint_text(Painter& painter, uint32_t line, uint16_t col) {
// TODO: A line cache would use more memory but save a lot of IO.
// Only the new lines/characters would need to be refetched.
auto r = screen_rect();
auto& lines = info_.newlines;
auto line_start = info_.line_start(line);
// Draw the lines from the file
for (uint32_t i = 0; i < max_line && i < lines.size(); ++i) {
auto line_end = lines[line + i];
int32_t read_length = max_col;
// Don't read past end of the line.
if (line_start + col + (uint32_t)read_length > line_end)
read_length = line_end - col - line_start;
if (read_length > 0)
painter.draw_string(
{0, r.location().y() + (int)i * char_height},
style_default, read(line_start + col, read_length));
// Erase empty line sectons.
if (read_length >= 0) {
int32_t clear_width = max_col - read_length;
if (clear_width > 0)
painter.fill_rectangle(
{(max_col - clear_width) * char_width,
r.location().y() + (int)i * char_height,
clear_width * char_width, char_height},
style_default.background);
}
line_start = lines[line + i] + 1 /* newline */;
}
}
void TextEditorView::paint_cursor(Painter& painter) {
auto draw_cursor = [this, &painter](uint32_t line, uint16_t col, Color c) {
auto r = screen_rect();
line = line - paint_state_.first_line;
col = col - paint_state_.first_col;
painter.draw_rectangle(
{(int)col * char_width - 1,
r.location().y() + (int)line * char_height,
char_width + 1, char_height},
c);
};
// TOOD: bug where cursor doesn't clear at EOF.
// TODO: XOR cursor?
// Clear old cursor.
draw_cursor(paint_state_.line, paint_state_.col, style_default.background);
draw_cursor(cursor_.line, cursor_.col, style_default.foreground);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
}
uint16_t TextEditorView::line_length() const {
return info_.line_length(cursor_.line);
}
} // namespace ui

View File

@ -0,0 +1,170 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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.
*/
#ifndef __UI_TEXT_EDITOR_H__
#define __UI_TEXT_EDITOR_H__
#include "ui.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_widget.hpp"
//#include "ui_textentry.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include <string>
#include <vector>
namespace ui {
enum class LineEnding : uint8_t {
LF,
CRLF
};
enum class ScrollDirection : uint8_t {
Vertical,
Horizontal
};
// TODO: RAM is _very_ limited. Need to
// rework this to not store every line.
// Should be able to get away with only
// abount one screen of lines so long as
// you can't scroll more than one screen
// at a time.
struct FileInfo {
/* Offsets of newlines. */
std::vector<uint32_t> newlines;
LineEnding line_ending;
File::Size size;
bool truncated;
uint32_t line_count() const {
return newlines.size();
}
uint32_t line_start(uint32_t line) const {
return line == 0 ? 0 : (newlines[line - 1] + 1);
}
uint16_t line_length(uint32_t line) const {
if (line >= line_count())
return 0;
auto start = line_start(line);
auto end = newlines[line];
return end - start;
}
};
/*class TextViewer : public Widget {
};*/
class TextEditorView : public View {
public:
TextEditorView(NavigationView& nav);
// TextEditorView(NavigationView& nav, const std::filesystem::path& path);
std::string title() const override {
return "Notepad";
};
void on_focus() override;
void paint(Painter& painter) override;
bool on_key(KeyEvent delta) override;
bool on_encoder(EncoderEvent delta) override;
private:
static constexpr uint8_t max_line = 32;
static constexpr uint8_t max_col = 48;
static constexpr int8_t char_width = 5;
static constexpr int8_t char_height = 8;
// TODO: should these be common somewhere?
static constexpr Style style_default{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
int16_t delta_line,
int16_t delta_col);
void refresh_ui();
void refresh_file_info();
void open_file(const std::filesystem::path& path);
std::string read(uint32_t offset, uint32_t length = 30);
void paint_text(Painter& painter, uint32_t line, uint16_t col);
void paint_cursor(Painter& painter);
// Gets the length of the current line.
uint16_t line_length() const;
NavigationView& nav_;
File file_{};
FileInfo info_{};
// LogFile log_{ };
struct {
// Previous cursor state.
uint32_t line{};
uint16_t col{};
// Previous draw state.
uint32_t first_line{};
uint16_t first_col{};
bool redraw_text{true};
bool has_file{false};
} paint_state_{};
struct {
uint32_t line{};
uint16_t col{};
ScrollDirection dir{ScrollDirection::Vertical};
} cursor_{};
/* 8px grid is 30 wide, 38 tall. */
/* 16px font height or 19 rows. */
/* Titlebar is 16px tall, so 18 rows left. */
/* 240 x 320, (304 with titlebar) */
// TODO: The scrollable view should be its own widget
// otherwise control navigation doesn't work.
Button button_open{
{24 * 8, 34 * 8, 6 * 8, 4 * 8},
"Open"};
Text text_position{
{0 * 8, 36 * 8, 24 * 8, 2 * 8},
""};
};
} // namespace ui
#endif // __UI_TEXT_EDITOR_H__

View File

@ -140,11 +140,11 @@ ViewWavView::ViewWavView(
auto open_view = nav.push<FileLoadView>(".WAV"); auto open_view = nav.push<FileLoadView>(".WAV");
open_view->on_changed = [this](std::filesystem::path file_path) { open_view->on_changed = [this](std::filesystem::path file_path) {
if (!wav_reader->open(file_path)) { if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file.", INFO, nullptr); nav_.display_modal("Error", "Couldn't open file.");
return; return;
} }
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) { if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.", INFO, nullptr); nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
return; return;
} }
load_wav(file_path); load_wav(file_path);

View File

@ -32,7 +32,7 @@
#include "ui_widget.hpp" #include "ui_widget.hpp"
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line #define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
#define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit \ #define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller // It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE #define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE

View File

@ -20,9 +20,12 @@
*/ */
#include "log_file.hpp" #include "log_file.hpp"
#include "string_format.hpp" #include "string_format.hpp"
Optional<File::Error> LogFile::write_entry(const std::string& entry) {
return write_entry(rtc_time::now(), entry);
}
Optional<File::Error> LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) { Optional<File::Error> LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
std::string timestamp = to_string_timestamp(datetime); std::string timestamp = to_string_timestamp(datetime);
return write_line(timestamp + " " + entry); return write_line(timestamp + " " + entry);

View File

@ -25,10 +25,7 @@
#include <string> #include <string>
#include "file.hpp" #include "file.hpp"
#include "rtc_time.hpp"
#include "lpc43xx_cpp.hpp"
using namespace lpc43xx;
#define LOG_ROOT_DIR "LOGS" #define LOG_ROOT_DIR "LOGS"
@ -42,6 +39,7 @@ class LogFile {
return file.append(filename); return file.append(filename);
} }
Optional<File::Error> write_entry(const std::string& entry);
Optional<File::Error> write_entry(const rtc::RTC& datetime, const std::string& entry); Optional<File::Error> write_entry(const rtc::RTC& datetime, const std::string& entry);
private: private:

View File

@ -29,4 +29,15 @@ void on_tick_second() {
signal_tick_second.emit(); signal_tick_second.emit();
} }
rtc::RTC now() {
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
return datetime;
}
rtc::RTC now(rtc::RTC& out_datetime) {
rtcGetTime(&RTCD1, &out_datetime);
return out_datetime;
}
} /* namespace rtc_time */ } /* namespace rtc_time */

View File

@ -24,12 +24,21 @@
#include "signal.hpp" #include "signal.hpp"
#include "lpc43xx_cpp.hpp"
using namespace lpc43xx;
namespace rtc_time { namespace rtc_time {
extern Signal<> signal_tick_second; extern Signal<> signal_tick_second;
void on_tick_second(); void on_tick_second();
/* Returns the current RTCTime from the RTC. */
rtc::RTC now();
/* Returns the current RTCTime from the RTC. */
rtc::RTC now(rtc::RTC& out_datetime);
} /* namespace rtc_time */ } /* namespace rtc_time */
#endif /*__RTC_TIME_H__*/ #endif /*__RTC_TIME_H__*/

View File

@ -213,6 +213,21 @@ std::string to_string_FAT_timestamp(const FATTimestamp& timestamp) {
to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0'); to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
} }
std::string to_string_file_size(uint32_t file_size) {
static const std::string suffix[5] = {"B", "kB", "MB", "GB", "??"};
size_t suffix_index = 0;
while (file_size >= 1024) {
file_size /= 1024;
suffix_index++;
}
if (suffix_index > 4)
suffix_index = 4;
return to_string_dec_uint(file_size) + suffix[suffix_index];
}
std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision) { std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision) {
const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000}; const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
std::string string{""}; std::string string{""};

View File

@ -56,6 +56,9 @@ std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format =
std::string to_string_timestamp(const rtc::RTC& value); std::string to_string_timestamp(const rtc::RTC& value);
std::string to_string_FAT_timestamp(const FATTimestamp& timestamp); std::string to_string_FAT_timestamp(const FATTimestamp& timestamp);
// Gets a human readable file size string.
std::string to_string_file_size(uint32_t file_size);
std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision); std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision);
double get_decimals(double num, int16_t mult, bool round = false); // euquiq added double get_decimals(double num, int16_t mult, bool round = false); // euquiq added

View File

@ -0,0 +1,710 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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_font_fixed_5x8.hpp"
#include <cstdint>
namespace ui {
namespace font {
namespace {
const uint8_t fixed_5x8_glyph_data[] = {
// Index: 0 (0x00) Char: 0x0020 (' ')
0x00,
0x00,
0x00,
0x00,
0x00,
// Index: 1 (0x01) Char: 0x0021 ('!')
0x80,
0x10,
0x42,
0x00,
0x01,
// Index: 2 (0x02) Char: 0x0022 ('"')
0x40,
0x29,
0x00,
0x00,
0x00,
// Index: 3 (0x03) Char: 0x0023 ('#')
0x00,
0xA8,
0xA7,
0x9E,
0x02,
// Index: 4 (0x04) Char: 0x0024 ('$')
0x80,
0x38,
0x83,
0x1C,
0x01,
// Index: 5 (0x05) Char: 0x0025 ('%')
0x00,
0x80,
0x44,
0x44,
0x02,
// Index: 6 (0x06) Char: 0x0026 ('&')
0x40,
0x94,
0xA1,
0xCA,
0x02,
// Index: 7 (0x07) Char: 0x0027 (''')
0x80,
0x10,
0x00,
0x00,
0x00,
// Index: 8 (0x08) Char: 0x0028 ('(')
0x80,
0x08,
0x21,
0x04,
0x01,
// Index: 9 (0x09) Char: 0x0029 (')')
0x40,
0x10,
0x42,
0x88,
0x00,
// Index: 10 (0x0A) Char: 0x002A ('*')
0x00,
0x28,
0xE2,
0x88,
0x02,
// Index: 11 (0x0B) Char: 0x002B ('+')
0x00,
0x00,
0xE2,
0x08,
0x00,
// Index: 12 (0x0C) Char: 0x002C (',')
0x00,
0x00,
0x00,
0x88,
0x00,
// Index: 13 (0x0D) Char: 0x002D ('-')
0x00,
0x00,
0xE0,
0x00,
0x00,
// Index: 14 (0x0E) Char: 0x002E ('.')
0x00,
0x00,
0x00,
0x80,
0x00,
// Index: 15 (0x0F) Char: 0x002F ('/')
0x00,
0x21,
0x42,
0x84,
0x00,
// Index: 16 (0x10) Char: 0x0030 ('0')
0xC0,
0xA4,
0xB6,
0x92,
0x01,
// Index: 17 (0x11) Char: 0x0031 ('1')
0x80,
0x18,
0x42,
0x88,
0x03,
// Index: 18 (0x12) Char: 0x0032 ('2')
0xC0,
0x24,
0x64,
0xC2,
0x03,
// Index: 19 (0x13) Char: 0x0033 ('3')
0xC0,
0x24,
0x82,
0x92,
0x01,
// Index: 20 (0x14) Char: 0x0034 ('4')
0x00,
0x31,
0x95,
0x1E,
0x02,
// Index: 21 (0x15) Char: 0x0035 ('5')
0xE0,
0x85,
0x83,
0x92,
0x01,
// Index: 22 (0x16) Char: 0x0036 ('6')
0xC0,
0x84,
0x93,
0x92,
0x01,
// Index: 23 (0x17) Char: 0x0037 ('7')
0xE0,
0x21,
0x44,
0x84,
0x00,
// Index: 24 (0x18) Char: 0x0038 ('8')
0xC0,
0x24,
0x93,
0x92,
0x01,
// Index: 25 (0x19) Char: 0x0039 ('9')
0xC0,
0xA4,
0xE4,
0x90,
0x01,
// Index: 26 (0x1A) Char: 0x003A (':')
0x00,
0x00,
0x01,
0x04,
0x00,
// Index: 27 (0x1B) Char: 0x003B (';')
0x00,
0x00,
0x02,
0x88,
0x00,
// Index: 28 (0x1C) Char: 0x003C ('<')
0x00,
0x10,
0x11,
0x04,
0x01,
// Index: 29 (0x1D) Char: 0x003D ('=')
0x00,
0x00,
0x07,
0x1C,
0x00,
// Index: 30 (0x1E) Char: 0x003E ('>')
0x00,
0x08,
0x82,
0x88,
0x00,
// Index: 31 (0x1F) Char: 0x003F ('?')
0x80,
0x28,
0x44,
0x00,
0x01,
// Index: 32 (0x20) Char: 0x0040 ('@')
0xC0,
0xA4,
0xD6,
0x82,
0x01,
// Index: 33 (0x21) Char: 0x0041 ('A')
0xC0,
0xA4,
0xF4,
0x52,
0x02,
// Index: 34 (0x22) Char: 0x0042 ('B')
0xE0,
0xA4,
0x93,
0xD2,
0x01,
// Index: 35 (0x23) Char: 0x0043 ('C')
0xC0,
0xA4,
0x10,
0x92,
0x01,
// Index: 36 (0x24) Char: 0x0044 ('D')
0xE0,
0xA4,
0x94,
0xD2,
0x01,
// Index: 37 (0x25) Char: 0x0045 ('E')
0xE0,
0x85,
0x13,
0xC2,
0x03,
// Index: 38 (0x26) Char: 0x0046 ('F')
0xE0,
0x85,
0x13,
0x42,
0x00,
// Index: 39 (0x27) Char: 0x0047 ('G')
0xC0,
0xA4,
0xD0,
0x92,
0x01,
// Index: 40 (0x28) Char: 0x0048 ('H')
0x20,
0xA5,
0x97,
0x52,
0x02,
// Index: 41 (0x29) Char: 0x0049 ('I')
0xC0,
0x11,
0x42,
0x88,
0x03,
// Index: 42 (0x2A) Char: 0x004A ('J')
0xC0,
0x21,
0x84,
0x92,
0x01,
// Index: 43 (0x2B) Char: 0x004B ('K')
0x20,
0x95,
0x51,
0x52,
0x02,
// Index: 44 (0x2C) Char: 0x004C ('L')
0x20,
0x84,
0x10,
0xC2,
0x01,
// Index: 45 (0x2D) Char: 0x004D ('M')
0xA0,
0xBD,
0x95,
0x52,
0x02,
// Index: 46 (0x2E) Char: 0x004E ('N')
0x20,
0xA5,
0xD5,
0x52,
0x02,
// Index: 47 (0x2F) Char: 0x004F ('O')
0xC0,
0xA4,
0x94,
0x92,
0x01,
// Index: 48 (0x30) Char: 0x0050 ('P')
0xE0,
0xA4,
0x74,
0x42,
0x00,
// Index: 49 (0x31) Char: 0x0051 ('Q')
0xC0,
0xA4,
0x94,
0x8A,
0x02,
// Index: 50 (0x32) Char: 0x0052 ('R')
0xE0,
0xA4,
0x74,
0x52,
0x02,
// Index: 51 (0x33) Char: 0x0053 ('S')
0xC0,
0x24,
0x41,
0x92,
0x01,
// Index: 52 (0x34) Char: 0x0054 ('T')
0xC0,
0x11,
0x42,
0x08,
0x01,
// Index: 53 (0x35) Char: 0x0055 ('U')
0x20,
0xA5,
0x94,
0x92,
0x03,
// Index: 54 (0x36) Char: 0x0056 ('V')
0x20,
0xA5,
0x94,
0x8A,
0x00,
// Index: 55 (0x37) Char: 0x0057 ('W')
0x20,
0xA5,
0xB4,
0x5E,
0x01,
// Index: 56 (0x38) Char: 0x0058 ('X')
0x20,
0x25,
0x63,
0x52,
0x02,
// Index: 57 (0x39) Char: 0x0059 ('Y')
0x20,
0xA5,
0x64,
0x08,
0x01,
// Index: 58 (0x3A) Char: 0x005A ('Z')
0xE0,
0x21,
0x22,
0xC2,
0x03,
// Index: 59 (0x3B) Char: 0x005B ('[')
0xC0,
0x08,
0x21,
0x84,
0x01,
// Index: 60 (0x3C) Char: 0x005C ('\')
0x40,
0x08,
0x42,
0x10,
0x02,
// Index: 61 (0x3D) Char: 0x005D (']')
0xC0,
0x10,
0x42,
0x88,
0x01,
// Index: 62 (0x3E) Char: 0x005E ('^')
0x00,
0x10,
0x05,
0x00,
0x00,
// Index: 63 (0x3F) Char: 0x005F ('_')
0x00,
0x00,
0x00,
0xC0,
0x03,
// Index: 64 (0x40) Char: 0x0060 ('`')
0x40,
0x10,
0x00,
0x00,
0x00,
// Index: 65 (0x41) Char: 0x0061 ('a')
0x00,
0x00,
0x83,
0x9E,
0x03,
// Index: 66 (0x42) Char: 0x0062 ('b')
0x20,
0x84,
0x93,
0x92,
0x01,
// Index: 67 (0x43) Char: 0x0063 ('c')
0x00,
0x00,
0x17,
0x82,
0x03,
// Index: 68 (0x44) Char: 0x0064 ('d')
0x00,
0x21,
0x97,
0x92,
0x01,
// Index: 69 (0x45) Char: 0x0065 ('e')
0x00,
0x00,
0xF3,
0x82,
0x01,
// Index: 70 (0x46) Char: 0x0066 ('f')
0x80,
0x89,
0x23,
0x84,
0x00,
// Index: 71 (0x47) Char: 0x0067 ('g')
0x00,
0x98,
0xE4,
0x90,
0x01,
// Index: 72 (0x48) Char: 0x0068 ('h')
0x20,
0x84,
0x93,
0x52,
0x02,
// Index: 73 (0x49) Char: 0x0069 ('i')
0x80,
0x00,
0x43,
0x08,
0x02,
// Index: 74 (0x4A) Char: 0x006A ('j')
0x00,
0x01,
0x86,
0x90,
0x01,
// Index: 75 (0x4B) Char: 0x006B ('k')
0x40,
0x08,
0x65,
0x94,
0x02,
// Index: 76 (0x4C) Char: 0x006C ('l')
0xC0,
0x10,
0x42,
0x08,
0x02,
// Index: 77 (0x4D) Char: 0x006D ('m')
0x00,
0x80,
0xF6,
0x56,
0x02,
// Index: 78 (0x4E) Char: 0x006E ('n')
0x00,
0x80,
0x93,
0x52,
0x02,
// Index: 79 (0x4F) Char: 0x006F ('o')
0x00,
0x00,
0x93,
0x92,
0x01,
// Index: 80 (0x50) Char: 0x0070 ('p')
0x00,
0x98,
0x74,
0x42,
0x00,
// Index: 81 (0x51) Char: 0x0071 ('q')
0x00,
0x98,
0xE4,
0x10,
0x02,
// Index: 82 (0x52) Char: 0x0072 ('r')
0x00,
0x00,
0x65,
0x84,
0x00,
// Index: 83 (0x53) Char: 0x0073 ('s')
0x00,
0x00,
0x66,
0x90,
0x03,
// Index: 84 (0x54) Char: 0x0074 ('t')
0x80,
0x10,
0x47,
0x08,
0x02,
// Index: 85 (0x55) Char: 0x0075 ('u')
0x00,
0x80,
0x94,
0x92,
0x03,
// Index: 86 (0x56) Char: 0x0076 ('v')
0x00,
0x80,
0x94,
0x8A,
0x00,
// Index: 87 (0x57) Char: 0x0077 ('w')
0x00,
0x80,
0xB4,
0x5E,
0x01,
// Index: 88 (0x58) Char: 0x0078 ('x')
0x00,
0x00,
0x45,
0x88,
0x02,
// Index: 89 (0x59) Char: 0x0079 ('y')
0x00,
0x00,
0xE5,
0x90,
0x01,
// Index: 90 (0x5A) Char: 0x007A ('z')
0x00,
0x00,
0xC7,
0x84,
0x03,
// Index: 91 (0x5B) Char: 0x007B ('{')
0x80,
0x11,
0x41,
0x08,
0x03,
// Index: 92 (0x5C) Char: 0x007C ('|')
0x80,
0x10,
0x42,
0x08,
0x01,
// Index: 93 (0x5D) Char: 0x007D ('}')
0xC0,
0x10,
0x44,
0x88,
0x01,
// Index: 94 (0x5E) Char: 0x007E ('~')
0x00,
0x00,
0x55,
0x00,
0x00,
};
}
const ui::Font fixed_5x8{
5,
8,
fixed_5x8_glyph_data,
0x20,
95,
};
} /* namespace font */
} /* namespace ui */

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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.
*/
#ifndef __UI_FONT_FIXED_5X8_H__
#define __UI_FONT_FIXED_5X8_H__
#include "ui_text.hpp"
namespace ui {
namespace font {
extern const ui::Font fixed_5x8;
} // namespace font
} // namespace ui
#endif /*__UI_FONT_FIXED_5X8_H__*/

View File

@ -66,6 +66,7 @@
#include "ui_sonde.hpp" #include "ui_sonde.hpp"
#include "ui_sstvtx.hpp" #include "ui_sstvtx.hpp"
//#include "ui_test.hpp" //#include "ui_test.hpp"
#include "ui_text_editor.hpp"
#include "ui_tone_search.hpp" #include "ui_tone_search.hpp"
#include "ui_touchtunes.hpp" #include "ui_touchtunes.hpp"
#include "ui_playlist.hpp" #include "ui_playlist.hpp"
@ -578,7 +579,7 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
//{ "Test app", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); } }, //{ "Test app", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); } },
{"Freq. manager", ui::Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }}, {"Freq. manager", ui::Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
{"File manager", ui::Color::yellow(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }}, {"File manager", ui::Color::yellow(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
//{ "Notepad", ui::Color::dark_grey(), &bitmap_icon_notepad, [&nav](){ nav.push<NotImplementedView>(); } }, {"Notepad", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
{"Signal gen", ui::Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }}, {"Signal gen", ui::Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
//{ "Tone search", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); } }, //{ "Tone search", ui::Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); } },
{"Wav viewer", ui::Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }}, {"Wav viewer", ui::Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
@ -783,8 +784,11 @@ ModalMessageView::ModalMessageView(
if (type == INFO) { if (type == INFO) {
add_child(&button_ok); add_child(&button_ok);
button_ok.on_select = [&nav](Button&) { button_ok.on_select = [this, &nav](Button&) {
nav.pop(); if (on_choice_)
on_choice_(true); // Assumes handler will pop.
else
nav.pop();
}; };
} else if (type == YESNO) { } else if (type == YESNO) {
add_children({&button_yes, add_children({&button_yes,

View File

@ -35,7 +35,7 @@ class Optional {
: value_{std::move(value)}, valid_{true} {}; : value_{std::move(value)}, valid_{true} {};
bool is_valid() const { return valid_; }; bool is_valid() const { return valid_; };
T value() const { return value_; }; const T& value() const { return value_; };
private: private:
T value_; T value_;