Add Security+ 2.0 TX App (#3217)

* Add U64 BoundSetting
* Add secplus-tx
* Add secplus license
* Update icon
* UI update
* Add filesystem::path BoundSetting
* Add file UI
* Rename file
* Add save button
* Fix warnings, add integer conversions to UI macros
* UI update
* Copilot suggestions, name edit UI
* Fix buffer length
* Style update
* Avoid recursion in Widget::style()
* Apply suggestions from code review
* Move secplustx.cpp to external
* Shorten title
* Space optimizations, remove FilePath setting
* Remove assertions
* Change namespace
* Change title to match
This commit is contained in:
Synray
2026-06-15 10:28:47 -07:00
committed by GitHub
parent b84a424875
commit d8a1e2530a
15 changed files with 627 additions and 30 deletions
+8 -3
View File
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
case SettingType::I64:
parse_int(value, as<int64_t>());
break;
case SettingType::U64:
parse_int(value, as<uint64_t>());
break;
case SettingType::I32:
parse_int(value, as<int32_t>());
break;
@@ -87,6 +90,9 @@ void BoundSetting::write(File& file) const {
case SettingType::I64:
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
break;
case SettingType::U64:
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
break;
case SettingType::I32:
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
break;
@@ -97,9 +103,8 @@ void BoundSetting::write(File& file) const {
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
break;
case SettingType::String: {
const auto& str = as<std::string>();
file.write(str.data(), str.length());
break;
file.write_line(as<std::string>());
return;
}
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
+4
View File
@@ -48,6 +48,7 @@ class BoundSetting {
/* The type of bound setting. */
enum class SettingType : uint8_t {
I64,
U64,
I32,
U32,
U8,
@@ -60,6 +61,9 @@ class BoundSetting {
BoundSetting(std::string_view name, int64_t* target)
: name_{name}, target_{target}, type_{SettingType::I64} {}
BoundSetting(std::string_view name, uint64_t* target)
: name_{name}, target_{target}, type_{SettingType::U64} {}
BoundSetting(std::string_view name, int32_t* target)
: name_{name}, target_{target}, type_{SettingType::I32} {}
+7 -1
View File
@@ -327,7 +327,7 @@ set(EXTCPPSRC
external/rtty_tx/ui_rtty_tx.cpp
external/rtty_tx/baudot.cpp
#pocsag_tx
#pocsag_tx
external/pocsag_tx/main.cpp
external/pocsag_tx/ui_pocsag_tx.cpp
@@ -362,6 +362,11 @@ set(EXTCPPSRC
#hard_reset
external/hard_reset/main.cpp
external/hard_reset/ui_hard_reset.cpp
#secplustx
external/secplustx/main.cpp
external/secplustx/ui_secplustx.cpp
external/secplustx/secplustx.cpp
)
set(EXTAPPLIST
@@ -451,6 +456,7 @@ set(EXTAPPLIST
two_tone_pager
two_tone_rx
hard_reset
secplustx
)
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
+7
View File
@@ -110,6 +110,7 @@ MEMORY
ram_external_app_two_tone_rx (rwx) : org = 0xAE050000, len = 32k
ram_external_app_flex_tx (rwx) : org = 0xAE060000, len = 32k
ram_external_app_hard_reset (rwx) : org = 0xAE070000, len = 32k
ram_external_app_secplustx (rwx) : org = 0xAE080000, len = 32k
}
SECTIONS
@@ -635,4 +636,10 @@ SECTIONS
KEEP(*(.external_app.app_hard_reset.application_information));
*(*ui*external_app*hard_reset*);
} > ram_external_app_hard_reset
.external_app_secplustx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_secplustx.application_information));
*(*ui*external_app*secplustx*);
} > ram_external_app_secplustx
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2026 Synray
*
* 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 "ui_navigation.hpp"
#include "ui_secplustx.hpp"
#include "external_app.hpp"
namespace ui::external_app::ui_secplustx {
void initialize_app(ui::NavigationView& nav) {
nav.push<SecplusTXView>();
}
} // namespace ui::external_app::ui_secplustx
extern "C" {
__attribute__((section(".external_app.app_secplustx.application_information"), used)) application_information_t _application_information_secplustx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::ui_secplustx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Security+",
/*.bitmap_data = */ {
0b00100000,
0b00000000,
0b00100000,
0b00100000,
0b00100000,
0b01110000,
0b00100000,
0b00100000,
0b11100000,
0b00000111,
0b11110000,
0b00001111,
0b00110000,
0b00001100,
0b00110000,
0b00001100,
0b11110000,
0b00001111,
0b11110000,
0b00001111,
0b01110000,
0b00001101,
0b10110000,
0b00001110,
0b01110000,
0b00001101,
0b10110000,
0b00001110,
0b11110000,
0b00001111,
0b11100000,
0b00000111,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+150
View File
@@ -0,0 +1,150 @@
/*
* Copyright 2022 Clayton Smith (argilo@gmail.com)
*
* This file is part of secplus.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "secplustx.hpp"
namespace ui::external_app::ui_secplustx {
static void v2_calc_parity(const uint64_t fixed, uint32_t* data) {
uint32_t parity = (fixed >> 32) & 0xf;
int8_t offset;
*data &= 0xffff0fff;
for (offset = 0; offset < 32; offset += 4) {
parity ^= ((*data >> offset) & 0xf);
}
*data |= (parity << 12);
}
static void encode_v2_rolling(const uint32_t rolling,
uint32_t* rolling_halves) {
uint32_t rolling_reversed = 0;
int8_t i, half;
for (i = 0; i < 28; i++) {
rolling_reversed |= ((rolling >> i) & 1) << (28 - i - 1);
}
rolling_halves[0] = 0;
rolling_halves[1] = 0;
for (half = 0; half < 2; half++) {
for (i = 0; i < 8; i += 2) {
rolling_halves[half] |= rolling_reversed % 3 << i;
rolling_reversed /= 3;
}
}
for (half = 0; half < 2; half++) {
for (i = 10; i < 18; i += 2) {
rolling_halves[half] |= rolling_reversed % 3 << i;
rolling_reversed /= 3;
}
}
rolling_halves[0] |= (rolling_reversed % 3) << 8;
rolling_reversed /= 3;
rolling_halves[1] |= (rolling_reversed % 3) << 8;
}
static const int8_t ORDER[16] = {9, 33, 6, -1, 24, 18, 36, -1,
24, 36, 6, -1, -1, -1, -1, -1};
static const int8_t INVERT[16] = {6, 2, 1, -1, 7, 5, 3, -1,
4, 0, 5, -1, -1, -1, -1, -1};
static void v2_scramble(const uint32_t* parts, const uint8_t frame_type, uint8_t* packet_half) {
const int8_t order = ORDER[packet_half[0] >> 4];
const int8_t invert = INVERT[packet_half[0] & 0xf];
int8_t i;
uint8_t out_offset = 10;
int8_t end;
uint32_t parts_permuted[3];
end = (frame_type == 0 ? 5 : 8);
for (i = 1; i < end; i++) {
packet_half[i] = 0;
}
parts_permuted[0] =
(invert & 4) ? ~parts[(order >> 4) & 3] : parts[(order >> 4) & 3];
parts_permuted[1] =
(invert & 2) ? ~parts[(order >> 2) & 3] : parts[(order >> 2) & 3];
parts_permuted[2] = (invert & 1) ? ~parts[order & 3] : parts[order & 3];
end = (frame_type == 0 ? 8 : 0);
for (i = 18 - 1; i >= end; i--) {
packet_half[out_offset >> 3] |= ((parts_permuted[0] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
packet_half[out_offset >> 3] |= ((parts_permuted[1] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
packet_half[out_offset >> 3] |= ((parts_permuted[2] >> i) & 1)
<< (7 - (out_offset % 8));
out_offset++;
}
}
static void encode_v2_half_parts(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
uint32_t parts[3];
parts[0] = ((fixed >> 10) << 8) | (data >> 8);
parts[1] = ((fixed & 0x3ff) << 8) | (data & 0xff);
parts[2] = rolling;
packet_half[0] = (uint8_t)rolling;
v2_scramble(parts, frame_type, packet_half);
}
static int8_t v2_check_limits(const uint32_t rolling, const uint64_t fixed) {
if ((rolling >> 28) != 0) {
return -1;
}
if ((fixed >> 40) != 0) {
return -1;
}
return 0;
}
static void encode_v2_half(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) {
encode_v2_half_parts(rolling, fixed, data, frame_type, packet_half);
/* shift indicator two bits to the right */
packet_half[1] |= (packet_half[0] & 0x3) << 6;
packet_half[0] >>= 2;
/* set frame type */
packet_half[0] |= (frame_type << 6);
}
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2) {
int8_t err = 0;
uint32_t rolling_halves[2];
err = v2_check_limits(rolling, fixed);
if (err < 0) {
return err;
}
encode_v2_rolling(rolling, rolling_halves);
v2_calc_parity(fixed, &data);
encode_v2_half(rolling_halves[0], fixed >> 20, data >> 16, frame_type,
packet1);
encode_v2_half(rolling_halves[1], fixed & 0xfffff, data & 0xffff, frame_type,
packet2);
return 0;
}
}; // namespace ui::external_app::ui_secplustx
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2022 Clayton Smith (argilo@gmail.com)
*
* This file is part of secplus.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#ifndef __SECPLUSTX__
#define __SECPLUSTX__
#include <cstdint>
namespace ui::external_app::ui_secplustx {
int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2);
};
#endif
+227
View File
@@ -0,0 +1,227 @@
#include "ui_secplustx.hpp"
#include <string_view>
#include <string>
#include <utility>
#include <vector>
#include "secplustx.hpp"
#include "ui_fileman.hpp"
#include "file_reader.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "optional.hpp"
using namespace portapack;
using namespace ui;
namespace fs = std::filesystem;
namespace ui::external_app::ui_secplustx {
static constexpr uint8_t secplus_repeat_count = 3;
static constexpr float secplus_sample_rate = OOK_SAMPLERATE / 4000.0f;
Optional<SecplusData> SecplusTXView::read_secplus_file(const fs::path& file_path) {
File file{};
if (auto error = file.open(file_path)) return {};
std::string raw = *FileLineReader{file}.begin();
std::vector chunks = split_string(raw, ';');
if (chunks.empty()) return {};
SecplusData data{};
data.version = static_cast<SecplusVersion>(std::strtoul(chunks[0].data(), NULL, 10));
switch (data.version) {
case SecplusVersion::V1:
return {}; // unimplemented
case SecplusVersion::V2: {
if (chunks.size() != 6) return {};
data.name = std::string{chunks[1]};
if (data.name.empty()) data.name = "Remote";
data.has_data = std::strtoul(chunks[2].data(), NULL, 10);
data.fixed_code = std::strtoull(chunks[3].data(), NULL, 16);
data.rolling_code = std::strtoul(chunks[4].data(), NULL, 16);
data.data = std::strtoul(chunks[5].data(), NULL, 16);
return data;
}
default:
return {};
}
}
bool SecplusTXView::write_secplus_file(const fs::path& file_path, const SecplusData& data) {
ensure_directory(secplus_dir);
File file{};
if (auto error = file.create(file_path)) {
return false;
}
switch (data.version) {
case SecplusVersion::V1:
return false; // unimplemented
case SecplusVersion::V2: {
std::string formatted = to_string_dec_uint(static_cast<uint8_t>(data.version));
formatted += ';';
formatted += data.name;
formatted += ';';
formatted += to_string_dec_uint(data.has_data);
formatted += ';';
formatted += to_string_hex(data.fixed_code);
formatted += ';';
formatted += to_string_hex(data.rolling_code);
formatted += ';';
formatted += to_string_hex(data.data);
file.write_line(formatted);
file.close();
return true;
}
default:
return false;
}
}
SecplusTXView::SecplusTXView(NavigationView& nav)
: nav_{nav} {
if (!fs::file_exists(file_path)) write_secplus_file(file_path, data);
baseband::run_image(portapack::spi_flash::image_tag_ook);
add_children({&button_open,
&button_save,
&field_name,
&labels,
&field_fixed,
&field_rolling,
&has_data,
&field_data,
&learn_mode,
&autosave,
&progressbar,
&tx_view});
button_open.on_select = [this](const Button&) {
ensure_directory(secplus_dir);
auto open_view = nav_.push<FileLoadView>(".SECPLUS");
open_view->push_dir(secplus_dir);
open_view->on_changed = [this](fs::path path) { file_path = path.string(); };
nav_.set_on_pop([this]() { reload_data(); });
};
button_save.on_select = [this](const Button&) { write_secplus_file(file_path, data); };
field_name.on_select = [this, &nav](TextField&) {
buffer = data.name;
text_prompt(nav_, buffer, field_name.parent_rect().width() / UI_POS_DEFAULT_WIDTH, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
data.name = new_name;
field_name.set_text(new_name);
save_data();
});
};
field_fixed.on_change = [this](SymField& field) { data.fixed_code = field.to_integer(); };
field_rolling.on_change = [this](SymField& field) { data.rolling_code = field.to_integer(); };
field_data.on_change = [this](SymField& field) { data.data = field.to_integer(); };
has_data.on_select = [this](Checkbox& checkbox, bool value) {
data.has_data = value;
// fade if inactive, otherwise use default style
const Style* new_style = value ? &style() : Theme::getInstance()->fg_medium;
checkbox.set_style(new_style);
field_data.set_style(new_style);
field_data.set_focusable(value);
};
tx_view.on_edit_frequency = [this]() {
auto new_view = nav_.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [](rf::Frequency f) {
transmitter_model.set_target_frequency(f);
};
};
tx_view.on_start = [this]() { start_tx(); };
tx_view.on_stop = [this]() {
baseband::kill_ook();
stop_tx();
};
autosave.set_value(true);
reload_data();
}
void SecplusTXView::save_data() {
if (autosave.value()) write_secplus_file(file_path, data);
}
void SecplusTXView::reload_data() {
if (auto result = read_secplus_file(file_path)) data = std::move(*result);
// always update data, use default values if read failed
field_name.set_text(data.name);
has_data.set_value(data.has_data);
field_rolling.set_value(data.rolling_code);
field_fixed.set_value(data.fixed_code);
field_data.set_value(data.data);
}
void SecplusTXView::start_tx() {
uint8_t packet1[8]{};
uint8_t packet2[8]{};
size_t bitstream_length = 0;
constexpr uint8_t packet1_indicator = 0b00;
constexpr uint8_t packet2_indicator = 0b01;
auto encode_packet = [&bitstream_length](uint8_t indicator, auto& packet, bool has_data) {
constexpr uint32_t preamble = 0b0000000000000000'1111;
constexpr uint32_t blank_size = 24;
auto manchester_encode = [&bitstream_length](auto& x, uint32_t size) {
for (uint32_t i = 0; i < size; ++i) {
bool bit = (x >> (size - 1 - i)) & 1;
encoders::bitstream_append(bitstream_length, 2, bit ? 0b01 : 0b10);
}
};
manchester_encode(preamble, 20);
manchester_encode(indicator, 2);
for (uint8_t byte = 0; byte < (has_data ? 8 : 5); ++byte) manchester_encode(packet[byte], 8);
encoders::bitstream_append(bitstream_length, blank_size, 0);
};
if (encode_v2(data.rolling_code, data.fixed_code, data.data, data.has_data, packet1, packet2) < 0) {
nav_.display_modal("Error", "Invalid rolling/fixed code");
return;
}
encode_packet(packet1_indicator, packet1, has_data.value());
encode_packet(packet2_indicator, packet2, has_data.value());
if (!learn_mode.value()) {
field_rolling.set_value(++data.rolling_code);
field_rolling.set_dirty();
}
progressbar.set_max(secplus_repeat_count);
progressbar.set_value(1);
tx_view.set_transmitting(true);
transmitter_model.enable();
baseband::set_ook_data(
bitstream_length,
secplus_sample_rate,
secplus_repeat_count,
0);
}
void SecplusTXView::stop_tx() {
transmitter_model.disable();
progressbar.set_value(0);
tx_view.set_transmitting(false);
}
void SecplusTXView::on_tx_progress(uint32_t progress, bool done) {
progressbar.set_value(progress + 1);
if (done) {
stop_tx();
save_data();
}
}
SecplusTXView::~SecplusTXView() {
transmitter_model.disable();
baseband::shutdown();
save_data();
}
} // namespace ui::external_app::ui_secplustx
@@ -0,0 +1,96 @@
#ifndef __UI_SECPLUSTX__
#define __UI_SECPLUSTX__
#include <cstdint>
#include <string>
#include "ui.hpp"
#include "file.hpp"
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "file_path.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "encoders.hpp"
#include "string_format.hpp"
namespace ui::external_app::ui_secplustx {
enum class SecplusVersion : uint8_t {
V1,
V2,
};
struct SecplusData {
SecplusVersion version;
std::string name;
bool has_data;
uint64_t fixed_code;
uint32_t rolling_code;
uint32_t data;
};
class SecplusTXView : public View {
public:
void focus() override { button_open.focus(); }
SecplusTXView(NavigationView& nav);
~SecplusTXView();
std::string title() const override {
return "Security+";
}
private:
std::string file_path = (secplus_dir / "DEFAULT.SECPLUS").string();
SecplusData data{SecplusVersion::V2, "Remote", false, 0, 0, 0};
std::string buffer{};
NavigationView& nav_;
TxRadioState radio_state_{
315000000,
1750000,
OOK_SAMPLERATE};
app_settings::SettingsManager settings_{"tx_secplus", app_settings::Mode::TX, {{"file_path"sv, &file_path}}};
Button button_open{{UI_POS_X(1), UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Open"};
Button button_save{{screen_width / 2, UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Save"};
// remote data
TextField field_name{{UI_POS_X(1), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(2), UI_POS_HEIGHT(1)}, "Remote"};
Labels labels{
{{UI_POS_X(1), UI_POS_Y(4)}, "Fixed:", Theme::getInstance()->fg_medium->foreground},
{{UI_POS_X(1), UI_POS_Y(5)}, "Rolling:", Theme::getInstance()->fg_medium->foreground}};
SymField field_fixed{{UI_POS_X(10), UI_POS_Y(4)}, 10, SymField::Type::Hex, true};
SymField field_rolling{{UI_POS_X(10), UI_POS_Y(5)}, 7, SymField::Type::Hex, true};
Checkbox has_data{{UI_POS_X(1), UI_POS_Y(6)}, 5, "Data:", true};
SymField field_data{{UI_POS_X(10), UI_POS_Y(6)}, 8, SymField::Type::Hex, true};
// options
Checkbox learn_mode{{UI_POS_X(1), UI_POS_Y(7)}, 5, "Learn", true};
Checkbox autosave{{UI_POS_X(1), UI_POS_Y(8)}, 8, "Autosave", true};
ProgressBar progressbar{{UI_POS_X(2), UI_POS_Y_BOTTOM(5.75), UI_POS_WIDTH_REMAINING(4), UI_POS_HEIGHT(1)}};
TransmitterView tx_view{UI_POS_Y_BOTTOM(4), 1000000, 0};
Optional<SecplusData> read_secplus_file(const std::filesystem::path& file_path);
bool write_secplus_file(const std::filesystem::path& file_path, const SecplusData& data);
void save_data();
void reload_data();
void start_tx();
void stop_tx();
void on_tx_progress(uint32_t progress, bool done);
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.progress, message.done);
}};
};
} // namespace ui::external_app::ui_secplustx
#endif
+1
View File
@@ -57,4 +57,5 @@ const std::filesystem::path macaddress_dir = u"MACADDRESS";
const std::filesystem::path splash_dot_bmp = u"/splash.bmp";
const std::filesystem::path keeloq_keys_dir = u"KEELOQKEYS";
const std::filesystem::path keeloq_remotes_dir = u"KEELOQREMOTES";
const std::filesystem::path secplus_dir = u"SECPLUS";
const std::filesystem::path epirb_dir = u"EPIRB";
+1
View File
@@ -58,6 +58,7 @@ extern const std::filesystem::path waterfalls_dir;
extern const std::filesystem::path macaddress_dir;
extern const std::filesystem::path keeloq_keys_dir;
extern const std::filesystem::path keeloq_remotes_dir;
extern const std::filesystem::path secplus_dir;
extern const std::filesystem::path epirb_dir;
extern const std::filesystem::path splash_dot_bmp;
+16 -16
View File
@@ -26,6 +26,9 @@
namespace ui {
using Coord = int16_t;
using Dim = int16_t;
// Positioning helpers PER CHARACTERS (8*16)
// EACH parameters must be used az CHAR position, not PX coordinates. So If you wanna use the 8,16 coordinates (that is the second character in X and second character in Y you must use UI_POS_X(1) UI_POS_Y(1) (since we count from 0)
@@ -36,31 +39,31 @@ namespace ui {
// default font width
#define UI_POS_DEFAULT_WIDTH 8
// px position of the linenum-th character (Y)
#define UI_POS_Y(linenum) ((int)((linenum) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_Y(linenum) ((Coord)((linenum) * UI_POS_DEFAULT_HEIGHT))
// px position of the linenum-th character from the bottom of the screen (Y) (please calculate the +1 line top-bar to it too if that is visible!)
#define UI_POS_Y_BOTTOM(linenum) ((int)(screen_height - (linenum) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_Y_BOTTOM(linenum) ((Coord)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT)))
// px position of the linenum-th character from the left of the screen (X)
#define UI_POS_X(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH))
#define UI_POS_X(charnum) ((Coord)((charnum) * UI_POS_DEFAULT_WIDTH))
// px position of the linenum-th character from the right of the screen (X)
#define UI_POS_X_RIGHT(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH)))
#define UI_POS_X_RIGHT(charnum) ((Coord)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH)))
// px position of the left character from the center of the screen (X) (for N character wide string)
#define UI_POS_X_CENTER(charnum) ((int)((screen_width / 2) - ((charnum) * UI_POS_DEFAULT_WIDTH / 2)))
// px position of the currcol in a table with colnum number of columns, where one coloumn is charnum characters wide maximum
#define UI_POS_X_TABLE(colnum, currcol) ((currcol) * (screen_width / (colnum)))
#define UI_POS_X_CENTER(charnum) ((Coord)((screen_width / 2) - (int)((charnum) * UI_POS_DEFAULT_WIDTH / 2)))
// px position of the currcol in a table with colnum number of columns, where one column is charnum characters wide maximum
#define UI_POS_X_TABLE(colnum, currcol) ((Coord)((currcol) * (screen_width / (colnum))))
// px width of N characters
#define UI_POS_WIDTH(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH))
#define UI_POS_WIDTH(charnum) ((Dim)((charnum) * UI_POS_DEFAULT_WIDTH))
// px width of the screen
#define UI_POS_MAXWIDTH (screen_width)
// px height of N line
#define UI_POS_HEIGHT(linecount) ((int)((linecount) * UI_POS_DEFAULT_HEIGHT))
#define UI_POS_HEIGHT(linecount) ((Dim)((linecount) * UI_POS_DEFAULT_HEIGHT))
// px height of the screen's percent
#define UI_POS_HEIGHT_PERCENT(percent) ((int)(screen_height * (percent) / 100))
#define UI_POS_HEIGHT_PERCENT(percent) ((Dim)(screen_height * (percent) / 100))
// remaining px from the linenum-th line to the bottom of the screen. (please calculate the +1 line top-bar to it too if that is visible!)
#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum) * UI_POS_DEFAULT_HEIGHT)))
#define UI_POS_HEIGHT_REMAINING(linenum) ((Dim)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT)))
// remaining px from the charnum-th character to the right of the screen
#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH)))
#define UI_POS_WIDTH_REMAINING(charnum) ((Dim)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH)))
// px width of the screen's percent
#define UI_POS_WIDTH_PERCENT(percent) ((int)(screen_width * (percent) / 100))
#define UI_POS_WIDTH_PERCENT(percent) ((Dim)(screen_width * (percent) / 100))
// px width of the screen
#define UI_POS_MAXHEIGHT (screen_height)
@@ -85,9 +88,6 @@ namespace ui {
#define DEG_TO_RAD(d) (d * (2 * pi) / 360.0)
using Coord = int16_t;
using Dim = int16_t;
extern uint16_t screen_width;
extern uint16_t screen_height;
+5 -1
View File
@@ -206,7 +206,11 @@ void Widget::set_style(const Style* new_style) {
}
const Style& Widget::style() const {
return style_ ? *style_ : parent()->style();
for (const Widget* curr = this; curr; curr = curr->parent()) {
if (curr->style_) return *curr->style_;
}
// default style should always be set by SystemView
return *style_;
}
void Widget::drawn(bool v) {
+4 -8
View File
@@ -201,15 +201,11 @@ void Widget::set_style(const Style* new_style) {
}
const Style& Widget::style() const {
if (style_ != nullptr)
return *style_;
else {
auto p = parent();
if (p == nullptr)
// TODO: debug
while (true);
return p->style();
for (const Widget* curr = this; curr; curr = curr->parent()) {
if (curr->style_) return *curr->style_;
}
// default style should always be set by SystemView
return *style_;
}
void Widget::drawn(bool v) {
+1 -1
View File
@@ -24,4 +24,4 @@
# external app address ranges below must match those in linker file "external.ld"
maximum_application_size = 32*1024
external_apps_address_start = 0xADB00000
external_apps_address_end = 0xAE060000
external_apps_address_end = 0xAE090000