diff --git a/dist/proto_pirate.fap b/dist/proto_pirate.fap new file mode 100644 index 0000000..54b938c Binary files /dev/null and b/dist/proto_pirate.fap differ diff --git a/helpers/protopirate_types.h b/helpers/protopirate_types.h index c8b5799..1c0b02d 100644 --- a/helpers/protopirate_types.h +++ b/helpers/protopirate_types.h @@ -10,6 +10,7 @@ typedef enum { ProtoPirateViewWidget, ProtoPirateViewReceiver, ProtoPirateViewReceiverInfo, + ProtoPirateViewAbout, } ProtoPirateView; typedef enum { diff --git a/protopirate_app.c b/protopirate_app.c index a54ec5a..51db7ca 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -67,6 +67,10 @@ ProtoPirateApp* protopirate_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget)); + // About View + app->view_about = view_alloc(); + view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about); + // Receiver app->protopirate_receiver = protopirate_view_receiver_alloc(); view_dispatcher_add_view( @@ -171,6 +175,10 @@ void protopirate_app_free(ProtoPirateApp* app) { view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); variable_item_list_free(app->variable_item_list); + // About View + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout); + view_free(app->view_about); + // Widget view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget); widget_free(app->widget); diff --git a/protopirate_app_i.h b/protopirate_app_i.h index d0c5ab4..de5e408 100644 --- a/protopirate_app_i.h +++ b/protopirate_app_i.h @@ -46,6 +46,7 @@ struct ProtoPirateApp { VariableItemList* variable_item_list; Submenu* submenu; Widget* widget; + View* view_about; ProtoPirateReceiver* protopirate_receiver; ProtoPirateReceiverInfo* protopirate_receiver_info; ProtoPirateTxRx* txrx; diff --git a/scenes/protopirate_scene_about.c b/scenes/protopirate_scene_about.c index e9c235b..ef74508 100644 --- a/scenes/protopirate_scene_about.c +++ b/scenes/protopirate_scene_about.c @@ -1,38 +1,166 @@ // scenes/protopirate_scene_about.c #include "../protopirate_app_i.h" +#include +#include -void protopirate_scene_about_on_enter(void* context) { - furi_assert(context); - ProtoPirateApp* app = context; +#define CREDITS_START_Y 28 +#define CREDITS_END_Y 52 +#define CREDIT_LINE_HEIGHT 10 +#define SCROLL_SPEED 1 - widget_add_string_multiline_element( - app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "ProtoPirate"); +static const char* credits[] = { + "Protocols: L0rdDiakon", + "Protocols: YougZ", + "Protocols: RocketGod", + "App: RocketGod", + "App: MMX", + // can add more +}; - widget_add_string_multiline_element( - app->widget, - 0, - 12, - AlignLeft, - AlignTop, - FontSecondary, - "Kia protocols by RocketGod\n" - "L0rdDiakon & YougZ\n\n\n" - "App by RocketGod & MMX"); +#define CREDITS_COUNT (sizeof(credits) / sizeof(credits[0])) - widget_add_string_element( - app->widget, 0, 64 - 12, AlignLeft, AlignBottom, FontSecondary, "Version: 1.1"); +typedef struct { + uint8_t frame; + uint8_t seed; + int16_t scroll_offset; +} GlitchState; - view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); +static GlitchState g_state = {0}; + +static void draw_noise_pixels(Canvas* canvas, uint8_t density) { + for(uint8_t i = 0; i < density; i++) { + canvas_draw_dot(canvas, rand() % 128, rand() % 64); + } } -bool protopirate_scene_about_on_event(void* context, SceneManagerEvent event) { +static void about_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + + srand(g_state.seed); + canvas_clear(canvas); + + // Light background static + canvas_set_color(canvas, ColorBlack); + draw_noise_pixels(canvas, 6 + (rand() % 6)); + + // Occasional subtle x-jitter + int8_t x_off = (rand() % 15 == 0) ? ((rand() % 4) - 2) : 0; + + // Animated TPP decoration (centered) + canvas_set_font(canvas, FontKeyboard); + if(g_state.frame % 8 < 4) { + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<"); + } else { + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<"); + } + + // Draw credits region (clip area) + canvas_set_font(canvas, FontSecondary); + + // Calculate total scroll height + int16_t total_height = CREDITS_COUNT * CREDIT_LINE_HEIGHT; + + // Draw scrolling credits + for(size_t i = 0; i < CREDITS_COUNT; i++) { + int16_t y = CREDITS_START_Y + (i * CREDIT_LINE_HEIGHT) - g_state.scroll_offset; + + // Wrap around for endless scroll + while(y < CREDITS_START_Y - CREDIT_LINE_HEIGHT) { + y += total_height; + } + while(y > CREDITS_START_Y + total_height) { + y -= total_height; + } + + // Only draw if in visible region + if(y >= CREDITS_START_Y - CREDIT_LINE_HEIGHT && y <= CREDITS_END_Y) { + canvas_draw_str(canvas, x_off, y, credits[i]); + } + } + + // Draw fade/mask bars at top and bottom of credits area + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 128, CREDITS_START_Y - CREDIT_LINE_HEIGHT); + canvas_draw_box(canvas, 0, CREDITS_END_Y, 128, 14); + + // Redraw header over mask + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, x_off, 10, "ProtoPirate v1.1"); + + canvas_set_font(canvas, FontKeyboard); + if(g_state.frame % 8 < 4) { + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<"); + } else { + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<"); + } + + // Redraw static in header area + srand(g_state.seed + 1); + for(uint8_t i = 0; i < 3; i++) { + canvas_draw_dot(canvas, rand() % 128, rand() % (CREDITS_START_Y - CREDIT_LINE_HEIGHT)); + } + + // Footer: The Pirate's Plunder Discord + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, "discord.gg/thepirates"); + + // Rare subtle glitch bar + if(rand() % 30 == 0) { + canvas_set_color(canvas, ColorXOR); + uint8_t y = rand() % 60; + canvas_draw_box(canvas, 0, y, 128, 2); + } +} + +static bool about_input_callback(InputEvent* event, void* context) { UNUSED(context); UNUSED(event); return false; } +void protopirate_scene_about_on_enter(void* context) { + furi_assert(context); + ProtoPirateApp* app = context; + + g_state.frame = 0; + g_state.seed = furi_get_tick() & 0xFF; + g_state.scroll_offset = 0; + + view_set_draw_callback(app->view_about, about_draw_callback); + view_set_input_callback(app->view_about, about_input_callback); + view_set_context(app->view_about, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout); +} + +bool protopirate_scene_about_on_event(void* context, SceneManagerEvent event) { + ProtoPirateApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + g_state.frame++; + g_state.seed = rand(); + + if(g_state.frame % 2 == 0) { + g_state.scroll_offset += SCROLL_SPEED; + int16_t total_height = CREDITS_COUNT * CREDIT_LINE_HEIGHT; + if(g_state.scroll_offset >= total_height) { + g_state.scroll_offset = 0; + } + } + + view_commit_model(app->view_about, false); + consumed = true; + } + + return consumed; +} + void protopirate_scene_about_on_exit(void* context) { furi_assert(context); ProtoPirateApp* app = context; - widget_reset(app->widget); -} + + view_set_draw_callback(app->view_about, NULL); + view_set_input_callback(app->view_about, NULL); +} \ No newline at end of file