mirror of
https://github.com/LouisShark/chatgpt_system_prompt.git
synced 2025-07-16 03:16:37 -04:00
1453 lines
40 KiB
Text
1453 lines
40 KiB
Text
Example of flipper zero app source code :
|
|
|
|
- This is counter increamnt by InputKeyDown and InputKeyUp in flipper zero :
|
|
|
|
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <stdlib.h>
|
|
#include <gui/elements.h>
|
|
#include <input/input.h>
|
|
#include <stdio.h>
|
|
|
|
typedef struct {
|
|
int number;
|
|
bool confirm;
|
|
} AppState;
|
|
|
|
// Render callback
|
|
static void render_callback(Canvas* canvas, void* context) {
|
|
AppState* app_state = (AppState*)context;
|
|
|
|
// Clear the canvas
|
|
canvas_clear(canvas);
|
|
|
|
// Set font and draw text
|
|
canvas_set_font(canvas, FontSecondary);
|
|
elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "BMI Calculator");
|
|
|
|
char numStr[12];
|
|
snprintf(numStr, sizeof(numStr), "%d", app_state->number);
|
|
elements_multiline_text_aligned(canvas, 64, 32, AlignCenter, AlignTop, numStr);
|
|
}
|
|
|
|
// Input callback
|
|
static void input_callback(InputEvent* input_event, void* context) {
|
|
AppState* app_state = (AppState*)context;
|
|
|
|
if(input_event->type == InputTypeShort) {
|
|
switch(input_event->key) {
|
|
case InputKeyUp:
|
|
app_state->number++;
|
|
break;
|
|
case InputKeyDown:
|
|
app_state->number--;
|
|
break;
|
|
case InputKeyOk:
|
|
app_state->confirm = true;
|
|
break;
|
|
case InputKeyBack:
|
|
// Add any action if required when back button is pressed
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t flippertools_app(void) {
|
|
AppState app_state;
|
|
app_state.number = 0;
|
|
app_state.confirm = false;
|
|
|
|
// Create and open a viewport
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, render_callback, &app_state);
|
|
|
|
// Setup GUI
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
// Create an input queue
|
|
FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
|
view_port_input_callback_set(view_port, input_callback, &app_state);
|
|
|
|
// Main loop
|
|
while(!app_state.confirm) {
|
|
view_port_update(view_port);
|
|
furi_delay_ms(100);
|
|
|
|
// Check for input events
|
|
InputEvent input_event;
|
|
while(furi_message_queue_get(input_queue, &input_event, 0) == FuriStatusOk) {
|
|
input_callback(&input_event, &app_state);
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
furi_message_queue_free(input_queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
- This Flipper application lets you view all information's regarding your flippers experience and butthurt.
|
|
|
|
Features:
|
|
|
|
General XP and Butthurt View
|
|
Daily XP View and descriptions to each category.
|
|
Daily Butthurt View and descriptions.
|
|
Backup View to create and load backups of your flippers experience and butthurt.
|
|
|
|
|
|
full code : doc.c file :
|
|
#include "doc_i.h"
|
|
#include "helpers/doc_storage_helper.h"
|
|
|
|
#include <dialogs/dialogs.h>
|
|
#include <scenes/doc_scene.h>
|
|
|
|
bool doc_custom_event_callback(void* ctx, uint32_t event) {
|
|
furi_assert(ctx);
|
|
Doc* app = ctx;
|
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
|
}
|
|
|
|
bool doc_back_event_callback(void* ctx) {
|
|
furi_assert(ctx);
|
|
Doc* app = ctx;
|
|
return scene_manager_handle_back_event(app->scene_manager);
|
|
}
|
|
|
|
/*
|
|
* The Dolphin service only saves 30s after the latest change,
|
|
* it can take up to that amount to get the real current values.
|
|
* The timer gets called after 10s, for 3 times and then stops.
|
|
*/
|
|
void doc_dolphin_timer_callback(void* ctx) {
|
|
furi_assert(ctx);
|
|
Doc* app = ctx;
|
|
|
|
app->dolphin_timer_counter++;
|
|
|
|
if(app->dolphin_timer_counter <= 3) {
|
|
FURI_LOG_D(TAG, "Loading new state after %hhus", app->dolphin_timer_counter * 10);
|
|
doc_dolphin_state_load(app->dolphin);
|
|
furi_delay_ms(20);
|
|
|
|
if(app->in_selection) {
|
|
doc_selection_request_redraw(app->selection);
|
|
}
|
|
if(app->in_description) {
|
|
doc_description_request_redraw(app->description);
|
|
}
|
|
}
|
|
|
|
if(app->dolphin_timer_counter == 3) {
|
|
FURI_LOG_D(TAG, "30s reached, stopping timer.");
|
|
furi_timer_stop(app->dolphin_timer);
|
|
}
|
|
}
|
|
|
|
Doc* doc_alloc() {
|
|
Doc* app = malloc(sizeof(Doc));
|
|
|
|
app->dolphin = malloc(sizeof(DolphinState));
|
|
app->dolphin_timer = furi_timer_alloc(doc_dolphin_timer_callback, FuriTimerTypePeriodic, app);
|
|
app->file_path = furi_string_alloc();
|
|
|
|
//? ------------- Records -------------
|
|
app->gui = furi_record_open(RECORD_GUI);
|
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
|
//? ----------- Records End -----------
|
|
|
|
// ViewDispatcher & SceneManager
|
|
app->view_dispatcher = view_dispatcher_alloc();
|
|
app->scene_manager = scene_manager_alloc(&doc_scene_handlers, app);
|
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
|
view_dispatcher_set_custom_event_callback(app->view_dispatcher, doc_custom_event_callback);
|
|
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, doc_back_event_callback);
|
|
|
|
//! -------------- DEBUG --------------
|
|
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
|
notification_message(app->notification, &sequence_display_backlight_on);
|
|
//! ------------ DEBUG END ------------
|
|
|
|
//? -------------- Views --------------
|
|
app->selection = doc_selection_alloc();
|
|
view_dispatcher_add_view(app->view_dispatcher, DocSelectionView, doc_selection_get_view(app->selection));
|
|
app->description = doc_description_alloc();
|
|
view_dispatcher_add_view(app->view_dispatcher, DocDescriptionView, doc_description_get_view(app->description));
|
|
app->text_input = text_input_alloc();
|
|
view_dispatcher_add_view(app->view_dispatcher, DocTextInputView, text_input_get_view(app->text_input));
|
|
//? ------------ Views End ------------
|
|
|
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
|
return app;
|
|
}
|
|
|
|
void doc_free(Doc* app) {
|
|
|
|
//! -------------- DEBUG --------------
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
app->notification = NULL;
|
|
//! ------------ DEBUG END ------------
|
|
|
|
//? -------------- Views --------------
|
|
view_dispatcher_remove_view(app->view_dispatcher, DocSelectionView);
|
|
doc_selection_free(app->selection);
|
|
view_dispatcher_remove_view(app->view_dispatcher, DocDescriptionView);
|
|
doc_description_free(app->description);
|
|
view_dispatcher_remove_view(app->view_dispatcher, DocTextInputView);
|
|
text_input_free(app->text_input);
|
|
//? ------------ Views End ------------
|
|
|
|
// ViewDispatcher & SceneManager
|
|
scene_manager_free(app->scene_manager);
|
|
view_dispatcher_free(app->view_dispatcher);
|
|
|
|
//? ------------- Records -------------
|
|
furi_record_close(RECORD_GUI);
|
|
app->gui = NULL;
|
|
furi_record_close(RECORD_DIALOGS);
|
|
app->dialogs = NULL;
|
|
//? ----------- Records End -----------
|
|
|
|
furi_string_free(app->file_path);
|
|
furi_timer_free(app->dolphin_timer);
|
|
free(app->dolphin);
|
|
|
|
free(app);
|
|
}
|
|
|
|
uint32_t doc_app(void* p) {
|
|
UNUSED(p);
|
|
Doc* app = doc_alloc();
|
|
|
|
doc_dolphin_state_load(app->dolphin);
|
|
furi_timer_start(app->dolphin_timer, 10 * 1000);
|
|
scene_manager_next_scene(app->scene_manager, DocSceneMenu);
|
|
view_dispatcher_run(app->view_dispatcher);
|
|
|
|
doc_free(app);
|
|
return 0;
|
|
}
|
|
|
|
doc_i.h file :
|
|
#pragma once
|
|
|
|
#include <helpers/dolphin_state.h>
|
|
|
|
//? -------------- Views --------------
|
|
#include <views/doc_selection.h>
|
|
#include <views/doc_description.h>
|
|
#include <gui/modules/text_input.h>
|
|
//? ------------ Views End ------------
|
|
|
|
#include <gui/gui.h>
|
|
#include <gui/view_dispatcher.h>
|
|
|
|
#include <dialogs/dialogs.h>
|
|
|
|
//! -------------- DEBUG --------------
|
|
#include <notification/notification_messages.h>
|
|
//! ------------ DEBUG END ------------
|
|
|
|
#define TAG "FlipperDoc"
|
|
|
|
typedef struct {
|
|
DolphinState* dolphin;
|
|
FuriTimer* dolphin_timer;
|
|
uint8_t dolphin_timer_counter;
|
|
|
|
//? -------------- Views --------------
|
|
DocSelection* selection;
|
|
bool in_selection;
|
|
DocDescription* description;
|
|
bool in_description;
|
|
TextInput* text_input;
|
|
//? ------------ Views End ------------
|
|
|
|
Gui* gui;
|
|
ViewDispatcher* view_dispatcher;
|
|
SceneManager* scene_manager;
|
|
|
|
DialogsApp* dialogs;
|
|
FuriString* file_path;
|
|
char text_input_array[50];
|
|
|
|
//! -------------- DEBUG --------------
|
|
NotificationApp* notification;
|
|
//! ------------ DEBUG END ------------
|
|
} Doc;
|
|
|
|
enum {
|
|
DocSelectionView,
|
|
DocDescriptionView,
|
|
DocTextInputView,
|
|
};
|
|
|
|
doc_icons.h file :
|
|
#pragma once
|
|
|
|
#include <gui/icon.h>
|
|
|
|
extern const Icon I_flipperdoc;
|
|
extern const Icon I_doc_smallscreen_light;
|
|
extern const Icon I_doc_bigscreen_light;
|
|
extern const Icon I_doc_button_left_small;
|
|
extern const Icon I_doc_button_up;
|
|
extern const Icon I_doc_button_down;
|
|
|
|
|
|
|
|
application.fam :
|
|
|
|
# For details & more options, see documentation/AppManifests.md in firmware repo
|
|
|
|
App(
|
|
appid="flipperdoc",
|
|
name="Flipper Doctor",
|
|
apptype=FlipperAppType.EXTERNAL,
|
|
entry_point="doc_app",
|
|
stack_size=1 * 1024,
|
|
fap_category="Tools",
|
|
fap_version="0.1",
|
|
fap_icon="images/flipperdoc.png",
|
|
fap_author="JulanDeAlb",
|
|
fap_weburl="https://github.com/julandealb/flipperdoc",
|
|
fap_icon_assets="images",
|
|
)
|
|
|
|
|
|
views/doc_description.c :
|
|
|
|
|
|
#include "doc_description.h"
|
|
#include "doc_view_common.h"
|
|
|
|
#include <doc_icons.h>
|
|
|
|
struct DocDescription {
|
|
View* view;
|
|
DocDescriptionCallback callback;
|
|
void* ctx;
|
|
};
|
|
|
|
typedef struct {
|
|
FuriString* text;
|
|
|
|
uint8_t category;
|
|
|
|
//Internal
|
|
uint8_t size;
|
|
uint8_t index;
|
|
} DocDescriptionViewModel;
|
|
|
|
static void doc_description_draw_callback(Canvas* canvas, void* ctx) {
|
|
furi_assert(ctx);
|
|
DocDescriptionViewModel* vm = ctx;
|
|
|
|
canvas_draw_icon(canvas, 0, 0, &I_doc_bigscreen_light);
|
|
|
|
// Scrolling Arrow
|
|
if(vm->index > 0) {
|
|
canvas_draw_icon(canvas, 113, 13, &I_doc_button_up);
|
|
}
|
|
if(vm->size > 4 &&
|
|
vm->index < vm->size - 4) {
|
|
canvas_draw_icon(canvas, 113, 39, &I_doc_button_down);
|
|
}
|
|
|
|
// Text
|
|
doc_draw_text(canvas, vm->text, 18, 110, 16, 10, vm->index, 4);
|
|
}
|
|
|
|
static bool doc_description_input_callback(InputEvent* event, void* ctx) {
|
|
furi_assert(ctx);
|
|
DocDescription* instance = ctx;
|
|
bool consumed = false;
|
|
|
|
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
|
switch(event->key) {
|
|
case InputKeyUp:
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* model,
|
|
{
|
|
if(model->index > 0) {
|
|
model->index--;
|
|
}
|
|
},
|
|
true);
|
|
consumed = true;
|
|
break;
|
|
case InputKeyDown:
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* model,
|
|
{
|
|
if(model->index < model->size - 4) {
|
|
model->index++;
|
|
}
|
|
},
|
|
true);
|
|
consumed = true;
|
|
break;
|
|
case InputKeyBack:
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* model,
|
|
{
|
|
model->index = 0;
|
|
},
|
|
false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
//? Basic Functions
|
|
View* doc_description_get_view(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
return instance->view;
|
|
}
|
|
|
|
void doc_description_set_callback(DocDescription* instance, DocDescriptionCallback callback, void* ctx) {
|
|
furi_assert(instance);
|
|
furi_assert(callback);
|
|
|
|
instance->callback = callback;
|
|
instance->ctx = ctx;
|
|
}
|
|
|
|
DocDescription* doc_description_alloc() {
|
|
DocDescription* instance = malloc(sizeof(DocDescription));
|
|
|
|
instance->view = view_alloc();
|
|
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DocDescriptionViewModel));
|
|
view_set_draw_callback(instance->view, doc_description_draw_callback);
|
|
view_set_input_callback(instance->view, doc_description_input_callback);
|
|
view_set_context(instance->view, instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
vm->text = furi_string_alloc();
|
|
}, false);
|
|
|
|
return instance;
|
|
}
|
|
|
|
void doc_description_free(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
furi_string_free(vm->text);
|
|
}, false);
|
|
|
|
view_free(instance->view);
|
|
free(instance);
|
|
}
|
|
//? Basic Functions End
|
|
|
|
//? Custom Functions
|
|
FuriString* doc_description_get_string(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
|
|
FuriString* text;
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
text = vm->text;
|
|
}, true);
|
|
|
|
return text;
|
|
}
|
|
|
|
uint8_t doc_description_get_category(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
|
|
uint8_t category;
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
category = vm->category;
|
|
}, false);
|
|
|
|
return category;
|
|
}
|
|
|
|
void doc_description_set_category(DocDescription* instance, uint8_t category) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
vm->category = category;
|
|
}, true);
|
|
}
|
|
|
|
void doc_description_set_size(DocDescription* instance, uint8_t size) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocDescriptionViewModel* vm,
|
|
{
|
|
vm->size = size;
|
|
}, true);
|
|
}
|
|
|
|
void doc_description_request_redraw(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
if(instance->callback != NULL) {
|
|
instance->callback(instance->ctx, (uint8_t) - 1);
|
|
}
|
|
}
|
|
|
|
void doc_description_force_redraw(DocDescription* instance) {
|
|
furi_assert(instance);
|
|
with_view_model(instance->view, DocDescriptionViewModel* vm, { UNUSED(vm); }, true);
|
|
}
|
|
//? Custom Functions End
|
|
|
|
|
|
views/doc_description.h :
|
|
|
|
#pragma once
|
|
|
|
#include <gui/view.h>
|
|
|
|
typedef struct DocDescription DocDescription;
|
|
typedef void (*DocDescriptionCallback)(void* ctx, uint8_t index);
|
|
|
|
//? Basic Functions
|
|
View* doc_description_get_view(DocDescription* instance);
|
|
void doc_description_set_callback(DocDescription* instance, DocDescriptionCallback callback, void* ctx);
|
|
DocDescription* doc_description_alloc();
|
|
void doc_description_free(DocDescription* instance);
|
|
//? Basic Functions End
|
|
|
|
//? Custom Functions
|
|
FuriString* doc_description_get_string(DocDescription* instance);
|
|
uint8_t doc_description_get_category(DocDescription* instance);
|
|
void doc_description_set_category(DocDescription* instance, uint8_t category);
|
|
void doc_description_set_size(DocDescription* instance, uint8_t size);
|
|
void doc_description_request_redraw(DocDescription* instance);
|
|
void doc_description_force_redraw(DocDescription* instance);
|
|
//? Custom Functions End
|
|
|
|
views/doc_selection.c :
|
|
|
|
#include "doc_selection.h"
|
|
#include "doc_view_common.h"
|
|
|
|
#include <doc_icons.h>
|
|
|
|
struct DocSelection {
|
|
View* view;
|
|
DocSelectionCallback callback;
|
|
void* ctx;
|
|
};
|
|
|
|
typedef struct {
|
|
const char* title;
|
|
FuriString* text;
|
|
const char* footer;
|
|
|
|
uint8_t category;
|
|
|
|
//Internal
|
|
uint8_t size;
|
|
uint8_t position;
|
|
uint8_t window_position;
|
|
} DocSelectionViewModel;
|
|
|
|
static void doc_selection_draw_callback(Canvas* canvas, void* ctx) {
|
|
furi_assert(ctx);
|
|
DocSelectionViewModel* vm = ctx;
|
|
|
|
canvas_draw_icon(canvas, 0, 0, &I_doc_smallscreen_light);
|
|
|
|
// Selection Arrow
|
|
uint8_t selection_index = vm->position - vm->window_position;
|
|
if(selection_index == 0) {
|
|
canvas_draw_icon(canvas, 123, 16, &I_doc_button_left_small);
|
|
} else if(selection_index == 1) {
|
|
canvas_draw_icon(canvas, 123, 25, &I_doc_button_left_small);
|
|
} else {
|
|
canvas_draw_icon(canvas, 123, 34, &I_doc_button_left_small);
|
|
}
|
|
|
|
// Title
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_draw_str_aligned(canvas, 85, 11, AlignCenter, AlignBottom, vm->title);
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
// Text
|
|
doc_draw_text(canvas, vm->text, 44, 121, 22, 9, vm->window_position, 3);
|
|
|
|
// Footer
|
|
canvas_draw_str_aligned(canvas, 87, 54, AlignCenter, AlignBottom, vm->footer);
|
|
}
|
|
|
|
static bool doc_selection_input_callback(InputEvent* event, void* ctx) {
|
|
furi_assert(ctx);
|
|
DocSelection* instance = ctx;
|
|
bool consumed = false;
|
|
|
|
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
|
|
switch(event->key) {
|
|
case InputKeyUp:
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* model,
|
|
{
|
|
if(model->position > 0) {
|
|
model->position--;
|
|
|
|
if(model->position == model->window_position && model->window_position > 0) {
|
|
model->window_position--;
|
|
}
|
|
} else {
|
|
model->position = model->size - 1;
|
|
|
|
if(model->position > 2) {
|
|
model->window_position = model->position - 2;
|
|
}
|
|
}
|
|
},
|
|
true);
|
|
consumed = true;
|
|
break;
|
|
case InputKeyDown:
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* model,
|
|
{
|
|
if(model->position < model->size - 1) {
|
|
model->position++;
|
|
|
|
if(model->position - model->window_position > 1 && model->window_position < model->size - 3) {
|
|
model->window_position++;
|
|
}
|
|
} else {
|
|
model->position = 0;
|
|
model->window_position = 0;
|
|
}
|
|
},
|
|
true);
|
|
consumed = true;
|
|
break;
|
|
case InputKeyOk:
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* model,
|
|
{
|
|
if(instance->callback) {
|
|
instance->callback(instance->ctx, model->position);
|
|
}
|
|
},
|
|
false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
//? Basic Functions
|
|
View* doc_selection_get_view(DocSelection* instance) {
|
|
furi_assert(instance);
|
|
return instance->view;
|
|
}
|
|
|
|
void doc_selection_set_callback(DocSelection* instance, DocSelectionCallback callback, void* ctx) {
|
|
furi_assert(instance);
|
|
furi_assert(callback);
|
|
|
|
instance->callback = callback;
|
|
instance->ctx = ctx;
|
|
}
|
|
|
|
DocSelection* doc_selection_alloc() {
|
|
DocSelection* instance = malloc(sizeof(DocSelection));
|
|
|
|
instance->view = view_alloc();
|
|
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DocSelectionViewModel));
|
|
view_set_draw_callback(instance->view, doc_selection_draw_callback);
|
|
view_set_input_callback(instance->view, doc_selection_input_callback);
|
|
view_set_context(instance->view, instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
vm->text = furi_string_alloc();
|
|
}, false);
|
|
|
|
return instance;
|
|
}
|
|
|
|
void doc_selection_free(DocSelection* instance) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
furi_string_free(vm->text);
|
|
}, false);
|
|
|
|
view_free(instance->view);
|
|
free(instance);
|
|
}
|
|
//? Basic Functions End
|
|
|
|
//? Custom Functions
|
|
void doc_selection_set_title(DocSelection* instance, const char* title) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
vm->title = title;
|
|
}, true);
|
|
}
|
|
|
|
FuriString* doc_selection_get_string(DocSelection* instance) {
|
|
furi_assert(instance);
|
|
|
|
FuriString* text;
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
text = vm->text;
|
|
}, true);
|
|
|
|
return text;
|
|
}
|
|
|
|
void doc_selection_set_footer(DocSelection* instance, const char* footer) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
vm->footer = footer;
|
|
}, true);
|
|
}
|
|
|
|
void doc_selection_set_index(DocSelection* instance, uint8_t index) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* model,
|
|
{
|
|
uint8_t position = index;
|
|
if(position >= model->size) {
|
|
position = 0;
|
|
}
|
|
|
|
model->position = position;
|
|
model->window_position = position;
|
|
|
|
if(model->window_position > 0) {
|
|
model->window_position -= 1;
|
|
}
|
|
|
|
if(model->size <= 3) {
|
|
model->window_position = 0;
|
|
} else {
|
|
if(model->window_position >= model->size - 3) {
|
|
model->window_position = model->size - 3;
|
|
}
|
|
}
|
|
},
|
|
true);
|
|
}
|
|
|
|
void doc_selection_set_size(DocSelection* instance, uint8_t size) {
|
|
furi_assert(instance);
|
|
|
|
with_view_model(
|
|
instance->view,
|
|
DocSelectionViewModel* vm,
|
|
{
|
|
vm->size = size;
|
|
}, true);
|
|
}
|
|
|
|
void doc_selection_request_redraw(DocSelection* instance) {
|
|
furi_assert(instance);
|
|
if(instance->callback != NULL) {
|
|
instance->callback(instance->ctx, (uint8_t) - 1);
|
|
}
|
|
}
|
|
|
|
void doc_selection_force_redraw(DocSelection* instance) {
|
|
furi_assert(instance);
|
|
with_view_model(instance->view, DocSelectionViewModel* vm, { UNUSED(vm); }, true);
|
|
}
|
|
//? Custom Functions End
|
|
|
|
views/doc_view_common.c :
|
|
#include "doc_view_common.h"
|
|
|
|
void doc_draw_text(Canvas* canvas, FuriString* text,
|
|
uint8_t x_one, uint8_t x_two, uint8_t y,
|
|
uint8_t font_height, uint8_t start_index, uint8_t max_index) {
|
|
furi_assert(canvas);
|
|
|
|
FuriString* str = furi_string_alloc();
|
|
const char* start = furi_string_get_cstr(text);
|
|
char* mid;
|
|
char* end;
|
|
uint8_t temp_index = 0;
|
|
|
|
do {
|
|
mid = strchr(start, '\t');
|
|
end = strchr(start, '\n');
|
|
|
|
if(mid && end && 0 < end - mid) {
|
|
furi_string_set_strn(str, start, mid - start);
|
|
|
|
if(temp_index >= start_index) {
|
|
canvas_draw_str_aligned(canvas, x_one, y, AlignLeft, AlignBottom, furi_string_get_cstr(str));
|
|
}
|
|
}
|
|
|
|
if(end && mid) {
|
|
furi_string_set_strn(str, mid, end - start);
|
|
start = end + 1;
|
|
} else if(end) {
|
|
furi_string_set_strn(str, start, end - start);
|
|
start = end + 1;
|
|
} else {
|
|
furi_string_set(str, start);
|
|
}
|
|
|
|
if(temp_index >= start_index) {
|
|
canvas_draw_str_aligned(canvas, x_two, y, AlignRight, AlignBottom, furi_string_get_cstr(str));
|
|
y += font_height;
|
|
}
|
|
|
|
temp_index++;
|
|
} while(end && y < 64 && temp_index <= start_index + max_index - 1);
|
|
|
|
furi_string_free(str);
|
|
}
|
|
|
|
views/doc_view_common.h :
|
|
|
|
#pragma once
|
|
|
|
#include <gui/view.h>
|
|
|
|
void doc_draw_text(Canvas* canvas, FuriString* text,
|
|
uint8_t x_one, uint8_t x_two, uint8_t y,
|
|
uint8_t font_height, uint8_t start_index, uint8_t max_index);
|
|
|
|
|
|
|
|
|
|
this app will generate a password in flipper zero , source code :
|
|
|
|
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <gui/elements.h>
|
|
#include <input/input.h>
|
|
#include <notification/notification_messages.h>
|
|
#include <stdlib.h>
|
|
#include <passgen_icons.h>
|
|
|
|
#define PASSGEN_MAX_LENGTH 16
|
|
#define PASSGEN_CHARACTERS_LENGTH (26*4)
|
|
|
|
#define PASSGEN_DIGITS "0123456789"
|
|
#define PASSGEN_LETTERS_LOW "abcdefghijklmnopqrstuvwxyz"
|
|
#define PASSGEN_LETTERS_UP "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
#define PASSGEN_SPECIAL "!#$%^&*.-_"
|
|
|
|
typedef enum PassGen_Alphabet
|
|
{
|
|
Digits = 1,
|
|
Lowercase = 2,
|
|
|
|
Uppercase = 4,
|
|
Special = 8,
|
|
|
|
DigitsLower = Digits | Lowercase,
|
|
DigitsAllLetters = Digits | Lowercase | Uppercase,
|
|
Mixed = DigitsAllLetters | Special
|
|
} PassGen_Alphabet;
|
|
|
|
const int AlphabetLevels[] = { Digits, Lowercase, DigitsLower, DigitsAllLetters, Mixed };
|
|
const char* AlphabetLevelNames[] = { "1234", "abcd", "ab12", "Ab12", "Ab1#" };
|
|
const int AlphabetLevelsCount = sizeof(AlphabetLevels) / sizeof(int);
|
|
|
|
const NotificationSequence PassGen_Alert_vibro = {
|
|
&message_vibro_on,
|
|
&message_blue_255,
|
|
&message_delay_50,
|
|
&message_vibro_off,
|
|
NULL,
|
|
};
|
|
|
|
typedef struct {
|
|
FuriMessageQueue* input_queue;
|
|
ViewPort* view_port;
|
|
Gui* gui;
|
|
FuriMutex** mutex;
|
|
NotificationApp* notify;
|
|
char password[PASSGEN_MAX_LENGTH+1];
|
|
char alphabet[PASSGEN_CHARACTERS_LENGTH+1];
|
|
int length;
|
|
int level;
|
|
} PassGen;
|
|
|
|
void state_free(PassGen* app) {
|
|
gui_remove_view_port(app->gui, app->view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(app->view_port);
|
|
furi_message_queue_free(app->input_queue);
|
|
furi_mutex_free(app->mutex);
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
free(app);
|
|
}
|
|
|
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
|
PassGen* app = ctx;
|
|
if(input_event->type == InputTypeShort) {
|
|
furi_message_queue_put(app->input_queue, input_event, 0);
|
|
}
|
|
}
|
|
|
|
static void render_callback(Canvas* canvas, void* ctx) {
|
|
char str_length[8];
|
|
PassGen* app = ctx;
|
|
furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk);
|
|
|
|
canvas_clear(canvas);
|
|
canvas_draw_box(canvas, 0, 0, 128, 14);
|
|
canvas_set_color(canvas, ColorWhite);
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_draw_str(canvas, 2, 11, "Password Generator");
|
|
|
|
canvas_set_color(canvas, ColorBlack);
|
|
canvas_draw_str_aligned(canvas, 64, 35, AlignCenter, AlignCenter, app->password);
|
|
|
|
// Navigation menu:
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_draw_icon(canvas, 96, 52, &I_Pin_back_arrow_10x8);
|
|
canvas_draw_str(canvas, 108, 60, "Exit");
|
|
|
|
canvas_draw_icon(canvas, 54, 52, &I_Vertical_arrow_7x9);
|
|
canvas_draw_str(canvas, 64, 60, AlphabetLevelNames[app->level]);
|
|
|
|
snprintf(str_length, sizeof(str_length), "Len: %d", app->length);
|
|
canvas_draw_icon(canvas, 4, 53, &I_Horizontal_arrow_9x7);
|
|
canvas_draw_str(canvas, 15, 60, str_length);
|
|
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
|
|
void build_alphabet(PassGen* app)
|
|
{
|
|
PassGen_Alphabet mode = AlphabetLevels[app->level];
|
|
app->alphabet[0] = '\0';
|
|
if ((mode & Digits) != 0)
|
|
strcat(app->alphabet, PASSGEN_DIGITS);
|
|
if ((mode & Lowercase) != 0)
|
|
strcat(app->alphabet, PASSGEN_LETTERS_LOW);
|
|
if ((mode & Uppercase) != 0)
|
|
strcat(app->alphabet, PASSGEN_LETTERS_UP);
|
|
if ((mode & Special) != 0)
|
|
strcat(app->alphabet, PASSGEN_SPECIAL);
|
|
}
|
|
|
|
PassGen* state_init() {
|
|
PassGen* app = malloc(sizeof(PassGen));
|
|
app->length = 8;
|
|
app->level = 2;
|
|
build_alphabet(app);
|
|
app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
|
app->view_port = view_port_alloc();
|
|
app->gui = furi_record_open(RECORD_GUI);
|
|
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
view_port_input_callback_set(app->view_port, input_callback, app);
|
|
view_port_draw_callback_set(app->view_port, render_callback, app);
|
|
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
|
|
|
app->notify = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
return app;
|
|
}
|
|
|
|
void generate(PassGen* app)
|
|
{
|
|
int hi = strlen(app->alphabet);
|
|
for (int i=0; i<app->length; i++)
|
|
{
|
|
int x = rand() % hi;
|
|
app->password[i]=app->alphabet[x];
|
|
}
|
|
app->password[app->length] = '\0';
|
|
}
|
|
|
|
void update_password(PassGen* app, bool vibro)
|
|
{
|
|
generate(app);
|
|
|
|
if (vibro)
|
|
notification_message(app->notify, &PassGen_Alert_vibro);
|
|
else
|
|
notification_message(app->notify, &sequence_blink_blue_100);
|
|
view_port_update(app->view_port);
|
|
}
|
|
|
|
int32_t passgenapp(void) {
|
|
PassGen* app = state_init();
|
|
generate(app);
|
|
|
|
while(1) {
|
|
InputEvent input;
|
|
while(furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk) {
|
|
furi_check(furi_mutex_acquire(app->mutex, FuriWaitForever) == FuriStatusOk);
|
|
|
|
if (input.type == InputTypeShort)
|
|
{
|
|
switch (input.key) {
|
|
case InputKeyBack:
|
|
furi_mutex_release(app->mutex);
|
|
state_free(app);
|
|
return 0;
|
|
case InputKeyDown:
|
|
if (app->level > 0)
|
|
{
|
|
app->level--;
|
|
build_alphabet(app);
|
|
update_password(app, false);
|
|
}
|
|
else
|
|
notification_message(app->notify, &sequence_blink_red_100);
|
|
break;
|
|
case InputKeyUp:
|
|
if (app->level < AlphabetLevelsCount - 1)
|
|
{
|
|
app->level++;
|
|
build_alphabet(app);
|
|
update_password(app, false);
|
|
}
|
|
else
|
|
notification_message(app->notify, &sequence_blink_red_100);
|
|
break;
|
|
case InputKeyLeft:
|
|
if (app->length > 1)
|
|
{
|
|
app->length--;
|
|
update_password(app, false);
|
|
}
|
|
else
|
|
notification_message(app->notify, &sequence_blink_red_100);
|
|
break;
|
|
case InputKeyRight:
|
|
if (app->length < PASSGEN_MAX_LENGTH)
|
|
{
|
|
app->length++;
|
|
update_password(app, false);
|
|
}
|
|
else
|
|
notification_message(app->notify, &sequence_blink_red_100);
|
|
break;
|
|
case InputKeyOk:
|
|
update_password(app, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
}
|
|
state_free(app);
|
|
return 0;
|
|
}
|
|
|
|
|
|
Simple Flashlight source code for flipper zero Plugin :
|
|
|
|
|
|
// by @xMasterX
|
|
|
|
#include <furi.h>
|
|
#include <furi_hal_power.h>
|
|
#include <gui/gui.h>
|
|
#include <input/input.h>
|
|
#include <stdlib.h>
|
|
#include <gui/elements.h>
|
|
|
|
typedef enum {
|
|
EventTypeTick,
|
|
EventTypeKey,
|
|
} EventType;
|
|
|
|
typedef struct {
|
|
EventType type;
|
|
InputEvent input;
|
|
} PluginEvent;
|
|
|
|
typedef struct {
|
|
FuriMutex* mutex;
|
|
bool is_on;
|
|
} PluginState;
|
|
|
|
static void render_callback(Canvas* const canvas, void* ctx) {
|
|
furi_assert(ctx);
|
|
const PluginState* plugin_state = ctx;
|
|
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
|
|
|
canvas_set_font(canvas, FontPrimary);
|
|
elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Flashlight");
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
if(!plugin_state->is_on) {
|
|
elements_multiline_text_aligned(
|
|
canvas, 64, 28, AlignCenter, AlignTop, "Press OK button turn on");
|
|
} else {
|
|
elements_multiline_text_aligned(canvas, 64, 28, AlignCenter, AlignTop, "Light is on!");
|
|
elements_multiline_text_aligned(
|
|
canvas, 64, 40, AlignCenter, AlignTop, "Press OK button to off");
|
|
}
|
|
|
|
furi_mutex_release(plugin_state->mutex);
|
|
}
|
|
|
|
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
|
|
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
}
|
|
|
|
static void flash_toggle(PluginState* const plugin_state) {
|
|
furi_hal_gpio_write(&gpio_ext_pc3, false);
|
|
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
|
|
|
if(plugin_state->is_on) {
|
|
furi_hal_gpio_write(&gpio_ext_pc3, false);
|
|
plugin_state->is_on = false;
|
|
} else {
|
|
furi_hal_gpio_write(&gpio_ext_pc3, true);
|
|
plugin_state->is_on = true;
|
|
}
|
|
}
|
|
|
|
int32_t flashlight_app() {
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
|
|
|
PluginState* plugin_state = malloc(sizeof(PluginState));
|
|
|
|
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
if(!plugin_state->mutex) {
|
|
FURI_LOG_E("flashlight", "cannot create mutex\r\n");
|
|
furi_message_queue_free(event_queue);
|
|
free(plugin_state);
|
|
return 255;
|
|
}
|
|
|
|
// Set system callbacks
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, render_callback, plugin_state);
|
|
view_port_input_callback_set(view_port, input_callback, event_queue);
|
|
|
|
// Open GUI and register view_port
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
PluginEvent event;
|
|
for(bool processing = true; processing;) {
|
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
|
|
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
|
|
|
if(event_status == FuriStatusOk) {
|
|
// press events
|
|
if(event.type == EventTypeKey) {
|
|
if(event.input.type == InputTypePress) {
|
|
switch(event.input.key) {
|
|
case InputKeyUp:
|
|
case InputKeyDown:
|
|
case InputKeyRight:
|
|
case InputKeyLeft:
|
|
break;
|
|
case InputKeyOk:
|
|
flash_toggle(plugin_state);
|
|
break;
|
|
case InputKeyBack:
|
|
processing = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
furi_mutex_release(plugin_state->mutex);
|
|
view_port_update(view_port);
|
|
}
|
|
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
furi_message_queue_free(event_queue);
|
|
furi_mutex_free(plugin_state->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
color guess source code for flipper zero app :
|
|
|
|
#include "color_guess.h"
|
|
#include "helpers/digits.h"
|
|
|
|
bool color_guess_custom_event_callback(void* context, uint32_t event) {
|
|
furi_assert(context);
|
|
ColorGuess* app = context;
|
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
|
}
|
|
|
|
void color_guess_tick_event_callback(void* context) {
|
|
furi_assert(context);
|
|
ColorGuess* app = context;
|
|
scene_manager_handle_tick_event(app->scene_manager);
|
|
}
|
|
|
|
//leave app if back button pressed
|
|
bool color_guess_navigation_event_callback(void* context) {
|
|
furi_assert(context);
|
|
ColorGuess* app = context;
|
|
return scene_manager_handle_back_event(app->scene_manager);
|
|
}
|
|
|
|
ColorGuess* color_guess_app_alloc() {
|
|
ColorGuess* app = malloc(sizeof(ColorGuess));
|
|
app->gui = furi_record_open(RECORD_GUI);
|
|
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
|
app->error = false;
|
|
|
|
// Set Defaults if no config exists
|
|
app->haptic = 1;
|
|
app->led = 1;
|
|
app->save_settings = 1;
|
|
|
|
// Load configs
|
|
color_guess_read_settings(app);
|
|
|
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
|
notification_message(notification, &sequence_display_backlight_on);
|
|
|
|
//Scene additions
|
|
app->view_dispatcher = view_dispatcher_alloc();
|
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
|
|
app->scene_manager = scene_manager_alloc(&color_guess_scene_handlers, app);
|
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
|
view_dispatcher_set_navigation_event_callback(
|
|
app->view_dispatcher, color_guess_navigation_event_callback);
|
|
view_dispatcher_set_tick_event_callback(
|
|
app->view_dispatcher, color_guess_tick_event_callback, 100);
|
|
view_dispatcher_set_custom_event_callback(
|
|
app->view_dispatcher, color_guess_custom_event_callback);
|
|
app->submenu = submenu_alloc();
|
|
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher, ColorGuessViewIdMenu, submenu_get_view(app->submenu));
|
|
app->variable_item_list = variable_item_list_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ColorGuessViewIdSettings,
|
|
variable_item_list_get_view(app->variable_item_list));
|
|
app->color_guess_startscreen = color_guess_startscreen_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ColorGuessViewIdStartscreen,
|
|
color_guess_startscreen_get_view(app->color_guess_startscreen));
|
|
app->color_guess_color_set = color_guess_color_set_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ColorGuessViewIdColorSet,
|
|
color_guess_color_set_get_view(app->color_guess_color_set));
|
|
app->color_guess_play = color_guess_play_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ColorGuessViewIdPlay,
|
|
color_guess_play_get_view(app->color_guess_play));
|
|
|
|
//End Scene Additions
|
|
|
|
return app;
|
|
}
|
|
|
|
void color_guess_app_free(ColorGuess* app) {
|
|
furi_assert(app);
|
|
|
|
// Scene manager
|
|
scene_manager_free(app->scene_manager);
|
|
|
|
// View Dispatcher
|
|
view_dispatcher_remove_view(app->view_dispatcher, ColorGuessViewIdMenu);
|
|
view_dispatcher_remove_view(app->view_dispatcher, ColorGuessViewIdStartscreen);
|
|
view_dispatcher_remove_view(app->view_dispatcher, ColorGuessViewIdColorSet);
|
|
view_dispatcher_remove_view(app->view_dispatcher, ColorGuessViewIdPlay);
|
|
view_dispatcher_remove_view(app->view_dispatcher, ColorGuessViewIdSettings);
|
|
submenu_free(app->submenu);
|
|
|
|
view_dispatcher_free(app->view_dispatcher);
|
|
|
|
// GUI
|
|
furi_record_close(RECORD_GUI);
|
|
|
|
app->view_port = NULL;
|
|
app->gui = NULL;
|
|
app->notification = NULL;
|
|
|
|
//Remove whatever is left
|
|
free(app);
|
|
}
|
|
|
|
int32_t color_guess_app(void* p) {
|
|
UNUSED(p);
|
|
ColorGuess* app = color_guess_app_alloc();
|
|
if(app->error) {
|
|
return 255;
|
|
}
|
|
|
|
if(!furi_hal_region_is_provisioned()) {
|
|
color_guess_app_free(app);
|
|
return 1;
|
|
}
|
|
|
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
|
|
|
scene_manager_next_scene(app->scene_manager, ColorGuessSceneStartscreen);
|
|
|
|
furi_hal_power_suppress_charge_enter();
|
|
|
|
view_dispatcher_run(app->view_dispatcher);
|
|
|
|
color_guess_save_settings(app);
|
|
|
|
furi_hal_power_suppress_charge_exit();
|
|
|
|
color_guess_app_free(app);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
example of hello world with face draw :
|
|
|
|
helloworld.c :
|
|
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <stdlib.h>
|
|
#include <gui/elements.h>
|
|
|
|
// Render callback
|
|
static void render_callback(Canvas* canvas, void* context) {
|
|
// Clear the canvas
|
|
canvas_clear(canvas);
|
|
|
|
// Set font and draw "Hello World" with a twist
|
|
canvas_set_font(canvas, FontSecondary);
|
|
elements_multiline_text_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Hello World!");
|
|
|
|
// Draw a smiley face under the text
|
|
canvas_draw_circle(canvas, 64, 50, 10);
|
|
canvas_draw_line(canvas, 60, 47, 62, 47); // Corrected line
|
|
canvas_draw_line(canvas, 66, 47, 68, 47); // Corrected line
|
|
}
|
|
|
|
// Input callback - exit on back button
|
|
static void input_callback(InputEvent* input_event, void* context) {
|
|
bool* running = context;
|
|
if(input_event->type == InputTypeShort && input_event->key == InputKeyBack) {
|
|
*running = false;
|
|
}
|
|
}
|
|
|
|
// Main app function
|
|
int32_t hello_world_app(void) {
|
|
// Setup GUI and viewport
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
ViewPort* view_port = view_port_alloc();
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
view_port_draw_callback_set(view_port, render_callback, NULL);
|
|
|
|
// Main loop
|
|
bool running = true;
|
|
view_port_input_callback_set(view_port, input_callback, &running);
|
|
while(running) {
|
|
view_port_update(view_port);
|
|
furi_delay_ms(100);
|
|
}
|
|
|
|
// Cleanup
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
return 0;
|
|
}
|
|
|
|
application.fam :
|
|
|
|
App(
|
|
appid="hello_world",
|
|
name="Hello World",
|
|
apptype=FlipperAppType.EXTERNAL,
|
|
entry_point="hello_world_app",
|
|
stack_size=1 * 1024,
|
|
fap_category="Tools",
|
|
fap_version="1.0",
|
|
fap_icon="images/icon.png",
|
|
fap_author="YourName",
|
|
fap_weburl="https://yourwebsite.com"
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|