Standalone app api v3 (#2772)

Added file io, and updated some ui elements.
Also added Digital Rain standalone app for an example.
This commit is contained in:
Totoo 2025-09-01 11:50:46 +02:00 committed by GitHub
parent 776c9bc7c9
commit b15bb59678
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 14474 additions and 12 deletions

View file

@ -640,6 +640,7 @@ BLERxView::BLERxView(NavigationView& nav)
};
options_filter.on_change = [this](size_t index, int32_t v) {
(void)v;
filter_index = (uint8_t)index;
recent.clear();
recent_entries_view.set_dirty();

View file

@ -27,6 +27,9 @@
#include "ui_font_fixed_5x8.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "portapack.hpp"
#include "file.hpp"
namespace ui {
@ -93,8 +96,90 @@ bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
return dev->i2c_read(cmd, cmd_len, data, data_len);
}
// v3
// Version 3
FRESULT ext_f_open(FIL* fp, const TCHAR* path, BYTE mode) {
return f_open(fp, path, mode);
}
FRESULT ext_f_close(FIL* fp) {
return f_close(fp);
}
FRESULT ext_f_read(FIL* fp, void* buff, UINT btr, UINT* br) {
return f_read(fp, buff, btr, br);
}
FRESULT ext_f_write(FIL* fp, const void* buff, UINT btw, UINT* bw) {
return f_write(fp, buff, btw, bw);
}
FRESULT ext_f_lseek(FIL* fp, FSIZE_t ofs) {
return f_lseek(fp, ofs);
}
FRESULT ext_f_truncate(FIL* fp) {
return f_truncate(fp);
}
FRESULT ext_f_sync(FIL* fp) {
return f_sync(fp);
}
FRESULT ext_f_opendir(DIR* dp, const TCHAR* path) {
return f_opendir(dp, path);
}
FRESULT ext_f_closedir(DIR* dp) {
return f_closedir(dp);
}
FRESULT ext_f_readdir(DIR* dp, FILINFO* fno) {
return f_readdir(dp, fno);
}
FRESULT ext_f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern) {
return f_findfirst(dp, fno, path, pattern);
}
FRESULT ext_f_findnext(DIR* dp, FILINFO* fno) {
return f_findnext(dp, fno);
}
FRESULT ext_f_mkdir(const TCHAR* path) {
return f_mkdir(path);
}
FRESULT ext_f_unlink(const TCHAR* path) {
return f_unlink(path);
}
FRESULT ext_f_rename(const TCHAR* path_old, const TCHAR* path_new) {
return f_rename(path_old, path_new);
}
FRESULT ext_f_stat(const TCHAR* path, FILINFO* fno) {
return f_stat(path, fno);
}
FRESULT ext_f_utime(const TCHAR* path, const FILINFO* fno) {
return f_utime(path, fno);
}
FRESULT ext_f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) {
return f_getfree(path, nclst, fatfs);
}
FRESULT ext_f_mount(FATFS* fs, const TCHAR* path, BYTE opt) {
return f_mount(fs, path, opt);
}
int ext_f_putc(TCHAR c, FIL* fp) {
return f_putc(c, fp);
}
int ext_f_puts(const TCHAR* str, FIL* cp) {
return f_puts(str, cp);
}
int ext_f_printf(FIL* fp, const TCHAR* str, ...) {
return f_printf(fp, str);
}
TCHAR* ext_f_gets(TCHAR* buff, int len, FIL* fp) {
return f_gets(buff, len, fp);
}
void ext_draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count) {
portapack::display.draw_pixels(r, colors, count);
}
void ext_draw_pixel(const ui::Point p, const ui::Color color) {
portapack::display.draw_pixel(p, color);
}
StandaloneView* standaloneView = nullptr;
void exit_app() {
if (standaloneView) standaloneView->exit();
}
void set_dirty() {
if (standaloneView != nullptr)
standaloneView->set_dirty();
@ -121,6 +206,33 @@ standalone_application_api_t api = {
/* .i2c_read = */ &i2c_read,
/* .panic = */ &chDbgPanic,
/* .set_dirty = */ &set_dirty,
// Version 3
.f_open = &ext_f_open,
.f_close = &ext_f_close,
.f_read = &ext_f_read,
.f_write = &ext_f_write,
.f_lseek = &ext_f_lseek,
.f_truncate = &ext_f_truncate,
.f_sync = &ext_f_sync,
.f_opendir = &ext_f_opendir,
.f_closedir = &ext_f_closedir,
.f_readdir = &ext_f_readdir,
.f_findfirst = &ext_f_findfirst,
.f_findnext = &ext_f_findnext,
.f_mkdir = &ext_f_mkdir,
.f_unlink = &ext_f_unlink,
.f_rename = &ext_f_rename,
.f_stat = &ext_f_stat,
.f_utime = &ext_f_utime,
.f_getfree = &ext_f_getfree,
.f_mount = &ext_f_mount,
.f_putc = &ext_f_putc,
.f_puts = &ext_f_puts,
.f_printf = &ext_f_printf,
.f_gets = &ext_f_gets,
.draw_pixels = &ext_draw_pixels,
.draw_pixel = &ext_draw_pixel,
.exit_app = &exit_app,
};
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
@ -158,22 +270,21 @@ bool StandaloneView::on_key(const KeyEvent key) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnKeyEvent((uint8_t)key);
}
return false;
return true;
}
bool StandaloneView::on_encoder(const EncoderEvent event) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnEncoder((int32_t)event);
}
return false;
return true;
}
bool StandaloneView::on_touch(const TouchEvent event) {
if (get_application_information()->header_version > 1) {
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
}
return true;
return false;
}
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
@ -202,4 +313,8 @@ void StandaloneView::on_before_detach() {
context().focus_manager().clearMirror();
}
void StandaloneView::exit() {
nav_.pop();
}
} // namespace ui

View file

@ -51,6 +51,8 @@ class StandaloneView : public View {
void frame_sync();
void exit();
private:
bool initialized = false;
NavigationView& nav_;

View file

@ -4,6 +4,12 @@ namespace ui {
ThemeTemplate* Theme::current = nullptr;
void Theme::destroy() {
if (current != nullptr)
delete current;
current = nullptr;
}
ThemeTemplate* Theme::getInstance() {
if (current == nullptr) SetTheme(DefaultGrey);
return Theme::current;

View file

@ -113,7 +113,7 @@ class Theme {
static void SetTheme(ThemeId theme);
static ThemeTemplate* current;
static void destroy(); // used from standalone app, to prevent memleak
private:
};

View file

@ -256,7 +256,7 @@ void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
for (int i = 0; i < geomap_rect_width; i++) {
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
}
delete zoom_out_buffer;
delete[] zoom_out_buffer;
}
}
@ -660,7 +660,7 @@ bool GeoMap::init() {
map_height = 32768;
}
map_visible = map_opened;
map_visible = map_opened || has_osm;
map_center_x = map_width >> 1;
map_center_y = map_height >> 1;
@ -670,7 +670,7 @@ bool GeoMap::init() {
map_bottom = sin(-85.05 * pi / 180); // Map bitmap only goes from about -85 to 85 lat
map_world_lon = map_width / (2 * pi);
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
return map_opened || has_osm;
return map_opened;
}
void GeoMap::set_mode(GeoMapMode mode) {

View file

@ -347,7 +347,7 @@ namespace ui {
return false;
// TODO: move this to m4 memory space
auto app_image = reinterpret_cast<uint8_t*>(portapack::memory::map::m4_code.end() - app.size());
auto app_image = reinterpret_cast<uint8_t*>(portapack::memory::map::local_sram_0.base());
// read file in 512 byte chunks
for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) {

View file

@ -27,8 +27,9 @@
#include <stddef.h>
#include "ui.hpp"
#include "file.hpp"
#define CURRENT_STANDALONE_APPLICATION_API_VERSION 2
#define CURRENT_STANDALONE_APPLICATION_API_VERSION 3
struct standalone_application_api_t {
// Version 1
@ -58,7 +59,52 @@ struct standalone_application_api_t {
void (*panic)(const char* msg);
void (*set_dirty)();
// TODO: add filesystem access functions
// Version 3
FRESULT(*f_open)
(FIL* fp, const TCHAR* path, BYTE mode);
FRESULT(*f_close)
(FIL* fp);
FRESULT(*f_read)
(FIL* fp, void* buff, UINT btr, UINT* br);
FRESULT(*f_write)
(FIL* fp, const void* buff, UINT btw, UINT* bw);
FRESULT(*f_lseek)
(FIL* fp, FSIZE_t ofs);
FRESULT(*f_truncate)
(FIL* fp);
FRESULT(*f_sync)
(FIL* fp);
FRESULT(*f_opendir)
(DIR* dp, const TCHAR* path);
FRESULT(*f_closedir)
(DIR* dp);
FRESULT(*f_readdir)
(DIR* dp, FILINFO* fno);
FRESULT(*f_findfirst)
(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);
FRESULT(*f_findnext)
(DIR* dp, FILINFO* fno);
FRESULT(*f_mkdir)
(const TCHAR* path);
FRESULT(*f_unlink)
(const TCHAR* path);
FRESULT(*f_rename)
(const TCHAR* path_old, const TCHAR* path_new);
FRESULT(*f_stat)
(const TCHAR* path, FILINFO* fno);
FRESULT(*f_utime)
(const TCHAR* path, const FILINFO* fno);
FRESULT(*f_getfree)
(const TCHAR* path, DWORD* nclst, FATFS** fatfs);
FRESULT(*f_mount)
(FATFS* fs, const TCHAR* path, BYTE opt);
int (*f_putc)(TCHAR c, FIL* fp);
int (*f_puts)(const TCHAR* str, FIL* cp);
int (*f_printf)(FIL* fp, const TCHAR* str, ...);
TCHAR* (*f_gets)(TCHAR* buff, int len, FIL* fp);
void (*draw_pixels)(const ui::Rect r, const ui::Color* const colors, const size_t count);
void (*draw_pixel)(const ui::Point p, const ui::Color color);
void (*exit_app)();
// TODO: add baseband access functions
// HOW TO extend this interface:

View file

@ -3,9 +3,10 @@ cmake_minimum_required(VERSION 3.16)
project(standalone_apps)
add_subdirectory(pacman)
add_subdirectory(digitalrain)
add_custom_target(
standalone_apps
DEPENDS pacman_app
DEPENDS pacman_app digitalrain_app
)

View file

@ -0,0 +1,234 @@
#
# Copyright (C) 2024 Bernd Herzog
#
# 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.
#
##############################################################################
# Build global options
# NOTE: Can be overridden externally.
#
enable_language(C CXX ASM)
include(CheckCXXCompilerFlag)
project(digitalrain_app)
# Compiler options here.
set(USE_OPT "-Os -g --specs=nano.specs --specs=nosys.specs")
# C specific options here (added to USE_OPT).
set(USE_COPT "-std=gnu99")
# C++ specific options here (added to USE_OPT).
check_cxx_compiler_flag("-std=c++20" cpp20_supported)
if(cpp20_supported)
set(USE_CPPOPT "-std=c++20")
else()
set(USE_CPPOPT "-std=c++17")
endif()
set(USE_CPPOPT "${USE_CPPOPT} -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -fno-use-cxa-atexit")
# Enable this if you want the linker to remove unused code and data
set(USE_LINK_GC yes)
# Linker extra options here.
set(USE_LDOPT)
# Enable this if you want link time optimizations (LTO) - this flag affects chibios only
set(USE_LTO no)
# If enabled, this option allows to compile the application in THUMB mode.
set(USE_THUMB yes)
# Enable this if you want to see the full log while compiling.
set(USE_VERBOSE_COMPILE no)
#
# Build global options
##############################################################################
##############################################################################
# Architecture or project specific options
#
# Enables the use of FPU on Cortex-M4 (no, softfp, hard).
set(USE_FPU no)
#
# Architecture or project specific options
##############################################################################
##############################################################################
# Project, sources and paths
#
# Define linker script file here
set(LDSCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/external.ld)
# C sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
FILE(GLOB_RECURSE Sources_C ${CMAKE_CURRENT_LIST_DIR}/*.c)
set(CSRC
${Sources_C}
)
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
FILE(GLOB_RECURSE Sources_CPP ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
set(CPPSRC
${Sources_CPP}
)
# C sources to be compiled in ARM mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
set(ACSRC)
# C++ sources to be compiled in ARM mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
set(ACPPSRC)
# C sources to be compiled in THUMB mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
set(TCSRC)
# C sources to be compiled in THUMB mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
set(TCPPSRC)
# List ASM source files here
set(ASMSRC)
set(INCDIR
${CMAKE_CURRENT_SOURCE_DIR}
${COMMON}
${COMMON}/../application
${COMMON}/../application/hw
)
#
# Project, sources and paths
##############################################################################
##############################################################################
# Compiler settings
#
# TODO: Entertain using MCU=cortex-m0.small-multiply for LPC43xx M0 core.
# However, on GCC-ARM-Embedded 4.9 2015q2, it seems to produce non-functional
# binaries.
set(MCU cortex-m0)
# ARM-specific options here
set(AOPT)
# THUMB-specific options here
set(TOPT "-mthumb -DTHUMB")
# Define C warning options here
set(CWARN "-Wall -Wextra -Wstrict-prototypes")
# Define C++ warning options here
set(CPPWARN "-Wall -Wextra -Wno-psabi")
#
# Compiler settings
##############################################################################
##############################################################################
# Start of default section
#
# List all default C defines here, like -D_DEBUG=1
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0")
# List all default ASM defines here, like -D_DEBUG=1
set(DADEFS)
# List all default directories to look for include files here
set(DINCDIR)
# List the default directory to look for the libraries here
set(DLIBDIR)
# List all default libraries here
set(DLIBS)
#
# End of default section
##############################################################################
##############################################################################
# Start of user section
#
# List all user C define here, like -D_DEBUG=1
set(UDEFS)
# Define ASM defines here
set(UADEFS)
# List all user directories here
set(UINCDIR)
# List the user directory to look for the libraries here
set(ULIBDIR)
# List all user libraries here
set(ULIBS)
#
# End of user defines
##############################################################################
set(RULESPATH ${CHIBIOS}/os/ports/GCC/ARMCMx)
include(${RULESPATH}/rules.cmake)
##############################################################################
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
add_definitions(${DEFS})
include_directories(. ${INCDIR})
link_directories(${LLIBDIR})
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
# redirect std lib memory allocations
target_link_libraries(${PROJECT_NAME}.elf "-Wl,-wrap,_malloc_r")
target_link_libraries(${PROJECT_NAME}.elf "-Wl,-wrap,_free_r")
target_link_libraries(${PROJECT_NAME}.elf "-Wl,--print-memory-usage")
add_custom_command(
OUTPUT ${PROJECT_NAME}.ppmp
COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.ppmp
DEPENDS ${PROJECT_NAME}.elf
)
add_custom_target(
${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}.ppmp
)

View file

@ -0,0 +1,153 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* 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 "digitalrain.hpp"
#include <memory>
#include <string>
StandaloneViewMirror* standaloneViewMirror = nullptr;
ui::Context* context = nullptr;
void initialize(const standalone_application_api_t& api) {
_api = &api;
context = new ui::Context();
standaloneViewMirror = new StandaloneViewMirror(*context, {0, 16, 240, 304});
}
// event 1 == frame sync. called each 1/60th of second, so 6 = 100ms
void on_event(const uint32_t& events) {
if ((events & 1) == 1) standaloneViewMirror->need_refresh();
}
void shutdown() {
delete standaloneViewMirror;
delete context;
}
void PaintViewMirror() {
ui::Painter painter;
if (standaloneViewMirror)
painter.paint_widget_tree(standaloneViewMirror);
}
ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) {
if (!w->hidden()) {
// To achieve reverse depth ordering (last object drawn is
// considered "top"), descend first.
for (const auto child : w->children()) {
const auto touched_widget = touch_widget(child, event);
if (touched_widget) {
return touched_widget;
}
}
const auto r = w->screen_rect();
if (r.contains(event.point)) {
if (w->on_touch(event)) {
// This widget responded. Return it up the call stack.
return w;
}
}
}
return nullptr;
}
ui::Widget* captured_widget{nullptr};
void OnTouchEvent(int, int, uint32_t) {
if (standaloneViewMirror) {
_api->exit_app();
/*
ui::TouchEvent event{{x, y}, static_cast<ui::TouchEvent::Type>(type)};
if (event.type == ui::TouchEvent::Type::Start) {
captured_widget = touch_widget(standaloneViewMirror, event);
if (captured_widget) {
captured_widget->focus();
captured_widget->set_dirty();
}
}
if (captured_widget)
captured_widget->on_touch(event);
*/
}
}
void OnFocus() {
if (standaloneViewMirror)
standaloneViewMirror->focus();
}
bool OnKeyEvent(uint8_t) {
// ui::KeyEvent key = (ui::KeyEvent)key_val;
if (context) {
_api->exit_app();
/* auto focus_widget = context->focus_manager().focus_widget();
if (focus_widget) {
if (focus_widget->on_key(key))
return true;
context->focus_manager().update(standaloneViewMirror, key);
if (focus_widget != context->focus_manager().focus_widget())
return true;
else {
if (key == ui::KeyEvent::Up || key == ui::KeyEvent::Back || key == ui::KeyEvent::Left) {
focus_widget->blur();
return false;
}
}
}*/
}
return false;
}
bool OnEncoder(int32_t) {
if (context) {
_api->exit_app();
/*
auto focus_widget = context->focus_manager().focus_widget();
if (focus_widget) return focus_widget->on_encoder((ui::EncoderEvent)delta);
*/
}
return false;
}
bool OnKeyboad(uint8_t) {
if (context) {
_api->exit_app();
/*
auto focus_widget = context->focus_manager().focus_widget();
if (focus_widget)
return focus_widget->on_keyboard((ui::KeyboardEvent)key);
*/
}
return false;
}

View file

@ -0,0 +1,221 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* 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.
*/
#pragma once
#include "standalone_app.hpp"
#include "ui/ui_widget.hpp"
#include "ui/theme.hpp"
#include "ui/string_format.hpp"
#include <string.h>
#include "ui/ui_font_fixed_5x8.hpp"
#include "ui/ui_painter.hpp"
#include <string>
#include <random>
#include <cstdlib> // for std::rand() and std::srand()
#include <ctime> // for std::time()
void initialize(const standalone_application_api_t& api);
void on_event(const uint32_t& events);
void shutdown();
void OnFocus();
bool OnKeyEvent(uint8_t);
bool OnEncoder(int32_t);
void OnTouchEvent(int, int, uint32_t);
bool OnKeyboad(uint8_t);
void PaintViewMirror();
extern const standalone_application_api_t* _api;
class DigitalRain {
private:
ui::Painter painter{};
static const int WIDTH = 240;
static const int HEIGHT = 325;
static const int MARGIN_TOP = 20;
static const int CHAR_WIDTH = 5;
static const int CHAR_HEIGHT = 8;
static const int COLS = WIDTH / CHAR_WIDTH;
static const int ROWS = (HEIGHT - MARGIN_TOP) / CHAR_HEIGHT;
static const int MAX_DROPS = 36;
const ui::Font& font = ui::font::fixed_5x8();
struct Drop {
uint8_t x;
int16_t y;
uint8_t length;
uint8_t speed;
uint8_t morph_counter[16];
char chars[16];
int16_t old_y; // Track previous position for clearing
bool active;
};
Drop drops[MAX_DROPS];
const char char_set[16] = {
'@', '#', '$', '0', '1', '2', '>', '<',
'/', '\\', '[', ']', '{', '}', '.', ' '};
inline int random(int min, int max) {
return min + (std::rand() % (max - min + 1));
}
void init_drop(uint8_t index, bool force_top = false) {
drops[index].x = random(0, COLS - 1);
drops[index].y = force_top ? -random(0, 5) : -5;
drops[index].old_y = drops[index].y; // Initialize old position
drops[index].length = random(5, 15);
drops[index].speed = random(1, 3);
drops[index].active = true;
for (uint8_t i = 0; i < 16; i++) {
drops[index].chars[i] = char_set[random(0, 15)];
drops[index].morph_counter[i] = random(2, 6);
}
}
void clear_drop_trail(const Drop& drop) {
// Convert to int16_t for consistent type comparison
int16_t start_y = std::max<int16_t>(0, drop.old_y - drop.length + 1);
int16_t end_y = std::min<int16_t>(ROWS - 1, drop.old_y);
if (start_y <= end_y) {
int16_t pixel_y = start_y * CHAR_HEIGHT + MARGIN_TOP;
uint16_t height = (end_y - start_y + 1) * CHAR_HEIGHT;
painter.fill_rectangle_unrolled8(
{static_cast<int16_t>(drop.x * CHAR_WIDTH),
pixel_y,
CHAR_WIDTH,
height},
ui::Color::black());
}
}
void morph_characters(Drop& drop) {
for (uint8_t i = 0; i < drop.length; i++) {
if (--drop.morph_counter[i] == 0) {
drop.chars[i] = char_set[random(0, 15)];
drop.morph_counter[i] = random(2, 6);
}
}
}
public:
DigitalRain() {
std::srand(0);
for (uint8_t i = 0; i < MAX_DROPS; ++i) {
init_drop(i, true);
}
}
void update() {
for (uint8_t i = 0; i < MAX_DROPS; ++i) {
if (!drops[i].active) continue;
// Store old position before updating
drops[i].old_y = drops[i].y;
// Update position
drops[i].y += drops[i].speed;
morph_characters(drops[i]);
// Reset drop if off screen
if (drops[i].y - drops[i].length > ROWS) {
clear_drop_trail(drops[i]); // Clear final position
init_drop(i);
}
}
}
void render() {
for (uint8_t i = 0; i < MAX_DROPS; ++i) {
if (!drops[i].active) continue;
// Clear previous position
clear_drop_trail(drops[i]);
// Draw new position
for (uint8_t j = 0; j < drops[i].length; ++j) {
int y = drops[i].y - j;
if (y >= 0 && y < ROWS) {
ui::Point p{
static_cast<int16_t>(drops[i].x * CHAR_WIDTH),
static_cast<int16_t>(y * CHAR_HEIGHT + MARGIN_TOP)};
ui::Color fg;
if (j == 0) {
fg = ui::Color::white();
} else if (j < 3) {
fg = ui::Color(0, 255, 0);
} else {
uint8_t intensity = std::max(40, 180 - (j * 15));
fg = ui::Color(0, intensity, 0);
}
std::string ch(1, drops[i].chars[j]);
painter.draw_string(
p,
font,
fg,
ui::Color::black(),
ch);
}
}
}
}
};
class StandaloneViewMirror : public ui::View {
public:
StandaloneViewMirror(ui::Context& context, const ui::Rect parent_rect)
: View{parent_rect}, context_(context) {
set_style(ui::Theme::getInstance()->bg_darkest);
}
~StandaloneViewMirror() {
ui::Theme::destroy();
}
ui::Context& context() const override {
return context_;
}
void focus() override {
}
bool need_refresh() {
update++;
if (update % 2 == 0) {
return false;
}
digitalRain.update();
digitalRain.render();
return true;
}
private:
ui::Context& context_;
ui::Console console{{0, 0, 240, 320}};
DigitalRain digitalRain{};
uint8_t update = 0;
};

View file

@ -0,0 +1,118 @@
/*
Copyright (C) 2024 Bernd Herzog
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
MEMORY
{
ram : org = 0xADB10000, len = 64k /* DO NOT CHANGE the address. We make the image relocateable on load. It needs to be 0xADB10000 */
}
__ram_start__ = ORIGIN(ram);
__ram_size__ = LENGTH(ram);
__ram_end__ = __ram_start__ + __ram_size__;
SECTIONS
{
. = 0;
_text = .;
startup : ALIGN(16) SUBALIGN(16)
{
KEEP(*(.standalone_application_information));
} > ram
constructors : ALIGN(4) SUBALIGN(4)
{
PROVIDE(__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE(__init_array_end = .);
} > ram
destructors : ALIGN(4) SUBALIGN(4)
{
PROVIDE(__fini_array_start = .);
KEEP(*(.fini_array))
KEEP(*(SORT(.fini_array.*)))
PROVIDE(__fini_array_end = .);
} > ram
.text : ALIGN(16) SUBALIGN(16)
{
*(.text.startup.*)
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
*(.glue_7t)
*(.glue_7)
*(.gcc*)
} > ram
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > ram
.ARM.exidx : {
PROVIDE(__exidx_start = .);
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
PROVIDE(__exidx_end = .);
} > ram
.eh_frame_hdr :
{
*(.eh_frame_hdr)
} > ram
.eh_frame : ONLY_IF_RO
{
*(.eh_frame)
} > ram
.textalign : ONLY_IF_RO
{
. = ALIGN(8);
} > ram
.bss ALIGN(4) : ALIGN(4)
{
. = ALIGN(4);
PROVIDE(_bss_start = .);
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
PROVIDE(_bss_end = .);
} > ram
. = ALIGN(4);
_etext = .;
_textdata = _etext;
.data ALIGN(4) : AT (_textdata)
{
. = ALIGN(4);
PROVIDE(_data = .);
*(.data)
*(.data.*)
*(.ramtext)
. = ALIGN(4);
PROVIDE(_edata = .);
} > ram
}
PROVIDE(end = .);
_end = .;

View file

@ -0,0 +1,312 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT file system module R0.12c /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2017, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/
#ifndef _FATFS
#define _FATFS 68300 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h" /* Basic integer types */
#include "ffconf.h" /* FatFs configuration options */
#if _FATFS != _FFCONF
#error Wrong configuration file (ffconf.h).
#endif
/* Definitions of volume management */
#if _MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
#endif
/* Type of path name strings on FatFs API */
#if _LFN_UNICODE /* Unicode (UTF-16) string */
#if _USE_LFN == 0
#error _LFN_UNICODE must be 0 at non-LFN cfg.
#endif
#ifndef _INC_TCHAR
typedef WCHAR TCHAR;
#define _T(x) L##x
#define _TEXT(x) L##x
#endif
#else /* ANSI/OEM string */
#ifndef _INC_TCHAR
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size variables */
#if _FS_EXFAT
#if _USE_LFN == 0
#error LFN must be enabled when enable exFAT
#endif
typedef QWORD FSIZE_t;
#else
typedef DWORD FSIZE_t;
#endif
/* File system object structure (FATFS) */
typedef struct {
BYTE fs_type; /* File system type (0:N/A) */
BYTE drv; /* Physical drive number */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if _MAX_SS != _MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if _USE_LFN != 0
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if _FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if _FS_RPATH != 0
DWORD cdir; /* Current directory start cluster (0:root) */
#if _FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (_FDID) */
typedef struct {
FATFS* fs; /* Pointer to the owner file system object */
WORD id; /* Owner file system mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:flagmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if _FS_EXFAT
DWORD n_cont; /* Size of first fragment, clusters - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when sclust != 0 and non-directory object) */
#endif
#if _FS_LOCK != 0
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} _FDID;
/* File object structure (FIL) */
typedef struct {
_FDID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
#if !_FS_READONLY
DWORD dir_sect; /* Sector number containing the directory entry */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */
#endif
#if _USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
_FDID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if _USE_LFN != 0
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if _USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if _USE_LFN != 0
TCHAR altname[13]; /* Altenative file name */
TCHAR fname[_MAX_LFN + 1]; /* Primary file name */
#else
TCHAR fname[13]; /* File name */
#endif
} FILINFO;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
/*
//MOVED TO API
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode); / Open or create a file *
FRESULT f_close(FIL* fp); / Close an open file object *
FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br); / Read data from the file *
FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw); / Write data to the file *
FRESULT f_lseek(FIL* fp, FSIZE_t ofs); / Move file pointer of the file object *
FRESULT f_truncate(FIL* fp); / Truncate the file *
FRESULT f_sync(FIL* fp); / Flush cached data of the writing file *
FRESULT f_opendir(DIR* dp, const TCHAR* path); / Open a directory *
FRESULT f_closedir(DIR* dp); / Close an open directory *
FRESULT f_readdir(DIR* dp, FILINFO* fno); / Read a directory item *
FRESULT f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); / Find first file *
FRESULT f_findnext(DIR* dp, FILINFO* fno); / Find next file *
FRESULT f_mkdir(const TCHAR* path); / Create a sub diectory *
FRESULT f_unlink(const TCHAR* path); / Delete an existing fileor directory *
FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new); / Rename/Move a file or directory *
FRESULT f_stat(const TCHAR* path, FILINFO* fno); / Get file status *
FRESULT f_chmod(const TCHAR* path, BYTE attr, BYTE mask); / Change attribute of a file/dir *
FRESULT f_utime(const TCHAR* path, const FILINFO* fno); / Change timestamp of a file/dir *
FRESULT f_chdir(const TCHAR* path); / Change current directory *
FRESULT f_chdrive(const TCHAR* path); / Change current drive *
FRESULT f_getcwd(TCHAR* buff, UINT len); / Get current directory *
FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs); / Get number of free clusters on the drive *
FRESULT f_getlabel(const TCHAR* path, TCHAR* label, DWORD* vsn); / Get volume label *
FRESULT f_setlabel(const TCHAR* label); / Set volume label *
FRESULT f_forward(FIL* fp, UINT (*func)(const BYTE*, UINT), UINT btf, UINT* bf); / Forward data to the stream *
FRESULT f_expand(FIL* fp, FSIZE_t szf, BYTE opt); / Allocate a contiguous block to the file *
FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt); / Mount/Unmount a logical drive *
FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); / Create a FAT volume *
FRESULT f_fdisk(BYTE pdrv, const DWORD* szt, void* work); / Divide a physical drive into some partitions *
int f_putc(TCHAR c, FIL* fp); / Put a character to the file *
int f_puts(const TCHAR* str, FIL* cp); / Put a string to the file *
int f_printf(FIL* fp, const TCHAR* str, ...); / Put a formatted string to the file *
TCHAR* f_gets(TCHAR* buff, int len, FIL* fp); / Get a string from the file *
*/
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#include "fileext.hpp"
#endif /* _FATFS */

View file

@ -0,0 +1,242 @@
/* CHIBIOS FIX */
/*---------------------------------------------------------------------------/
/ FatFs - FAT file system module configuration file
/---------------------------------------------------------------------------*/
#define _FFCONF 68300 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define _FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define _FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: All basic functions are enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define _USE_STRFUNC 1
/* This option switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define _USE_FIND 1
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define _USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define _USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define _USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define _USE_CHMOD 1
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
#define _USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define _USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define _CODE_PAGE 437
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
/
/ 1 - ASCII (No support of extended character. Non-LFN cfg. only)
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
*/
#define _USE_LFN 3
#define _MAX_LFN 255
/* The _USE_LFN switches the support of long file name (LFN).
/
/ 0: Disable support of LFN. _MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added
/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and
/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255.
/ It should be set 255 to support full featured LFN operations.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree(), must be added to the project. */
#define _LFN_UNICODE 1
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
/ This option also affects behavior of string I/O functions. */
#define _STRF_ENCODE 3
/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
/
/ 0: ANSI/OEM
/ 1: UTF-16LE
/ 2: UTF-16BE
/ 3: UTF-8
/
/ This option has no effect when _LFN_UNICODE == 0. */
#define _FS_RPATH 0
/* This option configures support of relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define _VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define _STR_VOLUME_ID 0
#define _VOLUME_STRS "RAM", "NAND", "CF", "SD", "SD2", "USB", "USB2", "USB3"
/* _STR_VOLUME_ID switches string support of volume ID.
/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
/ number in the path name. _VOLUME_STRS defines the drive ID strings for each
/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for
/ the drive ID strings are: A-Z and 0-9. */
#define _MULTI_PARTITION 0
/* This option switches support of multi-partition on a physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When multi-partition is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define _MIN_SS 512
#define _MAX_SS 512
/* These options configure the range of sector size to be supported. (512, 1024,
/ 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
/ to variable sector size and GET_SECTOR_SIZE command needs to be implemented to
/ the disk_ioctl() function. */
#define _USE_TRIM 0
/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define _FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define _FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked _MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the file system object (FATFS) is used for the file data transfer. */
#define _FS_EXFAT 1
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define _FS_NORTC 0
#define _NORTC_MON 1
#define _NORTC_MDAY 1
#define _NORTC_YEAR 2016
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time.
/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to get current time form real-time clock. _NORTC_MON,
/ _NORTC_MDAY and _NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
#define _FS_LOCK 0
/* The option _FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define _FS_REENTRANT 1
#define _FS_TIMEOUT 1000
#define _SYNC_t void*
/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The _FS_TIMEOUT defines timeout period in unit of time tick.
/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/* #include <windows.h> // O/S definitions */
/*--- End of configuration options ---*/

View file

@ -0,0 +1,42 @@
extern "C" FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode);
extern "C" FRESULT f_close(FIL* fp);
extern "C" FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br);
extern "C" FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw);
extern "C" FRESULT f_lseek(FIL* fp, FSIZE_t ofs);
extern "C" FRESULT f_truncate(FIL* fp);
extern "C" FRESULT f_sync(FIL* fp);
extern "C" FRESULT f_opendir(DIR* dp, const TCHAR* path);
extern "C" FRESULT f_closedir(DIR* dp);
extern "C" FRESULT f_readdir(DIR* dp, FILINFO* fno);
extern "C" FRESULT f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);
extern "C" FRESULT f_findnext(DIR* dp, FILINFO* fno);
extern "C" FRESULT f_mkdir(const TCHAR* path);
extern "C" FRESULT f_unlink(const TCHAR* path);
extern "C" FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new);
extern "C" FRESULT f_stat(const TCHAR* path, FILINFO* fno);
extern "C" FRESULT f_chmod(const TCHAR* path, BYTE attr, BYTE mask);
extern "C" FRESULT f_utime(const TCHAR* path, const FILINFO* fno);
extern "C" FRESULT f_chdir(const TCHAR* path);
extern "C" FRESULT f_chdrive(const TCHAR* path);
extern "C" FRESULT f_getcwd(TCHAR* buff, UINT len);
extern "C" FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs);
extern "C" FRESULT f_getlabel(const TCHAR* path, TCHAR* label, DWORD* vsn);
extern "C" FRESULT f_setlabel(const TCHAR* label);
extern "C" FRESULT f_forward(FIL* fp, UINT (*func)(const BYTE*, UINT), UINT btf, UINT* bf);
extern "C" FRESULT f_expand(FIL* fp, FSIZE_t szf, BYTE opt);
extern "C" FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt);
extern "C" FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len);
extern "C" FRESULT f_fdisk(BYTE pdrv, const DWORD* szt, void* work);
extern "C" int f_putc(TCHAR c, FIL* fp);
extern "C" int f_puts(const TCHAR* str, FIL* cp);
extern "C" int f_printf(FIL* fp, const TCHAR* str, ...);
extern "C" TCHAR* f_gets(TCHAR* buff, int len, FIL* fp);

View file

@ -0,0 +1,38 @@
/*-------------------------------------------*/
/* Integer type definitions for FatFs module */
/*-------------------------------------------*/
#ifndef _FF_INTEGER
#define _FF_INTEGER
#ifdef _WIN32 /* FatFs development platform */
#include <windows.h>
#include <tchar.h>
typedef unsigned __int64 QWORD;
#else /* Embedded platform */
/* These types MUST be 16-bit or 32-bit */
typedef int INT;
typedef unsigned int UINT;
/* This type MUST be 8-bit */
typedef unsigned char BYTE;
/* These types MUST be 16-bit */
typedef short SHORT;
typedef unsigned short WORD;
typedef unsigned short WCHAR;
/* These types MUST be 32-bit */
typedef long LONG;
typedef unsigned long DWORD;
/* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */
typedef unsigned long long QWORD;
#endif
#endif

View file

@ -0,0 +1,178 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* 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 "standalone_app.hpp"
#include "digitalrain.hpp"
#include <memory>
const standalone_application_api_t* _api;
extern "C" {
__attribute__((section(".standalone_application_information"), used)) standalone_application_information_t _standalone_application_information = {
/*.header_version = */ 2,
/*.app_name = */ "DigitalRain",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xFC,
0x3F,
0xFE,
0x7F,
0x02,
0x40,
0xBA,
0x45,
0x02,
0x40,
0xFE,
0x7F,
0xFE,
0x7F,
0x92,
0x7C,
0x92,
0x7C,
0xFC,
0x3F,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = 16 bit: 5R 6G 5B*/ 0x0000FFE0,
/*.menu_location = */ app_location_t::GAMES,
/*.initialize_app = */ initialize,
/*.on_event = */ on_event,
/*.shutdown = */ shutdown,
/*PaintViewMirror */ PaintViewMirror,
/*OnTouchEvent */ OnTouchEvent,
/*OnFocus */ OnFocus,
/*OnKeyEvent */ OnKeyEvent,
/*OnEncoder */ OnEncoder,
/*OnKeyboard */ OnKeyboad};
}
/* Implementing abort() eliminates requirement for _getpid(), _kill(), _exit(). */
extern "C" void abort() {
while (true);
}
// replace memory allocations to use heap from chibios
extern "C" void* malloc(size_t size) {
return _api->malloc(size);
}
extern "C" void* calloc(size_t num, size_t size) {
return _api->calloc(num, size);
}
extern "C" void* realloc(void* p, size_t size) {
return _api->realloc(p, size);
}
extern "C" void free(void* p) {
_api->free(p);
}
// redirect std lib memory allocations (sprintf, etc.)
extern "C" void* __wrap__malloc_r(size_t size) {
return _api->malloc(size);
}
extern "C" void __wrap__free_r(void* p) {
_api->free(p);
}
// redirect file I/O
extern "C" FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode) {
return _api->f_open(fp, path, mode);
}
extern "C" FRESULT f_close(FIL* fp) {
return _api->f_close(fp);
}
extern "C" FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br) {
return _api->f_read(fp, buff, btr, br);
}
extern "C" FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw) {
return _api->f_write(fp, buff, btw, bw);
}
extern "C" FRESULT f_lseek(FIL* fp, FSIZE_t ofs) {
return _api->f_lseek(fp, ofs);
}
extern "C" FRESULT f_truncate(FIL* fp) {
return _api->f_truncate(fp);
}
extern "C" FRESULT f_sync(FIL* fp) {
return _api->f_sync(fp);
}
extern "C" FRESULT f_opendir(DIR* dp, const TCHAR* path) {
return _api->f_opendir(dp, path);
}
extern "C" FRESULT f_closedir(DIR* dp) {
return _api->f_closedir(dp);
}
extern "C" FRESULT f_readdir(DIR* dp, FILINFO* fno) {
return _api->f_readdir(dp, fno);
}
extern "C" FRESULT f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern) {
return _api->f_findfirst(dp, fno, path, pattern);
}
extern "C" FRESULT f_findnext(DIR* dp, FILINFO* fno) {
return _api->f_findnext(dp, fno);
}
extern "C" FRESULT f_mkdir(const TCHAR* path) {
return _api->f_mkdir(path);
}
extern "C" FRESULT f_unlink(const TCHAR* path) {
return _api->f_unlink(path);
}
extern "C" FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new) {
return _api->f_rename(path_old, path_new);
}
extern "C" FRESULT f_stat(const TCHAR* path, FILINFO* fno) {
return _api->f_stat(path, fno);
}
extern "C" FRESULT f_utime(const TCHAR* path, const FILINFO* fno) {
return _api->f_utime(path, fno);
}
extern "C" FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) {
return _api->f_getfree(path, nclst, fatfs);
}
extern "C" FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt) {
return _api->f_mount(fs, path, opt);
}
extern "C" int f_putc(TCHAR c, FIL* fp) {
return _api->f_putc(c, fp);
}
extern "C" int f_puts(const TCHAR* str, FIL* cp) {
return _api->f_puts(str, cp);
}
extern "C" int f_printf(FIL* fp, const TCHAR* str, ...) {
return _api->f_printf(fp, str);
}
extern "C" TCHAR* f_gets(TCHAR* buff, int len, FIL* fp) {
return _api->f_gets(buff, len, fp);
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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.
*/
#pragma pack(push, 1)
struct bmp_header_t {
uint16_t signature;
uint32_t size;
uint16_t reserved_1;
uint16_t reserved_2;
uint32_t image_data;
uint32_t BIH_size;
uint32_t width;
int32_t height; // can be negative, to signal the bottom-up or reserve status
uint16_t planes;
uint16_t bpp;
uint32_t compression;
uint32_t data_size;
uint32_t h_res;
uint32_t v_res;
uint32_t colors_count;
uint32_t icolors_count;
};
#pragma pack(pop)
#pragma pack(push, 1)
struct bmp_palette_t {
struct color_t {
uint8_t B;
uint8_t G;
uint8_t R;
uint8_t A;
} color[16];
};
#pragma pack(pop)

View file

@ -0,0 +1,306 @@
/*
* Copyright (C) 2024 HTotoo
*
* 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 "bmpfile.hpp"
bool BMPFile::is_loaded() {
return is_opened;
}
// fix height info
uint32_t BMPFile::get_real_height() {
if (!is_opened) return 0;
return bmp_header.height >= 0 ? (uint32_t)bmp_header.height : (uint32_t)(-1 * bmp_header.height);
}
// get bmp width
uint32_t BMPFile::get_width() {
if (!is_opened) return 0;
return bmp_header.width;
}
// get if the rows are bottom up (for most bmp), or up to bottom (negative height, we use it for write)
bool BMPFile::is_bottomup() {
return (bmp_header.height >= 0);
}
BMPFile::~BMPFile() {
close();
}
// closes file
void BMPFile::close() {
is_opened = false;
bmpimage.close();
}
// creates a new bmp file. hardcoded to 3 byte (24-bit) colour depth
bool BMPFile::create(const std::filesystem::path& file, uint32_t x, uint32_t y) {
is_opened = false;
is_read_only = true;
bmpimage.close(); // if already open, close before open a new
if (file_exists(file)) {
delete_file(file); // overwrite
}
auto result = bmpimage.open(file, false, true);
if (!result.value().ok()) return false;
file_pos = 0;
byte_per_row = (x * 3 % 4 == 0) ? x * 3 : (x * 3 + (4 - ((x * 3) % 4))); // with padding
bmpimage.seek(file_pos);
bmp_header.signature = 0x4D42;
bmp_header.planes = 1;
bmp_header.compression = 0;
bmp_header.bpp = 24; // 3 byte depth
bmp_header.width = x;
bmp_header.height = 0; // for now, will expand
bmp_header.image_data = 0x36;
bmp_header.BIH_size = 0x28;
bmp_header.h_res = 100;
bmp_header.v_res = 100;
byte_per_px = 3;
type = 1;
bmp_header.size = sizeof(bmp_header) + get_real_height() * byte_per_row; // with padding! --will update later with expand
bmp_header.data_size = bmp_header.size - sizeof(bmp_header_t);
bmp_header.colors_count = 0;
bmp_header.icolors_count = 0;
bmpimage.write(&bmp_header, sizeof(bmp_header_t));
file_pos = bmp_header.image_data;
is_opened = true;
is_read_only = false;
if (!expand_y(y)) return false; // will fill with 0, and update header data
seek(0, 0);
return true;
}
// opens the file and parses header data. return true on success
bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
is_opened = false;
is_read_only = true;
bmpimage.close();
auto result = bmpimage.open(file, readonly, false);
if (!result.value().ok()) return false;
file_pos = 0;
bmpimage.seek(file_pos);
bmpimage.read(&bmp_header, sizeof(bmp_header_t));
if (!((bmp_header.signature == 0x4D42) &&
(bmp_header.planes == 1) &&
(bmp_header.compression == 0 || bmp_header.compression == 3))) {
return false;
}
switch (bmp_header.bpp) {
case 8:
type = 4;
byte_per_px = 1;
// bmpimage.seek(sizeof(bmp_header_t));
// bmpimage.read(color_palette, 1024);
return false; // niy
break;
case 16:
byte_per_px = 2;
type = 5;
if (bmp_header.compression == 3) {
return false;
} // niy
break;
case 24:
type = 1;
byte_per_px = 3;
break;
case 32:
type = 2;
byte_per_px = 4;
break;
default:
// not supported
return false;
}
byte_per_row = (bmp_header.width * byte_per_px + 3) & ~3;
file_pos = bmp_header.image_data;
is_opened = true;
is_read_only = readonly;
currx = 0;
curry = 0;
return true;
}
// jumps to next pixel. false on the end
bool BMPFile::advance_curr_px(uint32_t num = 1) {
if (curry >= get_real_height()) return false;
uint32_t rowsToAdvance = (currx + num) / bmp_header.width;
uint32_t nx = (currx + num) % bmp_header.width;
uint32_t ny = curry + rowsToAdvance;
if (ny >= get_real_height()) {
return false;
}
seek(nx, ny);
return true;
}
// reads next px, then advance the pos (and seek). return false on error
bool BMPFile::read_next_px(ui::Color& px, bool seek = true) {
if (!is_opened) return false;
uint8_t buffer[4];
auto res = bmpimage.read(buffer, byte_per_px);
if (res.is_error()) return false;
switch (type) {
case 5: {
// ARGB1555
uint16_t val = buffer[0] | (buffer[1] << 8);
// Extract components
//*a = (val >> 15) & 0x01; // 1-bit alpha
uint8_t r = (val >> 10) & 0x1F; // 5-bit red
uint8_t g = (val >> 5) & 0x1F; // 5-bit green
uint8_t b = (val)&0x1F; // 5-bit blue
// expand
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
px = ui::Color(r, g, b);
break;
}
case 2: // 32
px = ui::Color(buffer[2], buffer[1], buffer[0]);
break;
case 4: { // 8-bit
// uint8_t index = buffer[0];
// px = ui::Color(color_palette[index][2], color_palette[index][1], color_palette[index][0]); // Palette is BGR
// px = ui::Color(buffer[0]); // niy, since needs a lot of ram for the palette
break;
}
case 1: // 24
default:
px = ui::Color(buffer[2], buffer[1], buffer[0]);
break;
}
if (seek) advance_curr_px();
return true;
}
// if you set this, then the expanded part (or the newly created) will be filled with this color. but the expansion or the creation will be slower.
void BMPFile::set_bg_color(ui::Color background) {
bg = background;
use_bg = true;
}
// delete bg color. default. creation or expansion will be fast, but the file will contain random garbage. no problem if you write all pixels later.
void BMPFile::delete_bg_color() {
use_bg = false;
}
// writes a color data to the current position, and advances 1 px. true on success, false on error
bool BMPFile::write_next_px(ui::Color& px) {
if (!is_opened || is_read_only) return false;
uint8_t buffer[4];
switch (type) {
case 5:
case 0: // R5G6B5
case 3: // A1R5G5B5
if (!type) {
buffer[0] = (px.r() << 3) | (px.g() >> 3);
buffer[1] = (px.g() << 5) | px.b();
} else {
buffer[0] = (1 << 7) | (px.r() << 2) | (px.g() >> 3);
buffer[1] = (px.g() << 5) | px.b();
}
break;
case 1: // 24
default:
buffer[2] = px.r();
buffer[1] = px.g();
buffer[0] = px.b();
break;
case 2: // 32
buffer[2] = px.r();
buffer[1] = px.g();
buffer[0] = px.b();
buffer[3] = 255;
break;
case 4: // 8-bit
return false;
}
auto res = bmpimage.write(buffer, byte_per_px);
if (res.is_error()) return false;
advance_curr_px();
return true;
}
// positions in the file to the given pixel. 0 based indexing
bool BMPFile::seek(uint32_t x, uint32_t y) {
if (!is_opened) return false;
if (x >= bmp_header.width) return false;
if (y >= get_real_height()) return false;
if (!BMPFile::is_bottomup()) {
file_pos = bmp_header.image_data; // nav to start pos.
file_pos += y * byte_per_row;
file_pos += x * byte_per_px;
bmpimage.seek(file_pos);
currx = x;
curry = y;
} else {
file_pos = bmp_header.image_data; // nav to start pos.
file_pos += (get_real_height() - y - 1) * byte_per_row;
file_pos += x * byte_per_px;
bmpimage.seek(file_pos);
currx = x;
curry = y;
}
return true;
}
// expands the image with a delta (y). also seek's t it's begining. in bottumup format, it should be used carefully!
bool BMPFile::expand_y_delta(uint32_t delta_y) {
return expand_y(get_real_height() + delta_y);
}
// expands the image to a new y size. also seek's t it's begining. in bottumup format, it should be used carefully!
bool BMPFile::expand_y(uint32_t new_y) {
if (!is_opened) return false; // not yet opened
uint32_t old_height = get_real_height();
if (new_y < old_height) return true; // already bigger
if (is_read_only) return false; // can't expand
uint32_t delta = (new_y - old_height) * byte_per_row;
bmp_header.size += delta;
bmp_header.data_size += delta;
bmp_header.height = -1 * new_y; //-1*, so no bottom-up structure needed. easier to expand.
bmpimage.seek(0);
bmpimage.write(&bmp_header, sizeof(bmp_header)); // overwrite header
bmpimage.seek(bmp_header.size); // seek to new end to expand
// fill with bg color if needed
if (use_bg) {
seek(0, old_height); // to the new begin
size_t newpxcount = ((new_y - old_height) * bmp_header.width);
for (size_t i = 0; i < newpxcount; ++i)
write_next_px(bg);
}
if (is_bottomup()) {
seek(0, new_y - old_height); // seek to the new chunk begin
} else {
seek(0, curry + 1); // seek to the begin of the new chunk
}
return true;
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2024 HTotoo
*
* 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 __BMPFILE__H
#define __BMPFILE__H
#include <cstring>
#include <string>
#include "file.hpp"
#include "bmp.hpp"
#include "ui.hpp"
class BMPFile {
public:
~BMPFile();
bool open(const std::filesystem::path& file, bool readonly);
bool create(const std::filesystem::path& file, uint32_t x, uint32_t y);
void close();
bool is_loaded();
bool seek(uint32_t x, uint32_t y);
bool expand_y(uint32_t new_y);
bool expand_y_delta(uint32_t delta_y);
uint32_t getbpr() { return byte_per_row; };
bool read_next_px(ui::Color& px, bool seek);
bool write_next_px(ui::Color& px);
uint32_t get_real_height();
uint32_t get_width();
bool is_bottomup();
void set_bg_color(ui::Color background);
void delete_bg_color();
private:
bool advance_curr_px(uint32_t num);
bool is_opened = false;
bool is_read_only = true;
File bmpimage{};
size_t file_pos = 0;
bmp_header_t bmp_header{};
uint8_t type = 0;
uint8_t byte_per_px = 1;
uint32_t byte_per_row = 0;
uint32_t currx = 0;
uint32_t curry = 0;
ui::Color bg{};
// uint8_t color_palette[256][4];
bool use_bg = false;
};
#endif

View file

@ -0,0 +1,120 @@
/*
* 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 __CIRCULAR_BUFFER_H__
#define __CIRCULAR_BUFFER_H__
#include <stddef.h> // For size_t
#include <memory>
/* Implements a fixed-size, circular buffer.
* NB: Holds Capacity - 1 items.
* There are no bounds checks on accessors so ensure there are
* items in the buffer before accessing front/back/operator[]. */
template <typename T, size_t Capacity>
class CircularBuffer {
public:
CircularBuffer() = default;
CircularBuffer(const CircularBuffer&) = delete;
CircularBuffer(CircularBuffer&&) = delete;
CircularBuffer& operator=(const CircularBuffer&) = delete;
CircularBuffer& operator=(CircularBuffer&&) = delete;
void push_front(T val) {
head_ = head_ > 0 ? head_ - 1 : last_index;
if (head_ == end_)
pop_back_internal();
data_[head_] = std::move(val);
}
void pop_front() {
if (!empty())
pop_front_internal();
}
void push_back(T val) {
data_[end_] = std::move(val);
end_ = end_ < last_index ? end_ + 1 : 0;
if (head_ == end_)
pop_front_internal();
}
void pop_back() {
if (!empty())
pop_back_internal();
}
T& operator[](size_t ix) & {
ix += head_;
if (ix >= Capacity)
ix -= Capacity;
return data_[ix];
}
const T& operator[](size_t ix) const& {
return const_cast<CircularBuffer*>(this)->operator[](ix);
}
const T& front() const& {
return data_[head_];
}
const T& back() const& {
auto end = end_ > 0 ? end_ - 1 : last_index;
return data_[end];
}
size_t size() const& {
auto end = end_;
if (end < head_)
end += Capacity;
return end - head_;
}
bool empty() const {
return head_ == end_;
}
void clear() {
head_ = 0;
end_ = 0;
}
private:
void pop_front_internal() {
head_ = head_ < last_index ? head_ + 1 : 0;
}
void pop_back_internal() {
end_ = end_ > 0 ? end_ - 1 : last_index;
}
static constexpr size_t last_index = Capacity - 1;
size_t head_{0};
size_t end_{0};
T data_[Capacity]{};
};
#endif /*__CIRCULAR_BUFFER_H__*/

View file

@ -0,0 +1,114 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __COMPLEX_H__
#define __COMPLEX_H__
#include <cstdint>
#include <complex>
#include <cmath>
constexpr float pi{3.141592653589793238462643383279502884f};
namespace std {
template <>
struct complex<int8_t> {
public:
typedef int8_t value_type;
typedef uint16_t rep_type;
constexpr complex(
int8_t re = 0,
int8_t im = 0)
: _v{re, im} {
}
constexpr int8_t real() const { return _v[0]; }
constexpr int8_t imag() const { return _v[1]; }
void real(int8_t v) { _v[0] = v; }
void imag(int8_t v) { _v[1] = v; }
constexpr uint16_t __rep() const {
return _rep;
}
private:
union {
value_type _v[2];
rep_type _rep;
};
};
template <>
struct complex<int16_t> {
public:
typedef int16_t value_type;
typedef uint32_t rep_type;
constexpr complex(
int16_t re = 0,
int16_t im = 0)
: _v{re, im} {
}
constexpr int16_t real() const { return _v[0]; }
constexpr int16_t imag() const { return _v[1]; }
void real(int16_t v) { _v[0] = v; }
void imag(int16_t v) { _v[1] = v; }
template <class X>
complex<int16_t>& operator+=(const complex<X>& other) {
_v[0] += other.real();
_v[1] += other.imag();
return *this;
}
constexpr uint32_t __rep() const {
return _rep;
}
constexpr operator std::complex<float>() const {
return {
static_cast<float>(_v[0]),
static_cast<float>(_v[1])};
}
private:
union {
value_type _v[2];
rep_type _rep;
};
};
} /* namespace std */
using complex8_t = std::complex<int8_t>;
using complex16_t = std::complex<int16_t>;
using complex32_t = std::complex<int32_t>;
static_assert(sizeof(complex8_t) == 2, "complex8_t size wrong");
static_assert(sizeof(complex16_t) == 4, "complex16_t size wrong");
static_assert(sizeof(complex32_t) == 8, "complex32_t size wrong");
#endif /*__COMPLEX_H__*/

View file

@ -0,0 +1,641 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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 "file.hpp"
#include "complex.hpp"
#include <algorithm>
#include <codecvt>
#include <cstring>
#include <locale>
namespace fs = std::filesystem;
static const fs::path c8_ext{u".C8"};
static const fs::path c16_ext{u".C16"};
Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
auto result = f_open(&f, reinterpret_cast<const TCHAR*>(filename.c_str()), mode);
if (result == FR_OK) {
if (mode & FA_OPEN_ALWAYS) {
const auto result = f_lseek(&f, f_size(&f));
if (result != FR_OK) {
f_close(&f);
}
}
}
if (result == FR_OK) {
return {};
} else {
return {result};
}
}
/*
* @param read_only: open in readonly mode
* @param create: create if it doesnt exist
*/
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
if (create)
mode |= FA_OPEN_ALWAYS;
return open_fatfs(filename, mode);
}
Optional<File::Error> File::append(const std::filesystem::path& filename) {
return open_fatfs(filename, FA_WRITE | FA_OPEN_ALWAYS);
}
Optional<File::Error> File::create(const std::filesystem::path& filename) {
return open_fatfs(filename, FA_WRITE | FA_CREATE_ALWAYS);
}
File::~File() {
f_close(&f);
}
void File::close() {
f_close(&f);
}
File::Result<File::Size> File::read(void* data, Size bytes_to_read) {
UINT bytes_read = 0;
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
if (result == FR_OK) {
return {static_cast<size_t>(bytes_read)};
} else {
return {static_cast<Error>(result)};
}
}
File::Result<File::Size> File::write(const void* data, Size bytes_to_write) {
UINT bytes_written = 0;
const auto result = f_write(&f, data, bytes_to_write, &bytes_written);
if (result == FR_OK) {
if (bytes_to_write == bytes_written) {
return {static_cast<File::Size>(bytes_written)};
} else {
return Error{FR_DISK_FULL};
}
} else {
return {static_cast<Error>(result)};
}
}
File::Offset File::tell() const {
return f_tell(&f);
}
File::Result<bool> File::eof() {
return f_eof(&f);
}
File::Result<File::Offset> File::seek(Offset new_position) {
/* NOTE: Returns *old* position, not new position */
const auto old_position = tell();
const auto result = (old_position == new_position) ? FR_OK : f_lseek(&f, new_position);
if (result != FR_OK) {
return {static_cast<Error>(result)};
}
if (f_tell(&f) != new_position) {
return {static_cast<Error>(FR_BAD_SEEK)};
}
return {static_cast<File::Offset>(old_position)};
}
File::Result<File::Offset> File::truncate() {
const auto position = f_tell(&f);
const auto result = f_truncate(&f);
if (result != FR_OK) {
return {static_cast<Error>(result)};
}
return {static_cast<File::Offset>(position)};
}
File::Size File::size() const {
return f_size(&f);
}
Optional<File::Error> File::write_line(const std::string& s) {
const auto result_s = write(s.c_str(), s.size());
if (result_s.is_error()) {
return {result_s.error()};
}
const auto result_crlf = write("\r\n", 2);
if (result_crlf.is_error()) {
return {result_crlf.error()};
}
return {};
}
Optional<File::Error> File::sync() {
const auto result = f_sync(&f);
if (result == FR_OK) {
return {};
} else {
return {result};
}
}
File::Result<std::string> File::read_file(const std::filesystem::path& filename) {
constexpr size_t buffer_size = 0x80;
char* buffer[buffer_size];
File f;
auto error = f.open(filename);
if (error)
return *error;
std::string content;
content.resize(f.size());
auto str = &content[0];
auto total_read = 0u;
while (true) {
auto read = f.read(buffer, buffer_size);
if (!read)
return read.error();
memcpy(str, buffer, *read);
str += *read;
total_read += *read;
if (*read < buffer_size)
break;
}
content.resize(total_read);
return content;
}
/* Range used for filename matching.
* Start and end are inclusive positions of "???" */
struct pattern_range {
size_t start;
size_t end;
};
/* Finds the last file matching the specified pattern that
* can be automatically incremented (digits in pattern).
* NB: assumes a patten with contiguous '?' like "FOO_???.txt". */
static std::filesystem::path find_last_ordinal_match(
const std::filesystem::path& folder,
const std::filesystem::path& pattern,
pattern_range range) {
auto last_match = std::filesystem::path();
auto can_increment = [range](const auto& path) {
for (auto i = range.start; i <= range.end; ++i)
if (!isdigit(path.native()[i]))
return false;
return true;
};
for (const auto& entry : std::filesystem::directory_iterator(folder, pattern)) {
if (std::filesystem::is_regular_file(entry.status()) && can_increment(entry.path())) {
const auto& match = entry.path();
if (match > last_match) {
last_match = match;
}
}
}
return last_match;
}
/* Given a file path like "FOO_0001.txt" increment it to "FOO_0002.txt". */
static std::filesystem::path increment_filename_ordinal(
const std::filesystem::path& path,
pattern_range range) {
auto name = path.filename().native();
for (auto i = range.end; i >= range.start; --i) {
auto& c = name[i];
// Not a digit or would overflow the counter.
if (c < u'0' || c > u'9' || (c == u'9' && i == range.start))
return {};
if (c == u'9')
c = '0';
else {
c++;
break;
}
}
return {name};
}
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& filename_pattern) {
auto path = filename_pattern.parent_path();
auto pattern = filename_pattern.filename();
auto range = pattern_range{
pattern.native().find_first_of(u'?'),
pattern.native().find_last_of(u'?')};
const auto match = find_last_ordinal_match(path, pattern, range);
if (match.empty()) {
auto pattern_str = pattern.native();
for (auto i = range.start; i <= range.end; ++i)
pattern_str[i] = u'0';
return path / pattern_str;
}
auto next_name = increment_filename_ordinal(match, range);
return next_name.empty() ? next_name : path / next_name;
}
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory,
const std::filesystem::path& extension) {
std::vector<std::filesystem::path> file_list{};
scan_root_files(directory, extension, [&file_list](const std::filesystem::path& p) {
file_list.push_back(p);
});
return file_list;
}
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory) {
std::vector<std::filesystem::path> directory_list{};
for (const auto& entry : std::filesystem::directory_iterator(directory, "*")) {
if (std::filesystem::is_directory(entry.status())) {
directory_list.push_back(entry.path());
}
}
return directory_list;
}
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path) {
return {f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()))};
}
std::filesystem::filesystem_error rename_file(
const std::filesystem::path& file_path,
const std::filesystem::path& new_name) {
return {f_rename(reinterpret_cast<const TCHAR*>(file_path.c_str()), reinterpret_cast<const TCHAR*>(new_name.c_str()))};
}
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path) {
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
uint8_t buffer[buffer_size];
File src;
File dst;
auto error = src.open(file_path);
if (error) return error.value();
error = dst.create(dest_path);
if (error) return error.value();
while (true) {
auto result = src.read(buffer, buffer_size);
if (result.is_error()) return result.error();
result = dst.write(buffer, *result);
if (result.is_error()) return result.error();
if (*result < buffer_size)
break;
}
return {};
}
FATTimestamp file_created_date(const std::filesystem::path& file_path) {
FILINFO filinfo;
f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return {filinfo.fdate, filinfo.ftime};
}
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp) {
FILINFO filinfo{};
filinfo.fdate = timestamp.FAT_date;
filinfo.ftime = timestamp.FAT_time;
return f_utime(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
}
std::filesystem::filesystem_error make_new_file(
const std::filesystem::path& file_path) {
File f;
auto error = f.create(file_path);
if (error)
return *error;
return {};
}
std::filesystem::filesystem_error make_new_directory(
const std::filesystem::path& dir_path) {
return {f_mkdir(reinterpret_cast<const TCHAR*>(dir_path.c_str()))};
}
std::filesystem::filesystem_error ensure_directory(
const std::filesystem::path& dir_path) {
if (dir_path.empty() || std::filesystem::file_exists(dir_path))
return {};
auto result = ensure_directory(dir_path.parent_path());
if (result.code())
return result;
return make_new_directory(dir_path);
}
namespace std {
namespace filesystem {
std::string filesystem_error::what() const {
switch (err) {
case FR_OK:
return "ok";
case FR_DISK_ERR:
return "disk error";
case FR_INT_ERR:
return "insanity detected";
case FR_NOT_READY:
return "SD card not ready";
case FR_NO_FILE:
return "no file";
case FR_NO_PATH:
return "no path";
case FR_INVALID_NAME:
return "invalid name";
case FR_DENIED:
return "denied";
case FR_EXIST:
return "exists";
case FR_INVALID_OBJECT:
return "invalid object";
case FR_WRITE_PROTECTED:
return "write protected";
case FR_INVALID_DRIVE:
return "invalid drive";
case FR_NOT_ENABLED:
return "not enabled";
case FR_NO_FILESYSTEM:
return "no filesystem";
case FR_MKFS_ABORTED:
return "mkfs aborted";
case FR_TIMEOUT:
return "timeout";
case FR_LOCKED:
return "locked";
case FR_NOT_ENOUGH_CORE:
return "not enough core";
case FR_TOO_MANY_OPEN_FILES:
return "too many open files";
case FR_INVALID_PARAMETER:
return "invalid parameter";
case FR_EOF:
return "end of file";
case FR_DISK_FULL:
return "disk full";
case FR_BAD_SEEK:
return "bad seek";
case FR_UNEXPECTED:
return "unexpected";
default:
return "unknown";
}
}
path path::parent_path() const {
const auto index = _s.find_last_of(preferred_separator);
if (index == _s.npos) {
return {}; // NB: Deviation from STL.
} else {
return _s.substr(0, index);
}
}
path path::extension() const {
const auto t = filename().native();
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
} else {
return t.substr(index);
}
}
path path::filename() const {
const auto index = _s.find_last_of(preferred_separator);
if (index == _s.npos) {
return _s;
} else {
return _s.substr(index + 1);
}
}
path path::stem() const {
const auto t = filename().native();
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return t;
} else {
return t.substr(0, index);
}
}
std::string path::string() const {
std::wstring_convert<std::codecvt_utf8_utf16<path::value_type>, path::value_type> conv;
return conv.to_bytes(native());
}
// appends a string to the end of filename, but leaves the extension asd.txt + "fg" -> asdfg.txt
path& path::append_filename(const string_type& str) {
const auto t = extension().native();
_s.erase(_s.size() - t.size()); // remove extension
_s += str; // append string
_s += t; // add back extension
return *this;
}
path& path::replace_extension(const path& replacement) {
const auto t = extension().native();
_s.erase(_s.size() - t.size());
if (!replacement._s.empty()) {
if (replacement._s.front() != u'.') {
_s += u'.';
}
_s += replacement._s;
}
return *this;
}
bool operator==(const path& lhs, const path& rhs) {
return lhs.native() == rhs.native();
}
bool operator!=(const path& lhs, const path& rhs) {
return !(lhs == rhs);
}
bool operator<(const path& lhs, const path& rhs) {
return lhs.native() < rhs.native();
}
bool operator>(const path& lhs, const path& rhs) {
return lhs.native() > rhs.native();
}
path operator+(const path& lhs, const path& rhs) {
path result = lhs;
result += rhs;
return result;
}
path operator/(const path& lhs, const path& rhs) {
path result = lhs;
result /= rhs;
return result;
}
bool path_iequal(
const path& lhs,
const path& rhs) {
const auto& lhs_str = lhs.native();
const auto& rhs_str = rhs.native();
// NB: Not correct for Unicode/locales.
if (lhs_str.length() == rhs_str.length()) {
for (size_t i = 0; i < lhs_str.length(); ++i)
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
return false;
return true;
}
return false;
}
bool is_cxx_capture_file(const path& filename) {
auto ext = filename.extension();
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext);
}
uint8_t capture_file_sample_size(const path& filename) {
if (path_iequal(filename.extension(), c8_ext))
return sizeof(complex8_t);
if (path_iequal(filename.extension(), c16_ext))
return sizeof(complex16_t);
return 0;
}
directory_iterator::directory_iterator(
const std::filesystem::path& path,
const std::filesystem::path& wild)
: path_{path}, wild_{wild} {
impl = std::make_shared<Impl>();
auto result = f_findfirst(&impl->dir, &impl->filinfo,
path_.tchar(), wild_.tchar());
if (result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
impl.reset();
// TODO: Throw exception if/when I enable exceptions...
}
}
directory_iterator& directory_iterator::operator++() {
const auto result = f_findnext(&impl->dir, &impl->filinfo);
if ((result != FR_OK) || (impl->filinfo.fname[0] == 0)) {
impl.reset();
}
return *this;
}
bool is_directory(const file_status s) {
return (s & AM_DIR);
}
bool is_regular_file(const file_status s) {
return !(s & AM_DIR);
}
bool file_exists(const path& file_path) {
FILINFO filinfo;
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return fr == FR_OK;
}
bool is_directory(const path& file_path) {
FILINFO filinfo;
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return fr == FR_OK && is_directory(static_cast<file_status>(filinfo.fattrib));
}
bool is_empty_directory(const path& file_path) {
DIR dir;
FILINFO filinfo;
if (!is_directory(file_path))
return false;
auto result = f_findfirst(&dir, &filinfo, reinterpret_cast<const TCHAR*>(file_path.c_str()), (const TCHAR*)u"*");
return !((result == FR_OK) && (filinfo.fname[0] != (TCHAR)'\0'));
}
int file_count(const path& directory) {
int count{0};
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
(void)entry; // avoid unused warning
++count;
}
return count;
}
space_info space(const path& p) {
DWORD free_clusters{0};
FATFS* fs;
if (f_getfree(reinterpret_cast<const TCHAR*>(p.c_str()), &free_clusters, &fs) == FR_OK) {
#if _MAX_SS != _MIN_SS
static_assert(false, "FatFs not configured for fixed sector size");
#else
const std::uintmax_t cluster_bytes = fs->csize * _MIN_SS;
return {
(fs->n_fatent - 2) * cluster_bytes,
free_clusters * cluster_bytes,
free_clusters * cluster_bytes,
};
#endif
} else {
return {0, 0, 0};
}
}
} /* namespace filesystem */
} /* namespace std */

View file

@ -0,0 +1,368 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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 __FILE_H__
#define __FILE_H__
#include "ff.h"
#include "optional.hpp"
#include "result.hpp"
#include <cstddef>
#include <cstdint>
#include <string>
#include <array>
#include <memory>
#include <iterator>
#include <vector>
namespace std {
namespace filesystem {
struct filesystem_error {
constexpr filesystem_error() = default;
constexpr filesystem_error(
FRESULT fatfs_error)
: err{fatfs_error} {
}
constexpr filesystem_error(
unsigned int other_error)
: err{other_error} {
}
uint32_t code() const {
return err;
}
std::string what() const;
bool ok() const {
return err == FR_OK;
}
private:
uint32_t err{FR_OK};
};
struct path {
using string_type = std::u16string;
using value_type = string_type::value_type;
static constexpr value_type preferred_separator = u'/';
path()
: _s{} {
}
path(const path& p)
: _s{p._s} {
}
path(path&& p)
: _s{std::move(p._s)} {
}
template <class Source>
path(const Source& source)
: path{std::begin(source), std::end(source)} {
}
template <class InputIt>
path(InputIt first,
InputIt last)
: _s{first, last} {
}
path(const char16_t* const s)
: _s{s} {
}
path(const TCHAR* const s)
: _s{reinterpret_cast<const std::filesystem::path::value_type*>(s)} {
}
path& operator=(const path& p) {
_s = p._s;
return *this;
}
path& operator=(path&& p) {
_s = std::move(p._s);
return *this;
}
path parent_path() const;
path extension() const;
path filename() const;
path stem() const;
bool empty() const {
return _s.empty();
}
const value_type* c_str() const {
return native().c_str();
}
const TCHAR* tchar() const {
return reinterpret_cast<const TCHAR*>(native().c_str());
}
const string_type& native() const {
return _s;
}
std::string string() const;
path& operator+=(const path& p) {
_s += p._s;
return *this;
}
path& operator+=(const string_type& str) {
_s += str;
return *this;
}
path& operator/=(const path& p) {
if (_s.back() != preferred_separator && p._s.front() != preferred_separator)
_s += preferred_separator;
_s += p._s;
return *this;
}
path& replace_extension(const path& replacement = path());
path& append_filename(const string_type& str);
private:
string_type _s;
};
bool operator==(const path& lhs, const path& rhs);
bool operator!=(const path& lhs, const path& rhs);
bool operator<(const path& lhs, const path& rhs);
bool operator>(const path& lhs, const path& rhs);
path operator+(const path& lhs, const path& rhs);
path operator/(const path& lhs, const path& rhs);
/* Case insensitive path equality on underlying "native" string. */
bool path_iequal(const path& lhs, const path& rhs);
bool is_cxx_capture_file(const path& filename);
uint8_t capture_file_sample_size(const path& filename);
using file_status = BYTE;
/* The largest block that can be read/written to a file. */
constexpr uint16_t max_file_block_size = 512;
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
static_assert(sizeof(path::value_type) == sizeof(TCHAR), "FatFs TCHAR size != std::filesystem::path::value_type");
struct space_info {
static_assert(sizeof(std::uintmax_t) >= 8, "std::uintmax_t too small (<uint64_t)");
std::uintmax_t capacity;
std::uintmax_t free;
std::uintmax_t available;
};
struct directory_entry : public FILINFO {
file_status status() const {
return fattrib;
}
std::uintmax_t size() const {
return fsize;
};
const std::filesystem::path path() const noexcept { return {fname}; };
};
class directory_iterator {
struct Impl {
DIR dir;
directory_entry filinfo;
~Impl() {
f_closedir(&dir);
}
};
std::shared_ptr<Impl> impl{};
std::filesystem::path path_{};
std::filesystem::path wild_{};
friend bool operator!=(const directory_iterator& lhs, const directory_iterator& rhs);
public:
using difference_type = std::ptrdiff_t;
using value_type = directory_entry;
using pointer = const directory_entry*;
using reference = const directory_entry&;
using iterator_category = std::input_iterator_tag;
directory_iterator() noexcept {};
directory_iterator(const std::filesystem::path& path,
const std::filesystem::path& wild);
~directory_iterator() {}
directory_iterator& operator++();
reference operator*() const {
// TODO: Exception or assert if impl == nullptr.
return impl->filinfo;
}
};
inline const directory_iterator& begin(const directory_iterator& iter) noexcept {
return iter;
};
inline directory_iterator end(const directory_iterator&) noexcept {
return {};
};
inline bool operator!=(const directory_iterator& lhs, const directory_iterator& rhs) {
return lhs.impl != rhs.impl;
};
bool is_directory(const file_status s);
bool is_regular_file(const file_status s);
bool file_exists(const path& file_path);
bool is_directory(const path& file_path);
bool is_empty_directory(const path& file_path);
int file_count(const path& dir_path);
space_info space(const path& p);
} /* namespace filesystem */
} /* namespace std */
struct FATTimestamp {
uint16_t FAT_date;
uint16_t FAT_time;
};
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);
FATTimestamp file_created_date(const std::filesystem::path& file_path);
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp);
std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
template <typename TCallback>
void scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension, const TCallback& fn) {
for (const auto& entry : std::filesystem::directory_iterator(directory, extension)) {
if (std::filesystem::is_regular_file(entry.status()))
fn(entry.path());
}
}
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
/* Gets an auto incrementing filename stem.
* Pattern should be like "FOO_???.txt" where ??? will be replaced by digits.
* Pattern may also contain a folder path like "LOGS/FOO_???.txt".
* Pattern '?' must be contiguous (bad: "FOO?_??")
* Returns empty path if a filename could not be created. */
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& pattern);
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
/* Dangerous to expose these, as FatFs native error values are byte-sized. However,
* my filesystem_error implementation is fine with it. */
#define FR_DISK_FULL (0x100)
#define FR_EOF (0x101)
#define FR_BAD_SEEK (0x102)
#define FR_UNEXPECTED (0x103)
/* NOTE: sizeof(File) == 556 bytes because of the FIL's buf member. */
class File {
public:
using Size = uint64_t;
using Offset = uint64_t;
using Timestamp = uint32_t;
using Error = std::filesystem::filesystem_error;
template <typename T>
using Result = Result<T, Error>;
File() {};
~File();
File(File&& other) {
std::swap(f, other.f);
}
File& operator=(File&& other) {
std::swap(f, other.f);
return *this;
}
/* Prevent copies */
File(const File&) = delete;
File& operator=(const File&) = delete;
// TODO: Return Result<>.
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
void close();
Optional<Error> append(const std::filesystem::path& filename);
Optional<Error> create(const std::filesystem::path& filename);
Result<Size> read(void* data, const Size bytes_to_read);
Result<Size> write(const void* data, Size bytes_to_write);
Offset tell() const;
Result<Offset> seek(uint64_t Offset);
Result<Offset> truncate();
Size size() const;
Result<bool> eof();
template <size_t N>
Result<Size> write(const std::array<uint8_t, N>& data) {
return write(data.data(), N);
}
Optional<Error> write_line(const std::string& s);
// TODO: Return Result<>.
Optional<Error> sync();
/* Reads the entire file contents to a string.
* NB: This will likely fail for files larger than ~10kB. */
static Result<std::string> read_file(const std::filesystem::path& filename);
private:
FIL f{};
Optional<Error> open_fatfs(const std::filesystem::path& filename, BYTE mode);
};
#endif /*__FILE_H__*/

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 Mark Thompson
*
* 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 "file_path.hpp"
#include "file.hpp"
const std::filesystem::path adsb_dir = u"ADSB";
const std::filesystem::path ais_dir = u"AIS";
const std::filesystem::path apps_dir = u"APPS";
const std::filesystem::path aprs_dir = u"APRS";
const std::filesystem::path audio_dir = u"AUDIO";
const std::filesystem::path blerx_dir = u"BLERX";
const std::filesystem::path bletx_dir = u"BLETX";
const std::filesystem::path captures_dir = u"CAPTURES";
const std::filesystem::path cvsfiles_dir = u"CVSFILES";
const std::filesystem::path debug_dir = u"DEBUG";
const std::filesystem::path firmware_dir = u"FIRMWARE";
const std::filesystem::path freqman_dir = u"FREQMAN";
const std::filesystem::path gps_dir = u"GPS";
const std::filesystem::path logs_dir = u"LOGS";
const std::filesystem::path looking_glass_dir = u"LOOKINGGLASS";
const std::filesystem::path playlist_dir = u"PLAYLIST";
const std::filesystem::path remotes_dir = u"REMOTES";
const std::filesystem::path repeat_rec_path = u"CAPTURES";
const std::filesystem::path samples_dir = u"SAMPLES";
const std::filesystem::path screenshots_dir = u"SCREENSHOTS";
const std::filesystem::path settings_dir = u"SETTINGS";
const std::filesystem::path spectrum_dir = u"SPECTRUM";
const std::filesystem::path splash_dir = u"SPLASH";
const std::filesystem::path sstv_dir = u"SSTV";
const std::filesystem::path wav_dir = u"WAV";
const std::filesystem::path whipcalc_dir = u"WHIPCALC";
const std::filesystem::path ook_editor_dir = u"OOKFILES";
const std::filesystem::path hopper_dir = u"HOPPER";
const std::filesystem::path subghz_dir = u"SUBGHZ";
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
const std::filesystem::path macaddress_dir = u"MACADDRESS";

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2024 Mark Thompson
*
* 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 __FILE_PATH_H__
#define __FILE_PATH_H__
#include "file.hpp"
extern const std::filesystem::path adsb_dir;
extern const std::filesystem::path ais_dir;
extern const std::filesystem::path apps_dir;
extern const std::filesystem::path aprs_dir;
extern const std::filesystem::path audio_dir;
extern const std::filesystem::path blerx_dir;
extern const std::filesystem::path bletx_dir;
extern const std::filesystem::path captures_dir;
extern const std::filesystem::path cvsfiles_dir;
extern const std::filesystem::path debug_dir;
extern const std::filesystem::path firmware_dir;
extern const std::filesystem::path freqman_dir;
extern const std::filesystem::path gps_dir;
extern const std::filesystem::path logs_dir;
extern const std::filesystem::path looking_glass_dir;
extern const std::filesystem::path playlist_dir;
extern const std::filesystem::path remotes_dir;
extern const std::filesystem::path repeat_rec_path;
extern const std::filesystem::path samples_dir;
extern const std::filesystem::path screenshots_dir;
extern const std::filesystem::path settings_dir;
extern const std::filesystem::path spectrum_dir;
extern const std::filesystem::path splash_dir;
extern const std::filesystem::path sstv_dir;
extern const std::filesystem::path wav_dir;
extern const std::filesystem::path whipcalc_dir;
extern const std::filesystem::path ook_editor_dir;
extern const std::filesystem::path hopper_dir;
extern const std::filesystem::path subghz_dir;
extern const std::filesystem::path waterfalls_dir;
extern const std::filesystem::path macaddress_dir;
#endif /* __FILE_PATH_H__ */

View file

@ -0,0 +1,48 @@
/*
* 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 "file_reader.hpp"
#include <string_view>
#include <vector>
/* Splits the string on the specified char and returns
* a vector of string_views. NB: the lifetime of the
* string to split must be maintained while the views
* are used or they will dangle. */
std::vector<std::string_view> split_string(std::string_view str, char c) {
std::vector<std::string_view> cols;
size_t start = 0;
while (start < str.length()) {
auto it = str.find(c, start);
if (it == str.npos)
break;
cols.emplace_back(&str[start], it - start);
start = it + 1;
}
if (start <= str.length() && !str.empty())
cols.emplace_back(&str[start], str.length() - start);
return cols;
}

View file

@ -0,0 +1,151 @@
/*
* 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 __FILE_READER_HPP__
#define __FILE_READER_HPP__
#include "file.hpp"
#include <cstring>
#include <cstdlib>
#include <string>
#include <string_view>
#include <vector>
/* BufferType requires the following members
* Size size()
* Result<Size> read(void* data, Size bytes_to_read)
* Result<Offset> seek(uint32_t offset)
*/
/* Iterates lines in buffer split on '\n'.
* NB: very basic iterator impl, don't try anything fancy with it.
* For example, you _must_ deref the iterator after advancing it. */
template <typename BufferType>
class BufferLineReader {
public:
struct iterator {
bool operator!=(const iterator& other) {
return this->pos_ != other.pos_ || this->reader_ != other.reader_;
}
const std::string& operator*() {
if (!cached_) {
bool ok = reader_->read_line(*this);
cached_ = true;
if (!ok) *this = reader_->end();
}
return line_data_;
}
iterator& operator++() {
const auto size = reader_->size();
if (pos_ < size) {
cached_ = false;
pos_ += line_data_.length();
}
if (pos_ >= size)
*this = reader_->end();
return *this;
}
typename BufferType::Size pos_{};
BufferLineReader* reader_{};
bool cached_ = false;
std::string line_data_{};
};
BufferLineReader(BufferType& buffer)
: buffer_{buffer} {}
iterator begin() { return {0, this}; }
iterator end() { return {size(), this}; }
typename BufferType::Size size() const { return buffer_.size(); }
private:
BufferType& buffer_;
bool read_line(iterator& it) {
constexpr size_t buf_size = 0x80;
char buf[buf_size];
uint32_t offset = 0;
it.line_data_.resize(buf_size);
buffer_.seek(it.pos_);
while (true) {
auto read = buffer_.read(buf, buf_size);
if (!read)
return false;
// Find newline.
auto len = 0u;
for (; len < *read; ++len) {
if (buf[len] == '\n') {
++len;
break;
}
}
// Reallocate if needed.
if (offset + len >= it.line_data_.length())
it.line_data_.resize(offset + len);
std::strncpy(&it.line_data_[offset], buf, len);
offset += len;
if (len < buf_size)
break;
}
it.line_data_.resize(offset);
return true;
}
};
using FileLineReader = BufferLineReader<File>;
/* Splits the string on the specified char and returns
* a vector of string_views. NB: the lifetime of the
* string to split must be maintained while the views
* are used or they will dangle. */
std::vector<std::string_view> split_string(std::string_view str, char c);
/* Returns the number of lines in a file. */
template <typename BufferType>
uint32_t count_lines(BufferLineReader<BufferType>& reader) {
uint32_t count = 0;
auto it = reader.begin();
do {
*it; // Necessary to force the file read.
++count;
} while (++it != reader.end());
return count;
}
#endif

View file

@ -0,0 +1,543 @@
/*
* 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 __FILE_WRAPPER_HPP__
#define __FILE_WRAPPER_HPP__
#include "circular_buffer.hpp"
#include "file.hpp"
#include "optional.hpp"
#include <functional>
#include <memory>
#include <string_view>
enum class LineEnding : uint8_t {
LF,
CRLF
};
/* TODO:
* - CRLF handling.
* - Avoid full re-read on edits.
* - Would need to read old/new text when editing to track newlines.
* - How to surface errors? Exceptions?
*/
/* FatFs docs http://elm-chan.org/fsw/ff/00index_e.html */
/* BufferType requires the following members
* Size size()
* Result<Size> read(void* data, Size bytes_to_read)
* Result<Size> write(const void* data, Size bytes_to_write)
* Result<Offset> seek(uint32_t offset)
* Result<Offset> truncate()
* Optional<Error> sync()
*/
/* Wraps a buffer and provides an API for accessing lines efficiently. */
template <typename BufferType, uint32_t CacheSize>
class BufferWrapper {
public:
using Offset = uint32_t;
using Line = uint32_t;
using Column = uint32_t;
using Size = File::Size;
using Range = struct {
// Offset of the start, inclusive.
Offset start;
// Offset of the end, exclusive.
Offset end;
Offset length() const { return end - start; }
};
BufferWrapper(BufferType* buffer)
: wrapped_{buffer} {
initialize();
}
virtual ~BufferWrapper() {}
std::function<void(Size, Size)> on_read_progress{};
/* Prevent copies */
BufferWrapper(const BufferWrapper&) = delete;
BufferWrapper& operator=(const BufferWrapper&) = delete;
Optional<std::string> get_text(Line line, Column col, Offset length) {
std::string buffer;
buffer.resize(length);
auto result = get_text(line, col, &buffer[0], length);
if (!result)
return {};
buffer.resize(*result);
return buffer;
}
Optional<Offset> get_text(Line line, Column col, char* output, Offset length) {
auto range = line_range(line);
int32_t to_read = length;
if (!range)
return {};
// Don't read past end of line.
if (range->start + col + to_read >= range->end)
to_read = range->end - col - range->start;
if (to_read <= 0)
return {};
return read(range->start + col, output, to_read);
}
/* Gets the size of the buffer in bytes. */
Size size() const { return wrapped_->size(); }
/* Get the count of the lines in the buffer. */
uint32_t line_count() const { return line_count_; }
/* Gets the range of the line if valid. */
Optional<Range> line_range(Line line) {
ensure_cached(line);
auto index = index_for_line(line);
if (!index)
return {};
auto start = *index == 0 ? start_offset_ : (newlines_[*index - 1] + 1);
auto end = newlines_[*index] + 1;
return Range{start, end};
}
/* Gets the length of the line, or 0 if invalid. */
Offset line_length(Line line) {
auto range = line_range(line);
return range ? range->length() : 0;
}
/* Gets the buffer offset of the line & col if valid. */
Optional<Offset> get_offset(Line line, Column col) {
auto range = line_range(line);
if (range)
return range->start + col;
return {};
}
/* Gets the index of the first line in the cache.
* Only really useful for unit testing or diagnostics. */
Offset start_line() { return start_line_; };
/* Inserts a line before the specified line or at the
* end of the buffer if line >= line_count. */
void insert_line(Line line) {
auto range = line_range(line);
if (range)
replace_range({range->start, range->start}, "\n");
else if (line >= line_count_)
replace_range({(Offset)size(), (Offset)size()}, "\n");
}
/* Deletes the specified line. */
void delete_line(Line line) {
auto range = line_range(line);
if (range)
replace_range(*range, {});
}
/* Replace the specified range with the string contents.
* A range with start/end set to the same value will insert.
* A range with an empty string will delete. */
void replace_range(Range range, std::string_view value) {
if (range.start > size() || range.end > size() || range.start > range.end)
return;
/* If delta_length == 0, it's an overwrite. Could still have
* added or removed newlines so caches will need to be rebuilt.
* If delta_length > 0, the file needs to grow and content needs
* to be shifted forward until the end of the range.
* If delta_length < 0, the file needs to be truncated and the
* content after the value needs to be shifted backward. */
int32_t delta_length = value.length() - range.length();
if (delta_length > 0)
expand(range.end, delta_length);
else if (delta_length < 0)
shrink(range.end, delta_length);
write(range.start, value);
wrapped_->sync();
rebuild_cache();
}
protected:
BufferWrapper() {}
void set_buffer(BufferType* buffer) {
wrapped_ = buffer;
initialize();
}
private:
/* Number of newline offsets to cache. */
static constexpr Offset max_newlines = CacheSize;
/* Size of stack buffer used for reading/writing. */
static constexpr Offset buffer_size = 512;
void initialize() {
start_offset_ = 0;
start_line_ = 0;
line_count_ = 0;
rebuild_cache();
}
void rebuild_cache() {
newlines_.clear();
// Special case for empty files to keep them consistent.
if (size() == 0) {
line_count_ = 1;
newlines_.push_back(0);
return;
}
// TODO: think through this for edit cases.
// E.g. don't read to end, maybe could specify
// a range to re-read because it should be possible
// to tell where the dirty regions are. After the
// dirty region, it should be possible to fixup
// the line_count data.
// TODO: seems like shrink/expand could do this while
// they are running.
line_count_ = start_line_;
Offset offset = start_offset_;
// Report progress every N lines.
constexpr auto report_interval = 100u;
auto result = next_newline(offset);
auto next_report = report_interval;
while (result) {
++line_count_;
if (newlines_.size() < max_newlines)
newlines_.push_back(*result);
offset = *result + 1;
if (on_read_progress && line_count_ > next_report) {
on_read_progress(offset, size());
next_report = line_count_ + report_interval;
}
result = next_newline(offset);
}
}
Optional<Offset> read(Offset offset, char* buffer, Offset length) {
if (offset + length > size())
return {};
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, length);
if (result.is_error())
return {};
return *result;
}
bool write(Offset offset, std::string_view value) {
wrapped_->seek(offset);
auto result = wrapped_->write(value.data(), value.length());
return result.is_ok();
}
/* Returns the index of the line in the newline cache if valid. */
Optional<Offset> index_for_line(Line line) const {
if (line >= line_count_)
return {};
Offset actual = line - start_line_;
if (actual >= newlines_.size()) // NB: underflow wrap.
return {};
return actual;
}
/* Ensure specified line is in the newline cache. */
void ensure_cached(Line line) {
if (line >= line_count_)
return;
auto index = index_for_line(line);
if (index)
return;
if (line < start_line_) {
while (line < start_line_ && start_offset_ >= 2) {
// start_offset_ - 1 should be a newline. Need to
// find the new value for start_offset_. start_line_
// has to be > 0 to get into this block so there should
// always be one newline before start_offset_.
auto offset = previous_newline(start_offset_ - 2);
newlines_.push_front(start_offset_ - 1);
if (!offset) {
// Must be at beginning.
start_line_ = 0;
start_offset_ = 0;
} else {
// Found an previous newline, the new start_line_
// starts at the newline offset + 1.
start_line_--;
start_offset_ = *offset + 1;
}
}
} else {
while (line >= start_line_ + newlines_.size()) {
auto offset = next_newline(newlines_.back() + 1);
if (offset) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
/* Finding the first newline backward from offset. */
Optional<Offset> previous_newline(Offset offset) {
char buffer[buffer_size];
auto to_read = buffer_size;
do {
if (offset < to_read) {
// NB: Char at 'offset' was read in the previous iteration.
to_read = offset;
offset = 0;
} else
offset -= to_read;
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, to_read);
if (result.is_error())
break;
// Find newlines in the buffer backwards.
for (int32_t i = *result - 1; i >= 0; --i) {
switch (buffer[i]) {
case '\n':
return offset + i;
}
}
if (offset == 0)
break;
} while (true);
return {}; // Didn't find one.
}
/* Finding the first newline forward from offset. */
Optional<Offset> next_newline(Offset offset) {
// EOF, no more newlines to find.
if (offset >= size())
return {};
char buffer[buffer_size];
wrapped_->seek(offset);
while (true) {
auto result = wrapped_->read(buffer, buffer_size);
if (result.is_error())
return {};
// Find newlines in the buffer.
for (Offset i = 0; i < *result; ++i) {
switch (buffer[i]) {
case '\n':
return offset + i;
}
}
offset += *result;
if (*result < buffer_size)
break;
}
// For consistency, treat the end of the file as a "newline".
return size() - 1;
}
/* Grow the file and move file content so that the
* content at src is shifted forward by 'delta'. */
void expand(Offset src, int32_t delta) {
if (delta <= 0) // Not an expand.
return;
char buffer[buffer_size];
auto to_read = buffer_size;
// Number of bytes left to shift.
Offset remaining = size() - src;
Offset offset = size();
Size report_total = remaining;
Size report_interval = report_total / 8;
Size next_report = remaining - report_interval;
while (remaining > 0) {
offset -= std::min(remaining, buffer_size);
to_read = std::min(remaining, buffer_size);
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, to_read);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error())
break;
remaining -= *result;
if (on_read_progress && remaining <= next_report) {
on_read_progress(report_total - remaining, report_total);
next_report = remaining > report_interval ? remaining - report_interval : 0;
}
}
}
/* Shrink the file and move file content so that the
* content at src is shifted backward by 'delta'. */
void shrink(Offset src, int32_t delta) {
if (delta >= 0) // Not a shrink.
return;
char buffer[buffer_size];
auto offset = src;
Size report_total = size();
Size report_interval = report_total / 8;
Size next_report = offset + report_interval;
while (true) {
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, buffer_size);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error() || *result < buffer_size)
break;
offset += *result;
if (on_read_progress && offset >= next_report) {
on_read_progress(offset, report_total);
next_report = offset + report_interval;
}
}
// Delete the extra bytes at the end of the file.
wrapped_->truncate();
}
BufferType* wrapped_{};
/* Total number of lines in the buffer. */
Offset line_count_{0};
/* The offset and line of the newlines cache. */
Offset start_offset_{0};
Offset start_line_{0};
LineEnding line_ending_{LineEnding::LF};
CircularBuffer<Offset, max_newlines + 1> newlines_{};
};
/* A BufferWrapper over a file. */
class FileWrapper : public BufferWrapper<File, 64> {
public:
template <typename T>
using Result = File::Result<T>;
using Error = File::Error;
static Result<std::unique_ptr<FileWrapper>> open(
const std::filesystem::path& path,
bool create = false,
std::function<void(Size, Size)> on_read_progress = nullptr) {
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
auto error = fw->file_.open(path, /*read_only*/ false, create);
if (error)
return *error;
if (on_read_progress)
fw->on_read_progress = on_read_progress;
fw->initialize();
return fw;
}
/* Underlying file. */
File& file() { return file_; }
/* Swaps out the underlying file for the specified file.
* The swapped file is expected have the same contents.
* For copy-on-write scenario with a temp file. */
bool assume_file(const std::filesystem::path& path) {
File file;
auto error = file.open(path, /*read_only*/ false);
if (error)
return false;
file_ = std::move(file);
return true;
}
private:
FileWrapper() {}
void initialize() {
set_buffer(&file_);
}
File file_{};
};
template <uint32_t CacheSize = 64, typename T>
BufferWrapper<T, CacheSize> wrap_buffer(T& buffer) {
return {&buffer};
}
#endif // __FILE_WRAPPER_HPP__

View file

@ -0,0 +1,4 @@
#pragma once
#define PI 3.1415926535897932384626433832795
#define M_PI PI

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __OPTIONAL_H__
#define __OPTIONAL_H__
#include <utility>
template <typename T>
class Optional {
public:
constexpr Optional()
: value_{}, valid_{false} {}
constexpr Optional(const T& value)
: value_{value}, valid_{true} {}
constexpr Optional(T&& value)
: value_{std::move(value)}, valid_{true} {}
constexpr Optional& operator=(const T& value) {
value_ = value;
valid_ = true;
return *this;
}
constexpr Optional& operator=(T&& value) {
value_ = std::move(value);
valid_ = true;
return *this;
}
bool is_valid() const { return valid_; }
operator bool() const { return valid_; }
// TODO: Throw if not valid?
T& value() & { return value_; }
T& operator*() & { return value_; }
const T& value() const& { return value_; }
const T& operator*() const& { return value_; }
T&& value() && { return std::move(value_); }
T&& operator*() && { return std::move(value_); }
T* operator->() { return &value_; }
const T* operator->() const { return &value_; }
private:
T value_;
bool valid_;
};
#endif /*__OPTIONAL_H__*/

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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 <utility>
template <typename TValue, typename TError>
struct Result {
enum class Type {
Success,
Error,
} type;
union {
TValue value_;
TError error_;
};
bool is_ok() const {
return type == Type::Success;
}
operator bool() const {
return is_ok();
}
bool is_error() const {
return type == Type::Error;
}
const TValue& value() const& {
return value_;
}
const TValue& operator*() const& {
return value_;
}
TValue&& operator*() && {
return std::move(value_);
}
const TError& error() const& {
return error_;
}
Result() = delete;
constexpr Result(TValue&& value)
: type{Type::Success},
value_{std::forward<TValue>(value)} {
}
constexpr Result(TError error)
: type{Type::Error},
error_{error} {
}
~Result() {
if (is_ok())
value_.~TValue();
else
error_.~TError();
}
};

View file

@ -0,0 +1,147 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __SINE_TABLE_H__
#define __SINE_TABLE_H__
// TODO: Including only for pi. Need separate math.hpp...
#include "complex.hpp"
#include <array>
#include <cmath>
/*
import numpy
length = 256
w = numpy.arange(length, dtype=numpy.float64) * (2 * numpy.pi / length)
v = numpy.sin(w)
print(v)
*/
constexpr uint16_t sine_table_f32_period = 256;
// periode is 256 . means sine_table_f32[0]= sine_table_f32[0+256], sine_table_f32[1]=sine_table_f32[1+256] (those two added manualy)
// Then table has 258 values ,256:[0,..255] + [256] and [257], those two are used when we interpolate[255] with [255+1], and [256] with [256+1]
// [256] index is needed in the function sin_f32() when we are inputing very small radian values , example , sin_f32((-1e-14) in radians)
static constexpr std::array<float, sine_table_f32_period + 2> sine_table_f32{
0.00000000e+00, 2.45412285e-02, 4.90676743e-02,
7.35645636e-02, 9.80171403e-02, 1.22410675e-01,
1.46730474e-01, 1.70961889e-01, 1.95090322e-01,
2.19101240e-01, 2.42980180e-01, 2.66712757e-01,
2.90284677e-01, 3.13681740e-01, 3.36889853e-01,
3.59895037e-01, 3.82683432e-01, 4.05241314e-01,
4.27555093e-01, 4.49611330e-01, 4.71396737e-01,
4.92898192e-01, 5.14102744e-01, 5.34997620e-01,
5.55570233e-01, 5.75808191e-01, 5.95699304e-01,
6.15231591e-01, 6.34393284e-01, 6.53172843e-01,
6.71558955e-01, 6.89540545e-01, 7.07106781e-01,
7.24247083e-01, 7.40951125e-01, 7.57208847e-01,
7.73010453e-01, 7.88346428e-01, 8.03207531e-01,
8.17584813e-01, 8.31469612e-01, 8.44853565e-01,
8.57728610e-01, 8.70086991e-01, 8.81921264e-01,
8.93224301e-01, 9.03989293e-01, 9.14209756e-01,
9.23879533e-01, 9.32992799e-01, 9.41544065e-01,
9.49528181e-01, 9.56940336e-01, 9.63776066e-01,
9.70031253e-01, 9.75702130e-01, 9.80785280e-01,
9.85277642e-01, 9.89176510e-01, 9.92479535e-01,
9.95184727e-01, 9.97290457e-01, 9.98795456e-01,
9.99698819e-01, 1.00000000e+00, 9.99698819e-01,
9.98795456e-01, 9.97290457e-01, 9.95184727e-01,
9.92479535e-01, 9.89176510e-01, 9.85277642e-01,
9.80785280e-01, 9.75702130e-01, 9.70031253e-01,
9.63776066e-01, 9.56940336e-01, 9.49528181e-01,
9.41544065e-01, 9.32992799e-01, 9.23879533e-01,
9.14209756e-01, 9.03989293e-01, 8.93224301e-01,
8.81921264e-01, 8.70086991e-01, 8.57728610e-01,
8.44853565e-01, 8.31469612e-01, 8.17584813e-01,
8.03207531e-01, 7.88346428e-01, 7.73010453e-01,
7.57208847e-01, 7.40951125e-01, 7.24247083e-01,
7.07106781e-01, 6.89540545e-01, 6.71558955e-01,
6.53172843e-01, 6.34393284e-01, 6.15231591e-01,
5.95699304e-01, 5.75808191e-01, 5.55570233e-01,
5.34997620e-01, 5.14102744e-01, 4.92898192e-01,
4.71396737e-01, 4.49611330e-01, 4.27555093e-01,
4.05241314e-01, 3.82683432e-01, 3.59895037e-01,
3.36889853e-01, 3.13681740e-01, 2.90284677e-01,
2.66712757e-01, 2.42980180e-01, 2.19101240e-01,
1.95090322e-01, 1.70961889e-01, 1.46730474e-01,
1.22410675e-01, 9.80171403e-02, 7.35645636e-02,
4.90676743e-02, 2.45412285e-02, 1.22464680e-16,
-2.45412285e-02, -4.90676743e-02, -7.35645636e-02,
-9.80171403e-02, -1.22410675e-01, -1.46730474e-01,
-1.70961889e-01, -1.95090322e-01, -2.19101240e-01,
-2.42980180e-01, -2.66712757e-01, -2.90284677e-01,
-3.13681740e-01, -3.36889853e-01, -3.59895037e-01,
-3.82683432e-01, -4.05241314e-01, -4.27555093e-01,
-4.49611330e-01, -4.71396737e-01, -4.92898192e-01,
-5.14102744e-01, -5.34997620e-01, -5.55570233e-01,
-5.75808191e-01, -5.95699304e-01, -6.15231591e-01,
-6.34393284e-01, -6.53172843e-01, -6.71558955e-01,
-6.89540545e-01, -7.07106781e-01, -7.24247083e-01,
-7.40951125e-01, -7.57208847e-01, -7.73010453e-01,
-7.88346428e-01, -8.03207531e-01, -8.17584813e-01,
-8.31469612e-01, -8.44853565e-01, -8.57728610e-01,
-8.70086991e-01, -8.81921264e-01, -8.93224301e-01,
-9.03989293e-01, -9.14209756e-01, -9.23879533e-01,
-9.32992799e-01, -9.41544065e-01, -9.49528181e-01,
-9.56940336e-01, -9.63776066e-01, -9.70031253e-01,
-9.75702130e-01, -9.80785280e-01, -9.85277642e-01,
-9.89176510e-01, -9.92479535e-01, -9.95184727e-01,
-9.97290457e-01, -9.98795456e-01, -9.99698819e-01,
-1.00000000e+00, -9.99698819e-01, -9.98795456e-01,
-9.97290457e-01, -9.95184727e-01, -9.92479535e-01,
-9.89176510e-01, -9.85277642e-01, -9.80785280e-01,
-9.75702130e-01, -9.70031253e-01, -9.63776066e-01,
-9.56940336e-01, -9.49528181e-01, -9.41544065e-01,
-9.32992799e-01, -9.23879533e-01, -9.14209756e-01,
-9.03989293e-01, -8.93224301e-01, -8.81921264e-01,
-8.70086991e-01, -8.57728610e-01, -8.44853565e-01,
-8.31469612e-01, -8.17584813e-01, -8.03207531e-01,
-7.88346428e-01, -7.73010453e-01, -7.57208847e-01,
-7.40951125e-01, -7.24247083e-01, -7.07106781e-01,
-6.89540545e-01, -6.71558955e-01, -6.53172843e-01,
-6.34393284e-01, -6.15231591e-01, -5.95699304e-01,
-5.75808191e-01, -5.55570233e-01, -5.34997620e-01,
-5.14102744e-01, -4.92898192e-01, -4.71396737e-01,
-4.49611330e-01, -4.27555093e-01, -4.05241314e-01,
-3.82683432e-01, -3.59895037e-01, -3.36889853e-01,
-3.13681740e-01, -2.90284677e-01, -2.66712757e-01,
-2.42980180e-01, -2.19101240e-01, -1.95090322e-01,
-1.70961889e-01, -1.46730474e-01, -1.22410675e-01,
-9.80171403e-02, -7.35645636e-02, -4.90676743e-02,
-2.45412285e-02, 0.00000000e+00, 2.45412285e-02};
inline float sin_f32(const float w) {
const float x = w / (2 * pi); // normalization
const float x_frac = x - std::floor(x); // [0, 1]
const float n = x_frac * sine_table_f32_period;
const uint16_t n_int = static_cast<uint16_t>(n);
const float n_frac = n - n_int;
const float p0 = sine_table_f32[n_int];
const float p1 = sine_table_f32[n_int + 1];
const float diff = p1 - p0;
const float result = p0 + n_frac * diff; // linear interpolation
return result;
}
#endif /*__SINE_TABLE_H__*/

View file

@ -0,0 +1 @@
#include "standalone_app.hpp"

View file

@ -0,0 +1,569 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 "string_format.hpp"
#include <algorithm>
#include <array>
#include <complex>
#include <cstdint>
#include <cstddef>
#include <initializer_list>
#include <memory>
#include <string_view>
#include <type_traits>
using namespace std::literals;
/* This takes a pointer to the end of a buffer
* and fills it backwards towards the front.
* The return value 'q' is a pointer to the start.
* TODO: use std::array for all this. */
template <typename Int>
static char *to_string_dec_uint_internal(
char *p,
Int n)
{
*p = 0;
auto q = p;
do
{
*(--q) = n % 10 + '0';
n /= 10;
} while (n != 0);
return q;
}
static char *to_string_dec_uint_pad_internal(
char *const term,
const uint32_t n,
const int32_t l,
const char fill)
{
auto q = to_string_dec_uint_internal(term, n);
// Fill with padding if needed.
// TODO: use std::array instead. There's no
// bounds checks on any of this!
if (fill)
{
while ((term - q) < l)
{
*(--q) = fill;
}
}
return q;
}
static char *to_string_dec_uint_internal(uint64_t n, StringFormatBuffer &buffer, size_t &length)
{
auto end = &buffer.back();
auto start = to_string_dec_uint_internal(end, n);
length = end - start;
return start;
}
char *to_string_dec_uint(uint64_t n, StringFormatBuffer &buffer, size_t &length)
{
return to_string_dec_uint_internal(n, buffer, length);
}
char *to_string_dec_int(int64_t n, StringFormatBuffer &buffer, size_t &length)
{
bool negative = n < 0;
auto start = to_string_dec_uint(negative ? -n : n, buffer, length);
if (negative)
{
*(--start) = '-';
++length;
}
return start;
}
std::string to_string_dec_int(int64_t n)
{
StringFormatBuffer b{};
size_t len{};
char *str = to_string_dec_int(n, b, len);
return std::string(str, len);
}
std::string to_string_dec_uint(uint64_t n)
{
StringFormatBuffer b{};
size_t len{};
char *str = to_string_dec_uint(n, b, len);
return std::string(str, len);
}
std::string to_string_bin(
const uint32_t n,
const uint8_t l)
{
char p[33];
for (uint8_t c = 0; c < l; c++)
{
if (n & (1 << (l - 1 - c)))
p[c] = '1';
else
p[c] = '0';
}
p[l] = 0;
return p;
}
std::string to_string_dec_uint(
const uint32_t n,
const int32_t l,
const char fill)
{
char p[16];
auto term = p + sizeof(p) - 1;
auto q = to_string_dec_uint_pad_internal(term, n, l, fill);
// Right justify.
// (This code is redundant and won't do anything if a fill character was specified)
while ((term - q) < l)
{
*(--q) = ' ';
}
return q;
}
std::string to_string_dec_int(
const int32_t n,
const int32_t l,
const char fill)
{
const size_t negative = (n < 0) ? 1 : 0;
uint32_t n_abs = negative ? -n : n;
char p[16];
auto term = p + sizeof(p) - 1;
auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);
// Add sign.
if (negative)
{
*(--q) = '-';
}
// Right justify.
// (This code is redundant and won't do anything if a fill character was specified)
while ((term - q) < l)
{
*(--q) = ' ';
}
return q;
}
std::string to_string_decimal(float decimal, int8_t precision)
{
double integer_part;
double fractional_part;
std::string result;
if (precision > 9)
precision = 9; // we will convert to uin32_t, and that is the max it can hold.
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
if (fractional_part < 0)
{
fractional_part = -fractional_part;
}
result = to_string_dec_int(integer_part) + "." + to_string_dec_uint(fractional_part, precision, '0');
return result;
}
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l)
{
double integer_part;
double fractional_part;
std::string result;
if (precision > 9)
precision = 9; // we will convert to uin32_t, and that is the max it can hold.
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
if (fractional_part < 0)
{
fractional_part = -fractional_part;
}
result = to_string_dec_int(integer_part) + "." + to_string_dec_uint(fractional_part, precision, '0');
// Add padding with spaces to meet the length requirement
if (result.length() < (uint32_t)l)
{
int padding_length = l - result.length();
std::string padding(padding_length, ' ');
result = padding + result;
}
return result;
}
// right-justified frequency in Hz, always 10 characters
std::string to_string_freq(const uint64_t f)
{
std::string final_str{""};
if (f < 1000000)
final_str = to_string_dec_int(f, 10, ' ');
else
final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
return final_str;
}
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
std::string to_string_short_freq(const uint64_t f)
{
auto final_str = to_string_dec_int((f + 50) / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
return final_str;
}
// non-justified non-padded frequency in MHz, rounded to specified number of decimal places
std::string to_string_rounded_freq(const uint64_t f, int8_t precision)
{
std::string final_str{""};
static constexpr uint32_t pow10[7] = {
1,
10,
100,
1000,
10000,
100000,
1000000,
};
if (precision < 1)
{
final_str = to_string_dec_uint(f / 1000000);
}
else
{
if (precision > 6)
precision = 6;
uint32_t divisor = pow10[6 - precision];
final_str = to_string_dec_uint((f + (divisor / 2)) / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
}
return final_str;
}
std::string to_string_time_ms(const uint32_t ms)
{
std::string final_str{""};
if (ms < 1000)
{
final_str = to_string_dec_uint(ms) + "ms";
}
else
{
auto seconds = ms / 1000;
if (seconds >= 60)
final_str = to_string_dec_uint(seconds / 60) + "m";
return final_str + to_string_dec_uint(seconds % 60) + "s";
}
return final_str;
}
static char *to_string_hex_internal(char *ptr, uint64_t value, uint8_t length)
{
if (length == 0)
return ptr;
*(--ptr) = uint_to_char(value & 0xF, 16);
return to_string_hex_internal(ptr, value >> 4, length - 1);
}
std::string to_string_hex(uint64_t value, int32_t length)
{
constexpr uint8_t buffer_length = 33;
char buffer[buffer_length];
char *ptr = &buffer[buffer_length - 1];
*ptr = '\0';
length = std::min<uint8_t>(buffer_length - 1, length);
return to_string_hex_internal(ptr, value, length);
}
std::string to_string_hex_array(uint8_t *array, int32_t length)
{
std::string str_return;
str_return.reserve(length * 2);
for (uint8_t i = 0; i < length; i++)
str_return += to_string_hex(array[i], 2);
return str_return;
}
// TODO: wire standalone api:
/*
std::string to_string_datetime(const rtc::RTC &value, const TimeFormat format)
{
std::string string{""};
if (format == YMDHMS)
{
string += to_string_dec_uint(value.year(), 4) + "-" +
to_string_dec_uint(value.month(), 2, '0') + "-" +
to_string_dec_uint(value.day(), 2, '0') + " ";
}
string += to_string_dec_uint(value.hour(), 2, '0') + ":" +
to_string_dec_uint(value.minute(), 2, '0');
if ((format == YMDHMS) || (format == HMS))
string += ":" + to_string_dec_uint(value.second(), 2, '0');
return string;
}
std::string to_string_timestamp(const rtc::RTC &value)
{
return to_string_dec_uint(value.year(), 4, '0') +
to_string_dec_uint(value.month(), 2, '0') +
to_string_dec_uint(value.day(), 2, '0') +
to_string_dec_uint(value.hour(), 2, '0') +
to_string_dec_uint(value.minute(), 2, '0') +
to_string_dec_uint(value.second(), 2, '0');
}
std::string to_string_FAT_timestamp(const FATTimestamp &timestamp)
{
return to_string_dec_uint((timestamp.FAT_date >> 9) + 1980) + "-" +
to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0') + "-" +
to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0') + " " +
to_string_dec_uint((timestamp.FAT_time >> 11), 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 to_string_mac_address(const uint8_t *macAddress, uint8_t length, bool noColon)
{
std::string string;
string += to_string_hex(macAddress[0], 2);
for (int i = 1; i < length; i++)
{
string += noColon ? to_string_hex(macAddress[i], 2) : ":" + to_string_hex(macAddress[i], 2);
}
return string;
}
std::string to_string_formatted_mac_address(const char *macAddress)
{
std::string formattedAddress;
for (int i = 0; i < 12; i += 2)
{
if (i > 0)
{
formattedAddress += ':';
}
formattedAddress += macAddress[i];
formattedAddress += macAddress[i + 1];
}
return formattedAddress;
}
void generateRandomMacAddress(char *macAddress)
{
const char hexDigits[] = "0123456789ABCDEF";
// Generate 12 random hexadecimal characters
for (int i = 0; i < 12; i++)
{
int randomIndex = rand() % 16;
macAddress[i] = hexDigits[randomIndex];
}
macAddress[12] = '\0'; // Null-terminate the string
}
// TODO: wire standalone api:
/*
uint64_t readUntil(File &file, char *result, std::size_t maxBufferSize, char delimiter)
{
std::size_t bytesRead = 0;
while (true)
{
char ch;
File::Result<File::Size> readResult = file.read(&ch, 1);
if (readResult.is_ok() && readResult.value() > 0)
{
if (ch == delimiter)
{
// Found a space character, stop reading
break;
}
else if (bytesRead < maxBufferSize)
{
// Append the character to the result if there's space
result[bytesRead++] = ch;
}
else
{
// Buffer is full, break to prevent overflow
break;
}
}
else
{
break; // End of file or error
}
}
// Null-terminate the result string
result[bytesRead] = '\0';
return bytesRead;
}
*/
std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision)
{
const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
std::string string{""};
uint32_t prefix_index = base_unit;
double integer_part;
double fractional_part;
precision = std::min((uint32_t)4, precision);
while (n > 1000)
{
n /= 1000.0;
prefix_index++;
}
fractional_part = modf(n, &integer_part) * powers_of_ten[precision];
if (fractional_part < 0)
fractional_part = -fractional_part;
string = to_string_dec_int(integer_part);
if (precision)
string += '.' + to_string_dec_uint(fractional_part, precision, '0');
if (prefix_index != 3)
string += unit_prefix[prefix_index];
return string;
}
double get_decimals(double num, int16_t mult, bool round)
{
num -= int(num); // keep decimals only
num *= mult; // Shift decimals into integers
if (!round)
return num;
int16_t intnum = int(num); // Round it up if necessary
num -= intnum; // Get decimal part
if (num > .5)
intnum++; // Round up
return intnum;
}
static const char *whitespace_str = " \t\r\n";
std::string trim(std::string_view str)
{
auto first = str.find_first_not_of(whitespace_str);
if (first == std::string::npos)
return {};
auto last = str.find_last_not_of(whitespace_str);
return std::string{str.substr(first, last - first + 1)};
}
std::string trimr(std::string_view str)
{
size_t last = str.find_last_not_of(whitespace_str);
return std::string{last != std::string::npos ? str.substr(0, last + 1) : ""};
}
std::string truncate(std::string_view str, size_t length)
{
return std::string{str.length() <= length ? str : str.substr(0, length)};
}
uint8_t char_to_uint(char c, uint8_t radix)
{
uint8_t v = 0;
if (c >= '0' && c <= '9')
v = c - '0';
else if (c >= 'A' && c <= 'F')
v = c - 'A' + 10; // A is dec: 10
else if (c >= 'a' && c <= 'f')
v = c - 'a' + 10; // A is dec: 10
return v < radix ? v : 0;
}
char uint_to_char(uint8_t val, uint8_t radix)
{
if (val >= radix)
return 0;
if (val < 10)
return '0' + val;
else
return 'A' + val - 10; // A is dec: 10
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __STRING_FORMAT_H__
#define __STRING_FORMAT_H__
#include <cstdint>
#include <array>
#include <string>
#include <string_view>
// #include "file.hpp"
// BARF! rtc::RTC is leaking everywhere.
// #include "lpc43xx_cpp.hpp"
// using namespace lpc43xx;
enum TimeFormat
{
YMDHMS = 0,
HMS = 1,
HM = 2
};
const char unit_prefix[7]{'n', 'u', 'm', 0, 'k', 'M', 'G'};
using StringFormatBuffer = std::array<char, 24>;
/* Integer conversion without memory allocations. */
char *to_string_dec_int(int64_t n, StringFormatBuffer &buffer, size_t &length);
char *to_string_dec_uint(uint64_t n, StringFormatBuffer &buffer, size_t &length);
std::string to_string_dec_int(int64_t n);
std::string to_string_dec_uint(uint64_t n);
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
std::string to_string_decimal(float decimal, int8_t precision);
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l);
std::string to_string_hex(uint64_t n, int32_t length);
std::string to_string_hex_array(uint8_t *array, int32_t length);
/* Helper to select length based on type size. */
template <typename T>
std::string to_string_hex(T n)
{
return to_string_hex(n, sizeof(T) * 2); // Two digits/byte.
}
std::string to_string_freq(const uint64_t f);
std::string to_string_short_freq(const uint64_t f);
std::string to_string_rounded_freq(const uint64_t f, int8_t precision);
std::string to_string_time_ms(const uint32_t ms);
// TODO: wire standalone api: std::string to_string_datetime(const rtc::RTC &value, const TimeFormat format = YMDHMS);
// TODO: wire standalone api: std::string to_string_timestamp(const rtc::RTC &value);
// TODO: wire standalone api: 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);
// Converts Mac Address to string.
std::string to_string_mac_address(const uint8_t *macAddress, uint8_t length, bool noColon);
std::string to_string_formatted_mac_address(const char *macAddress);
void generateRandomMacAddress(char *macAddress);
// TODO: wire standalone api: uint64_t readUntil(File &file, char *result, std::size_t maxBufferSize, char delimiter);
/* Scales 'n' to be a value less than 1000. 'base_unit' is the index of the unit from
* 'unit_prefix' that 'n' is in initially. 3 is the index of the '1s' unit. */
std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision);
double get_decimals(double num, int16_t mult, bool round = false);
std::string trim(std::string_view str); // Remove whitespace at ends.
std::string trimr(std::string_view str); // Remove trailing spaces
std::string truncate(std::string_view, size_t length);
/* Gets the int value for a character given the radix.
* e.g. '5' => 5, 'D' => 13. Out of bounds => 0. */
uint8_t char_to_uint(char c, uint8_t radix = 10);
/* Gets the int value for a character given the radix.
* e.g. 5 => '5', 13 => 'D'. Out of bounds => '\0'. */
char uint_to_char(uint8_t val, uint8_t radix = 10);
#endif /*__STRING_FORMAT_H__*/

View file

@ -0,0 +1,870 @@
#include "theme.hpp"
namespace ui {
ThemeTemplate* Theme::current = nullptr;
ThemeTemplate* Theme::getInstance() {
if (current == nullptr) SetTheme(DefaultGrey);
return Theme::current;
}
void Theme::destroy() {
if (current != nullptr) {
delete current;
current = nullptr;
}
}
void Theme::SetTheme(ThemeId theme) {
if (current != nullptr) delete current;
switch (theme) {
case Yellow:
current = new ThemeYellow();
break;
case Aqua:
current = new ThemeAqua();
break;
case Green:
current = new ThemeGreen();
break;
case Red:
current = new ThemeRed();
break;
case Dark:
current = new ThemeDark();
break;
case DefaultGrey:
default:
current = new ThemeDefault();
break;
}
}
ThemeTemplate::~ThemeTemplate() {
delete bg_lightest;
delete bg_lightest_small;
delete bg_light;
delete bg_medium;
delete bg_dark;
delete bg_darker;
delete bg_darkest;
delete bg_darkest_small;
delete bg_important_small;
delete error_dark;
delete warning_dark;
delete ok_dark;
delete fg_dark;
delete fg_medium;
delete fg_light;
delete fg_red;
delete fg_green;
delete fg_yellow;
delete fg_orange;
delete fg_blue;
delete fg_cyan;
delete fg_darkcyan;
delete fg_magenta;
delete option_active;
delete status_active; // green, the status bar icons when active
delete bg_table_header;
}
ThemeYellow::ThemeYellow() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = {255, 255, 204},
.foreground = Color::black(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = {255, 255, 204},
.foreground = Color::black(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = {255, 255, 102},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = {204, 204, 0},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = {153, 153, 0},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = {102, 102, 0},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = {31, 31, 0},
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = Color::yellow(),
.foreground = {31, 31, 0},
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = {153, 153, 0},
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = {204, 204, 0},
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::light_grey(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = Color::orange(),
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0}; // green, the status bar icons when active
bg_table_header = new Color{205, 205, 0};
}
ThemeAqua::ThemeAqua() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = {204, 255, 255},
.foreground = Color::black(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = {204, 255, 255},
.foreground = Color::black(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = {102, 255, 255},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = {0, 144, 200},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 153, 153},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = {0, 102, 102},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = {0, 31, 31},
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = Color::yellow(),
.foreground = {0, 31, 31},
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = {0, 153, 153},
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = {0, 204, 204},
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::light_grey(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = {31, 31, 0},
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = {0, 31, 31},
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = Color::blue(),
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0}; // green, the status bar icons when active
bg_table_header = new Color{0, 205, 205};
}
ThemeDefault::ThemeDefault() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = Color::white(),
.foreground = Color::black(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = Color::white(),
.foreground = Color::black(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = Color::light_grey(),
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = Color::grey(),
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::dark_grey(),
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = Color::darker_grey(),
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = Color::black(),
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = Color::yellow(),
.foreground = Color::black(),
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::dark_grey(),
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::grey(),
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::light_grey(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = Color::blue(),
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0}; // green, the status bar icons when active
bg_table_header = new Color{0, 0, 255};
}
ThemeGreen::ThemeGreen() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = {0, 245, 29},
.foreground = Color::black(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = {0, 245, 29},
.foreground = Color::black(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = {0, 212, 25},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = {0, 143, 17},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 99, 12},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = {0, 79, 9},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = {0, 33, 4},
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = Color::yellow(),
.foreground = {0, 33, 4},
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = {0, 99, 12},
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = {0, 143, 17},
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::light_grey(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = {0, 33, 4},
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = Color::orange(),
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0}; // green, the status bar icons when active
bg_table_header = new Color{0, 205, 30};
}
ThemeRed::ThemeRed() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = {245, 29, 0},
.foreground = Color::black(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = {245, 29, 0},
.foreground = Color::black(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = {212, 25, 0},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = {143, 17, 0},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = {99, 12, 0},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = {79, 9, 0},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = {33, 4, 0},
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = Color::yellow(),
.foreground = {33, 4, 0},
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = {99, 12, 0},
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = {143, 17, 0},
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::light_grey(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = {33, 4, 0},
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = Color::orange(),
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0}; // green, the status bar icons when active
bg_table_header = new Color{205, 30, 0};
}
ThemeDark::ThemeDark() {
bg_lightest = new Style{
.font = font::fixed_8x16(),
.background = {32, 32, 32},
.foreground = Color::white(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8(),
.background = {32, 32, 32},
.foreground = Color::white(),
};
bg_light = new Style{
.font = font::fixed_8x16(),
.background = {24, 24, 24},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16(),
.background = {16, 16, 16},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16(),
.background = {8, 8, 8},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16(),
.background = {4, 4, 4},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8(),
.background = Color::black(),
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8(),
.background = {64, 64, 64},
.foreground = Color::white(),
};
error_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = {96, 96, 96},
};
fg_medium = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = {128, 128, 128},
};
fg_light = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::white(),
};
fg_red = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16(),
.background = Color::black(),
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16(),
.background = {64, 64, 64},
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0};
bg_table_header = new Color{48, 48, 48};
}
} // namespace ui

View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2024 HTotoo
*
* 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 __THEME_H__
#define __THEME_H__
#include <cstdint>
#include <cstddef>
#include <memory>
#include "ui_painter.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
class ThemeTemplate {
public:
~ThemeTemplate();
Style* bg_lightest;
Style* bg_lightest_small;
Style* bg_light;
Style* bg_medium;
Style* bg_dark;
Style* bg_darker;
Style* bg_darkest;
Style* bg_darkest_small;
Style* bg_important_small;
Style* error_dark;
Style* warning_dark;
Style* ok_dark;
Style* fg_dark;
Style* fg_medium;
Style* fg_light;
Style* fg_red;
Style* fg_green;
Style* fg_yellow;
Style* fg_orange;
Style* fg_blue;
Style* fg_cyan;
Style* fg_darkcyan;
Style* fg_magenta;
Style* option_active;
Color* status_active; // green, the status bar icons when active
Color* bg_table_header;
};
class ThemeDefault : public ThemeTemplate {
public:
ThemeDefault();
};
class ThemeYellow : public ThemeTemplate {
public:
ThemeYellow();
};
class ThemeAqua : public ThemeTemplate {
public:
ThemeAqua();
};
class ThemeGreen : public ThemeTemplate {
public:
ThemeGreen();
};
class ThemeRed : public ThemeTemplate {
public:
ThemeRed();
};
class ThemeDark : public ThemeTemplate {
public:
ThemeDark();
};
class Theme {
public:
enum ThemeId {
DefaultGrey = 0,
Yellow = 1,
Aqua = 2,
Green = 3,
Red = 4,
Dark = 5,
MAX
};
static ThemeTemplate* getInstance();
static void SetTheme(ThemeId theme);
static ThemeTemplate* current;
static void destroy();
private:
};
} // namespace ui
#endif /*__THEME_H__*/

View file

@ -0,0 +1,132 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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.hpp"
// #include "irq_controls.hpp"
#include "sine_table.hpp"
#include "utility.hpp"
#include <algorithm>
namespace ui
{
// CGA palette
// Index into this table should match STR_COLOR_ escape string in ui.hpp
Color term_colors[16] = {
Color::black(),
Color::dark_blue(),
Color::dark_green(),
Color::dark_cyan(),
Color::dark_red(),
Color::dark_magenta(),
Color::dark_yellow(),
Color::light_grey(),
Color::dark_grey(),
Color::blue(),
Color::green(),
Color::cyan(),
Color::red(),
Color::magenta(),
Color::yellow(),
Color::white()};
bool Rect::contains(const Point p) const
{
return (p.x() >= left()) && (p.y() >= top()) &&
(p.x() < right()) && (p.y() < bottom());
}
Rect Rect::intersect(const Rect &o) const
{
const auto x1 = std::max(left(), o.left());
const auto x2 = std::min(right(), o.right());
const auto y1 = std::max(top(), o.top());
const auto y2 = std::min(bottom(), o.bottom());
if ((x2 >= x1) && (y2 > y1))
{
return {x1, y1, x2 - x1, y2 - y1};
}
else
{
return {};
}
}
// TODO: This violates the principle of least surprise!
// This does a union, but that might not be obvious from "+=" syntax.
Rect &Rect::operator+=(const Rect &p)
{
if (is_empty())
{
*this = p;
}
if (!p.is_empty())
{
const auto x1 = std::min(left(), p.left());
const auto y1 = std::min(top(), p.top());
_pos = {x1, y1};
const auto x2 = std::max(right(), p.right());
const auto y2 = std::max(bottom(), p.bottom());
_size = {x2 - x1, y2 - y1};
}
return *this;
}
Rect &Rect::operator+=(const Point &p)
{
_pos += p;
return *this;
}
Rect &Rect::operator-=(const Point &p)
{
_pos -= p;
return *this;
}
Point polar_to_point(float angle, uint32_t distance)
{
// polar to compass with y negated for screen drawing
return Point(sin_f32(DEG_TO_RAD(-angle) + pi) * distance,
sin_f32(DEG_TO_RAD(-angle) - (pi / 2)) * distance);
}
Point fast_polar_to_point(int32_t angle, uint32_t distance)
{
// polar to compass with y negated for screen drawing
return Point((int16_sin_s4(((1 << 16) * (-angle + 180)) / 360) * distance) / (1 << 16),
(int16_sin_s4(((1 << 16) * (-angle - 90)) / 360) * distance) / (1 << 16));
}
bool key_is_long_pressed(KeyEvent key)
{
if (key < KeyEvent::Back)
{
// TODO: this would make more sense as a flag on KeyEvent
// passed up to the UI via event dispatch.
return false; // TODO: wire standalone api: switch_is_long_pressed(static_cast<Switch>(key));
}
return false;
}
} /* namespace ui */

View file

@ -0,0 +1,410 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_H__
#define __UI_H__
#include <cstdint>
namespace ui {
// Escape sequences for colored text; second character is index into term_colors[]
#define STR_COLOR_BLACK "\x1B\x00"
#define STR_COLOR_DARK_BLUE "\x1B\x01"
#define STR_COLOR_DARK_GREEN "\x1B\x02"
#define STR_COLOR_DARK_CYAN "\x1B\x03"
#define STR_COLOR_DARK_RED "\x1B\x04"
#define STR_COLOR_DARK_MAGENTA "\x1B\x05"
#define STR_COLOR_DARK_YELLOW "\x1B\x06"
#define STR_COLOR_LIGHT_GREY "\x1B\x07"
#define STR_COLOR_DARK_GREY "\x1B\x08"
#define STR_COLOR_BLUE "\x1B\x09"
#define STR_COLOR_GREEN "\x1B\x0A"
#define STR_COLOR_CYAN "\x1B\x0B"
#define STR_COLOR_RED "\x1B\x0C"
#define STR_COLOR_MAGENTA "\x1B\x0D"
#define STR_COLOR_YELLOW "\x1B\x0E"
#define STR_COLOR_WHITE "\x1B\x0F"
#define STR_COLOR_FOREGROUND "\x1B\x10"
#define DEG_TO_RAD(d) (d * (2 * pi) / 360.0)
using Coord = int16_t;
using Dim = int16_t;
constexpr uint16_t screen_width = 240;
constexpr uint16_t screen_height = 320;
/* Dimensions for the default font character. */
constexpr uint16_t char_width = 8;
constexpr uint16_t char_height = 16;
struct Color {
uint16_t v; // rrrrrGGGGGGbbbbb
constexpr Color()
: v{0} {
}
constexpr Color(
uint16_t v)
: v{v} {
}
constexpr Color(
uint8_t r,
uint8_t g,
uint8_t b)
: v{
static_cast<uint16_t>(
((r & 0xf8) << 8) | ((g & 0xfc) << 3) | ((b & 0xf8) >> 3))} {
}
uint8_t r() {
return (uint8_t)((v >> 8) & 0xf8);
}
uint8_t g() {
return (uint8_t)((v >> 3) & 0xfc);
}
uint8_t b() {
return (uint8_t)((v << 3) & 0xf8);
}
uint8_t to_greyscale() {
uint32_t r = (v >> 8) & 0xf8;
uint32_t g = (v >> 3) & 0xfc;
uint32_t b = (v << 3) & 0xf8;
uint32_t grey = ((r * 306) + (g * 601) + (b * 117)) >> 10;
return (uint8_t)grey;
}
uint16_t dark() {
// stripping bits 4 & 5 from each of the colors R/G/B
return (v & ((0xc8 << 8) | (0xcc << 3) | (0xc8 >> 3)));
}
Color operator-() const {
return (v ^ 0xffff);
}
/* Converts a 32-bit color into a 16-bit color.
* High byte is ignored. */
static constexpr Color RGB(uint32_t rgb) {
return {static_cast<uint8_t>((rgb >> 16) & 0xff),
static_cast<uint8_t>((rgb >> 8) & 0xff),
static_cast<uint8_t>(rgb & 0xff)};
}
static constexpr Color black() {
return {0, 0, 0};
}
static constexpr Color red() {
return {255, 0, 0};
}
static constexpr Color dark_red() {
return {159, 0, 0};
}
static constexpr Color orange() {
return {255, 175, 0};
}
static constexpr Color dark_orange() {
return {191, 95, 0};
}
static constexpr Color yellow() {
return {255, 255, 0};
}
static constexpr Color dark_yellow() {
return {191, 191, 0};
}
static constexpr Color green() {
return {0, 255, 0};
}
static constexpr Color dark_green() {
return {0, 159, 0};
}
static constexpr Color blue() {
return {0, 0, 255};
}
static constexpr Color dark_blue() {
return {0, 0, 191};
}
static constexpr Color cyan() {
return {0, 255, 255};
}
static constexpr Color dark_cyan() {
return {0, 191, 191};
}
static constexpr Color magenta() {
return {255, 0, 255};
}
static constexpr Color dark_magenta() {
return {191, 0, 191};
}
static constexpr Color white() {
return {255, 255, 255};
}
static constexpr Color light_grey() {
return {191, 191, 191};
}
static constexpr Color grey() {
return {127, 127, 127};
}
static constexpr Color dark_grey() {
return {63, 63, 63};
}
static constexpr Color darker_grey() {
return {31, 31, 31};
}
static constexpr Color purple() {
return {204, 0, 102};
}
};
extern Color term_colors[16];
struct ColorRGB888 {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct Point {
private:
Coord _x;
Coord _y;
public:
constexpr Point()
: _x{0},
_y{0} {
}
constexpr Point(
int x,
int y)
: _x{static_cast<Coord>(x)},
_y{static_cast<Coord>(y)} {
}
constexpr int x() const {
return _x;
}
constexpr int y() const {
return _y;
}
constexpr Point operator-() const {
return {-_x, -_y};
}
constexpr Point operator+(const Point& p) const {
return {_x + p._x, _y + p._y};
}
constexpr Point operator-(const Point& p) const {
return {_x - p._x, _y - p._y};
}
Point& operator+=(const Point& p) {
_x += p._x;
_y += p._y;
return *this;
}
Point& operator-=(const Point& p) {
_x -= p._x;
_y -= p._y;
return *this;
}
};
struct Size {
private:
Dim _w;
Dim _h;
public:
constexpr Size()
: _w{0},
_h{0} {
}
constexpr Size(
int w,
int h)
: _w{static_cast<Dim>(w)},
_h{static_cast<Dim>(h)} {
}
int width() const {
return _w;
}
int height() const {
return _h;
}
bool is_empty() const {
return (_w < 1) || (_h < 1);
}
};
struct Rect {
private:
Point _pos;
Size _size;
public:
constexpr Rect()
: _pos{},
_size{} {
}
constexpr Rect(
int x,
int y,
int w,
int h)
: _pos{x, y},
_size{w, h} {
}
constexpr Rect(
Point pos,
Size size)
: _pos(pos),
_size(size) {
}
Point location() const {
return _pos;
}
Size size() const {
return _size;
}
int top() const {
return _pos.y();
}
int bottom() const {
return _pos.y() + _size.height();
}
int left() const {
return _pos.x();
}
int right() const {
return _pos.x() + _size.width();
}
int width() const {
return _size.width();
}
int height() const {
return _size.height();
}
Point center() const {
return {_pos.x() + _size.width() / 2, _pos.y() + _size.height() / 2};
}
bool is_empty() const {
return _size.is_empty();
}
bool contains(const Point p) const;
Rect intersect(const Rect& o) const;
Rect operator+(const Point& p) const {
return {_pos + p, _size};
}
Rect& operator+=(const Rect& p);
Rect& operator+=(const Point& p);
Rect& operator-=(const Point& p);
operator bool() const {
return !_size.is_empty();
}
};
struct Bitmap {
const Size size;
const uint8_t* const data;
};
enum class KeyEvent : uint8_t {
/* Ordinals map to bit positions reported by CPLD */
Right = 0,
Left = 1,
Down = 2,
Up = 3,
Select = 4,
Dfu = 5,
Back = 6, /* Left and Up together */
};
using EncoderEvent = int32_t;
using KeyboardEvent = uint8_t;
struct TouchEvent {
enum class Type : uint32_t {
Start = 0,
Move = 1,
End = 2,
};
Point point;
Type type;
};
Point polar_to_point(float angle, uint32_t distance);
Point fast_polar_to_point(int32_t angle, uint32_t distance);
/* Default font glyph size. */
constexpr Size char_size{char_width, char_height};
bool key_is_long_pressed(KeyEvent key);
} /* namespace ui */
#endif /*__UI_H__*/

View file

@ -0,0 +1,211 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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_focus.hpp"
#include "ui_widget.hpp"
#include <cstdint>
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
#define pow2(x) ((x) * (x))
namespace ui {
Widget* FocusManager::focus_widget() const {
return focus_widget_;
}
void FocusManager::set_focus_widget(Widget* const new_focus_widget) {
// Widget already has focus.
if (new_focus_widget == focus_widget()) {
return;
}
if (new_focus_widget) {
if (!new_focus_widget->focusable())
return;
}
// Blur old widget.
// NB: This will crash if the focus_widget is a destroyed instance.
if (focus_widget()) {
focus_widget()->on_blur();
focus_widget()->set_dirty();
}
focus_widget_ = new_focus_widget;
if (focus_widget()) {
focus_widget()->on_focus();
focus_widget()->set_dirty();
}
}
using test_result_t = std::pair<Widget* const, const uint32_t>;
using test_fn = std::function<test_result_t(Widget* const)>;
using test_collection_t = std::vector<test_result_t>;
/* Walk all visible widgets in hierarchy, collecting those that pass test */
template <typename TestFn>
static void widget_collect_visible(Widget* const w, TestFn test, test_collection_t& collection) {
for (auto child : w->children()) {
if (!child->hidden()) {
const auto result = test(child);
if (result.first) {
collection.push_back(result);
}
widget_collect_visible(child, test, collection);
}
}
}
static int32_t rect_distances(
const KeyEvent direction,
const Rect& rect_start,
const Rect& rect_end) {
Coord on_axis_max, on_axis_min;
switch (direction) {
case KeyEvent::Right:
on_axis_max = rect_end.left();
on_axis_min = rect_start.right();
break;
case KeyEvent::Left:
on_axis_max = rect_start.left();
on_axis_min = rect_end.right();
break;
case KeyEvent::Down:
on_axis_max = rect_end.top();
on_axis_min = rect_start.bottom();
break;
case KeyEvent::Up:
on_axis_max = rect_start.top();
on_axis_min = rect_end.bottom();
break;
default:
return -1;
}
Coord on_axis_distance = on_axis_max - on_axis_min;
if (on_axis_distance < 0) {
return -1;
}
Coord perpendicular_axis_start, perpendicular_axis_end;
switch (direction) {
case KeyEvent::Right:
case KeyEvent::Left:
perpendicular_axis_start = rect_start.center().y();
perpendicular_axis_end = rect_end.center().y();
break;
case KeyEvent::Up:
case KeyEvent::Down:
perpendicular_axis_start = rect_start.center().x();
perpendicular_axis_end = rect_end.center().x();
break;
default:
return -1;
}
switch (direction) {
case KeyEvent::Right:
case KeyEvent::Left:
return pow2(std::abs(perpendicular_axis_end - perpendicular_axis_start) + 1) * (on_axis_distance + 1);
break;
case KeyEvent::Up:
case KeyEvent::Down:
return (std::abs(perpendicular_axis_end - perpendicular_axis_start) + 1) * pow2(on_axis_distance + 1);
break;
default:
return 0;
}
}
void FocusManager::update(
Widget* const top_widget,
const KeyEvent event) {
if (focus_widget()) {
const auto focus_screen_rect = focus_widget()->screen_rect();
const auto test_fn = [&focus_screen_rect, event](ui::Widget* const w) -> test_result_t {
// if( w->visible() && w->focusable() ) {
if (w->focusable()) {
const auto distance = rect_distances(event, focus_screen_rect, w->screen_rect());
if (distance >= 0) {
return {w, distance};
}
}
return {nullptr, 0};
};
const auto find_back_fn = [](ui::Widget* const w) -> test_result_t {
if (w->focusable() && (w->id == -1))
return {w, 0};
else
return {nullptr, 0};
};
test_collection_t collection;
widget_collect_visible(top_widget, test_fn, collection);
const auto compare_fn = [](const test_result_t& a, const test_result_t& b) {
return a.second < b.second;
};
const auto nearest = std::min_element(collection.cbegin(), collection.cend(), compare_fn);
// Up and left to indicate back
if (event == KeyEvent::Back) {
collection.clear();
widget_collect_visible(top_widget, find_back_fn, collection);
if (!collection.empty())
set_focus_widget(collection[0].first);
} else if (nearest != collection.cend()) {
// focus->blur();
const auto new_focus = (*nearest).first;
set_focus_widget(new_focus);
} else {
if ((focus_widget()->id >= 0) && (event == KeyEvent::Left)) {
// Stuck left, move to back button
collection.clear();
widget_collect_visible(top_widget, find_back_fn, collection);
if (!collection.empty())
set_focus_widget(collection[0].first);
}
}
}
}
} /* namespace ui */

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* 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_FOCUS_H__
#define __UI_FOCUS_H__
#include "ui.hpp"
namespace ui {
class Widget;
class FocusManager {
public:
Widget* focus_widget() const;
void set_focus_widget(Widget* const new_focus_widget);
void update(Widget* const top_widget, const KeyEvent event);
// void update(Widget* const top_widget, const TouchEvent event);
private:
Widget* focus_widget_{nullptr};
};
} /* namespace ui */
#endif /*__UI_FOCUS_H__*/

View file

@ -0,0 +1,45 @@
/*
* 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>
#include "standalone_app.hpp"
namespace ui {
namespace font {
ui::Font* fixed_5x8_font = nullptr;
ui::Font& fixed_5x8() {
if (fixed_5x8_font == nullptr) {
fixed_5x8_font = new ui::Font{
5,
8,
_api->fixed_5x8_glyph_data,
0x20,
95,
};
}
return *fixed_5x8_font;
}
} /* namespace font */
} /* namespace ui */

View file

@ -0,0 +1,36 @@
/*
* 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 ui::Font &fixed_5x8();
} // namespace font
} // namespace ui
#endif /*__UI_FONT_FIXED_5X8_H__*/

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_8x16.hpp"
#include <cstdint>
namespace ui {
namespace font {
ui::Font* fixed_8x16_font = nullptr;
ui::Font& fixed_8x16() {
if (fixed_8x16_font == nullptr) {
fixed_8x16_font = new ui::Font{
8,
16,
_api->fixed_8x16_glyph_data,
0x20,
223,
};
}
return *fixed_8x16_font;
}
} /* namespace font */
} /* namespace ui */

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_8X16_H__
#define __UI_FONT_FIXED_8X16_H__
#include "ui_text.hpp"
namespace ui
{
namespace font
{
extern ui::Font &fixed_8x16();
} /* namespace font */
} // namespace ui
#endif /*__UI_FONT_FIXED_8X16_H__*/

View file

@ -0,0 +1,985 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* 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_geomap.hpp"
#include <cstring>
#include <stdio.h>
#include <string_view>
#include "string_format.hpp"
#include "complex.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "file_path.hpp"
namespace ui {
GeoPos::GeoPos(
const Point pos,
const alt_unit altitude_unit,
const spd_unit speed_unit)
: altitude_unit_(altitude_unit), speed_unit_(speed_unit) {
set_parent_rect({pos, {screen_width, 3 * 16}});
add_children({&labels_position,
&label_spd_position,
&field_altitude,
&field_speed,
&text_alt_unit,
&text_speed_unit,
&field_lat_degrees,
&field_lat_minutes,
&field_lat_seconds,
&text_lat_decimal,
&field_lon_degrees,
&field_lon_minutes,
&field_lon_seconds,
&text_lon_decimal});
// Defaults
set_altitude(0);
set_speed(0);
set_lat(0);
set_lon(0);
const auto changed_fn = [this](int32_t) {
// Convert degrees/minutes/seconds fields to decimal (floating point) lat/lon degree
float lat_value = lat();
float lon_value = lon();
text_lat_decimal.set(to_string_decimal(lat_value, 5));
text_lon_decimal.set(to_string_decimal(lon_value, 5));
if (on_change && report_change)
on_change(altitude(), lat_value, lon_value, speed());
};
field_altitude.on_change = changed_fn;
field_speed.on_change = changed_fn;
field_lat_degrees.on_change = changed_fn;
field_lat_minutes.on_change = changed_fn;
field_lat_seconds.on_change = changed_fn;
field_lon_degrees.on_change = changed_fn;
field_lon_minutes.on_change = changed_fn;
field_lon_seconds.on_change = changed_fn;
const auto wrapped_lat_seconds = [this](int32_t v) {
field_lat_minutes.on_encoder(v);
};
const auto wrapped_lat_minutes = [this](int32_t v) {
field_lat_degrees.on_encoder((field_lat_degrees.value() >= 0) ? v : -v);
};
const auto wrapped_lon_seconds = [this](int32_t v) {
field_lon_minutes.on_encoder(v);
};
const auto wrapped_lon_minutes = [this](int32_t v) {
field_lon_degrees.on_encoder((field_lon_degrees.value() >= 0) ? v : -v);
};
field_lat_seconds.on_wrap = wrapped_lat_seconds;
field_lat_minutes.on_wrap = wrapped_lat_minutes;
field_lon_seconds.on_wrap = wrapped_lon_seconds;
field_lon_minutes.on_wrap = wrapped_lon_minutes;
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
if (speed_unit_ == MPH) text_speed_unit.set("mph");
if (speed_unit_ == KNOTS) text_speed_unit.set("knots");
if (speed_unit_ == HIDDEN) {
text_speed_unit.hidden(true);
label_spd_position.hidden(true);
field_speed.hidden(true);
}
}
void GeoPos::set_read_only(bool v) {
// only setting altitude to read-only (allow manual panning via lat/lon fields)
field_altitude.set_focusable(!v);
field_speed.set_focusable(!v);
}
// Stupid hack to avoid an event loop
void GeoPos::set_report_change(bool v) {
report_change = v;
}
void GeoPos::focus() {
if (field_altitude.focusable())
field_altitude.focus();
else
field_lat_degrees.focus();
}
void GeoPos::hide_altandspeed() {
// Color altitude grey to indicate it's not updated in manual panning mode
field_altitude.set_style(Theme::getInstance()->fg_medium);
field_speed.set_style(Theme::getInstance()->fg_medium);
}
void GeoPos::set_altitude(int32_t altitude) {
field_altitude.set_value(altitude);
}
void GeoPos::set_speed(int32_t speed) {
field_speed.set_value(speed);
}
void GeoPos::set_lat(float lat) {
field_lat_degrees.set_value(lat);
field_lat_minutes.set_value((uint32_t)abs(lat / (1.0 / 60)) % 60);
field_lat_seconds.set_value((uint32_t)abs(lat / (1.0 / 3600)) % 60);
}
void GeoPos::set_lon(float lon) {
field_lon_degrees.set_value(lon);
field_lon_minutes.set_value((uint32_t)abs(lon / (1.0 / 60)) % 60);
field_lon_seconds.set_value((uint32_t)abs(lon / (1.0 / 3600)) % 60);
}
float GeoPos::lat() {
if (field_lat_degrees.value() < 0) {
return -1 * (-1 * field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0));
} else {
return field_lat_degrees.value() + (field_lat_minutes.value() / 60.0) + (field_lat_seconds.value() / 3600.0);
}
};
float GeoPos::lon() {
if (field_lon_degrees.value() < 0) {
return -1 * (-1 * field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0));
} else {
return field_lon_degrees.value() + (field_lon_minutes.value() / 60.0) + (field_lon_seconds.value() / 3600.0);
}
};
int32_t GeoPos::altitude() {
return field_altitude.value();
};
int32_t GeoPos::speed() {
return field_speed.value();
};
GeoMap::GeoMap(
Rect parent_rect)
: Widget{parent_rect}, markerListLen(0) {
has_osm = use_osm = find_osm_file_tile();
}
bool GeoMap::on_encoder(const EncoderEvent delta) {
// Valid map_zoom values are -2 to -MAX_MAP_ZOOM_OUT, and +1 to +MAX_MAP_ZOOM_IN (values of 0 and -1 are not permitted)
if (delta > 0) {
if (map_zoom < MAX_MAP_ZOOM_IN) {
if (map_zoom == -2) {
map_zoom = 1;
} else {
// zoom in faster after exceeding the map resolution limit
map_zoom += (map_zoom >= MAP_ZOOM_RESOLUTION_LIMIT) ? map_zoom : 1;
}
}
map_osm_zoom++;
if (has_osm) set_osm_max_zoom();
} else if (delta < 0) {
if (map_zoom > -MAX_MAP_ZOOM_OUT) {
if (map_zoom == 1) {
map_zoom = -2;
} else {
if (map_zoom > MAP_ZOOM_RESOLUTION_LIMIT)
map_zoom /= 2;
else
map_zoom--;
}
}
if (map_osm_zoom > 0) map_osm_zoom--;
} else {
return false;
}
map_visible = map_opened && (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT);
if (use_osm) {
map_visible = true;
zoom_pixel_offset = 0;
} else {
zoom_pixel_offset = (map_visible && (map_zoom > 1)) ? (float)map_zoom / 2 : 0.0f;
}
// Trigger map redraw
redraw_map = true;
set_dirty();
return true;
}
void GeoMap::map_read_line_bin(ui::Color* buffer, uint16_t pixels) {
if (map_zoom == 1) {
map_file.read(buffer, pixels << 1);
} else if (map_zoom > 1) {
map_file.read(buffer, (pixels / map_zoom) << 1);
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
// Future TODO: Add dithering to smooth out the pixelation.
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
// For 240 width, than means no check is needed for map_zoom values up to 6.
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
for (int j = 0; j < map_zoom; j++) {
buffer[(i * map_zoom) + j] = buffer[i];
}
}
} else {
ui::Color* zoom_out_buffer = new ui::Color[(pixels * (-map_zoom))];
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
for (int i = 0; i < geomap_rect_width; i++) {
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
}
delete[] zoom_out_buffer;
}
}
void GeoMap::draw_markers(Painter& painter) {
for (int i = 0; i < markerListLen; ++i) {
draw_marker_item(painter, markerList[i], Color::blue(), Color::blue(), Color::magenta());
}
}
void GeoMap::draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor, const Color backColor) {
const auto r = screen_rect();
const ui::Point itemPoint = item_rect_pixel(item);
if ((itemPoint.x() >= 0) && (itemPoint.x() < r.width()) &&
(itemPoint.y() > 10) && (itemPoint.y() < r.height())) // Dont draw within symbol size of top
{
draw_marker(painter, {itemPoint.x() + r.left(), itemPoint.y() + r.top()}, item.angle, item.tag, color, fontColor, backColor);
}
}
// Calculate screen position of item, adjusted for zoom factor.
ui::Point GeoMap::item_rect_pixel(GeoMarker& item) {
if (!use_osm) {
const auto r = screen_rect();
const auto geomap_rect_half_width = r.width() / 2;
const auto geomap_rect_half_height = r.height() / 2;
GeoPoint mapPoint = lat_lon_to_map_pixel(item.lat, item.lon);
float x = mapPoint.x - x_pos;
float y = mapPoint.y - y_pos;
if (map_zoom > 1) {
x = x * map_zoom + zoom_pixel_offset;
y = y * map_zoom + zoom_pixel_offset;
} else if (map_zoom < 0) {
x = x / (-map_zoom);
y = y / (-map_zoom);
}
x += geomap_rect_half_width;
y += geomap_rect_half_height;
return {(int16_t)x, (int16_t)y};
}
// osm calculation
double y = lat_to_pixel_y_tile(item.lat, map_osm_zoom) - viewport_top_left_py;
double x = lon_to_pixel_x_tile(item.lon, map_osm_zoom) - viewport_top_left_px;
return {(int16_t)x, (int16_t)y};
}
/**
* @brief Converts longitude to a map tile's X-coordinate.
* @param lon The longitude in degrees.
* @param zoom The zoom level.
* @return The X-coordinate of the tile.
*/
int GeoMap::lon2tile(double lon, int zoom) {
return (int)floor((lon + 180.0) / 360.0 * pow(2.0, zoom));
}
/**
* @brief Converts latitude to a map tile's Y-coordinate.
* @param lat The latitude in degrees.
* @param zoom The zoom level.
* @return The Y-coordinate of the tile.
*/
int GeoMap::lat2tile(double lat, int zoom) {
// Convert latitude from degrees to radians for trigonometric functions
double lat_rad = lat * M_PI / 180.0;
// Perform the Mercator projection calculation
return (int)floor((1.0 - log(tan(lat_rad) + 1.0 / cos(lat_rad)) / M_PI) / 2.0 * pow(2.0, zoom));
}
void GeoMap::set_osm_max_zoom() {
if (map_osm_zoom > 20) map_osm_zoom = 20;
for (uint8_t i = map_osm_zoom; i > 0; i--) {
int tile_x = lon2tile(lon_, i);
int tile_y = lat2tile(lat_, i);
std::string filename = "/OSM/" + to_string_dec_int(i) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp";
std::filesystem::path file_path(filename);
if (file_exists(file_path)) {
map_osm_zoom = i;
return;
}
}
map_osm_zoom = 0; // should not happen
}
// checks if the tile file presents or not. to determine if we got osm or not
uint8_t GeoMap::find_osm_file_tile() {
std::string filename = "/OSM/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + "/" + to_string_dec_int(0) + ".bmp";
std::filesystem::path file_path(filename);
if (file_exists(file_path)) return 1;
return 0; // not found
}
// Converts latitude/longitude to pixel coordinates in binary map file.
// (Note that when map_zoom==1, one pixel in map file corresponds to 1 pixel on screen)
GeoPoint GeoMap::lat_lon_to_map_pixel(float lat, float lon) {
// Using WGS 84/Pseudo-Mercator projection
float x = (map_width * (lon + 180) / 360);
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
double lat_rad = sin(lat * pi / 180);
float y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset));
return {x, y};
}
// Draw grid in place of map (when zoom-in level is too high).
void GeoMap::draw_map_grid(ui::Rect r, Painter& painter) {
// Grid spacing is just based on zoom at the moment, and centered on screen.
// TODO: Maybe align with latitude/longitude seconds instead?
int grid_spacing = map_zoom * 2;
int x = (r.width() / 2) % grid_spacing;
int y = (r.height() / 2) % grid_spacing;
if (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT)
return;
painter.fill_rectangle({{0, r.top()}, {r.width(), r.height()}}, Theme::getInstance()->bg_darkest->background);
for (uint16_t line = y; line < r.height(); line += grid_spacing) {
painter.fill_rectangle({{0, r.top() + line}, {r.width(), 1}}, Theme::getInstance()->bg_darker->background);
}
for (uint16_t column = x; column < r.width(); column += grid_spacing) {
painter.fill_rectangle({{column, r.top()}, {1, r.height()}}, Theme::getInstance()->bg_darker->background);
}
}
double GeoMap::tile_pixel_x_to_lon(int x, int zoom) {
double map_width = pow(2.0, zoom) * TILE_SIZE;
return (x / map_width * 360.0) - 180.0;
}
double GeoMap::tile_pixel_y_to_lat(int y, int zoom) {
double map_height = pow(2.0, zoom) * TILE_SIZE;
double n = M_PI * (1.0 - 2.0 * y / map_height);
return atan(sinh(n)) * 180.0 / M_PI;
}
double GeoMap::lon_to_pixel_x_tile(double lon, int zoom) {
return ((lon + 180.0) / 360.0) * pow(2.0, zoom) * TILE_SIZE;
}
double GeoMap::lat_to_pixel_y_tile(double lat, int zoom) {
double lat_rad = lat * M_PI / 180.0;
double sin_lat = sin(lat_rad);
return ((1.0 - log((1.0 + sin_lat) / (1.0 - sin_lat)) / (2.0 * M_PI)) / 2.0) * pow(2.0, zoom) * TILE_SIZE;
}
bool GeoMap::draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int relative_y, Painter& painter) {
const ui::Rect r = screen_rect();
// Early exit if the tile is completely outside the viewport
if (relative_x >= r.width() || relative_y >= r.height() ||
relative_x + TILE_SIZE <= 0 || relative_y + TILE_SIZE <= 0) {
return true;
}
BMPFile bmp{};
bmp.open("/OSM/" + to_string_dec_int(zoom) + "/" + to_string_dec_int(tile_x) + "/" + to_string_dec_int(tile_y) + ".bmp", true);
// 1. Define the source and destination areas, starting with the full tile.
int src_x = 0;
int src_y = 0;
int dest_x = relative_x;
int dest_y = relative_y;
int clip_w = TILE_SIZE;
int clip_h = TILE_SIZE;
// 2. Clip left edge
if (dest_x < 0) {
src_x = -dest_x;
clip_w += dest_x;
dest_x = 0;
}
// 3. Clip top edge
if (dest_y < 0) {
src_y = -dest_y;
clip_h += dest_y;
dest_y = 0;
}
// 4. Clip right edge
if (dest_x + clip_w > r.width()) {
clip_w = r.width() - dest_x;
}
// 5. Clip bottom edge
if (dest_y + clip_h > r.height()) {
clip_h = r.height() - dest_y;
}
// 6. If clipping resulted in no visible area, we're done.
if (clip_w <= 0 || clip_h <= 0) {
return true;
}
if (!bmp.is_loaded()) {
// Draw an error rectangle using the calculated clipped dimensions
ui::Rect error_rect{{dest_x + r.left(), dest_y + r.top()}, {clip_w, clip_h}};
painter.fill_rectangle(error_rect, Theme::getInstance()->bg_lightest->background);
return false;
}
std::vector<ui::Color> line(clip_w);
for (int y = 0; y < clip_h; ++y) {
int source_row = src_y + y;
int dest_row = dest_y + y;
bmp.seek(src_x, source_row);
for (int x = 0; x < clip_w; ++x) {
bmp.read_next_px(line[x], true);
}
painter.draw_pixels({dest_x + r.left(), dest_row + r.top(), clip_w, 1}, line);
}
return true;
}
void GeoMap::paint(Painter& painter) {
const auto r = screen_rect();
std::array<ui::Color, geomap_rect_width> map_line_buffer;
int16_t zoom_seek_x, zoom_seek_y;
if (!use_osm) {
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
if (map_zoom <= 1) {
// Zooming out, or no zoom
const int min_diff = abs(map_zoom);
if ((int)abs(x_pos - prev_x_pos) >= min_diff)
redraw_map = true;
else if ((int)abs(y_pos - prev_y_pos) >= min_diff)
redraw_map = true;
} else {
// Zooming in; magnify position differences (utilizing zoom_pixel_offset)
if ((int)(abs(x_pos - prev_x_pos) * map_zoom) >= 1)
redraw_map = true;
else if ((int)(abs(y_pos - prev_y_pos) * map_zoom) >= 1)
redraw_map = true;
}
} else {
// using osm; needs to be stricter with the redraws, it'll be checked on move
}
if (redraw_map) {
redraw_map = false;
if (map_visible) {
if (!use_osm) {
prev_x_pos = x_pos; // Note x_pos/y_pos pixel position in map file now correspond to screen rect CENTER pixel
prev_y_pos = y_pos;
// Adjust starting corner position of map per zoom setting;
// When zooming in the map should technically by shifted left & up by another map_zoom/2 pixels but
// the map_read_line_bin() function doesn't handle that yet so we're adjusting markers instead (see zoom_pixel_offset).
if (map_zoom > 1) {
zoom_seek_x = x_pos - (float)r.width() / (2 * map_zoom);
zoom_seek_y = y_pos - (float)r.height() / (2 * map_zoom);
} else {
zoom_seek_x = x_pos - (r.width() * abs(map_zoom)) / 2;
zoom_seek_y = y_pos - (r.height() * abs(map_zoom)) / 2;
}
// Read from map file and display to zoomed scale
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
map_read_line_bin(map_line_buffer.data(), r.width());
for (uint16_t j = 0; j < duplicate_lines; j++) {
painter.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
}
}
} else {
// display osm tiles
// Convert center GPS to a global pixel coordinate
double global_center_px = lon_to_pixel_x_tile(lon_, map_osm_zoom);
double global_center_py = lat_to_pixel_y_tile(lat_, map_osm_zoom);
// Find the top-left corner of the screen (viewport) in global pixel coordinates
viewport_top_left_px = global_center_px - (r.width() / 2.0);
viewport_top_left_py = global_center_py - (r.height() / 2.0);
// Find the tile ID that contains the top-left corner of the viewport
int start_tile_x = floor(viewport_top_left_px / TILE_SIZE);
int start_tile_y = floor(viewport_top_left_py / TILE_SIZE);
// Calculate the crucial render offset.
// This determines how much the first tile is shifted to align the map correctly.
// This value will almost always be negative or zero.
double render_offset_x = -(viewport_top_left_px - (start_tile_x * TILE_SIZE));
double render_offset_y = -(viewport_top_left_py - (start_tile_y * TILE_SIZE));
// Determine how many tiles we need to draw to fill the screen
int tiles_needed_x = (r.width() / TILE_SIZE) + 2;
int tiles_needed_y = (r.height() / TILE_SIZE) + 2;
for (int y = 0; y < tiles_needed_y; ++y) {
for (int x = 0; x < tiles_needed_x; ++x) {
int current_tile_x = start_tile_x + x;
int current_tile_y = start_tile_y + y;
// Calculate the final on-screen drawing position for this tile.
// For the first tile (x=0, y=0), this will be the negative offset.
int draw_pos_x = round(render_offset_x + x * TILE_SIZE);
int draw_pos_y = round(render_offset_y + y * TILE_SIZE);
if (!draw_osm_file(map_osm_zoom, current_tile_x, current_tile_y, draw_pos_x, draw_pos_y, painter)) {
// already blanked it.
}
}
}
}
} else {
// No map data or excessive zoom; just draw a grid
draw_map_grid(r, painter);
}
// Draw crosshairs in center in manual panning mode
if (manual_panning_) {
painter.fill_rectangle({r.center() - Point(16, 1) + Point(zoom_pixel_offset, zoom_pixel_offset), {32, 2}}, Color::red());
painter.fill_rectangle({r.center() - Point(1, 16) + Point(zoom_pixel_offset, zoom_pixel_offset), {2, 32}}, Color::red());
}
// Draw the other markers
draw_markers(painter);
if (!use_osm) draw_scale(painter);
draw_mypos(painter);
if (has_osm) draw_switcher(painter);
set_clean();
}
// Draw the marker in the center
if (!manual_panning_ && !hide_center_marker_) {
draw_marker(painter, r.center() + Point(zoom_pixel_offset, zoom_pixel_offset), angle_, tag_, Color::red(), Color::white(), Color::black());
}
}
void GeoMap::draw_switcher(Painter& painter) {
painter.fill_rectangle({screen_rect().left(), screen_rect().top(), 3 * 20, 20}, Theme::getInstance()->bg_darker->background);
std::string_view txt = (use_osm) ? "B I N" : "O S M";
painter.draw_string({screen_rect().left() + 5, screen_rect().top() + 2}, *Theme::getInstance()->fg_light, txt);
}
bool GeoMap::on_keyboard(KeyboardEvent key) {
if (key == '+' || key == ' ') return on_encoder(1);
if (key == '-') return on_encoder(-1);
return false;
}
bool GeoMap::on_touch(const TouchEvent event) {
if (has_osm && event.type == TouchEvent::Type::Start && event.point.x() < screen_rect().left() + 3 * 20 && event.point.y() < screen_rect().top() + 20) {
use_osm = !use_osm;
move(lon_, lat_); // to re calculate the center for each map type
if (use_osm) set_osm_max_zoom();
redraw_map = true;
set_dirty();
return false; // false, because with true this hits 2 times
}
if ((event.type == TouchEvent::Type::Start) && (mode_ == PROMPT)) {
Point p;
set_highlighted(true);
if (on_move) {
if (!use_osm) {
p = event.point - screen_rect().center();
on_move(p.x() / 2.0 * lon_ratio, p.y() / 2.0 * lat_ratio, false);
} else {
p = event.point - screen_rect().location();
on_move(tile_pixel_x_to_lon(p.x() + viewport_top_left_px, map_osm_zoom), tile_pixel_y_to_lat(p.y() + viewport_top_left_py, map_osm_zoom), true);
}
return true;
}
}
return false;
}
void GeoMap::move(const float lon, const float lat) {
const auto r = screen_rect();
bool is_changed = (lon_ != lon || lat_ != lat);
lon_ = lon;
lat_ = lat;
if (!use_osm) {
// Calculate x_pos/y_pos in map file corresponding to CENTER pixel of screen rect
// (Note there is a 1:1 correspondence between map file pixels and screen pixels when map_zoom=1)
GeoPoint mapPoint = lat_lon_to_map_pixel(lat_, lon_);
x_pos = mapPoint.x;
y_pos = mapPoint.y;
// Cap position
if (x_pos > (map_width - r.width() / 2))
x_pos = map_width - r.width() / 2;
if (y_pos > (map_height + r.height() / 2))
y_pos = map_height - r.height() / 2;
// Scale calculation
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
pixels_per_km = (r.width() / 2) / km_per_deg_lon;
} else {
if (is_changed) {
set_osm_max_zoom();
redraw_map = true;
}
}
}
bool GeoMap::init() {
auto result = map_file.open(adsb_dir / u"world_map.bin");
map_opened = !result.is_valid();
if (map_opened) {
map_file.read(&map_width, 2);
map_file.read(&map_height, 2);
} else {
map_width = 32768;
map_height = 32768;
}
map_visible = map_opened || has_osm;
map_center_x = map_width >> 1;
map_center_y = map_height >> 1;
lon_ratio = 180.0 / map_center_x;
lat_ratio = -90.0 / map_center_y;
map_bottom = sin(-85.05 * pi / 180); // Map bitmap only goes from about -85 to 85 lat
map_world_lon = map_width / (2 * pi);
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
return map_opened;
}
void GeoMap::set_mode(GeoMapMode mode) {
mode_ = mode;
}
void GeoMap::set_manual_panning(bool v) {
manual_panning_ = v;
}
bool GeoMap::manual_panning() {
return manual_panning_;
}
void GeoMap::draw_scale(Painter& painter) {
const auto r = screen_rect();
uint32_t m = 800000;
uint32_t scale_width = (map_zoom > 0) ? m * map_zoom * pixels_per_km : m * pixels_per_km / (-map_zoom);
ui::Color scale_color = (map_visible) ? Color::black() : Color::white();
std::string km_string;
while (scale_width > (uint32_t)r.width() * (1000 / 2)) {
scale_width /= 2;
m /= 2;
}
scale_width /= 1000;
if (m < 1000) {
km_string = to_string_dec_uint(m) + "m";
} else {
m += 50; // (add rounding factor for div by 100 below)
uint32_t km = m / 1000;
m -= km * 1000;
if (m == 0) {
km_string = to_string_dec_uint(km) + " km";
} else {
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint(m / 100, 1) + "km";
}
}
painter.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 4}, {(uint16_t)scale_width, 2}}, scale_color);
painter.fill_rectangle({{r.right() - 5, r.bottom() - 8}, {2, 6}}, scale_color);
painter.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 8}, {2, 6}}, scale_color);
std::string_view sw = km_string;
ui::Point pos = {(uint16_t)(r.right() - 25 - scale_width - sw.length() * 5 / 2), r.bottom() - 10};
painter.draw_string(pos, *Theme::getInstance()->fg_light, sw);
}
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color, Painter& painter) {
Point arrow_a, arrow_b, arrow_c;
for (size_t thickness = 0; thickness < 3; thickness++) {
arrow_a = fast_polar_to_point((int)angle, size) + origin;
arrow_b = fast_polar_to_point((int)(angle + 180 - 35), size) + origin;
arrow_c = fast_polar_to_point((int)(angle + 180 + 35), size) + origin;
painter.draw_line(arrow_a, arrow_b, color);
painter.draw_line(arrow_b, arrow_c, color);
painter.draw_line(arrow_c, arrow_a, color);
size--;
}
painter.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
}
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
const auto r = screen_rect();
int tagOffset = 10;
if (mode_ == PROMPT) {
// Cross
painter.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
painter.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
tagOffset = 16;
} else if (angle_ < 360) {
// if we have a valid angle draw bearing
draw_bearing(itemPoint, itemAngle, 10, color, painter);
tagOffset = 10;
} else {
// draw a small cross
painter.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
painter.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
tagOffset = 8;
}
// center tag above point
if ((itemPoint.y() - r.top() >= 32) && (itemTag.find_first_not_of(' ') != itemTag.npos)) { // only draw tag if doesn't overlap top & not just spaces
painter.draw_string(itemPoint - Point(((int)itemTag.length() * 8 / 2), 14 + tagOffset),
style().font, fontColor, backColor, itemTag);
}
}
void GeoMap::draw_mypos(Painter& painter) {
if ((my_pos.lat < INVALID_LAT_LON) && (my_pos.lon < INVALID_LAT_LON))
draw_marker_item(painter, my_pos, Color::yellow());
}
void GeoMap::clear_markers() {
markerListLen = 0;
}
MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
const auto r = screen_rect();
MapMarkerStored ret;
// Check if it could be on screen
// (Shows more distant planes when zoomed out)
GeoPoint mapPoint = lat_lon_to_map_pixel(marker.lat, marker.lon);
int x_dist = abs((int)mapPoint.x - (int)x_pos);
int y_dist = abs((int)mapPoint.y - (int)y_pos);
int zoom_out = (map_zoom < 0) ? -map_zoom : 1;
if ((x_dist >= (zoom_out * r.width() / 2)) || (y_dist >= (zoom_out * r.height() / 2))) {
ret = MARKER_NOT_STORED;
} else if (markerListLen < NumMarkerListElements) {
markerList[markerListLen] = marker;
markerListLen++;
redraw_map = true;
ret = MARKER_STORED;
} else {
ret = MARKER_LIST_FULL;
}
return ret;
}
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
bool is_changed = (my_pos.lat != lat) || (my_pos.lon != lon);
my_pos.lat = lat;
my_pos.lon = lon;
my_altitude = altitude;
redraw_map = is_changed;
set_dirty();
}
void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
bool is_changed = (my_pos.angle != angle);
my_pos.angle = angle;
if (refresh && is_changed) {
redraw_map = true;
set_dirty();
}
}
MapType GeoMap::get_map_type() {
return use_osm ? MAP_TYPE_OSM : MAP_TYPE_BIN;
}
void GeoMapView::focus() {
geopos.focus();
if (!geomap.map_file_opened()) {
// nav_.display_modal("No map", "No world_map.bin file in\n/" + adsb_dir.string() + "/ directory", ABORT);
// TODO crate an error display
}
}
void GeoMapView::update_my_position(float lat, float lon, int32_t altitude) {
geomap.update_my_position(lat, lon, altitude);
}
void GeoMapView::update_my_orientation(uint16_t angle, bool refresh) {
geomap.update_my_orientation(angle, refresh);
}
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed) {
if (geomap.manual_panning()) {
geomap.set_dirty();
return;
}
bool is_changed = lat_ != lat || lon_ != lon || altitude_ != altitude || speed_ != speed || angle_ != angle;
lat_ = lat;
lon_ = lon;
altitude_ = altitude;
speed_ = speed;
// Stupid hack to avoid an event loop
geopos.set_report_change(false);
geopos.set_lat(lat_);
geopos.set_lon(lon_);
geopos.set_altitude(altitude_);
geopos.set_speed(speed_);
geopos.set_report_change(true);
geomap.set_angle(angle);
if (is_changed) geomap.move(lon_, lat_);
geomap.set_dirty();
}
void GeoMapView::update_tag(const std::string tag) {
geomap.set_tag(tag);
}
void GeoMapView::setup() {
add_child(&geomap);
geopos.set_altitude(altitude_);
geopos.set_lat(lat_);
geopos.set_lon(lon_);
geopos.on_change = [this](int32_t altitude, float lat, float lon, int32_t speed) {
bool is_changed = (altitude_ != altitude) || (lat_ != lat) || (lon_ != lon) || (speed_ != speed);
altitude_ = altitude;
lat_ = lat;
lon_ = lon;
speed_ = speed;
geopos.hide_altandspeed();
geomap.set_manual_panning(true);
if (is_changed) geomap.move(lon_, lat_);
geomap.set_dirty();
};
geomap.on_move = [this](float move_x, float move_y, bool absolute) {
if (absolute) {
lon_ = move_x;
lat_ = move_y;
} else {
lon_ += move_x;
lat_ += move_y;
}
// Stupid hack to avoid an event loop
geopos.set_report_change(false);
geopos.set_lon(lon_);
geopos.set_lat(lat_);
geopos.set_report_change(true);
geomap.move(lon_, lat_);
geomap.set_dirty();
};
}
GeoMapView::~GeoMapView() {
if (on_close_)
on_close_();
}
// Display mode
GeoMapView::GeoMapView(
const std::string& tag,
int32_t altitude,
GeoPos::alt_unit altitude_unit,
GeoPos::spd_unit speed_unit,
float lat,
float lon,
uint16_t angle,
const std::function<void(void)> on_close)
: altitude_(altitude),
altitude_unit_(altitude_unit),
speed_unit_(speed_unit),
lat_(lat),
lon_(lon),
angle_(angle),
on_close_(on_close) {
mode_ = DISPLAY;
add_child(&geopos);
geomap.init();
setup();
geomap.set_mode(mode_);
geomap.set_tag(tag);
geomap.set_angle(angle);
geomap.move(lon_, lat_);
geomap.set_focusable(true);
geopos.set_read_only(true);
}
// Prompt mode
GeoMapView::GeoMapView(
int32_t altitude,
GeoPos::alt_unit altitude_unit,
GeoPos::spd_unit speed_unit,
float lat,
float lon,
const std::function<void(int32_t, float, float, int32_t)> on_done)
: altitude_(altitude),
altitude_unit_(altitude_unit),
speed_unit_(speed_unit),
lat_(lat),
lon_(lon) {
mode_ = PROMPT;
add_child(&geopos);
geomap.init();
setup();
add_child(&button_ok);
geomap.set_mode(mode_);
geomap.move(lon_, lat_);
geomap.set_focusable(true);
button_ok.on_select = [this, on_done](Button&) {
if (on_done)
on_done(altitude_, lat_, lon_, speed_);
// exit handled on caller side
};
}
void GeoMapView::clear_markers() {
geomap.clear_markers();
}
MapMarkerStored GeoMapView::store_marker(GeoMarker& marker) {
return geomap.store_marker(marker);
}
MapType GeoMapView::get_map_type() {
return geomap.get_map_type();
}
} /* namespace ui */

View file

@ -0,0 +1,378 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* 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 __GEOMAP_H__
#define __GEOMAP_H__
#include "ui.hpp"
#include "ui_widget.hpp"
#include "file.hpp"
#include "bmpfile.hpp"
#include "mathdef.hpp"
#include <string_view>
namespace ui {
#define MAX_MAP_ZOOM_IN 4000
#define MAX_MAP_ZOOM_OUT 10
#define MAP_ZOOM_RESOLUTION_LIMIT 5 // Max zoom-in to show map; rect height & width must divide into this evenly
#define INVALID_LAT_LON 200
#define INVALID_ANGLE 400
#define GEOMAP_BANNER_HEIGHT (3 * 16)
#define GEOMAP_RECT_WIDTH 240
#define GEOMAP_RECT_HEIGHT (320 - 16 - GEOMAP_BANNER_HEIGHT)
#define TILE_SIZE 256
enum GeoMapMode {
DISPLAY,
PROMPT
};
struct GeoPoint {
public:
float x{0};
float y{0};
};
struct GeoMarker {
public:
float lat{0};
float lon{0};
uint16_t angle{0};
std::string tag{""};
GeoMarker& operator=(GeoMarker& rhs) {
lat = rhs.lat;
lon = rhs.lon;
angle = rhs.angle;
tag = rhs.tag;
return *this;
}
};
class GeoPos : public View {
public:
enum alt_unit {
FEET = 0,
METERS
};
enum spd_unit {
NONE = 0,
MPH,
KMPH,
KNOTS,
HIDDEN = 255
};
std::function<void(int32_t, float, float, int32_t)> on_change{};
GeoPos(const Point pos, const alt_unit altitude_unit, const spd_unit speed_unit);
void focus() override;
void set_read_only(bool v);
void set_altitude(int32_t altitude);
void set_speed(int32_t speed);
void set_lat(float lat);
void set_lon(float lon);
int32_t altitude();
int32_t speed();
void hide_altandspeed();
float lat();
float lon();
void set_report_change(bool v);
private:
bool read_only{false};
bool report_change{true};
alt_unit altitude_unit_{};
spd_unit speed_unit_{};
Labels labels_position{
{{1 * 8, 0 * 16}, "Alt:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 1 * 16}, "Lat: \xB0 ' \"", Theme::getInstance()->fg_light->foreground}, // 0xB0 is degree ° symbol in our 8x16 font
{{1 * 8, 2 * 16}, "Lon: \xB0 ' \"", Theme::getInstance()->fg_light->foreground},
};
Labels label_spd_position{
{{15 * 8, 0 * 16}, "Spd:", Theme::getInstance()->fg_light->foreground},
};
NumberField field_altitude{
{6 * 8, 0 * 16},
5,
{-1000, 50000},
250,
' '};
NumberField field_speed{
{19 * 8, 0 * 16},
4,
{0, 5000},
1,
' '};
Text text_alt_unit{
{12 * 8, 0 * 16, 2 * 8, 16},
""};
Text text_speed_unit{
{25 * 8, 0 * 16, 5 * 8, 16},
""};
NumberField field_lat_degrees{
{5 * 8, 1 * 16},
4,
{-90, 90},
1,
' '};
NumberField field_lat_minutes{
{10 * 8, 1 * 16},
2,
{0, 59},
1,
' ',
true};
NumberField field_lat_seconds{
{13 * 8, 1 * 16},
2,
{0, 59},
1,
' ',
true};
Text text_lat_decimal{
{17 * 8, 1 * 16, 13 * 8, 1 * 16},
""};
NumberField field_lon_degrees{
{5 * 8, 2 * 16},
4,
{-180, 180},
1,
' '};
NumberField field_lon_minutes{
{10 * 8, 2 * 16},
2,
{0, 59},
1,
' ',
true};
NumberField field_lon_seconds{
{13 * 8, 2 * 16},
2,
{0, 59},
1,
' ',
true};
Text text_lon_decimal{
{17 * 8, 2 * 16, 13 * 8, 1 * 16},
""};
};
enum MapMarkerStored {
MARKER_NOT_STORED,
MARKER_STORED,
MARKER_LIST_FULL
};
enum MapType {
MAP_TYPE_OSM,
MAP_TYPE_BIN
};
class GeoMap : public Widget {
public:
std::function<void(float, float, bool)> on_move{};
GeoMap(Rect parent_rect);
void paint(Painter& painter) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_keyboard(const KeyboardEvent event) override;
void update_my_position(float lat, float lon, int32_t altitude);
void update_my_orientation(uint16_t angle, bool refresh = false);
bool init();
void set_mode(GeoMapMode mode);
void set_manual_panning(bool v);
bool manual_panning();
void move(const float lon, const float lat);
void set_tag(std::string new_tag) {
tag_ = new_tag;
}
MapType get_map_type();
void set_angle(uint16_t new_angle) {
angle_ = new_angle;
}
bool map_file_opened() { return map_opened; }
void set_hide_center_marker(bool hide) {
hide_center_marker_ = hide;
}
bool hide_center_marker() { return hide_center_marker_; }
static const int NumMarkerListElements = 30;
void clear_markers();
MapMarkerStored store_marker(GeoMarker& marker);
static const Dim banner_height = GEOMAP_BANNER_HEIGHT;
static const Dim geomap_rect_width = GEOMAP_RECT_WIDTH;
static const Dim geomap_rect_height = GEOMAP_RECT_HEIGHT;
private:
void draw_scale(Painter& painter);
ui::Point item_rect_pixel(GeoMarker& item);
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_markers(Painter& painter);
void draw_mypos(Painter& painter);
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color, Painter& painter);
void draw_map_grid(ui::Rect r, Painter& painter);
void draw_switcher(Painter& painter);
void map_read_line_bin(ui::Color* buffer, uint16_t pixels);
// open street map related
uint8_t find_osm_file_tile();
void set_osm_max_zoom();
bool draw_osm_file(int zoom, int tile_x, int tile_y, int relative_x, int relative_y, Painter& painter);
int lon2tile(double lon, int zoom);
int lat2tile(double lat, int zoom);
double lon_to_pixel_x_tile(double lon, int zoom);
double lat_to_pixel_y_tile(double lat, int zoom);
double tile_pixel_x_to_lon(int x, int zoom);
double tile_pixel_y_to_lat(int y, int zoom);
uint8_t map_osm_zoom{3};
double viewport_top_left_px = 0;
double viewport_top_left_py = 0;
bool manual_panning_{false};
bool hide_center_marker_{false};
GeoMapMode mode_{};
File map_file{};
bool map_opened{};
bool map_visible{};
uint16_t map_width{}, map_height{};
int32_t map_center_x{}, map_center_y{};
int16_t map_zoom{1};
float lon_ratio{}, lat_ratio{};
double map_bottom{};
double map_world_lon{};
double map_offset{};
float x_pos{}, y_pos{};
float prev_x_pos{32767.0f}, prev_y_pos{32767.0f};
float lat_{};
float lon_{};
float zoom_pixel_offset{0.0f};
float pixels_per_km{};
uint16_t angle_{};
std::string tag_{};
// the portapack's position data ( for example injected from serial )
GeoMarker my_pos{INVALID_LAT_LON, INVALID_LAT_LON, INVALID_ANGLE, ""}; // lat, lon, angle, tag
int32_t my_altitude{0};
int markerListLen{0};
GeoMarker markerList[NumMarkerListElements];
bool redraw_map{true};
bool use_osm{false};
bool has_osm{false};
};
class GeoMapView : public View {
public:
GeoMapView(
const std::string& tag,
int32_t altitude,
GeoPos::alt_unit altitude_unit,
GeoPos::spd_unit speed_unit,
float lat,
float lon,
uint16_t angle,
const std::function<void(void)> on_close = nullptr);
GeoMapView(
int32_t altitude,
GeoPos::alt_unit altitude_unit,
GeoPos::spd_unit speed_unit,
float lat,
float lon,
const std::function<void(int32_t, float, float, int32_t)> on_done);
~GeoMapView();
GeoMapView(const GeoMapView&) = delete;
GeoMapView(GeoMapView&&) = delete;
GeoMapView& operator=(const GeoMapView&) = delete;
GeoMapView& operator=(GeoMapView&&) = delete;
void focus() override;
void update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed = 0);
void update_my_position(float lat, float lon, int32_t altitude);
void update_my_orientation(uint16_t angle, bool refresh = false);
MapType get_map_type();
std::string title() const override { return "Map view"; };
void clear_markers();
MapMarkerStored store_marker(GeoMarker& marker);
void update_tag(const std::string tag);
private:
void setup();
const std::function<void(int32_t, float, float, int32_t)> on_done{};
GeoMapMode mode_{};
int32_t altitude_{};
int32_t speed_{};
GeoPos::alt_unit altitude_unit_{};
GeoPos::spd_unit speed_unit_{};
float lat_{};
float lon_{};
uint16_t angle_{};
std::function<void(void)> on_close_{nullptr};
GeoPos geopos{
{0, 0},
altitude_unit_,
speed_unit_};
GeoMap geomap{
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
Button button_ok{
{screen_width - 15 * 8, 0, 15 * 8, 1 * 16},
"OK"};
};
} /* namespace ui */
#endif

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_painter.hpp"
#include "ui_widget.hpp"
// #include "portapack.hpp"
#include "standalone_app.hpp"
// using namespace portapack;
namespace ui {
Style Style::invert() const {
return {
.font = font,
.background = foreground,
.foreground = background};
}
int Painter::draw_char(Point p, const Style& style, char c) {
const auto glyph = style.font.glyph(c);
_api->draw_bitmap(p.x(), p.y(), glyph.size().width(), glyph.size().height(), glyph.pixels(), style.foreground.v, style.background.v);
return glyph.advance().x();
}
int Painter::draw_string(Point p, const Style& style, std::string_view text) {
return draw_string(p, style.font, style.foreground, style.background, text);
}
int Painter::draw_string(
Point p,
const Font& font,
Color foreground,
Color background,
std::string_view text) {
bool escape = false;
size_t width = 0;
Color pen = foreground;
for (auto c : text) {
if (escape) {
if (c < std::size(term_colors))
pen = term_colors[(uint8_t)c];
else
pen = foreground;
escape = false;
} else {
if (c == '\x1B') {
escape = true;
} else {
const auto glyph = font.glyph(c);
_api->draw_bitmap(p.x(), p.y(), glyph.size().width(), glyph.size().height(), glyph.pixels(), pen.v, background.v);
const auto advance = glyph.advance();
p += advance;
width += advance.x();
}
}
}
return width;
}
void Painter::draw_bitmap(Point p, const Bitmap& bitmap, Color foreground, Color background) {
// If bright foreground colors on white background, darken the foreground color to improve visibility
if ((background.v == ui::Color::white().v) && (foreground.to_greyscale() > 146))
foreground = foreground.dark();
_api->draw_bitmap(p.x(), p.y(), bitmap.size.width(), bitmap.size.height(), bitmap.data, foreground.v, background.v);
}
void Painter::draw_hline(Point p, int width, Color c) {
_api->fill_rectangle(p.x(), p.y(), width, 1, c.v);
}
void Painter::draw_vline(Point p, int height, Color c) {
_api->fill_rectangle(p.x(), p.y(), 1, height, c.v);
}
void Painter::draw_rectangle(Rect r, Color c) {
draw_hline(r.location(), r.width(), c);
draw_vline({r.left(), r.top() + 1}, r.height() - 2, c);
draw_vline({r.left() + r.width() - 1, r.top() + 1}, r.height() - 2, c);
draw_hline({r.left(), r.top() + r.height() - 1}, r.width(), c);
}
void Painter::fill_rectangle(Rect r, Color c) {
_api->fill_rectangle(r.left(), r.top(), r.width(), r.height(), c.v);
}
void Painter::fill_rectangle_unrolled8(Rect r, Color c) {
_api->fill_rectangle_unrolled8(r.left(), r.top(), r.width(), r.height(), c.v);
}
void Painter::paint_widget_tree(Widget* w) {
if (ui::is_dirty()) {
paint_widget(w);
ui::dirty_clear();
}
}
void Painter::paint_widget(Widget* w) {
if (w->hidden()) {
// Mark widget (and all children) as invisible.
w->visible(false);
} else {
// Mark this widget as visible and recurse.
w->visible(true);
if (w->dirty()) {
w->paint(*this);
// Force-paint all children.
for (const auto child : w->children()) {
child->set_dirty();
paint_widget(child);
}
w->set_clean();
} else {
// Selectively paint all children.
for (const auto child : w->children()) {
paint_widget(child);
}
}
}
}
void Painter::draw_pixel(const ui::Point p, const ui::Color color) {
_api->draw_pixel(p, color);
}
void Painter::draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count) {
_api->draw_pixels(r, colors, count);
}
void Painter::draw_line(const ui::Point start, const ui::Point end, const ui::Color color) {
int x0 = start.x();
int y0 = start.y();
int x1 = end.x();
int y1 = end.y();
int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2, e2;
for (;;) {
draw_pixel({static_cast<ui::Coord>(x0), static_cast<ui::Coord>(y0)}, color);
if (x0 == x1 && y0 == y1) break;
e2 = err;
if (e2 > -dx) {
err -= dy;
x0 += sx;
}
if (e2 < dy) {
err += dx;
y0 += sy;
}
}
}
} /* namespace ui */

View file

@ -0,0 +1,113 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_PAINTER_H__
#define __UI_PAINTER_H__
#include "ui.hpp"
#include "ui_text.hpp"
#include <string_view>
#include <array>
#include <vector>
namespace ui {
struct Style {
const Font& font;
const Color background;
const Color foreground;
Style invert() const;
};
/* Sometimes mutation is just the more readable thing... */
struct MutableStyle {
const Font* font;
Color background;
Color foreground;
MutableStyle(const Style& s)
: font{&s.font},
background{s.background},
foreground{s.foreground} {}
void invert() {
std::swap(background, foreground);
}
operator Style() const {
return {
.font = *font,
.background = background,
.foreground = foreground};
}
};
class Widget;
class Painter {
public:
Painter() {};
Painter(const Painter&) = delete;
Painter(Painter&&) = delete;
int draw_char(Point p, const Style& style, char c);
int draw_string(Point p, const Style& style, std::string_view text);
int draw_string(Point p, const Font& font, Color foreground, Color background, std::string_view text);
void draw_bitmap(Point p, const Bitmap& bitmap, Color background, Color foreground);
void draw_rectangle(Rect r, Color c);
void fill_rectangle(Rect r, Color c);
void fill_rectangle_unrolled8(Rect r, Color c);
void paint_widget_tree(Widget* w);
void draw_hline(Point p, int width, Color c);
void draw_vline(Point p, int height, Color c);
template <size_t N>
void draw_pixels(
const ui::Rect r,
const std::array<ui::Color, N>& colors) {
draw_pixels(r, colors.data(), colors.size());
}
void draw_pixels(
const ui::Rect r,
const std::vector<ui::Color>& colors) {
draw_pixels(r, colors.data(), colors.size());
}
void draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count);
void draw_line(const ui::Point start, const ui::Point end, const ui::Color color);
void draw_pixel(const ui::Point p, const ui::Color color);
private:
void paint_widget(Widget* w);
};
} /* namespace ui */
#endif /*__UI_PAINTER_H__*/

View file

@ -0,0 +1,74 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_text.hpp"
namespace ui {
Glyph Font::glyph(const char c) const {
size_t index;
if (c < c_start) {
// Non-display C0 Control characters - map to blank (index 0)
return {w, h, data};
}
// Handle gap in glyphs table for C1 Control characters 0x80-0x9F
if (c < C1_CONTROL_CHARS_START) {
// ASCII chars <0x80:
index = c - c_start;
} else if (c >= C1_CONTROL_CHARS_START + C1_CONTROL_CHARS_COUNT) {
// Latin 1 chars 0xA0-0xFF
index = c - c_start - C1_CONTROL_CHARS_COUNT;
} else {
// C1 Control characters - map to blank
return {w, h, data};
}
if (index >= c_count) { // Latin Extended characters > 0xFF - not supported
return {w, h, data};
} else {
return {w, h, &data[index * data_stride]};
}
}
Dim Font::line_height() const {
return h;
}
Dim Font::char_width() const {
return w;
}
Size Font::size_of(const std::string s) const {
Size size;
for (const auto c : s) {
const auto glyph_data = glyph(c);
size = {
size.width() + glyph_data.w(),
std::max(size.height(), glyph_data.h())};
}
return size;
}
} /* namespace ui */

View file

@ -0,0 +1,110 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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_H__
#define __UI_TEXT_H__
#include <cstdint>
#include <cstddef>
#include <string>
#include "ui.hpp"
#include "standalone_app.hpp"
extern const standalone_application_api_t* _api;
// C1 Control Characters are an unprintable range of glyphs missing from the font files
#define C1_CONTROL_CHARS_START 0x80
#define C1_CONTROL_CHARS_COUNT 32
namespace ui {
class Glyph {
public:
constexpr Glyph(
Dim w,
Dim h,
const uint8_t* const pixels)
: w_{static_cast<uint8_t>(w)},
h_{static_cast<uint8_t>(h)},
pixels_{pixels} {
}
int w() const {
return w_;
}
int h() const {
return h_;
}
Size size() const {
return {w_, h_};
}
Point advance() const {
return {w_, 0};
}
const uint8_t* pixels() const {
return pixels_;
}
private:
const uint8_t w_;
const uint8_t h_;
const uint8_t* const pixels_;
};
class Font {
public:
constexpr Font(
Dim w,
Dim h,
const uint8_t* data,
char c_start,
size_t c_count)
: w{w},
h{h},
data{data},
c_start{c_start},
c_count{c_count},
data_stride{(w * h + 7U) >> 3} {
}
Glyph glyph(const char c) const;
Dim line_height() const;
Dim char_width() const;
Size size_of(const std::string s) const;
private:
const Dim w;
const Dim h;
const uint8_t* const data;
const char c_start;
const size_t c_count;
const size_t data_stride;
};
} /* namespace ui */
#endif /*__UI_TEXT_H__*/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,244 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 "utility.hpp"
#include <cstdint>
#if 0
uint32_t gcd(const uint32_t u, const uint32_t v) {
/* From http://en.wikipedia.org/wiki/Binary_GCD_algorithm */
if( u == v ) {
return u;
}
if( u == 0 ) {
return v;
}
if( v == 0 ) {
return u;
}
if( ~u & 1 ) {
if( v & 1 ) {
return gcd(u >> 1, v);
} else {
return gcd(u >> 1, v >> 1) << 1;
}
}
if( ~v & 1 ) {
return gcd(u, v >> 1);
}
if( u > v ) {
return gcd((u - v) >> 1, v);
}
return gcd((v - u) >> 1, u);
}
#endif
float fast_log2(const float val) {
// Thank you Stack Overflow!
// http://stackoverflow.com/questions/9411823/fast-log2float-x-implementation-c
union {
float val;
int32_t x;
} u = {val};
float log_2 = (((u.x >> 23) & 255) - 128);
u.x &= ~(255 << 23);
u.x += (127 << 23);
log_2 += ((-0.34484843f) * u.val + 2.02466578f) * u.val - 0.67487759f;
return log_2;
}
float fast_pow2(const float val) {
union {
float f;
uint32_t n;
} u;
u.n = val * 8388608 + (0x3f800000 - 60801 * 8);
return u.f;
}
float mag2_to_dbv_norm(const float mag2) {
constexpr float mag2_log2_max = 0.0f; // std::log2(1.0f);
constexpr float log_mag2_mag_factor = 0.5f;
constexpr float log2_log10_factor = 0.3010299956639812f; // std::log10(2.0f);
constexpr float log10_dbv_factor = 20.0f;
constexpr float mag2_to_db_factor = log_mag2_mag_factor * log2_log10_factor * log10_dbv_factor;
return (fast_log2(mag2) - mag2_log2_max) * mag2_to_db_factor;
}
// Integer in and out approximation
// >40 times faster float sqrt(x*x+y*y) on Cortex M0
// derived from https://dspguru.com/dsp/tricks/magnitude-estimator/
int fast_int_magnitude(int y, int x) {
if (y < 0) {
y = -y;
}
if (x < 0) {
x = -x;
}
if (x > y) {
return ((x * 61) + (y * 26) + 32) / 64;
} else {
return ((y * 61) + (x * 26) + 32) / 64;
}
}
// Integer x and y returning an integer bearing in degrees
// Accurate to 0.5 degrees, so output scaled to whole degrees
// >60 times faster than float atan2 on Cortex M0
int int_atan2(int y, int x) {
// Number of bits to shift up before doing the maths. A larger shift
// may beable to gain accuracy, but it would cause the correction
// entries to be larger than 1 byte
static const int bits = 10;
static const int pi4 = (1 << bits);
static const int pi34 = (3 << bits);
// Special case
if (x == 0 && y == 0) {
return 0;
}
// Form an approximate angle
const int yabs = y >= 0 ? y : -y;
int angle;
if (x >= 0) {
angle = pi4 - pi4 * (x - yabs) / (x + yabs);
} else {
angle = pi34 - pi4 * (x + yabs) / (yabs - x);
}
// Correct the result using a lookup table
static const int8_t correct[32] = {0, -23, -42, -59, -72, -83, -89, -92, -92, -88, -81, -71, -58, -43, -27, -9, 9, 27, 43, 58, 71, 81, 88, 92, 92, 89, 83, 72, 59, 42, 23, 0};
static const int rnd = (1 << (bits - 1)) / 45; // Minor correction to round to correction values better (add 0.5)
const int idx = ((angle + rnd) >> (bits - 4)) & 0x1F;
angle += correct[idx];
// Scale for output in degrees
static const int half = (1 << (bits - 1));
angle = ((angle * 45) + half) >> bits; // Add on half before rounding
if (y < 0) {
angle = -angle;
}
return angle;
}
// 16 bit value represents a full cycle in but can handle multiples of this.
// Output in range +/- 16 bit value representing +/- 1.0
// 4th order cosine approximation has very small error
// >200 times faster tan float sin on Cortex M0
// see https://www.coranac.com/2009/07/sines/
int32_t int16_sin_s4(int32_t x) {
static const int qN = 14, qA = 16, qR = 12, B = 19900, C = 3516;
const int32_t c = x << (30 - qN); // Semi-circle info into carry.
x -= 1 << qN; // sine -> cosine calc
x = x << (31 - qN); // Mask with PI
x = x >> (31 - qN); // Note: SIGNED shift! (to qN)
x = x * x >> (2 * qN - 14); // x=x^2 To Q14
int32_t y = B - (x * C >> 14); // B - x^2*C
y = (1 << qA) - (x * y >> qR); // A - x^2*(B-x^2*C)
return c >= 0 ? y : -y;
}
/* GCD implementation derived from recursive implementation at
* http://en.wikipedia.org/wiki/Binary_GCD_algorithm
*/
static constexpr uint32_t gcd_top(const uint32_t u, const uint32_t v);
static constexpr uint32_t gcd_larger(const uint32_t u, const uint32_t v) {
return (u > v) ? gcd_top((u - v) >> 1, v) : gcd_top((v - u) >> 1, u);
}
static constexpr uint32_t gcd_u_odd_v_even(const uint32_t u, const uint32_t v) {
return (~v & 1) ? gcd_top(u, v >> 1) : gcd_larger(u, v);
}
static constexpr uint32_t gcd_v_odd(const uint32_t u, const uint32_t v) {
return (v & 1)
? gcd_top(u >> 1, v)
: (gcd_top(u >> 1, v >> 1) << 1);
}
static constexpr uint32_t gcd_u_even(const uint32_t u, const uint32_t v) {
return (~u & 1)
? gcd_v_odd(u, v)
: gcd_u_odd_v_even(u, v);
}
static constexpr uint32_t gcd_v_zero(const uint32_t u, const uint32_t v) {
return (v == 0) ? u : gcd_u_even(u, v);
}
static constexpr uint32_t gcd_u_zero(const uint32_t u, const uint32_t v) {
return (u == 0) ? v : gcd_v_zero(u, v);
}
static constexpr uint32_t gcd_uv_equal(const uint32_t u, const uint32_t v) {
return (u == v) ? u : gcd_u_zero(u, v);
}
static constexpr uint32_t gcd_top(const uint32_t u, const uint32_t v) {
return gcd_uv_equal(u, v);
}
uint32_t gcd(const uint32_t u, const uint32_t v) {
return gcd_top(u, v);
}
std::string join(char c, std::initializer_list<std::string_view> strings) {
std::string result;
size_t total_size = strings.size();
for (auto s : strings)
total_size += s.size();
result.reserve(total_size);
bool first = true;
for (auto s : strings) {
if (!first)
result += c;
else
first = false;
result += s;
}
return result;
}
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length) {
uint32_t checksum = 0;
for (uint32_t i = 0; i < length; i += 4)
checksum += *(uint32_t*)(buffer_address + i);
return checksum;
}

View file

@ -0,0 +1,222 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __UTILITY_H__
#define __UTILITY_H__
#include <algorithm>
#include <array>
#include <complex>
#include <cstdint>
#include <cstddef>
#include <initializer_list>
#include <memory>
#include <string_view>
#include <type_traits>
#define LOCATE_IN_RAM __attribute__((section(".ramtext")))
inline uint16_t fb_to_uint16(const std::string& fb) {
return (fb[1] << 8) + fb[0];
}
inline uint32_t fb_to_uint32(const std::string& fb) {
return (fb[3] << 24) + (fb[2] << 16) + (fb[1] << 8) + fb[0];
}
constexpr size_t operator"" _KiB(unsigned long long v) {
return v * 1024;
}
constexpr size_t operator"" _MiB(unsigned long long v) {
return v * 1024 * 1024;
}
template <typename E>
constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept {
/* Thanks, Scott Meyers! */
return static_cast<typename std::underlying_type<E>::type>(enumerator);
}
/* Increments an enum value. Enumerator values are assumed to be serial. */
template <typename E>
void incr(E& e) {
e = static_cast<E>(toUType(e) + 1);
}
inline uint32_t flp2(uint32_t v) {
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
return v - (v >> 1);
}
uint32_t gcd(const uint32_t u, const uint32_t v);
template <class T>
inline constexpr T pow(const T base, unsigned const exponent) {
return (exponent == 0) ? 1 : (base * pow(base, exponent - 1));
}
constexpr bool power_of_two(const size_t n) {
return (n & (n - 1)) == 0;
}
constexpr size_t log_2(const size_t n, const size_t p = 0) {
return (n <= 1) ? p : log_2(n / 2, p + 1);
}
float fast_log2(const float val);
float fast_pow2(const float val);
float mag2_to_dbv_norm(const float mag2);
inline float magnitude_squared(const std::complex<float> c) {
const auto r = c.real();
const auto r2 = r * r;
const auto i = c.imag();
const auto i2 = i * i;
return r2 + i2;
}
/* Compute the duration in ms of a buffer. */
inline uint32_t ms_duration(
uint64_t buffer_size,
uint32_t sample_rate,
uint32_t bytes_per_sample) {
/* bytes * sample * second * ms
* ------ ------- ------
* bytes samples seconds
*/
if (sample_rate == 0 || bytes_per_sample == 0)
return 0;
return buffer_size * 1000 / (bytes_per_sample * sample_rate);
}
int fast_int_magnitude(int y, int x);
int int_atan2(int y, int x);
int32_t int16_sin_s4(int32_t x);
template <typename TEnum>
struct is_flags_type {
static constexpr bool value = false;
};
template <typename TEnum>
constexpr bool is_flags_type_v = is_flags_type<TEnum>::value;
#define ENABLE_FLAGS_OPERATORS(type) \
template <> \
struct is_flags_type<type> { static constexpr bool value = true; };
template <typename TEnum>
constexpr std::enable_if_t<is_flags_type_v<TEnum>, TEnum> operator|(TEnum a, TEnum b) {
using under_t = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(static_cast<under_t>(a) | static_cast<under_t>(b));
}
template <typename TEnum>
constexpr std::enable_if_t<is_flags_type_v<TEnum>, bool> flags_enabled(TEnum value, TEnum flags) {
auto i_value = static_cast<std::underlying_type_t<TEnum>>(value);
auto i_flags = static_cast<std::underlying_type_t<TEnum>>(flags);
return (i_value & i_flags) == i_flags;
}
// TODO: Constrain to integrals?
/* Converts an integer into a byte array. */
template <typename T, size_t N = sizeof(T)>
constexpr std::array<uint8_t, N> to_byte_array(T value) {
std::array<uint8_t, N> bytes{};
for (size_t i = 0; i < N; ++i)
bytes[i] = (value >> ((N - i - 1) * 8)) & 0xFF;
return bytes;
}
/* Returns value constrained to min and max. */
template <class T>
constexpr const T& clip(const T& value, const T& minimum, const T& maximum) {
return std::max(std::min(value, maximum), minimum);
}
/* Saves state on construction and reverts it when destroyed. */
template <typename T>
struct Stash {
Stash(T& target)
: target_{target}, prev_{target} {
}
~Stash() {
target_ = std::move(prev_);
}
private:
T& target_;
T prev_;
};
// TODO: need to decide if this is inclusive or exclusive.
// The implementations are different and cause the subtle
// bugs mentioned below.
template <class T>
struct range_t {
const T minimum;
const T maximum;
constexpr const T& clip(const T& value) const {
return ::clip(value, minimum, maximum);
}
constexpr void reset_if_outside(T& value, const T& reset_value) const {
if ((value < minimum) ||
(value > maximum)) {
value = reset_value;
}
}
constexpr bool below_range(const T& value) const {
return value < minimum;
}
/* Exclusive of maximum. */
constexpr bool contains(const T& value) const {
return (value >= minimum) && (value < maximum);
}
/* Inclusive of maximum. */
constexpr bool contains_inc(const T& value) const {
return (value >= minimum) && (value <= maximum);
}
/* Exclusive of maximum. */
constexpr bool out_of_range(const T& value) const {
return !contains(value);
}
};
std::string join(char c, std::initializer_list<std::string_view> strings);
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length);
#endif /*__UTILITY_H__*/

View file

@ -0,0 +1,312 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT file system module R0.12c /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2017, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/
#ifndef _FATFS
#define _FATFS 68300 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h" /* Basic integer types */
#include "ffconf.h" /* FatFs configuration options */
#if _FATFS != _FFCONF
#error Wrong configuration file (ffconf.h).
#endif
/* Definitions of volume management */
#if _MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
#endif
/* Type of path name strings on FatFs API */
#if _LFN_UNICODE /* Unicode (UTF-16) string */
#if _USE_LFN == 0
#error _LFN_UNICODE must be 0 at non-LFN cfg.
#endif
#ifndef _INC_TCHAR
typedef WCHAR TCHAR;
#define _T(x) L##x
#define _TEXT(x) L##x
#endif
#else /* ANSI/OEM string */
#ifndef _INC_TCHAR
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size variables */
#if _FS_EXFAT
#if _USE_LFN == 0
#error LFN must be enabled when enable exFAT
#endif
typedef QWORD FSIZE_t;
#else
typedef DWORD FSIZE_t;
#endif
/* File system object structure (FATFS) */
typedef struct {
BYTE fs_type; /* File system type (0:N/A) */
BYTE drv; /* Physical drive number */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if _MAX_SS != _MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if _USE_LFN != 0
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if _FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if _FS_RPATH != 0
DWORD cdir; /* Current directory start cluster (0:root) */
#if _FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (_FDID) */
typedef struct {
FATFS* fs; /* Pointer to the owner file system object */
WORD id; /* Owner file system mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:flagmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if _FS_EXFAT
DWORD n_cont; /* Size of first fragment, clusters - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when sclust != 0 and non-directory object) */
#endif
#if _FS_LOCK != 0
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} _FDID;
/* File object structure (FIL) */
typedef struct {
_FDID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
#if !_FS_READONLY
DWORD dir_sect; /* Sector number containing the directory entry */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */
#endif
#if _USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
_FDID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if _USE_LFN != 0
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if _USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if _USE_LFN != 0
TCHAR altname[13]; /* Altenative file name */
TCHAR fname[_MAX_LFN + 1]; /* Primary file name */
#else
TCHAR fname[13]; /* File name */
#endif
} FILINFO;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
/*
//MOVED TO API
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode); / Open or create a file *
FRESULT f_close(FIL* fp); / Close an open file object *
FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br); / Read data from the file *
FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw); / Write data to the file *
FRESULT f_lseek(FIL* fp, FSIZE_t ofs); / Move file pointer of the file object *
FRESULT f_truncate(FIL* fp); / Truncate the file *
FRESULT f_sync(FIL* fp); / Flush cached data of the writing file *
FRESULT f_opendir(DIR* dp, const TCHAR* path); / Open a directory *
FRESULT f_closedir(DIR* dp); / Close an open directory *
FRESULT f_readdir(DIR* dp, FILINFO* fno); / Read a directory item *
FRESULT f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); / Find first file *
FRESULT f_findnext(DIR* dp, FILINFO* fno); / Find next file *
FRESULT f_mkdir(const TCHAR* path); / Create a sub diectory *
FRESULT f_unlink(const TCHAR* path); / Delete an existing fileor directory *
FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new); / Rename/Move a file or directory *
FRESULT f_stat(const TCHAR* path, FILINFO* fno); / Get file status *
FRESULT f_chmod(const TCHAR* path, BYTE attr, BYTE mask); / Change attribute of a file/dir *
FRESULT f_utime(const TCHAR* path, const FILINFO* fno); / Change timestamp of a file/dir *
FRESULT f_chdir(const TCHAR* path); / Change current directory *
FRESULT f_chdrive(const TCHAR* path); / Change current drive *
FRESULT f_getcwd(TCHAR* buff, UINT len); / Get current directory *
FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs); / Get number of free clusters on the drive *
FRESULT f_getlabel(const TCHAR* path, TCHAR* label, DWORD* vsn); / Get volume label *
FRESULT f_setlabel(const TCHAR* label); / Set volume label *
FRESULT f_forward(FIL* fp, UINT (*func)(const BYTE*, UINT), UINT btf, UINT* bf); / Forward data to the stream *
FRESULT f_expand(FIL* fp, FSIZE_t szf, BYTE opt); / Allocate a contiguous block to the file *
FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt); / Mount/Unmount a logical drive *
FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); / Create a FAT volume *
FRESULT f_fdisk(BYTE pdrv, const DWORD* szt, void* work); / Divide a physical drive into some partitions *
int f_putc(TCHAR c, FIL* fp); / Put a character to the file *
int f_puts(const TCHAR* str, FIL* cp); / Put a string to the file *
int f_printf(FIL* fp, const TCHAR* str, ...); / Put a formatted string to the file *
TCHAR* f_gets(TCHAR* buff, int len, FIL* fp); / Get a string from the file *
*/
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#include "fileext.hpp"
#endif /* _FATFS */

View file

@ -0,0 +1,242 @@
/* CHIBIOS FIX */
/*---------------------------------------------------------------------------/
/ FatFs - FAT file system module configuration file
/---------------------------------------------------------------------------*/
#define _FFCONF 68300 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define _FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define _FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: All basic functions are enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define _USE_STRFUNC 1
/* This option switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define _USE_FIND 1
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define _USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define _USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define _USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define _USE_CHMOD 1
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
#define _USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define _USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define _CODE_PAGE 437
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
/
/ 1 - ASCII (No support of extended character. Non-LFN cfg. only)
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
*/
#define _USE_LFN 3
#define _MAX_LFN 255
/* The _USE_LFN switches the support of long file name (LFN).
/
/ 0: Disable support of LFN. _MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added
/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and
/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255.
/ It should be set 255 to support full featured LFN operations.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree(), must be added to the project. */
#define _LFN_UNICODE 1
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
/ This option also affects behavior of string I/O functions. */
#define _STRF_ENCODE 3
/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
/
/ 0: ANSI/OEM
/ 1: UTF-16LE
/ 2: UTF-16BE
/ 3: UTF-8
/
/ This option has no effect when _LFN_UNICODE == 0. */
#define _FS_RPATH 0
/* This option configures support of relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define _VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define _STR_VOLUME_ID 0
#define _VOLUME_STRS "RAM", "NAND", "CF", "SD", "SD2", "USB", "USB2", "USB3"
/* _STR_VOLUME_ID switches string support of volume ID.
/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
/ number in the path name. _VOLUME_STRS defines the drive ID strings for each
/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for
/ the drive ID strings are: A-Z and 0-9. */
#define _MULTI_PARTITION 0
/* This option switches support of multi-partition on a physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When multi-partition is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define _MIN_SS 512
#define _MAX_SS 512
/* These options configure the range of sector size to be supported. (512, 1024,
/ 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
/ to variable sector size and GET_SECTOR_SIZE command needs to be implemented to
/ the disk_ioctl() function. */
#define _USE_TRIM 0
/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define _FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define _FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked _MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the file system object (FATFS) is used for the file data transfer. */
#define _FS_EXFAT 1
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define _FS_NORTC 0
#define _NORTC_MON 1
#define _NORTC_MDAY 1
#define _NORTC_YEAR 2016
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time.
/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to get current time form real-time clock. _NORTC_MON,
/ _NORTC_MDAY and _NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
#define _FS_LOCK 0
/* The option _FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define _FS_REENTRANT 1
#define _FS_TIMEOUT 1000
#define _SYNC_t void*
/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The _FS_TIMEOUT defines timeout period in unit of time tick.
/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/* #include <windows.h> // O/S definitions */
/*--- End of configuration options ---*/

View file

@ -0,0 +1,42 @@
extern "C" FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode);
extern "C" FRESULT f_close(FIL* fp);
extern "C" FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br);
extern "C" FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw);
extern "C" FRESULT f_lseek(FIL* fp, FSIZE_t ofs);
extern "C" FRESULT f_truncate(FIL* fp);
extern "C" FRESULT f_sync(FIL* fp);
extern "C" FRESULT f_opendir(DIR* dp, const TCHAR* path);
extern "C" FRESULT f_closedir(DIR* dp);
extern "C" FRESULT f_readdir(DIR* dp, FILINFO* fno);
extern "C" FRESULT f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);
extern "C" FRESULT f_findnext(DIR* dp, FILINFO* fno);
extern "C" FRESULT f_mkdir(const TCHAR* path);
extern "C" FRESULT f_unlink(const TCHAR* path);
extern "C" FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new);
extern "C" FRESULT f_stat(const TCHAR* path, FILINFO* fno);
extern "C" FRESULT f_chmod(const TCHAR* path, BYTE attr, BYTE mask);
extern "C" FRESULT f_utime(const TCHAR* path, const FILINFO* fno);
extern "C" FRESULT f_chdir(const TCHAR* path);
extern "C" FRESULT f_chdrive(const TCHAR* path);
extern "C" FRESULT f_getcwd(TCHAR* buff, UINT len);
extern "C" FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs);
extern "C" FRESULT f_getlabel(const TCHAR* path, TCHAR* label, DWORD* vsn);
extern "C" FRESULT f_setlabel(const TCHAR* label);
extern "C" FRESULT f_forward(FIL* fp, UINT (*func)(const BYTE*, UINT), UINT btf, UINT* bf);
extern "C" FRESULT f_expand(FIL* fp, FSIZE_t szf, BYTE opt);
extern "C" FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt);
extern "C" FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len);
extern "C" FRESULT f_fdisk(BYTE pdrv, const DWORD* szt, void* work);
extern "C" int f_putc(TCHAR c, FIL* fp);
extern "C" int f_puts(const TCHAR* str, FIL* cp);
extern "C" int f_printf(FIL* fp, const TCHAR* str, ...);
extern "C" TCHAR* f_gets(TCHAR* buff, int len, FIL* fp);

View file

@ -0,0 +1,38 @@
/*-------------------------------------------*/
/* Integer type definitions for FatFs module */
/*-------------------------------------------*/
#ifndef _FF_INTEGER
#define _FF_INTEGER
#ifdef _WIN32 /* FatFs development platform */
#include <windows.h>
#include <tchar.h>
typedef unsigned __int64 QWORD;
#else /* Embedded platform */
/* These types MUST be 16-bit or 32-bit */
typedef int INT;
typedef unsigned int UINT;
/* This type MUST be 8-bit */
typedef unsigned char BYTE;
/* These types MUST be 16-bit */
typedef short SHORT;
typedef unsigned short WORD;
typedef unsigned short WCHAR;
/* These types MUST be 32-bit */
typedef long LONG;
typedef unsigned long DWORD;
/* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */
typedef unsigned long long QWORD;
#endif
#endif