mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-09-26 19:40:55 -04:00
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:
parent
776c9bc7c9
commit
b15bb59678
61 changed files with 14474 additions and 12 deletions
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -51,6 +51,8 @@ class StandaloneView : public View {
|
|||
|
||||
void frame_sync();
|
||||
|
||||
void exit();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
234
firmware/standalone/digitalrain/CMakeLists.txt
Normal file
234
firmware/standalone/digitalrain/CMakeLists.txt
Normal 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
|
||||
)
|
153
firmware/standalone/digitalrain/digitalrain.cpp
Normal file
153
firmware/standalone/digitalrain/digitalrain.cpp
Normal 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;
|
||||
}
|
221
firmware/standalone/digitalrain/digitalrain.hpp
Normal file
221
firmware/standalone/digitalrain/digitalrain.hpp
Normal 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;
|
||||
};
|
118
firmware/standalone/digitalrain/external.ld
Normal file
118
firmware/standalone/digitalrain/external.ld
Normal 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 = .;
|
||||
|
312
firmware/standalone/digitalrain/ff.h
Normal file
312
firmware/standalone/digitalrain/ff.h
Normal 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 */
|
242
firmware/standalone/digitalrain/ffconf.h
Normal file
242
firmware/standalone/digitalrain/ffconf.h
Normal 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 ---*/
|
42
firmware/standalone/digitalrain/fileext.hpp
Normal file
42
firmware/standalone/digitalrain/fileext.hpp
Normal 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);
|
38
firmware/standalone/digitalrain/integer.h
Normal file
38
firmware/standalone/digitalrain/integer.h
Normal 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
|
178
firmware/standalone/digitalrain/main.cpp
Normal file
178
firmware/standalone/digitalrain/main.cpp
Normal 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);
|
||||
}
|
53
firmware/standalone/digitalrain/ui/bmp.hpp
Normal file
53
firmware/standalone/digitalrain/ui/bmp.hpp
Normal 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)
|
306
firmware/standalone/digitalrain/ui/bmpfile.cpp
Normal file
306
firmware/standalone/digitalrain/ui/bmpfile.cpp
Normal 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;
|
||||
}
|
71
firmware/standalone/digitalrain/ui/bmpfile.hpp
Normal file
71
firmware/standalone/digitalrain/ui/bmpfile.hpp
Normal 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
|
120
firmware/standalone/digitalrain/ui/circular_buffer.hpp
Normal file
120
firmware/standalone/digitalrain/ui/circular_buffer.hpp
Normal 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__*/
|
114
firmware/standalone/digitalrain/ui/complex.hpp
Normal file
114
firmware/standalone/digitalrain/ui/complex.hpp
Normal 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__*/
|
641
firmware/standalone/digitalrain/ui/file.cpp
Normal file
641
firmware/standalone/digitalrain/ui/file.cpp
Normal 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 */
|
368
firmware/standalone/digitalrain/ui/file.hpp
Normal file
368
firmware/standalone/digitalrain/ui/file.hpp
Normal 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__*/
|
55
firmware/standalone/digitalrain/ui/file_path.cpp
Normal file
55
firmware/standalone/digitalrain/ui/file_path.cpp
Normal 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";
|
59
firmware/standalone/digitalrain/ui/file_path.hpp
Normal file
59
firmware/standalone/digitalrain/ui/file_path.hpp
Normal 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__ */
|
48
firmware/standalone/digitalrain/ui/file_reader.cpp
Normal file
48
firmware/standalone/digitalrain/ui/file_reader.cpp
Normal 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;
|
||||
}
|
151
firmware/standalone/digitalrain/ui/file_reader.hpp
Normal file
151
firmware/standalone/digitalrain/ui/file_reader.hpp
Normal 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
|
543
firmware/standalone/digitalrain/ui/file_wrapper.hpp
Normal file
543
firmware/standalone/digitalrain/ui/file_wrapper.hpp
Normal 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__
|
4
firmware/standalone/digitalrain/ui/mathdef.hpp
Normal file
4
firmware/standalone/digitalrain/ui/mathdef.hpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#define PI 3.1415926535897932384626433832795
|
||||
#define M_PI PI
|
68
firmware/standalone/digitalrain/ui/optional.hpp
Normal file
68
firmware/standalone/digitalrain/ui/optional.hpp
Normal 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__*/
|
84
firmware/standalone/digitalrain/ui/result.hpp
Normal file
84
firmware/standalone/digitalrain/ui/result.hpp
Normal 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();
|
||||
}
|
||||
};
|
147
firmware/standalone/digitalrain/ui/sine_table.hpp
Normal file
147
firmware/standalone/digitalrain/ui/sine_table.hpp
Normal 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__*/
|
|
@ -0,0 +1 @@
|
|||
#include "standalone_app.hpp"
|
569
firmware/standalone/digitalrain/ui/string_format.cpp
Normal file
569
firmware/standalone/digitalrain/ui/string_format.cpp
Normal 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 ×tamp)
|
||||
{
|
||||
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
|
||||
}
|
106
firmware/standalone/digitalrain/ui/string_format.hpp
Normal file
106
firmware/standalone/digitalrain/ui/string_format.hpp
Normal 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 ×tamp);
|
||||
|
||||
// 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__*/
|
870
firmware/standalone/digitalrain/ui/theme.cpp
Normal file
870
firmware/standalone/digitalrain/ui/theme.cpp
Normal 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
|
122
firmware/standalone/digitalrain/ui/theme.hpp
Normal file
122
firmware/standalone/digitalrain/ui/theme.hpp
Normal 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__*/
|
132
firmware/standalone/digitalrain/ui/ui.cpp
Normal file
132
firmware/standalone/digitalrain/ui/ui.cpp
Normal 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 */
|
410
firmware/standalone/digitalrain/ui/ui.hpp
Normal file
410
firmware/standalone/digitalrain/ui/ui.hpp
Normal 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__*/
|
211
firmware/standalone/digitalrain/ui/ui_focus.cpp
Normal file
211
firmware/standalone/digitalrain/ui/ui_focus.cpp
Normal 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 */
|
45
firmware/standalone/digitalrain/ui/ui_focus.hpp
Normal file
45
firmware/standalone/digitalrain/ui/ui_focus.hpp
Normal 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__*/
|
45
firmware/standalone/digitalrain/ui/ui_font_fixed_5x8.cpp
Normal file
45
firmware/standalone/digitalrain/ui/ui_font_fixed_5x8.cpp
Normal 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 */
|
36
firmware/standalone/digitalrain/ui/ui_font_fixed_5x8.hpp
Normal file
36
firmware/standalone/digitalrain/ui/ui_font_fixed_5x8.hpp
Normal 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__*/
|
45
firmware/standalone/digitalrain/ui/ui_font_fixed_8x16.cpp
Normal file
45
firmware/standalone/digitalrain/ui/ui_font_fixed_8x16.cpp
Normal 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 */
|
36
firmware/standalone/digitalrain/ui/ui_font_fixed_8x16.hpp
Normal file
36
firmware/standalone/digitalrain/ui/ui_font_fixed_8x16.hpp
Normal 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__*/
|
985
firmware/standalone/digitalrain/ui/ui_geomap.cpp
Normal file
985
firmware/standalone/digitalrain/ui/ui_geomap.cpp
Normal 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 */
|
378
firmware/standalone/digitalrain/ui/ui_geomap.hpp
Normal file
378
firmware/standalone/digitalrain/ui/ui_geomap.hpp
Normal 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
|
177
firmware/standalone/digitalrain/ui/ui_painter.cpp
Normal file
177
firmware/standalone/digitalrain/ui/ui_painter.cpp
Normal 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 */
|
113
firmware/standalone/digitalrain/ui/ui_painter.hpp
Normal file
113
firmware/standalone/digitalrain/ui/ui_painter.hpp
Normal 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__*/
|
74
firmware/standalone/digitalrain/ui/ui_text.cpp
Normal file
74
firmware/standalone/digitalrain/ui/ui_text.cpp
Normal 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 */
|
110
firmware/standalone/digitalrain/ui/ui_text.hpp
Normal file
110
firmware/standalone/digitalrain/ui/ui_text.hpp
Normal 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__*/
|
3310
firmware/standalone/digitalrain/ui/ui_widget.cpp
Normal file
3310
firmware/standalone/digitalrain/ui/ui_widget.cpp
Normal file
File diff suppressed because it is too large
Load diff
1046
firmware/standalone/digitalrain/ui/ui_widget.hpp
Normal file
1046
firmware/standalone/digitalrain/ui/ui_widget.hpp
Normal file
File diff suppressed because it is too large
Load diff
244
firmware/standalone/digitalrain/ui/utility.cpp
Normal file
244
firmware/standalone/digitalrain/ui/utility.cpp
Normal 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;
|
||||
}
|
222
firmware/standalone/digitalrain/ui/utility.hpp
Normal file
222
firmware/standalone/digitalrain/ui/utility.hpp
Normal 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__*/
|
312
firmware/standalone/pacman/ff.h
Normal file
312
firmware/standalone/pacman/ff.h
Normal 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 */
|
242
firmware/standalone/pacman/ffconf.h
Normal file
242
firmware/standalone/pacman/ffconf.h
Normal 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 ---*/
|
42
firmware/standalone/pacman/fileext.hpp
Normal file
42
firmware/standalone/pacman/fileext.hpp
Normal 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);
|
38
firmware/standalone/pacman/integer.h
Normal file
38
firmware/standalone/pacman/integer.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue