Added multiple apps
Added Geiger Counter, Nightstand, Scrambler, Pomodoro
15
applications/plugins/brainfuck/application.fam
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
App(
|
||||||
|
appid="Brainfuck",
|
||||||
|
name="Brainfuck",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="brainfuck_app",
|
||||||
|
requires=[
|
||||||
|
"storage",
|
||||||
|
"gui",
|
||||||
|
],
|
||||||
|
stack_size=8 * 1024,
|
||||||
|
fap_icon="bfico.png",
|
||||||
|
fap_category="Misc",
|
||||||
|
fap_icon_assets="icons",
|
||||||
|
fap_icon_assets_symbol="brainfuck",
|
||||||
|
)
|
||||||
BIN
applications/plugins/brainfuck/bfico.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
149
applications/plugins/brainfuck/brainfuck.c
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include "brainfuck_i.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Due to the lack of documentation on the flipper i copied the picopass app,
|
||||||
|
ripped its insides out and used its hollow corpse to build this app inside of.
|
||||||
|
|
||||||
|
i dont know how this stuff works and after 6 hours of trying to learn it, i dont care
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool brainfuck_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
return scene_manager_handle_custom_event(brainfuck->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
return scene_manager_handle_back_event(brainfuck->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
BFApp* brainfuck_alloc() {
|
||||||
|
BFApp* brainfuck = malloc(sizeof(BFApp));
|
||||||
|
|
||||||
|
brainfuck->dataSize = 0;
|
||||||
|
brainfuck->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
brainfuck->scene_manager = scene_manager_alloc(&brainfuck_scene_handlers, brainfuck);
|
||||||
|
view_dispatcher_enable_queue(brainfuck->view_dispatcher);
|
||||||
|
view_dispatcher_set_event_callback_context(brainfuck->view_dispatcher, brainfuck);
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
brainfuck->view_dispatcher, brainfuck_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
brainfuck->view_dispatcher, brainfuck_back_event_callback);
|
||||||
|
|
||||||
|
// Open GUI record
|
||||||
|
brainfuck->gui = furi_record_open(RECORD_GUI);
|
||||||
|
view_dispatcher_attach_to_gui(
|
||||||
|
brainfuck->view_dispatcher, brainfuck->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
// Open Notification record
|
||||||
|
brainfuck->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
brainfuck->submenu = submenu_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
brainfuck->view_dispatcher, brainfuckViewMenu, submenu_get_view(brainfuck->submenu));
|
||||||
|
|
||||||
|
// Popup
|
||||||
|
brainfuck->popup = popup_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
brainfuck->view_dispatcher, brainfuckViewPopup, popup_get_view(brainfuck->popup));
|
||||||
|
|
||||||
|
// Text Input
|
||||||
|
brainfuck->text_input = text_input_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
brainfuck->view_dispatcher,
|
||||||
|
brainfuckViewTextInput,
|
||||||
|
text_input_get_view(brainfuck->text_input));
|
||||||
|
|
||||||
|
// Textbox
|
||||||
|
brainfuck->text_box = text_box_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
brainfuck->view_dispatcher, brainfuckViewTextBox, text_box_get_view(brainfuck->text_box));
|
||||||
|
brainfuck->text_box_store = furi_string_alloc();
|
||||||
|
|
||||||
|
// Dev environment
|
||||||
|
brainfuck->BF_dev_env = bf_dev_env_alloc(brainfuck);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
brainfuck->view_dispatcher, brainfuckViewDev, bf_dev_env_get_view(brainfuck->BF_dev_env));
|
||||||
|
|
||||||
|
// File path
|
||||||
|
brainfuck->BF_file_path = furi_string_alloc();
|
||||||
|
|
||||||
|
return brainfuck;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_free(BFApp* brainfuck) {
|
||||||
|
furi_assert(brainfuck);
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewMenu);
|
||||||
|
submenu_free(brainfuck->submenu);
|
||||||
|
|
||||||
|
// Popup
|
||||||
|
view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewPopup);
|
||||||
|
popup_free(brainfuck->popup);
|
||||||
|
|
||||||
|
// TextInput
|
||||||
|
view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewTextInput);
|
||||||
|
text_input_free(brainfuck->text_input);
|
||||||
|
|
||||||
|
// TextBox
|
||||||
|
view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewTextBox);
|
||||||
|
text_box_free(brainfuck->text_box);
|
||||||
|
furi_string_free(brainfuck->text_box_store);
|
||||||
|
|
||||||
|
//dev env
|
||||||
|
view_dispatcher_remove_view(brainfuck->view_dispatcher, brainfuckViewDev);
|
||||||
|
bf_dev_env_free(brainfuck->BF_dev_env);
|
||||||
|
|
||||||
|
// View Dispatcher
|
||||||
|
view_dispatcher_free(brainfuck->view_dispatcher);
|
||||||
|
|
||||||
|
// Scene Manager
|
||||||
|
scene_manager_free(brainfuck->scene_manager);
|
||||||
|
|
||||||
|
// GUI
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
brainfuck->gui = NULL;
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
brainfuck->notifications = NULL;
|
||||||
|
|
||||||
|
free(brainfuck);
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_show_loading_popup(void* context, bool show) {
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
|
||||||
|
|
||||||
|
if(show) {
|
||||||
|
// Raise timer priority so that animations can play
|
||||||
|
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
|
||||||
|
view_dispatcher_switch_to_view(brainfuck->view_dispatcher, brainfuckViewLoading);
|
||||||
|
} else {
|
||||||
|
// Restore default timer priority
|
||||||
|
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t brainfuck_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
BFApp* brainfuck = brainfuck_alloc();
|
||||||
|
if(!brainfuck) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
storage_simply_mkdir(storage, "/ext/brainfuck");
|
||||||
|
|
||||||
|
scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneStart);
|
||||||
|
|
||||||
|
view_dispatcher_run(brainfuck->view_dispatcher);
|
||||||
|
|
||||||
|
brainfuck_free(brainfuck);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
3
applications/plugins/brainfuck/brainfuck.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct BFApp BFApp;
|
||||||
89
applications/plugins/brainfuck/brainfuck_i.h
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct BFDevEnv BFDevEnv;
|
||||||
|
typedef struct BFExecEnv BFExecEnv;
|
||||||
|
typedef unsigned char byte;
|
||||||
|
|
||||||
|
#include "brainfuck.h"
|
||||||
|
#include "worker.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <gui/modules/popup.h>
|
||||||
|
#include <gui/modules/loading.h>
|
||||||
|
#include <gui/modules/text_input.h>
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
#include <gui/modules/text_box.h>
|
||||||
|
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
|
||||||
|
#include "scenes/brainfuck_scene.h"
|
||||||
|
|
||||||
|
#include "views/bf_dev_env.h"
|
||||||
|
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <lib/toolbox/path.h>
|
||||||
|
#include <brainfuck_icons.h>
|
||||||
|
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <stream/stream.h>
|
||||||
|
#include <stream/buffered_file_stream.h>
|
||||||
|
#include <toolbox/stream/file_stream.h>
|
||||||
|
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <notification/notification_app.h>
|
||||||
|
|
||||||
|
#define BF_INST_BUFFER_SIZE 2048
|
||||||
|
#define BF_OUTPUT_SIZE 512
|
||||||
|
#define BF_STACK_INITIAL_SIZE 128
|
||||||
|
#define BF_INPUT_BUFFER_SIZE 64
|
||||||
|
#define BF_STACK_STEP_SIZE 32
|
||||||
|
|
||||||
|
enum brainfuckCustomEvent {
|
||||||
|
// Reserve first 100 events for button types and indexes, starting from 0
|
||||||
|
brainfuckCustomEventReserved = 100,
|
||||||
|
|
||||||
|
brainfuckCustomEventViewExit,
|
||||||
|
brainfuckCustomEventWorkerExit,
|
||||||
|
brainfuckCustomEventByteInputDone,
|
||||||
|
brainfuckCustomEventTextInputDone,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeTick,
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
struct BFApp {
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Gui* gui;
|
||||||
|
NotificationApp* notifications;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
Submenu* submenu;
|
||||||
|
Popup* popup;
|
||||||
|
TextInput* text_input;
|
||||||
|
TextBox* text_box;
|
||||||
|
FuriString* text_box_store;
|
||||||
|
FuriString* BF_file_path;
|
||||||
|
BFDevEnv* BF_dev_env;
|
||||||
|
int dataSize;
|
||||||
|
char dataBuffer[BF_INST_BUFFER_SIZE];
|
||||||
|
char inputBuffer[BF_INPUT_BUFFER_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
brainfuckViewMenu,
|
||||||
|
brainfuckViewPopup,
|
||||||
|
brainfuckViewLoading,
|
||||||
|
brainfuckViewTextInput,
|
||||||
|
brainfuckViewTextBox,
|
||||||
|
brainfuckViewWidget,
|
||||||
|
brainfuckViewDev,
|
||||||
|
brainfuckViewExec,
|
||||||
|
} brainfuckView;
|
||||||
BIN
applications/plugins/brainfuck/icons/ButtonRightSmall_3x5.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeyBackspace_24x11.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeyInputSelected_30x11.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeyInput_30x11.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeyRunSelected_24x11.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeyRun_24x11.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/brainfuck/icons/KeySaveSelected_24x11.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/plugins/brainfuck/icons/KeySave_24x11.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/plugins/brainfuck/icons/bfico.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
30
applications/plugins/brainfuck/scenes/brainfuck_scene.c
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "brainfuck_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const brainfuck_on_enter_handlers[])(void*) = {
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const brainfuck_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const brainfuck_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers brainfuck_scene_handlers = {
|
||||||
|
.on_enter_handlers = brainfuck_on_enter_handlers,
|
||||||
|
.on_event_handlers = brainfuck_on_event_handlers,
|
||||||
|
.on_exit_handlers = brainfuck_on_exit_handlers,
|
||||||
|
.scene_num = brainfuckSceneNum,
|
||||||
|
};
|
||||||
29
applications/plugins/brainfuck/scenes/brainfuck_scene.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) brainfuckScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
brainfuckSceneNum,
|
||||||
|
} brainfuckScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers brainfuck_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||||
|
#include "brainfuck_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ADD_SCENE(brainfuck, start, Start)
|
||||||
|
ADD_SCENE(brainfuck, file_select, FileSelect)
|
||||||
|
ADD_SCENE(brainfuck, file_create, FileCreate)
|
||||||
|
ADD_SCENE(brainfuck, dev_env, DevEnv)
|
||||||
|
ADD_SCENE(brainfuck, exec_env, ExecEnv)
|
||||||
|
ADD_SCENE(brainfuck, set_input, SetInput)
|
||||||
16
applications/plugins/brainfuck/scenes/brainfuck_scene_dev.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
|
||||||
|
void brainfuck_scene_dev_env_on_enter(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewDev);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_dev_env_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_dev_env_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
16
applications/plugins/brainfuck/scenes/brainfuck_scene_exec.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
|
||||||
|
void brainfuck_scene_exec_env_on_enter(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_exec_env_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_exec_env_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
|
||||||
|
void file_name_text_input_callback(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, brainfuckCustomEventTextInputDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmpName[64] = {};
|
||||||
|
byte empty[1] = {0x00};
|
||||||
|
void brainfuck_scene_file_create_on_enter(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
TextInput* text_input = app->text_input;
|
||||||
|
|
||||||
|
text_input_set_header_text(text_input, "New script name");
|
||||||
|
text_input_set_result_callback(
|
||||||
|
text_input, file_name_text_input_callback, app, tmpName, 64, true);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_file_create_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BFApp* app = context;
|
||||||
|
UNUSED(app);
|
||||||
|
|
||||||
|
bool consumed = false;
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == brainfuckCustomEventTextInputDone) {
|
||||||
|
furi_string_cat_printf(app->BF_file_path, "/ext/brainfuck/%s.b", tmpName);
|
||||||
|
|
||||||
|
//remove old file
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
storage_simply_remove(storage, furi_string_get_cstr(app->BF_file_path));
|
||||||
|
|
||||||
|
//save new file
|
||||||
|
Stream* stream = buffered_file_stream_alloc(storage);
|
||||||
|
buffered_file_stream_open(
|
||||||
|
stream, furi_string_get_cstr(app->BF_file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||||
|
stream_write(stream, (const uint8_t*)empty, 1);
|
||||||
|
buffered_file_stream_close(stream);
|
||||||
|
|
||||||
|
//scene_manager_next_scene(app->scene_manager, brainfuckSceneFileSelect);
|
||||||
|
scene_manager_next_scene(app->scene_manager, brainfuckSceneDevEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_file_create_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
|
||||||
|
void brainfuck_scene_file_select_on_enter(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
|
||||||
|
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||||
|
FuriString* path;
|
||||||
|
path = furi_string_alloc();
|
||||||
|
furi_string_set(path, "/ext/brainfuck");
|
||||||
|
|
||||||
|
DialogsFileBrowserOptions browser_options;
|
||||||
|
dialog_file_browser_set_basic_options(&browser_options, ".b", &I_bfico);
|
||||||
|
browser_options.base_path = "/ext/brainfuck";
|
||||||
|
browser_options.hide_ext = false;
|
||||||
|
|
||||||
|
bool selected = dialog_file_browser_show(dialogs, path, path, &browser_options);
|
||||||
|
|
||||||
|
if(selected) {
|
||||||
|
furi_string_set(app->BF_file_path, path);
|
||||||
|
scene_manager_next_scene(app->scene_manager, brainfuckSceneDevEnv);
|
||||||
|
} else {
|
||||||
|
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, brainfuckSceneStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_file_select_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
|
||||||
|
void set_input_text_input_callback(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, brainfuckCustomEventTextInputDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_set_input_on_enter(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
TextInput* text_input = app->text_input;
|
||||||
|
|
||||||
|
text_input_set_header_text(text_input, "Edit input buffer");
|
||||||
|
text_input_set_result_callback(
|
||||||
|
text_input, set_input_text_input_callback, app, app->inputBuffer, 64, true);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, brainfuckViewTextInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_set_input_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BFApp* app = context;
|
||||||
|
|
||||||
|
bool consumed = false;
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == brainfuckCustomEventTextInputDone) {
|
||||||
|
scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
app->scene_manager, brainfuckSceneDevEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_set_input_on_exit(void* context) {
|
||||||
|
BFApp* app = context;
|
||||||
|
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, brainfuckSceneDevEnv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#include "../brainfuck_i.h"
|
||||||
|
enum SubmenuIndex {
|
||||||
|
SubmenuIndexNew,
|
||||||
|
SubmenuIndexOpen,
|
||||||
|
SubmenuIndexAbout,
|
||||||
|
};
|
||||||
|
|
||||||
|
void brainfuck_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
view_dispatcher_send_custom_event(brainfuck->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
void brainfuck_scene_start_on_enter(void* context) {
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
|
||||||
|
Submenu* submenu = brainfuck->submenu;
|
||||||
|
submenu_add_item(
|
||||||
|
submenu, "New", SubmenuIndexNew, brainfuck_scene_start_submenu_callback, brainfuck);
|
||||||
|
submenu_add_item(
|
||||||
|
submenu, "Open", SubmenuIndexOpen, brainfuck_scene_start_submenu_callback, brainfuck);
|
||||||
|
submenu_add_item(
|
||||||
|
submenu, "About", SubmenuIndexAbout, brainfuck_scene_start_submenu_callback, brainfuck);
|
||||||
|
|
||||||
|
submenu_set_selected_item(
|
||||||
|
submenu, scene_manager_get_scene_state(brainfuck->scene_manager, brainfuckSceneStart));
|
||||||
|
view_dispatcher_switch_to_view(brainfuck->view_dispatcher, brainfuckViewMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool brainfuck_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == SubmenuIndexNew) {
|
||||||
|
scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneFileCreate);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == SubmenuIndexOpen) {
|
||||||
|
scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneFileSelect);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == SubmenuIndexAbout) {
|
||||||
|
text_box_set_text(
|
||||||
|
brainfuck->text_box,
|
||||||
|
"FlipperBrainfuck\n\nAn F0 brainfuck intepretor\nBy github.com/Nymda");
|
||||||
|
scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneExecEnv);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
scene_manager_set_scene_state(brainfuck->scene_manager, brainfuckSceneStart, event.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void brainfuck_scene_start_on_exit(void* context) {
|
||||||
|
BFApp* brainfuck = context;
|
||||||
|
submenu_reset(brainfuck->submenu);
|
||||||
|
}
|
||||||
419
applications/plugins/brainfuck/views/bf_dev_env.c
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
#include "bf_dev_env.h"
|
||||||
|
#include <gui/elements.h>
|
||||||
|
|
||||||
|
typedef struct BFDevEnv {
|
||||||
|
View* view;
|
||||||
|
DevEnvOkCallback callback;
|
||||||
|
void* context;
|
||||||
|
BFApp* appDev;
|
||||||
|
} BFDevEnv;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t row;
|
||||||
|
uint32_t col;
|
||||||
|
} BFDevEnvModel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int up;
|
||||||
|
int down;
|
||||||
|
int left;
|
||||||
|
int right;
|
||||||
|
} bMapping;
|
||||||
|
|
||||||
|
static bool bf_dev_process_up(BFDevEnv* devEnv);
|
||||||
|
static bool bf_dev_process_down(BFDevEnv* devEnv);
|
||||||
|
static bool bf_dev_process_left(BFDevEnv* devEnv);
|
||||||
|
static bool bf_dev_process_right(BFDevEnv* devEnv);
|
||||||
|
static bool bf_dev_process_ok(BFDevEnv* devEnv, InputEvent* event);
|
||||||
|
|
||||||
|
BFApp* appDev;
|
||||||
|
FuriThread* workerThread;
|
||||||
|
|
||||||
|
char bfChars[9] = {'<', '>', '[', ']', '+', '-', '.', ',', 0x00};
|
||||||
|
|
||||||
|
int selectedButton = 0;
|
||||||
|
int saveNotifyCountdown = 0;
|
||||||
|
int execCountdown = 0;
|
||||||
|
|
||||||
|
char dspLine0[25] = {};
|
||||||
|
char dspLine1[25] = {};
|
||||||
|
char dspLine2[25] = {};
|
||||||
|
|
||||||
|
static bMapping buttonMappings[12] = {
|
||||||
|
{8, 8, 7, 1}, //0
|
||||||
|
{8, 8, 0, 2}, //1
|
||||||
|
{9, 9, 1, 3}, //2
|
||||||
|
{9, 9, 2, 4}, //3
|
||||||
|
{10, 10, 3, 5}, //4
|
||||||
|
{10, 10, 4, 6}, //5
|
||||||
|
{11, 11, 5, 7}, //6
|
||||||
|
{11, 11, 6, 0}, //7
|
||||||
|
|
||||||
|
{0, 0, 11, 9}, //8
|
||||||
|
{3, 3, 8, 10}, //9
|
||||||
|
{5, 5, 9, 11}, //10
|
||||||
|
{6, 6, 10, 8} //11
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BT_X 14
|
||||||
|
#define BT_Y 14
|
||||||
|
static void bf_dev_draw_button(Canvas* canvas, int x, int y, bool selected, const char* lbl) {
|
||||||
|
UNUSED(lbl);
|
||||||
|
|
||||||
|
if(selected) {
|
||||||
|
canvas_draw_rbox(canvas, x, y, BT_X, BT_Y, 3);
|
||||||
|
canvas_invert_color(canvas);
|
||||||
|
canvas_set_font(canvas, FontBatteryPercent);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl);
|
||||||
|
canvas_invert_color(canvas);
|
||||||
|
} else {
|
||||||
|
canvas_draw_rbox(canvas, x, y, BT_X, BT_Y, 3);
|
||||||
|
canvas_invert_color(canvas);
|
||||||
|
canvas_draw_rbox(canvas, x + 2, y - 1, BT_X - 2, BT_Y - 1, 3);
|
||||||
|
canvas_invert_color(canvas);
|
||||||
|
canvas_draw_rframe(canvas, x, y, BT_X, BT_Y, 3);
|
||||||
|
canvas_set_font(canvas, FontBatteryPercent);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas, x + (BT_X / 2), y + (BT_Y / 2) - 1, AlignCenter, AlignCenter, lbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bf_save_changes() {
|
||||||
|
//remove old file
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
storage_simply_remove(storage, furi_string_get_cstr(appDev->BF_file_path));
|
||||||
|
|
||||||
|
//save new file
|
||||||
|
Stream* stream = buffered_file_stream_alloc(storage);
|
||||||
|
buffered_file_stream_open(
|
||||||
|
stream, furi_string_get_cstr(appDev->BF_file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||||
|
stream_write(stream, (const uint8_t*)appDev->dataBuffer, appDev->dataSize);
|
||||||
|
buffered_file_stream_close(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bf_dev_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
UNUSED(_model);
|
||||||
|
|
||||||
|
if(saveNotifyCountdown > 0) {
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "SAVED");
|
||||||
|
saveNotifyCountdown--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bf_dev_draw_button(canvas, 1, 36, (selectedButton == 0), "+"); //T 0
|
||||||
|
bf_dev_draw_button(canvas, 17, 36, (selectedButton == 1), "-"); //T 1
|
||||||
|
bf_dev_draw_button(canvas, 33, 36, (selectedButton == 2), "<"); //T 2
|
||||||
|
bf_dev_draw_button(canvas, 49, 36, (selectedButton == 3), ">"); //T 3
|
||||||
|
bf_dev_draw_button(canvas, 65, 36, (selectedButton == 4), "["); //B 0
|
||||||
|
bf_dev_draw_button(canvas, 81, 36, (selectedButton == 5), "]"); //B 1
|
||||||
|
bf_dev_draw_button(canvas, 97, 36, (selectedButton == 6), "."); //B 2
|
||||||
|
bf_dev_draw_button(canvas, 113, 36, (selectedButton == 7), ","); //B 3
|
||||||
|
|
||||||
|
//backspace, input, run, save
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
1,
|
||||||
|
52,
|
||||||
|
(selectedButton == 8) ? &I_KeyBackspaceSelected_24x11 : &I_KeyBackspace_24x11);
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas, 45, 52, (selectedButton == 9) ? &I_KeyInputSelected_30x11 : &I_KeyInput_30x11);
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas, 77, 52, (selectedButton == 10) ? &I_KeyRunSelected_24x11 : &I_KeyRun_24x11);
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas, 103, 52, (selectedButton == 11) ? &I_KeySaveSelected_24x11 : &I_KeySave_24x11);
|
||||||
|
|
||||||
|
if(saveNotifyCountdown > 0) {
|
||||||
|
canvas_draw_icon(canvas, 98, 54, &I_ButtonRightSmall_3x5);
|
||||||
|
saveNotifyCountdown--;
|
||||||
|
}
|
||||||
|
|
||||||
|
//textbox
|
||||||
|
//grossly overcomplicated. not fixing it.
|
||||||
|
canvas_draw_rframe(canvas, 1, 1, 126, 33, 2);
|
||||||
|
canvas_set_font(canvas, FontBatteryPercent);
|
||||||
|
|
||||||
|
int dbOffset = 0;
|
||||||
|
if(appDev->dataSize > 72) {
|
||||||
|
dbOffset = (appDev->dataSize - 72);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(dspLine0, 0x00, 25);
|
||||||
|
memset(dspLine1, 0x00, 25);
|
||||||
|
memset(dspLine2, 0x00, 25);
|
||||||
|
|
||||||
|
int tpM = 0;
|
||||||
|
int tp0 = 0;
|
||||||
|
int tp1 = 0;
|
||||||
|
int tp2 = 0;
|
||||||
|
|
||||||
|
for(int p = dbOffset; p < appDev->dataSize; p++) {
|
||||||
|
if(tpM < 24 * 1) {
|
||||||
|
dspLine0[tp0] = appDev->dataBuffer[p];
|
||||||
|
tp0++;
|
||||||
|
} else if(tpM < 24 * 2) {
|
||||||
|
dspLine1[tp1] = appDev->dataBuffer[p];
|
||||||
|
tp1++;
|
||||||
|
} else if(tpM < 24 * 3) {
|
||||||
|
dspLine2[tp2] = appDev->dataBuffer[p];
|
||||||
|
tp2++;
|
||||||
|
}
|
||||||
|
tpM++;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_draw_str_aligned(canvas, 3, 8, AlignLeft, AlignCenter, dspLine0);
|
||||||
|
canvas_draw_str_aligned(canvas, 3, 17, AlignLeft, AlignCenter, dspLine1);
|
||||||
|
canvas_draw_str_aligned(canvas, 3, 26, AlignLeft, AlignCenter, dspLine2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BFDevEnv* devEnv = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
if(event->key == InputKeyRight) {
|
||||||
|
consumed = bf_dev_process_right(devEnv);
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
consumed = bf_dev_process_left(devEnv);
|
||||||
|
} else if(event->key == InputKeyUp) {
|
||||||
|
consumed = bf_dev_process_up(devEnv);
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
consumed = bf_dev_process_down(devEnv);
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyOk) {
|
||||||
|
consumed = bf_dev_process_ok(devEnv, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_process_up(BFDevEnv* devEnv) {
|
||||||
|
UNUSED(devEnv);
|
||||||
|
selectedButton = buttonMappings[selectedButton].up;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_process_down(BFDevEnv* devEnv) {
|
||||||
|
UNUSED(devEnv);
|
||||||
|
selectedButton = buttonMappings[selectedButton].down;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_process_left(BFDevEnv* devEnv) {
|
||||||
|
UNUSED(devEnv);
|
||||||
|
selectedButton = buttonMappings[selectedButton].left;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_process_right(BFDevEnv* devEnv) {
|
||||||
|
UNUSED(devEnv);
|
||||||
|
selectedButton = buttonMappings[selectedButton].right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bf_dev_process_ok(BFDevEnv* devEnv, InputEvent* event) {
|
||||||
|
UNUSED(devEnv);
|
||||||
|
UNUSED(event);
|
||||||
|
|
||||||
|
if(event->type != InputTypePress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(selectedButton) {
|
||||||
|
case 0: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '+';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '-';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '<';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '>';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '[';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = ']';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = '.';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7: {
|
||||||
|
if(appDev->dataSize < BF_INST_BUFFER_SIZE) {
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = ',';
|
||||||
|
appDev->dataSize++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8: {
|
||||||
|
if(appDev->dataSize > 0) {
|
||||||
|
appDev->dataSize--;
|
||||||
|
appDev->dataBuffer[appDev->dataSize] = (uint32_t)0x00;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9: {
|
||||||
|
scene_manager_next_scene(appDev->scene_manager, brainfuckSceneSetInput);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10: {
|
||||||
|
if(getStatus() != 0) {
|
||||||
|
killThread();
|
||||||
|
furi_thread_join(workerThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
bf_save_changes();
|
||||||
|
|
||||||
|
initWorker(appDev);
|
||||||
|
text_box_set_focus(appDev->text_box, TextBoxFocusEnd);
|
||||||
|
text_box_set_text(appDev->text_box, workerGetOutput());
|
||||||
|
|
||||||
|
workerThread = furi_thread_alloc_ex("Worker", 2048, (void*)beginWorker, NULL);
|
||||||
|
furi_thread_start(workerThread);
|
||||||
|
|
||||||
|
scene_manager_next_scene(appDev->scene_manager, brainfuckSceneExecEnv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 11: {
|
||||||
|
bf_save_changes();
|
||||||
|
saveNotifyCountdown = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool consumed = false;
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bf_dev_enter_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BFDevEnv* devEnv = context;
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
devEnv->view,
|
||||||
|
BFDevEnvModel * model,
|
||||||
|
{
|
||||||
|
model->col = 0;
|
||||||
|
model->row = 0;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
appDev = devEnv->appDev;
|
||||||
|
selectedButton = 0;
|
||||||
|
|
||||||
|
//exit the running thread if required
|
||||||
|
if(getStatus() != 0) {
|
||||||
|
killThread();
|
||||||
|
furi_thread_join(workerThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
//clear the bf instruction buffer
|
||||||
|
memset(appDev->dataBuffer, 0x00, BF_INST_BUFFER_SIZE * sizeof(char));
|
||||||
|
|
||||||
|
//open the file
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
Stream* stream = buffered_file_stream_alloc(storage);
|
||||||
|
buffered_file_stream_open(
|
||||||
|
stream, furi_string_get_cstr(appDev->BF_file_path), FSAM_READ, FSOM_OPEN_EXISTING);
|
||||||
|
|
||||||
|
//read into the buffer
|
||||||
|
appDev->dataSize = stream_size(stream);
|
||||||
|
stream_read(stream, (uint8_t*)appDev->dataBuffer, appDev->dataSize);
|
||||||
|
buffered_file_stream_close(stream);
|
||||||
|
|
||||||
|
//replaces any invalid characters with an underscore. strips out newlines, comments, etc
|
||||||
|
for(int i = 0; i < appDev->dataSize; i++) {
|
||||||
|
if(!strchr(bfChars, appDev->dataBuffer[i])) {
|
||||||
|
appDev->dataBuffer[i] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//find the end of the file to begin editing
|
||||||
|
int tptr = 0;
|
||||||
|
while(appDev->dataBuffer[tptr] != 0x00) {
|
||||||
|
tptr++;
|
||||||
|
}
|
||||||
|
appDev->dataSize = tptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BFDevEnv* bf_dev_env_alloc(BFApp* appDev) {
|
||||||
|
BFDevEnv* devEnv = malloc(sizeof(BFDevEnv));
|
||||||
|
|
||||||
|
devEnv->view = view_alloc();
|
||||||
|
devEnv->appDev = appDev;
|
||||||
|
view_allocate_model(devEnv->view, ViewModelTypeLocking, sizeof(BFDevEnvModel));
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
devEnv->view,
|
||||||
|
BFDevEnvModel * model,
|
||||||
|
{
|
||||||
|
model->col = 0;
|
||||||
|
model->row = 0;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
view_set_context(devEnv->view, devEnv);
|
||||||
|
view_set_draw_callback(devEnv->view, bf_dev_draw_callback);
|
||||||
|
view_set_input_callback(devEnv->view, bf_dev_input_callback);
|
||||||
|
view_set_enter_callback(devEnv->view, bf_dev_enter_callback);
|
||||||
|
return devEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bf_dev_env_free(BFDevEnv* devEnv) {
|
||||||
|
if(getStatus() != 0) {
|
||||||
|
killThread();
|
||||||
|
furi_thread_join(workerThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_assert(devEnv);
|
||||||
|
view_free(devEnv->view);
|
||||||
|
free(devEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* bf_dev_env_get_view(BFDevEnv* devEnv) {
|
||||||
|
furi_assert(devEnv);
|
||||||
|
return devEnv->view;
|
||||||
|
}
|
||||||
15
applications/plugins/brainfuck/views/bf_dev_env.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../brainfuck_i.h"
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
typedef void (*DevEnvOkCallback)(InputType type, void* context);
|
||||||
|
|
||||||
|
BFDevEnv* bf_dev_env_alloc(BFApp* application);
|
||||||
|
|
||||||
|
void bf_dev_set_file_path(FuriString* path);
|
||||||
|
|
||||||
|
void bf_dev_env_free(BFDevEnv* devEnv);
|
||||||
|
|
||||||
|
View* bf_dev_env_get_view(BFDevEnv* devEnv);
|
||||||
|
|
||||||
|
void bf_dev_env_set_ok(BFDevEnv* devEnv, DevEnvOkCallback callback, void* context);
|
||||||
276
applications/plugins/brainfuck/worker.c
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
#include "worker.h"
|
||||||
|
|
||||||
|
bool killswitch = false;
|
||||||
|
|
||||||
|
int status = 0; //0: idle, 1: running, 2: failure
|
||||||
|
|
||||||
|
char* inst = 0;
|
||||||
|
int instCount = 0;
|
||||||
|
int instPtr = 0;
|
||||||
|
int runOpCount = 0;
|
||||||
|
|
||||||
|
char* wOutput = 0;
|
||||||
|
int wOutputPtr = 0;
|
||||||
|
|
||||||
|
char* wInput = 0;
|
||||||
|
int wInputPtr = 0;
|
||||||
|
|
||||||
|
uint8_t* bfStack = 0;
|
||||||
|
int stackPtr = 0;
|
||||||
|
int stackSize = BF_STACK_INITIAL_SIZE;
|
||||||
|
int stackSizeReal = 0;
|
||||||
|
|
||||||
|
BFApp* wrkrApp = 0;
|
||||||
|
|
||||||
|
void killThread() {
|
||||||
|
killswitch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateInstPtr() {
|
||||||
|
if(instPtr > instCount || instPtr < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateStackPtr() {
|
||||||
|
if(stackPtr > stackSize || stackPtr < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* workerGetOutput() {
|
||||||
|
return wOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStackSize() {
|
||||||
|
return stackSizeReal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getOpCount() {
|
||||||
|
return runOpCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initWorker(BFApp* app) {
|
||||||
|
wrkrApp = app;
|
||||||
|
|
||||||
|
//rebuild output
|
||||||
|
if(wOutput) {
|
||||||
|
free(wOutput);
|
||||||
|
}
|
||||||
|
wOutput = (char*)malloc(BF_OUTPUT_SIZE);
|
||||||
|
wOutputPtr = 0;
|
||||||
|
|
||||||
|
//rebuild stack
|
||||||
|
if(bfStack) {
|
||||||
|
free(bfStack);
|
||||||
|
}
|
||||||
|
bfStack = (uint8_t*)malloc(BF_STACK_INITIAL_SIZE);
|
||||||
|
memset(bfStack, 0x00, BF_STACK_INITIAL_SIZE);
|
||||||
|
stackSize = BF_STACK_INITIAL_SIZE;
|
||||||
|
stackSizeReal = 0;
|
||||||
|
stackPtr = 0;
|
||||||
|
|
||||||
|
//set instructions
|
||||||
|
inst = wrkrApp->dataBuffer;
|
||||||
|
instCount = wrkrApp->dataSize;
|
||||||
|
instPtr = 0;
|
||||||
|
runOpCount = 0;
|
||||||
|
|
||||||
|
//set input
|
||||||
|
wInput = wrkrApp->inputBuffer;
|
||||||
|
wInputPtr = 0;
|
||||||
|
|
||||||
|
//set status
|
||||||
|
status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rShift() {
|
||||||
|
runOpCount++;
|
||||||
|
stackPtr++;
|
||||||
|
if(!validateStackPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(stackPtr > stackSize) {
|
||||||
|
stackSize += BF_STACK_STEP_SIZE;
|
||||||
|
void* tmp = realloc(bfStack, stackSize);
|
||||||
|
|
||||||
|
if(!tmp) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset((tmp + stackSize) - BF_STACK_STEP_SIZE, 0x00, BF_STACK_STEP_SIZE);
|
||||||
|
bfStack = (uint8_t*)tmp;
|
||||||
|
};
|
||||||
|
if(stackPtr > stackSizeReal) {
|
||||||
|
stackSizeReal = stackPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lShift() {
|
||||||
|
runOpCount++;
|
||||||
|
stackPtr--;
|
||||||
|
if(!validateStackPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void inc() {
|
||||||
|
runOpCount++;
|
||||||
|
if(!validateStackPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bfStack[stackPtr]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dec() {
|
||||||
|
runOpCount++;
|
||||||
|
if(!validateStackPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bfStack[stackPtr]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print() {
|
||||||
|
runOpCount++;
|
||||||
|
wOutput[wOutputPtr] = bfStack[stackPtr];
|
||||||
|
wOutputPtr++;
|
||||||
|
if(wOutputPtr > (BF_OUTPUT_SIZE - 1)) {
|
||||||
|
wOutputPtr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void input() {
|
||||||
|
runOpCount++;
|
||||||
|
|
||||||
|
bfStack[stackPtr] = (uint8_t)wInput[wInputPtr];
|
||||||
|
if(wInput[wInputPtr] == 0x00 || wInputPtr >= 64) {
|
||||||
|
wInputPtr = 0;
|
||||||
|
} else {
|
||||||
|
wInputPtr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
runOpCount++;
|
||||||
|
if(bfStack[stackPtr] == 0) {
|
||||||
|
int loopCount = 1;
|
||||||
|
while(loopCount > 0) {
|
||||||
|
instPtr++;
|
||||||
|
if(!validateInstPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(inst[instPtr] == '[') {
|
||||||
|
loopCount++;
|
||||||
|
} else if(inst[instPtr] == ']') {
|
||||||
|
loopCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void endLoop() {
|
||||||
|
runOpCount++;
|
||||||
|
if(bfStack[stackPtr] != 0) {
|
||||||
|
int loopCount = 1;
|
||||||
|
while(loopCount > 0) {
|
||||||
|
instPtr--;
|
||||||
|
if(!validateInstPtr()) {
|
||||||
|
status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(inst[instPtr] == ']') {
|
||||||
|
loopCount++;
|
||||||
|
} else if(inst[instPtr] == '[') {
|
||||||
|
loopCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const NotificationSequence led_on = {
|
||||||
|
&message_blue_255,
|
||||||
|
&message_do_not_reset,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NotificationSequence led_off = {
|
||||||
|
&message_green_0,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void beginWorker() {
|
||||||
|
status = 1;
|
||||||
|
while(inst[instPtr] != 0x00) {
|
||||||
|
if(runOpCount % 500 == 0) {
|
||||||
|
text_box_set_text(wrkrApp->text_box, workerGetOutput());
|
||||||
|
notification_message(wrkrApp->notifications, &led_on);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status == 2) {
|
||||||
|
status = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(killswitch) {
|
||||||
|
status = 0;
|
||||||
|
killswitch = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(inst[instPtr]) {
|
||||||
|
case '>':
|
||||||
|
rShift();
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
lShift();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
inc();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
dec();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
print();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
input();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
loop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ']':
|
||||||
|
endLoop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
instPtr++;
|
||||||
|
if(!validateInstPtr()) {
|
||||||
|
status = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_message(wrkrApp->notifications, &led_off);
|
||||||
|
text_box_set_text(wrkrApp->text_box, workerGetOutput());
|
||||||
|
status = 0;
|
||||||
|
}
|
||||||
9
applications/plugins/brainfuck/worker.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "brainfuck_i.h"
|
||||||
|
|
||||||
|
void initWorker(BFApp* application);
|
||||||
|
char* workerGetOutput();
|
||||||
|
int getStackSize();
|
||||||
|
int getOpCount();
|
||||||
|
int getStatus();
|
||||||
|
void beginWorker();
|
||||||
|
void killThread();
|
||||||
13
applications/plugins/geigercounter/application.fam
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
App(
|
||||||
|
appid="Geiger_Coutner",
|
||||||
|
name="Geiger Counter",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="flipper_geiger_app",
|
||||||
|
cdefines=["APP_GEIGER"],
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
fap_icon="geiger.png",
|
||||||
|
fap_category="GPIO",
|
||||||
|
)
|
||||||
227
applications/plugins/geigercounter/flipper_geiger.c
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// CC0 1.0 Universal (CC0 1.0)
|
||||||
|
// Public Domain Dedication
|
||||||
|
// https://github.com/nmrr
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <furi_hal_random.h>
|
||||||
|
#include <furi_hal_pwm.h>
|
||||||
|
#include <furi_hal_power.h>
|
||||||
|
|
||||||
|
#define SCREEN_SIZE_X 128
|
||||||
|
#define SCREEN_SIZE_Y 64
|
||||||
|
|
||||||
|
// FOR J305 GEIGER TUBE
|
||||||
|
#define CONVERSION_FACTOR 0.0081
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeInput,
|
||||||
|
ClockEventTypeTick,
|
||||||
|
EventGPIO,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EventType type;
|
||||||
|
InputEvent input;
|
||||||
|
} EventApp;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t cps, cpm;
|
||||||
|
uint32_t line[SCREEN_SIZE_X / 2];
|
||||||
|
float coef;
|
||||||
|
uint8_t data;
|
||||||
|
} mutexStruct;
|
||||||
|
|
||||||
|
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||||
|
UNUSED(ctx);
|
||||||
|
|
||||||
|
mutexStruct displayStruct;
|
||||||
|
mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block((ValueMutex*)ctx);
|
||||||
|
memcpy(&displayStruct, geigerMutex, sizeof(mutexStruct));
|
||||||
|
release_mutex((ValueMutex*)ctx, geigerMutex);
|
||||||
|
|
||||||
|
char buffer[32];
|
||||||
|
if(displayStruct.data == 0)
|
||||||
|
snprintf(
|
||||||
|
buffer, sizeof(buffer), "%ld cps - %ld cpm", displayStruct.cps, displayStruct.cpm);
|
||||||
|
else if(displayStruct.data == 1)
|
||||||
|
snprintf(
|
||||||
|
buffer,
|
||||||
|
sizeof(buffer),
|
||||||
|
"%ld cps - %.2f uSv/h",
|
||||||
|
displayStruct.cps,
|
||||||
|
((double)displayStruct.cpm * (double)CONVERSION_FACTOR));
|
||||||
|
else
|
||||||
|
snprintf(
|
||||||
|
buffer,
|
||||||
|
sizeof(buffer),
|
||||||
|
"%ld cps - %.2f mSv/y",
|
||||||
|
displayStruct.cps,
|
||||||
|
(((double)displayStruct.cpm * (double)CONVERSION_FACTOR)) * (double)8.76);
|
||||||
|
|
||||||
|
for(int i = 0; i < SCREEN_SIZE_X; i += 2) {
|
||||||
|
float Y = SCREEN_SIZE_Y - (displayStruct.line[i / 2] * displayStruct.coef);
|
||||||
|
|
||||||
|
canvas_draw_line(canvas, i, Y, i, SCREEN_SIZE_Y);
|
||||||
|
canvas_draw_line(canvas, i + 1, Y, i + 1, SCREEN_SIZE_Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignBottom, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
EventApp event = {.type = EventTypeInput, .input = *input_event};
|
||||||
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_tick(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
|
||||||
|
uint32_t randomNumber = furi_hal_random_get();
|
||||||
|
randomNumber &= 0xFFF;
|
||||||
|
if(randomNumber == 0) randomNumber = 1;
|
||||||
|
|
||||||
|
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, randomNumber, 50);
|
||||||
|
|
||||||
|
FuriMessageQueue* queue = ctx;
|
||||||
|
EventApp event = {.type = ClockEventTypeTick};
|
||||||
|
furi_message_queue_put(queue, &event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpiocallback(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* queue = ctx;
|
||||||
|
EventApp event = {.type = EventGPIO};
|
||||||
|
furi_message_queue_put(queue, &event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t flipper_geiger_app() {
|
||||||
|
EventApp event;
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
|
||||||
|
|
||||||
|
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh);
|
||||||
|
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 5, 50);
|
||||||
|
|
||||||
|
mutexStruct mutexVal;
|
||||||
|
mutexVal.cps = 0;
|
||||||
|
mutexVal.cpm = 0;
|
||||||
|
for(int i = 0; i < SCREEN_SIZE_X / 2; i++) mutexVal.line[i] = 0;
|
||||||
|
mutexVal.coef = 1;
|
||||||
|
mutexVal.data = 0;
|
||||||
|
|
||||||
|
uint32_t counter = 0;
|
||||||
|
|
||||||
|
ValueMutex state_mutex;
|
||||||
|
init_mutex(&state_mutex, &mutexVal, sizeof(mutexVal));
|
||||||
|
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, draw_callback, &state_mutex);
|
||||||
|
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||||
|
|
||||||
|
furi_hal_gpio_add_int_callback(&gpio_ext_pa7, gpiocallback, event_queue);
|
||||||
|
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
|
||||||
|
furi_timer_start(timer, 1000);
|
||||||
|
|
||||||
|
// ENABLE 5V pin
|
||||||
|
furi_hal_power_enable_otg();
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
|
||||||
|
|
||||||
|
uint8_t screenRefresh = 0;
|
||||||
|
|
||||||
|
if(event_status == FuriStatusOk) {
|
||||||
|
if(event.type == EventTypeInput) {
|
||||||
|
if(event.input.key == InputKeyBack) {
|
||||||
|
break;
|
||||||
|
} else if(event.input.key == InputKeyOk && event.input.type == InputTypeShort) {
|
||||||
|
counter = 0;
|
||||||
|
mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
geigerMutex->cps = 0;
|
||||||
|
geigerMutex->cpm = 0;
|
||||||
|
for(int i = 0; i < SCREEN_SIZE_X / 2; i++) geigerMutex->line[i] = 0;
|
||||||
|
|
||||||
|
screenRefresh = 1;
|
||||||
|
release_mutex(&state_mutex, geigerMutex);
|
||||||
|
} else if((event.input.key == InputKeyLeft &&
|
||||||
|
event.input.type == InputTypeShort)) {
|
||||||
|
mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
if(geigerMutex->data != 0)
|
||||||
|
geigerMutex->data--;
|
||||||
|
else
|
||||||
|
geigerMutex->data = 2;
|
||||||
|
|
||||||
|
screenRefresh = 1;
|
||||||
|
release_mutex(&state_mutex, geigerMutex);
|
||||||
|
} else if((event.input.key == InputKeyRight &&
|
||||||
|
event.input.type == InputTypeShort)) {
|
||||||
|
mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
if(geigerMutex->data != 2)
|
||||||
|
geigerMutex->data++;
|
||||||
|
else
|
||||||
|
geigerMutex->data = 0;
|
||||||
|
|
||||||
|
screenRefresh = 1;
|
||||||
|
release_mutex(&state_mutex, geigerMutex);
|
||||||
|
}
|
||||||
|
} else if(event.type == ClockEventTypeTick) {
|
||||||
|
mutexStruct* geigerMutex = (mutexStruct*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
for(int i = 0; i < SCREEN_SIZE_X / 2 - 1; i++)
|
||||||
|
geigerMutex->line[SCREEN_SIZE_X / 2 - 1 - i] =
|
||||||
|
geigerMutex->line[SCREEN_SIZE_X / 2 - 2 - i];
|
||||||
|
|
||||||
|
geigerMutex->line[0] = counter;
|
||||||
|
geigerMutex->cps = counter;
|
||||||
|
counter = 0;
|
||||||
|
|
||||||
|
geigerMutex->cpm = geigerMutex->line[0];
|
||||||
|
uint32_t max = geigerMutex->line[0];
|
||||||
|
for(int i = 1; i < SCREEN_SIZE_X / 2; i++) {
|
||||||
|
if(i < 60) geigerMutex->cpm += geigerMutex->line[i];
|
||||||
|
if(geigerMutex->line[i] > max) max = geigerMutex->line[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(max > 0)
|
||||||
|
geigerMutex->coef = ((float)(SCREEN_SIZE_Y - 15)) / ((float)max);
|
||||||
|
else
|
||||||
|
geigerMutex->coef = 1;
|
||||||
|
|
||||||
|
screenRefresh = 1;
|
||||||
|
release_mutex(&state_mutex, geigerMutex);
|
||||||
|
} else if(event.type == EventGPIO) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(screenRefresh == 1) view_port_update(view_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_power_disable_otg();
|
||||||
|
|
||||||
|
furi_hal_gpio_disable_int_callback(&gpio_ext_pa7);
|
||||||
|
furi_hal_gpio_remove_int_callback(&gpio_ext_pa7);
|
||||||
|
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
|
||||||
|
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
delete_mutex(&state_mutex);
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_timer_free(timer);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
applications/plugins/geigercounter/geiger.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
applications/plugins/nightstand/ClockIcon.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
13
applications/plugins/nightstand/application.fam
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
App(
|
||||||
|
appid="NightstandClock",
|
||||||
|
name="Nightstand Clock",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="clock_app",
|
||||||
|
requires=["gui"],
|
||||||
|
icon="A_Clock_14",
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
fap_icon="ClockIcon.png",
|
||||||
|
fap_category="Misc",
|
||||||
|
order=81,
|
||||||
|
)
|
||||||
|
|
||||||
338
applications/plugins/nightstand/clock_app.c
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <notification/notification_app.h>
|
||||||
|
|
||||||
|
#include "clock_app.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a modified version of the default clock app intended for use overnight
|
||||||
|
Up / Down control the displays brightness. Down at brightness 0 turns the notification LED on and off.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int brightness = 5;
|
||||||
|
bool led = false;
|
||||||
|
NotificationApp* notif = 0;
|
||||||
|
|
||||||
|
const NotificationMessage message_red_dim = {
|
||||||
|
.type = NotificationMessageTypeLedRed,
|
||||||
|
.data.led.value = 0xFF / 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NotificationMessage message_red_off = {
|
||||||
|
.type = NotificationMessageTypeLedRed,
|
||||||
|
.data.led.value = 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NotificationSequence led_on = {
|
||||||
|
&message_red_dim,
|
||||||
|
&message_do_not_reset,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NotificationSequence led_off = {
|
||||||
|
&message_red_off,
|
||||||
|
&message_do_not_reset,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NotificationSequence led_reset = {
|
||||||
|
&message_red_0,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_backlight_brightness(float brightness) {
|
||||||
|
notif->settings.display_brightness = brightness;
|
||||||
|
notification_message(notif, &sequence_display_backlight_on);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_up() {
|
||||||
|
if(brightness < 100) {
|
||||||
|
led = false;
|
||||||
|
notification_message(notif, &led_off);
|
||||||
|
brightness += 5;
|
||||||
|
}
|
||||||
|
set_backlight_brightness((float)(brightness / 100.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_down() {
|
||||||
|
if(brightness > 0) {
|
||||||
|
brightness -= 5;
|
||||||
|
if(brightness == 0) { //trigger only on the first brightness 5 -> 0 transition
|
||||||
|
led = true;
|
||||||
|
notification_message(notif, &led_on);
|
||||||
|
}
|
||||||
|
} else if(brightness == 0) { //trigger on every down press afterwards
|
||||||
|
led = !led;
|
||||||
|
if(led) {
|
||||||
|
notification_message(notif, &led_on);
|
||||||
|
} else {
|
||||||
|
notification_message(notif, &led_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_backlight_brightness((float)(brightness / 100.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_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 clock_render_callback(Canvas* const canvas, void* ctx) {
|
||||||
|
//canvas_clear(canvas);
|
||||||
|
//canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
//avoids a bug with the brightness being reverted after the backlight-off period
|
||||||
|
set_backlight_brightness((float)(brightness / 100.f));
|
||||||
|
|
||||||
|
ClockState* state = ctx;
|
||||||
|
if(furi_mutex_acquire(state->mutex, 200) != FuriStatusOk) {
|
||||||
|
//FURI_LOG_D(TAG, "Can't obtain mutex, requeue render");
|
||||||
|
PluginEvent event = {.type = EventTypeTick};
|
||||||
|
furi_message_queue_put(state->event_queue, &event, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriHalRtcDateTime curr_dt;
|
||||||
|
furi_hal_rtc_get_datetime(&curr_dt);
|
||||||
|
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||||
|
|
||||||
|
char time_string[TIME_LEN];
|
||||||
|
char date_string[DATE_LEN];
|
||||||
|
char meridian_string[MERIDIAN_LEN];
|
||||||
|
char timer_string[20];
|
||||||
|
|
||||||
|
if(state->time_format == LocaleTimeFormat24h) {
|
||||||
|
snprintf(
|
||||||
|
time_string, TIME_LEN, CLOCK_TIME_FORMAT, curr_dt.hour, curr_dt.minute, curr_dt.second);
|
||||||
|
} else {
|
||||||
|
bool pm = curr_dt.hour > 12;
|
||||||
|
bool pm12 = curr_dt.hour >= 12;
|
||||||
|
snprintf(
|
||||||
|
time_string,
|
||||||
|
TIME_LEN,
|
||||||
|
CLOCK_TIME_FORMAT,
|
||||||
|
pm ? curr_dt.hour - 12 : curr_dt.hour,
|
||||||
|
curr_dt.minute,
|
||||||
|
curr_dt.second);
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
meridian_string,
|
||||||
|
MERIDIAN_LEN,
|
||||||
|
MERIDIAN_FORMAT,
|
||||||
|
pm12 ? MERIDIAN_STRING_PM : MERIDIAN_STRING_AM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state->date_format == LocaleDateFormatYMD) {
|
||||||
|
snprintf(
|
||||||
|
date_string, DATE_LEN, CLOCK_ISO_DATE_FORMAT, curr_dt.year, curr_dt.month, curr_dt.day);
|
||||||
|
} else if(state->date_format == LocaleDateFormatMDY) {
|
||||||
|
snprintf(
|
||||||
|
date_string, DATE_LEN, CLOCK_RFC_DATE_FORMAT, curr_dt.month, curr_dt.day, curr_dt.year);
|
||||||
|
} else {
|
||||||
|
snprintf(
|
||||||
|
date_string, DATE_LEN, CLOCK_RFC_DATE_FORMAT, curr_dt.day, curr_dt.month, curr_dt.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timer_running = state->timer_running;
|
||||||
|
uint32_t timer_start_timestamp = state->timer_start_timestamp;
|
||||||
|
uint32_t timer_stopped_seconds = state->timer_stopped_seconds;
|
||||||
|
|
||||||
|
furi_mutex_release(state->mutex);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
|
||||||
|
if(timer_start_timestamp != 0) {
|
||||||
|
int32_t elapsed_secs = timer_running ? (curr_ts - timer_start_timestamp) :
|
||||||
|
timer_stopped_seconds;
|
||||||
|
snprintf(timer_string, 20, "%.2ld:%.2ld", elapsed_secs / 60, elapsed_secs % 60);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, time_string); // DRAW TIME
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, timer_string); // DRAW TIMER
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignTop, date_string); // DRAW DATE
|
||||||
|
elements_button_left(canvas, "Reset");
|
||||||
|
} else {
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, time_string);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str_aligned(canvas, 65, 17, AlignCenter, AlignCenter, date_string);
|
||||||
|
|
||||||
|
if(state->time_format == LocaleTimeFormat12h)
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 47, AlignCenter, AlignCenter, meridian_string);
|
||||||
|
}
|
||||||
|
if(timer_running) {
|
||||||
|
elements_button_center(canvas, "Stop");
|
||||||
|
} else if(timer_start_timestamp != 0 && !timer_running) {
|
||||||
|
elements_button_center(canvas, "Start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_state_init(ClockState* const state) {
|
||||||
|
state->time_format = locale_get_time_format();
|
||||||
|
|
||||||
|
state->date_format = locale_get_date_format();
|
||||||
|
|
||||||
|
//FURI_LOG_D(TAG, "Time format: %s", state->settings.time_format == H12 ? "12h" : "24h");
|
||||||
|
//FURI_LOG_D(TAG, "Date format: %s", state->settings.date_format == Iso ? "ISO 8601" : "RFC 5322");
|
||||||
|
//furi_hal_rtc_get_datetime(&state->datetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs every 1000ms by default
|
||||||
|
static void clock_tick(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
PluginEvent event = {.type = EventTypeTick};
|
||||||
|
// It's OK to loose this event if system overloaded
|
||||||
|
furi_message_queue_put(event_queue, &event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_start_stop(ClockState* plugin_state) {
|
||||||
|
// START/STOP TIMER
|
||||||
|
FuriHalRtcDateTime curr_dt;
|
||||||
|
furi_hal_rtc_get_datetime(&curr_dt);
|
||||||
|
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||||
|
|
||||||
|
if(plugin_state->timer_running) {
|
||||||
|
// Update stopped seconds
|
||||||
|
plugin_state->timer_stopped_seconds = curr_ts - plugin_state->timer_start_timestamp;
|
||||||
|
} else {
|
||||||
|
if(plugin_state->timer_start_timestamp == 0) {
|
||||||
|
// Set starting timestamp if this is first time
|
||||||
|
plugin_state->timer_start_timestamp = curr_ts;
|
||||||
|
} else {
|
||||||
|
// Timer was already running, need to slightly readjust so we don't
|
||||||
|
// count the intervening time
|
||||||
|
plugin_state->timer_start_timestamp = curr_ts - plugin_state->timer_stopped_seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugin_state->timer_running = !plugin_state->timer_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_reset_seconds(ClockState* plugin_state) {
|
||||||
|
if(plugin_state->timer_start_timestamp != 0) {
|
||||||
|
// Reset seconds
|
||||||
|
plugin_state->timer_running = false;
|
||||||
|
plugin_state->timer_start_timestamp = 0;
|
||||||
|
plugin_state->timer_stopped_seconds = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clock_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
ClockState* plugin_state = malloc(sizeof(ClockState));
|
||||||
|
|
||||||
|
plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||||
|
if(plugin_state->event_queue == NULL) {
|
||||||
|
FURI_LOG_E(TAG, "Cannot create event queue");
|
||||||
|
free(plugin_state);
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
//FURI_LOG_D(TAG, "Event queue created");
|
||||||
|
|
||||||
|
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
|
if(plugin_state->mutex == NULL) {
|
||||||
|
FURI_LOG_E(TAG, "Cannot create mutex");
|
||||||
|
furi_message_queue_free(plugin_state->event_queue);
|
||||||
|
free(plugin_state);
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
//FURI_LOG_D(TAG, "Mutex created");
|
||||||
|
|
||||||
|
clock_state_init(plugin_state);
|
||||||
|
|
||||||
|
// Set system callbacks
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, clock_render_callback, plugin_state);
|
||||||
|
view_port_input_callback_set(view_port, clock_input_callback, plugin_state->event_queue);
|
||||||
|
|
||||||
|
FuriTimer* timer =
|
||||||
|
furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, plugin_state->event_queue);
|
||||||
|
|
||||||
|
if(timer == NULL) {
|
||||||
|
FURI_LOG_E(TAG, "Cannot create timer");
|
||||||
|
furi_mutex_free(plugin_state->mutex);
|
||||||
|
furi_message_queue_free(plugin_state->event_queue);
|
||||||
|
free(plugin_state);
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
//FURI_LOG_D(TAG, "Timer created");
|
||||||
|
|
||||||
|
// Open GUI and register view_port
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||||
|
//FURI_LOG_D(TAG, "Timer started");
|
||||||
|
|
||||||
|
notif = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
float tmpBrightness = notif->settings.display_brightness;
|
||||||
|
|
||||||
|
notification_message(notif, &sequence_display_backlight_enforce_on);
|
||||||
|
notification_message(notif, &led_off);
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
PluginEvent event;
|
||||||
|
for(bool processing = true; processing;) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(plugin_state->event_queue, &event, 100);
|
||||||
|
|
||||||
|
if(event_status != FuriStatusOk) continue;
|
||||||
|
|
||||||
|
if(furi_mutex_acquire(plugin_state->mutex, FuriWaitForever) != FuriStatusOk) continue;
|
||||||
|
// press events
|
||||||
|
if(event.type == EventTypeKey) {
|
||||||
|
if(event.input.type == InputTypeLong) {
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyLeft:
|
||||||
|
// Reset seconds
|
||||||
|
timer_reset_seconds(plugin_state);
|
||||||
|
break;
|
||||||
|
case InputKeyOk:
|
||||||
|
// Toggle timer
|
||||||
|
timer_start_stop(plugin_state);
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
// Exit the plugin
|
||||||
|
processing = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if(event.input.type == InputTypeShort) {
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyUp:
|
||||||
|
handle_up();
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
handle_down();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /*else if(event.type == EventTypeTick) {
|
||||||
|
furi_hal_rtc_get_datetime(&plugin_state->datetime);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
view_port_update(view_port);
|
||||||
|
furi_mutex_release(plugin_state->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_timer_free(timer);
|
||||||
|
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(plugin_state->event_queue);
|
||||||
|
furi_mutex_free(plugin_state->mutex);
|
||||||
|
free(plugin_state);
|
||||||
|
|
||||||
|
set_backlight_brightness(tmpBrightness);
|
||||||
|
notification_message(notif, &sequence_display_backlight_enforce_auto);
|
||||||
|
notification_message(notif, &led_reset);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
39
applications/plugins/nightstand/clock_app.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <locale/locale.h>
|
||||||
|
|
||||||
|
#define TAG "Clock"
|
||||||
|
|
||||||
|
#define CLOCK_ISO_DATE_FORMAT "%.4d-%.2d-%.2d"
|
||||||
|
#define CLOCK_RFC_DATE_FORMAT "%.2d-%.2d-%.4d"
|
||||||
|
#define CLOCK_TIME_FORMAT "%.2d:%.2d:%.2d"
|
||||||
|
|
||||||
|
#define MERIDIAN_FORMAT "%s"
|
||||||
|
#define MERIDIAN_STRING_AM "AM"
|
||||||
|
#define MERIDIAN_STRING_PM "PM"
|
||||||
|
|
||||||
|
#define TIME_LEN 12
|
||||||
|
#define DATE_LEN 14
|
||||||
|
#define MERIDIAN_LEN 3
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeTick,
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EventType type;
|
||||||
|
InputEvent input;
|
||||||
|
} PluginEvent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LocaleDateFormat date_format;
|
||||||
|
LocaleTimeFormat time_format;
|
||||||
|
FuriHalRtcDateTime datetime;
|
||||||
|
FuriMutex* mutex;
|
||||||
|
FuriMessageQueue* event_queue;
|
||||||
|
uint32_t timer_start_timestamp;
|
||||||
|
uint32_t timer_stopped_seconds;
|
||||||
|
bool timer_running;
|
||||||
|
} ClockState;
|
||||||
12
applications/plugins/pomodoro/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="Pomodoro2",
|
||||||
|
name="Pomodoro 2",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="flipp_pomodoro_app",
|
||||||
|
requires=["gui", "notification", "dolphin"],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
fap_category="Tools",
|
||||||
|
fap_icon_assets="images",
|
||||||
|
fap_icon="flipp_pomodoro_10.png",
|
||||||
|
fap_icon_assets_symbol="flipp_pomodoro",
|
||||||
|
)
|
||||||
BIN
applications/plugins/pomodoro/flipp_pomodoro_10.png
Normal file
|
After Width: | Height: | Size: 157 B |
101
applications/plugins/pomodoro/flipp_pomodoro_app.c
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#include "flipp_pomodoro_app_i.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CustomEventConsumed = true,
|
||||||
|
CustomEventNotConsumed = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool flipp_pomodoro_app_back_event_callback(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void flipp_pomodoro_app_tick_event_callback(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
scene_manager_handle_custom_event(app->scene_manager, FlippPomodoroAppCustomEventTimerTick);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool flipp_pomodoro_app_custom_event_callback(void* ctx, uint32_t event) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
switch(event) {
|
||||||
|
case FlippPomodoroAppCustomEventStageSkip:
|
||||||
|
flipp_pomodoro__toggle_stage(app->state);
|
||||||
|
view_dispatcher_send_custom_event(
|
||||||
|
app->view_dispatcher, FlippPomodoroAppCustomEventStateUpdated);
|
||||||
|
return CustomEventConsumed;
|
||||||
|
case FlippPomodoroAppCustomEventStageComplete:
|
||||||
|
if(flipp_pomodoro__get_stage(app->state) == FlippPomodoroStageFocus) {
|
||||||
|
// REGISTER a deed on work stage complete to get an acheivement
|
||||||
|
DOLPHIN_DEED(DolphinDeedPluginGameWin);
|
||||||
|
};
|
||||||
|
|
||||||
|
flipp_pomodoro__toggle_stage(app->state);
|
||||||
|
notification_message(
|
||||||
|
app->notification_app,
|
||||||
|
stage_start_notification_sequence_map[flipp_pomodoro__get_stage(app->state)]);
|
||||||
|
view_dispatcher_send_custom_event(
|
||||||
|
app->view_dispatcher, FlippPomodoroAppCustomEventStateUpdated);
|
||||||
|
return CustomEventConsumed;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
FlippPomodoroApp* flipp_pomodoro_app_alloc() {
|
||||||
|
FlippPomodoroApp* app = malloc(sizeof(FlippPomodoroApp));
|
||||||
|
app->state = flipp_pomodoro__new();
|
||||||
|
|
||||||
|
app->scene_manager = scene_manager_alloc(&flipp_pomodoro_scene_handlers, app);
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->notification_app = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
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, flipp_pomodoro_app_custom_event_callback);
|
||||||
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
app->view_dispatcher, flipp_pomodoro_app_tick_event_callback, 1000);
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, flipp_pomodoro_app_back_event_callback);
|
||||||
|
|
||||||
|
app->timer_view = flipp_pomodoro_view_timer_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher,
|
||||||
|
FlippPomodoroAppViewTimer,
|
||||||
|
flipp_pomodoro_view_timer_get_view(app->timer_view));
|
||||||
|
|
||||||
|
scene_manager_next_scene(app->scene_manager, FlippPomodoroSceneTimer);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_app_free(FlippPomodoroApp* app) {
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
flipp_pomodoro_view_timer_free(app->timer_view);
|
||||||
|
flipp_pomodoro__destroy(app->state);
|
||||||
|
free(app);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t flipp_pomodoro_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
FlippPomodoroApp* app = flipp_pomodoro_app_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
|
||||||
|
flipp_pomodoro_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
32
applications/plugins/pomodoro/flipp_pomodoro_app.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include "views/flipp_pomodoro_timer_view.h"
|
||||||
|
|
||||||
|
#include "modules/flipp_pomodoro.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Reserve first 100 events for button types and indexes, starting from 0
|
||||||
|
FlippPomodoroAppCustomEventStageSkip = 100,
|
||||||
|
FlippPomodoroAppCustomEventStageComplete, // By Expiration
|
||||||
|
FlippPomodoroAppCustomEventTimerTick,
|
||||||
|
FlippPomodoroAppCustomEventStateUpdated,
|
||||||
|
} FlippPomodoroAppCustomEvent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Gui* gui;
|
||||||
|
NotificationApp* notification_app;
|
||||||
|
FlippPomodoroTimerView* timer_view;
|
||||||
|
FlippPomodoroState* state;
|
||||||
|
} FlippPomodoroApp;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FlippPomodoroAppViewTimer,
|
||||||
|
} FlippPomodoroAppView;
|
||||||
31
applications/plugins/pomodoro/flipp_pomodoro_app_i.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define FURI_DEBUG 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of dependencies for the main app
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Platform Imports
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_stack.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
|
||||||
|
// App resource imports
|
||||||
|
|
||||||
|
#include "helpers/time.h"
|
||||||
|
#include "helpers/notifications.h"
|
||||||
|
#include "modules/flipp_pomodoro.h"
|
||||||
|
#include "flipp_pomodoro_app.h"
|
||||||
|
#include "scenes/flipp_pomodoro_scene.h"
|
||||||
|
#include "views/flipp_pomodoro_timer_view.h"
|
||||||
|
|
||||||
|
// Auto-compiled icons
|
||||||
|
#include "flipp_pomodoro_icons.h"
|
||||||
5
applications/plugins/pomodoro/helpers/debug.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#define TAG "FlippPomodoro"
|
||||||
49
applications/plugins/pomodoro/helpers/notifications.c
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
const NotificationSequence work_start_notification = {
|
||||||
|
&message_display_backlight_on,
|
||||||
|
|
||||||
|
&message_vibro_on,
|
||||||
|
|
||||||
|
&message_note_b5,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_note_d5,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_sound_off,
|
||||||
|
&message_vibro_off,
|
||||||
|
|
||||||
|
&message_green_255,
|
||||||
|
&message_delay_1000,
|
||||||
|
&message_green_0,
|
||||||
|
&message_delay_250,
|
||||||
|
&message_green_255,
|
||||||
|
&message_delay_1000,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NotificationSequence rest_start_notification = {
|
||||||
|
&message_display_backlight_on,
|
||||||
|
|
||||||
|
&message_vibro_on,
|
||||||
|
|
||||||
|
&message_note_d5,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_note_b5,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_sound_off,
|
||||||
|
&message_vibro_off,
|
||||||
|
|
||||||
|
&message_red_255,
|
||||||
|
&message_delay_1000,
|
||||||
|
&message_red_0,
|
||||||
|
&message_delay_250,
|
||||||
|
&message_red_255,
|
||||||
|
&message_delay_1000,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
14
applications/plugins/pomodoro/helpers/notifications.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../modules/flipp_pomodoro.h"
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
extern const NotificationSequence work_start_notification;
|
||||||
|
extern const NotificationSequence rest_start_notification;
|
||||||
|
|
||||||
|
/// @brief Defines a notification sequence that should indicate start of specific pomodoro stage.
|
||||||
|
const NotificationSequence* stage_start_notification_sequence_map[] = {
|
||||||
|
[FlippPomodoroStageFocus] = &work_start_notification,
|
||||||
|
[FlippPomodoroStageRest] = &rest_start_notification,
|
||||||
|
[FlippPomodoroStageLongBreak] = &rest_start_notification,
|
||||||
|
};
|
||||||
20
applications/plugins/pomodoro/helpers/time.c
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include "time.h"
|
||||||
|
|
||||||
|
const int TIME_SECONDS_IN_MINUTE = 60;
|
||||||
|
const int TIME_MINUTES_IN_HOUR = 60;
|
||||||
|
|
||||||
|
uint32_t time_now() {
|
||||||
|
return furi_hal_rtc_get_timestamp();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeDifference time_difference_seconds(uint32_t begin, uint32_t end) {
|
||||||
|
const uint32_t duration_seconds = end - begin;
|
||||||
|
|
||||||
|
uint32_t minutes = (duration_seconds / TIME_MINUTES_IN_HOUR) % TIME_MINUTES_IN_HOUR;
|
||||||
|
uint32_t seconds = duration_seconds % TIME_SECONDS_IN_MINUTE;
|
||||||
|
|
||||||
|
return (
|
||||||
|
TimeDifference){.total_seconds = duration_seconds, .minutes = minutes, .seconds = seconds};
|
||||||
|
};
|
||||||
24
applications/plugins/pomodoro/helpers/time.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
extern const int TIME_SECONDS_IN_MINUTE;
|
||||||
|
extern const int TIME_MINUTES_IN_HOUR;
|
||||||
|
|
||||||
|
/// @brief Container for a time period
|
||||||
|
typedef struct {
|
||||||
|
uint8_t seconds;
|
||||||
|
uint8_t minutes;
|
||||||
|
uint32_t total_seconds;
|
||||||
|
} TimeDifference;
|
||||||
|
|
||||||
|
/// @brief Time by the moment of calling
|
||||||
|
/// @return A timestamp(seconds percision)
|
||||||
|
uint32_t time_now();
|
||||||
|
|
||||||
|
/// @brief Calculates difference between two provided timestamps
|
||||||
|
/// @param begin - start timestamp of the period
|
||||||
|
/// @param end - end timestamp of the period to measure
|
||||||
|
/// @return TimeDifference struct
|
||||||
|
TimeDifference time_difference_seconds(uint32_t begin, uint32_t end);
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
94
applications/plugins/pomodoro/modules/flipp_pomodoro.c
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include "../helpers/time.h"
|
||||||
|
#include "flipp_pomodoro.h"
|
||||||
|
|
||||||
|
PomodoroStage stages_sequence[] = {
|
||||||
|
FlippPomodoroStageFocus,
|
||||||
|
FlippPomodoroStageRest,
|
||||||
|
|
||||||
|
FlippPomodoroStageFocus,
|
||||||
|
FlippPomodoroStageRest,
|
||||||
|
|
||||||
|
FlippPomodoroStageFocus,
|
||||||
|
FlippPomodoroStageRest,
|
||||||
|
|
||||||
|
FlippPomodoroStageFocus,
|
||||||
|
FlippPomodoroStageLongBreak,
|
||||||
|
};
|
||||||
|
|
||||||
|
char* current_stage_label[] = {
|
||||||
|
[FlippPomodoroStageFocus] = "Continue focus for:",
|
||||||
|
[FlippPomodoroStageRest] = "Keep rest for:",
|
||||||
|
[FlippPomodoroStageLongBreak] = "Long Break for:",
|
||||||
|
};
|
||||||
|
|
||||||
|
char* next_stage_label[] = {
|
||||||
|
[FlippPomodoroStageFocus] = "Focus",
|
||||||
|
[FlippPomodoroStageRest] = "Short Break",
|
||||||
|
[FlippPomodoroStageLongBreak] = "Long Break",
|
||||||
|
};
|
||||||
|
|
||||||
|
PomodoroStage flipp_pomodoro__stage_by_index(int index) {
|
||||||
|
const int one_loop_size = sizeof(stages_sequence);
|
||||||
|
return stages_sequence[index % one_loop_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void flipp_pomodoro__toggle_stage(FlippPomodoroState* state) {
|
||||||
|
furi_assert(state);
|
||||||
|
state->current_stage_index = state->current_stage_index + 1;
|
||||||
|
state->started_at_timestamp = time_now();
|
||||||
|
};
|
||||||
|
|
||||||
|
PomodoroStage flipp_pomodoro__get_stage(FlippPomodoroState* state) {
|
||||||
|
furi_assert(state);
|
||||||
|
return flipp_pomodoro__stage_by_index(state->current_stage_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
char* flipp_pomodoro__current_stage_label(FlippPomodoroState* state) {
|
||||||
|
furi_assert(state);
|
||||||
|
return current_stage_label[flipp_pomodoro__get_stage(state)];
|
||||||
|
};
|
||||||
|
|
||||||
|
char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state) {
|
||||||
|
furi_assert(state);
|
||||||
|
return next_stage_label[flipp_pomodoro__stage_by_index(state->current_stage_index + 1)];
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro__destroy(FlippPomodoroState* state) {
|
||||||
|
furi_assert(state);
|
||||||
|
free(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t flipp_pomodoro__current_stage_total_duration(FlippPomodoroState* state) {
|
||||||
|
const int32_t stage_duration_seconds_map[] = {
|
||||||
|
[FlippPomodoroStageFocus] = 25 * TIME_SECONDS_IN_MINUTE,
|
||||||
|
[FlippPomodoroStageRest] = 5 * TIME_SECONDS_IN_MINUTE,
|
||||||
|
[FlippPomodoroStageLongBreak] = 30 * TIME_SECONDS_IN_MINUTE,
|
||||||
|
};
|
||||||
|
|
||||||
|
return stage_duration_seconds_map[flipp_pomodoro__get_stage(state)];
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t flipp_pomodoro__stage_expires_timestamp(FlippPomodoroState* state) {
|
||||||
|
return state->started_at_timestamp + flipp_pomodoro__current_stage_total_duration(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state) {
|
||||||
|
const uint32_t stage_ends_at = flipp_pomodoro__stage_expires_timestamp(state);
|
||||||
|
return time_difference_seconds(time_now(), stage_ends_at);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state) {
|
||||||
|
const uint32_t expired_by = flipp_pomodoro__stage_expires_timestamp(state);
|
||||||
|
const uint8_t seamless_change_span_seconds = 1;
|
||||||
|
return (time_now() - seamless_change_span_seconds) >= expired_by;
|
||||||
|
};
|
||||||
|
|
||||||
|
FlippPomodoroState* flipp_pomodoro__new() {
|
||||||
|
FlippPomodoroState* state = malloc(sizeof(FlippPomodoroState));
|
||||||
|
const uint32_t now = time_now();
|
||||||
|
state->started_at_timestamp = now;
|
||||||
|
state->current_stage_index = 0;
|
||||||
|
return state;
|
||||||
|
};
|
||||||
54
applications/plugins/pomodoro/modules/flipp_pomodoro.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include "../helpers/time.h"
|
||||||
|
|
||||||
|
/// @brief Options of pomodoro stages
|
||||||
|
typedef enum {
|
||||||
|
FlippPomodoroStageFocus,
|
||||||
|
FlippPomodoroStageRest,
|
||||||
|
FlippPomodoroStageLongBreak,
|
||||||
|
} PomodoroStage;
|
||||||
|
|
||||||
|
/// @brief State of the pomodoro timer
|
||||||
|
typedef struct {
|
||||||
|
PomodoroStage stage;
|
||||||
|
uint8_t current_stage_index;
|
||||||
|
uint32_t started_at_timestamp;
|
||||||
|
} FlippPomodoroState;
|
||||||
|
|
||||||
|
/// @brief Generates initial state
|
||||||
|
/// @returns A new pre-populated state for pomodoro timer
|
||||||
|
FlippPomodoroState* flipp_pomodoro__new();
|
||||||
|
|
||||||
|
/// @brief Extract current stage of pomodoro
|
||||||
|
/// @param state - pointer to the state of pomorodo
|
||||||
|
/// @returns Current stage value
|
||||||
|
PomodoroStage flipp_pomodoro__get_stage(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Destroys state of timer and it's dependencies
|
||||||
|
void flipp_pomodoro__destroy(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Get remaining stage time.
|
||||||
|
/// @param state - pointer to the state of pomorodo
|
||||||
|
/// @returns Time difference to the end of current stage
|
||||||
|
TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Label of currently active stage
|
||||||
|
/// @param state - pointer to the state of pomorodo
|
||||||
|
/// @returns A string that explains current stage
|
||||||
|
char* flipp_pomodoro__current_stage_label(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Label of transition to the next stage
|
||||||
|
/// @param state - pointer to the state of pomorodo.
|
||||||
|
/// @returns string with the label of the "skipp" button
|
||||||
|
char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Check if current stage is expired
|
||||||
|
/// @param state - pointer to the state of pomorodo.
|
||||||
|
/// @returns expriations status - true means stage is expired
|
||||||
|
bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state);
|
||||||
|
|
||||||
|
/// @brief Rotate stage of the timer
|
||||||
|
/// @param state - pointer to the state of pomorodo.
|
||||||
|
void flipp_pomodoro__toggle_stage(FlippPomodoroState* state);
|
||||||
0
applications/plugins/pomodoro/scenes/.keep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ADD_SCENE(flipp_pomodoro, timer, Timer)
|
||||||
30
applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.c
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "flipp_pomodoro_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const flipp_pomodoro_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const flipp_pomodoro_scene_on_event_handlers[])(void* ctx, SceneManagerEvent event) = {
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const flipp_pomodoro_scene_on_exit_handlers[])(void* ctx) = {
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers flipp_pomodoro_scene_handlers = {
|
||||||
|
.on_enter_handlers = flipp_pomodoro_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = flipp_pomodoro_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = flipp_pomodoro_scene_on_exit_handlers,
|
||||||
|
.scene_num = FlippPomodoroSceneNum,
|
||||||
|
};
|
||||||
27
applications/plugins/pomodoro/scenes/flipp_pomodoro_scene.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) FlippPomodoroScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
FlippPomodoroSceneNum,
|
||||||
|
} FlippPomodoroScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers flipp_pomodoro_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* ctx, SceneManagerEvent event);
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* ctx);
|
||||||
|
#include "config/flipp_pomodoro_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include "../flipp_pomodoro_app.h"
|
||||||
|
#include "../views/flipp_pomodoro_timer_view.h"
|
||||||
|
|
||||||
|
enum { SceneEventConusmed = true, SceneEventNotConusmed = false };
|
||||||
|
|
||||||
|
uint8_t ExitSignal = 0;
|
||||||
|
|
||||||
|
void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
flipp_pomodoro_view_timer_set_state(
|
||||||
|
flipp_pomodoro_view_timer_get_view(app->timer_view), app->state);
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip);
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
|
||||||
|
flipp_pomodoro_scene_timer_sync_view_state(app);
|
||||||
|
flipp_pomodoro_view_timer_set_on_right_cb(
|
||||||
|
app->timer_view, flipp_pomodoro_scene_timer_on_next_stage, app);
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_scene_timer_handle_custom_event(
|
||||||
|
FlippPomodoroApp* app,
|
||||||
|
FlippPomodoroAppCustomEvent custom_event) {
|
||||||
|
if(custom_event == FlippPomodoroAppCustomEventTimerTick &&
|
||||||
|
flipp_pomodoro__is_stage_expired(app->state)) {
|
||||||
|
view_dispatcher_send_custom_event(
|
||||||
|
app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(custom_event == FlippPomodoroAppCustomEventStateUpdated) {
|
||||||
|
flipp_pomodoro_scene_timer_sync_view_state(app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool flipp_pomodoro_scene_timer_on_event(void* ctx, SceneManagerEvent event) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FlippPomodoroApp* app = ctx;
|
||||||
|
|
||||||
|
switch(event.type) {
|
||||||
|
case SceneManagerEventTypeCustom:
|
||||||
|
flipp_pomodoro_scene_timer_handle_custom_event(app, event.event);
|
||||||
|
return SceneEventConusmed;
|
||||||
|
case SceneManagerEventTypeBack:
|
||||||
|
return ExitSignal;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
return SceneEventNotConusmed;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_scene_timer_on_exit(void* ctx) {
|
||||||
|
UNUSED(ctx);
|
||||||
|
};
|
||||||
0
applications/plugins/pomodoro/views/.keep
Normal file
195
applications/plugins/pomodoro/views/flipp_pomodoro_timer_view.c
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#include "flipp_pomodoro_timer_view.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../helpers/debug.h"
|
||||||
|
#include "../flipp_pomodoro_app.h"
|
||||||
|
#include "../modules/flipp_pomodoro.h"
|
||||||
|
|
||||||
|
// Auto-compiled icons
|
||||||
|
#include "flipp_pomodoro_icons.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ViewInputConsumed = true,
|
||||||
|
ViewInputNotConusmed = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FlippPomodoroTimerView {
|
||||||
|
View* view;
|
||||||
|
FlippPomodoroTimerViewInputCb right_cb;
|
||||||
|
void* right_cb_ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
IconAnimation* icon;
|
||||||
|
FlippPomodoroState* state;
|
||||||
|
} FlippPomodoroTimerViewModel;
|
||||||
|
|
||||||
|
static const Icon* stage_background_image[] = {
|
||||||
|
[FlippPomodoroStageFocus] = &A_flipp_pomodoro_focus_64,
|
||||||
|
[FlippPomodoroStageRest] = &A_flipp_pomodoro_rest_64,
|
||||||
|
[FlippPomodoroStageLongBreak] = &A_flipp_pomodoro_rest_64,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
flipp_pomodoro_view_timer_draw_countdown(Canvas* canvas, TimeDifference remaining_time) {
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
const uint8_t right_border_margin = 1;
|
||||||
|
|
||||||
|
const uint8_t countdown_box_height = canvas_height(canvas) * 0.4;
|
||||||
|
const uint8_t countdown_box_width = canvas_width(canvas) * 0.5;
|
||||||
|
const uint8_t countdown_box_x =
|
||||||
|
canvas_width(canvas) - countdown_box_width - right_border_margin;
|
||||||
|
const uint8_t countdown_box_y = 15;
|
||||||
|
|
||||||
|
elements_bold_rounded_frame(
|
||||||
|
canvas, countdown_box_x, countdown_box_y, countdown_box_width, countdown_box_height);
|
||||||
|
|
||||||
|
FuriString* timer_string = furi_string_alloc();
|
||||||
|
furi_string_printf(timer_string, "%02u:%02u", remaining_time.minutes, remaining_time.seconds);
|
||||||
|
const char* remaining_stage_time_string = furi_string_get_cstr(timer_string);
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas,
|
||||||
|
countdown_box_x + (countdown_box_width / 2),
|
||||||
|
countdown_box_y + (countdown_box_height / 2),
|
||||||
|
AlignCenter,
|
||||||
|
AlignCenter,
|
||||||
|
remaining_stage_time_string);
|
||||||
|
|
||||||
|
furi_string_free(timer_string);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void draw_str_with_drop_shadow(
|
||||||
|
Canvas* canvas,
|
||||||
|
uint8_t x,
|
||||||
|
uint8_t y,
|
||||||
|
Align horizontal,
|
||||||
|
Align vertical,
|
||||||
|
const char* str) {
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
for(int x_off = -2; x_off <= 2; x_off++) {
|
||||||
|
for(int y_off = -2; y_off <= 2; y_off++) {
|
||||||
|
canvas_draw_str_aligned(canvas, x + x_off, y + y_off, horizontal, vertical, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
flipp_pomodoro_view_timer_draw_current_stage_label(Canvas* canvas, FlippPomodoroState* state) {
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
draw_str_with_drop_shadow(
|
||||||
|
canvas,
|
||||||
|
canvas_width(canvas),
|
||||||
|
0,
|
||||||
|
AlignRight,
|
||||||
|
AlignTop,
|
||||||
|
flipp_pomodoro__current_stage_label(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
if(!_model) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
FlippPomodoroTimerViewModel* model = _model;
|
||||||
|
|
||||||
|
canvas_clear(canvas);
|
||||||
|
if(model->icon) {
|
||||||
|
canvas_draw_icon_animation(canvas, 0, 0, model->icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
flipp_pomodoro_view_timer_draw_countdown(
|
||||||
|
canvas, flipp_pomodoro__stage_remaining_duration(model->state));
|
||||||
|
|
||||||
|
flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
|
||||||
|
};
|
||||||
|
|
||||||
|
bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
furi_assert(event);
|
||||||
|
FlippPomodoroTimerView* timer = ctx;
|
||||||
|
|
||||||
|
const bool should_trigger_right_event_cb = (event->type == InputTypePress) &&
|
||||||
|
(event->key == InputKeyRight) &&
|
||||||
|
(timer->right_cb != NULL);
|
||||||
|
|
||||||
|
if(should_trigger_right_event_cb) {
|
||||||
|
furi_assert(timer->right_cb);
|
||||||
|
furi_assert(timer->right_cb_ctx);
|
||||||
|
timer->right_cb(timer->right_cb_ctx);
|
||||||
|
return ViewInputConsumed;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ViewInputNotConusmed;
|
||||||
|
};
|
||||||
|
|
||||||
|
View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
|
||||||
|
furi_assert(timer);
|
||||||
|
return timer->view;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_assign_animation(View* view) {
|
||||||
|
with_view_model(
|
||||||
|
view,
|
||||||
|
FlippPomodoroTimerViewModel * model,
|
||||||
|
{
|
||||||
|
furi_assert(model->state);
|
||||||
|
if(model->icon) {
|
||||||
|
icon_animation_free(model->icon);
|
||||||
|
}
|
||||||
|
model->icon = icon_animation_alloc(
|
||||||
|
stage_background_image[flipp_pomodoro__get_stage(model->state)]);
|
||||||
|
view_tie_icon_animation(view, model->icon);
|
||||||
|
icon_animation_start(model->icon);
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() {
|
||||||
|
FlippPomodoroTimerView* timer = malloc(sizeof(FlippPomodoroTimerView));
|
||||||
|
timer->view = view_alloc();
|
||||||
|
|
||||||
|
view_allocate_model(timer->view, ViewModelTypeLockFree, sizeof(FlippPomodoroTimerViewModel));
|
||||||
|
view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
|
||||||
|
view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
|
||||||
|
view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
|
||||||
|
|
||||||
|
return timer;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_set_on_right_cb(
|
||||||
|
FlippPomodoroTimerView* timer,
|
||||||
|
FlippPomodoroTimerViewInputCb right_cb,
|
||||||
|
void* right_cb_ctx) {
|
||||||
|
furi_assert(right_cb);
|
||||||
|
furi_assert(right_cb_ctx);
|
||||||
|
timer->right_cb = right_cb;
|
||||||
|
timer->right_cb_ctx = right_cb_ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
|
||||||
|
furi_assert(view);
|
||||||
|
furi_assert(state);
|
||||||
|
with_view_model(
|
||||||
|
view, FlippPomodoroTimerViewModel * model, { model->state = state; }, false);
|
||||||
|
flipp_pomodoro_view_timer_assign_animation(view);
|
||||||
|
};
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) {
|
||||||
|
furi_assert(timer);
|
||||||
|
with_view_model(
|
||||||
|
timer->view,
|
||||||
|
FlippPomodoroTimerViewModel * model,
|
||||||
|
{ icon_animation_free(model->icon); },
|
||||||
|
false);
|
||||||
|
view_free(timer->view);
|
||||||
|
|
||||||
|
free(timer);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../modules/flipp_pomodoro.h"
|
||||||
|
|
||||||
|
typedef struct FlippPomodoroTimerView FlippPomodoroTimerView;
|
||||||
|
|
||||||
|
typedef void (*FlippPomodoroTimerViewInputCb)(void* ctx);
|
||||||
|
|
||||||
|
FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc();
|
||||||
|
|
||||||
|
View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer);
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer);
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state);
|
||||||
|
|
||||||
|
void flipp_pomodoro_view_timer_set_on_right_cb(
|
||||||
|
FlippPomodoroTimerView* timer,
|
||||||
|
FlippPomodoroTimerViewInputCb right_cb,
|
||||||
|
void* right_cb_ctx);
|
||||||
21
applications/plugins/scrambler/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 RaZe
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
16
applications/plugins/scrambler/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Setting up the Rubik's Cube Scrambler
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
To install the Rubik's Cube Scrambler, simply add the `rubiks_cube_scrambler` folder to your `application_users` folder.
|
||||||
|
|
||||||
|
## Cleaning the code and removing old files
|
||||||
|
Run `./fbt -c fap_rubiks_cube_scrambler` to clean the code and remove any old binaries or compilation artifacts.
|
||||||
|
|
||||||
|
## Compiling the FAP
|
||||||
|
To compile the FAP, run `./fbt fap_rubiks_cube_scrambler`.
|
||||||
|
|
||||||
|
## Launching the app
|
||||||
|
To run the Rubik's Cube Scrambler directly from the Flip.x0, use `./fbt launch_app APPSRC=rubiks_cube_scrambler`.
|
||||||
|
|
||||||
|
# A special thanks to Tanish for their c scrambler example 🙏
|
||||||
|
https://github.com/TanishBhongade/RubiksCubeScrambler-C/
|
||||||
20
applications/plugins/scrambler/application.fam
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# COMPILE ISTRUCTIONS:
|
||||||
|
|
||||||
|
# Clean the code and remove old binaries/compilation artefact
|
||||||
|
# ./fbt -c fap_rubiks_cube_scrambler
|
||||||
|
|
||||||
|
# Compile FAP
|
||||||
|
# ./fbt fap_rubiks_cube_scrambler
|
||||||
|
|
||||||
|
# Run application directly inside the Flip.x0
|
||||||
|
# ./fbt launch_app APPSRC=rubiks_cube_scrambler
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="Rubiks_Cube_Scrambler",
|
||||||
|
name="Rubik's Cube Scrambler",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="rubiks_cube_scrambler_main",
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
fap_category="Misc",
|
||||||
|
fap_icon="cube.png",
|
||||||
|
)
|
||||||
BIN
applications/plugins/scrambler/assets/1.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/scrambler/cube.png
Normal file
|
After Width: | Height: | Size: 96 B |
115
applications/plugins/scrambler/rubiks_cube_scrambler.c
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include "scrambler.h"
|
||||||
|
#include "furi_hal_random.h"
|
||||||
|
|
||||||
|
int scrambleStarted = 0;
|
||||||
|
char scramble_str[100] = {0};
|
||||||
|
char scramble_start[100] = {0};
|
||||||
|
char scramble_end[100] = {0};
|
||||||
|
int notifications_enabled = 0;
|
||||||
|
|
||||||
|
static void success_vibration() {
|
||||||
|
furi_hal_vibro_on(false);
|
||||||
|
furi_hal_vibro_on(true);
|
||||||
|
furi_delay_ms(50);
|
||||||
|
furi_hal_vibro_on(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void split_array(char original[], int size, char first[], char second[]) {
|
||||||
|
int mid = size / 2;
|
||||||
|
if(size % 2 != 0) {
|
||||||
|
mid++;
|
||||||
|
}
|
||||||
|
int first_index = 0, second_index = 0;
|
||||||
|
for(int i = 0; i < size; i++) {
|
||||||
|
if(i < mid) {
|
||||||
|
first[first_index++] = original[i];
|
||||||
|
} else {
|
||||||
|
if(i == mid && (original[i] == '2' || original[i] == '\'')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
second[second_index++] = original[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first[first_index] = '\0';
|
||||||
|
second[second_index] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||||
|
UNUSED(ctx);
|
||||||
|
canvas_clear(canvas);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 4, 13, "Rubik's Cube Scrambler");
|
||||||
|
|
||||||
|
if(scrambleStarted) {
|
||||||
|
genScramble();
|
||||||
|
scrambleReplace();
|
||||||
|
strcpy(scramble_str, printData());
|
||||||
|
if(notifications_enabled) {
|
||||||
|
success_vibration();
|
||||||
|
}
|
||||||
|
split_array(scramble_str, strlen(scramble_str), scramble_start, scramble_end);
|
||||||
|
scrambleStarted = 0;
|
||||||
|
}
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 28, AlignCenter, AlignCenter, scramble_start);
|
||||||
|
canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, scramble_end);
|
||||||
|
elements_button_center(canvas, "New");
|
||||||
|
|
||||||
|
elements_button_left(canvas, notifications_enabled ? "On" : "Off");
|
||||||
|
};
|
||||||
|
|
||||||
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t rubiks_cube_scrambler_main(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
InputEvent event;
|
||||||
|
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||||
|
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
|
||||||
|
view_port_draw_callback_set(view_port, draw_callback, NULL);
|
||||||
|
|
||||||
|
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||||
|
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(event.key == InputKeyOk && event.type == InputTypeShort) {
|
||||||
|
scrambleStarted = 1;
|
||||||
|
}
|
||||||
|
if(event.key == InputKeyLeft && event.type == InputTypeShort) {
|
||||||
|
if(notifications_enabled) {
|
||||||
|
notifications_enabled = 0;
|
||||||
|
} else {
|
||||||
|
notifications_enabled = 1;
|
||||||
|
success_vibration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(event.key == InputKeyBack) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
102
applications/plugins/scrambler/scrambler.c
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
Authors: Tanish Bhongade and RaZe
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include "furi_hal_random.h"
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include "scrambler.h"
|
||||||
|
|
||||||
|
// 6 moves along with direction
|
||||||
|
char moves[6] = {'R', 'U', 'F', 'B', 'L', 'D'};
|
||||||
|
char dir[4] = {' ', '\'', '2'};
|
||||||
|
const int SLEN = 20;
|
||||||
|
#define RESULT_SIZE 100
|
||||||
|
// Structure which holds main scramble
|
||||||
|
struct GetScramble {
|
||||||
|
char mainScramble[25][3];
|
||||||
|
};
|
||||||
|
struct GetScramble a; // Its object
|
||||||
|
|
||||||
|
// Function prototypes to avoid bugs
|
||||||
|
void scrambleReplace();
|
||||||
|
void genScramble();
|
||||||
|
void valid();
|
||||||
|
int getRand(int upr, int lwr);
|
||||||
|
char* printData();
|
||||||
|
void writeToFile();
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
/* int main(){
|
||||||
|
genScramble ();//Calling genScramble
|
||||||
|
scrambleReplace();//Calling scrambleReplace
|
||||||
|
valid();//Calling valid to validate the scramble
|
||||||
|
printData ();//Printing the final scramble
|
||||||
|
//writeToFile();//If you want to write to a file, please uncomment this
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} */
|
||||||
|
|
||||||
|
void genScramble() {
|
||||||
|
// Stage 1
|
||||||
|
for(int i = 0; i < SLEN; i++) {
|
||||||
|
strcpy(a.mainScramble[i], "00");
|
||||||
|
}
|
||||||
|
// This makes array like this 00 00 00.......
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrambleReplace() {
|
||||||
|
// Stage 2
|
||||||
|
// Actual process begins here
|
||||||
|
|
||||||
|
// Initialize the mainScramble array with all the possible moves
|
||||||
|
for(int i = 0; i < SLEN; i++) {
|
||||||
|
a.mainScramble[i][0] = moves[furi_hal_random_get() % 6];
|
||||||
|
a.mainScramble[i][1] = dir[furi_hal_random_get() % 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the Fisher-Yates shuffle
|
||||||
|
for(int i = 6 - 1; i > 0; i--) {
|
||||||
|
int j = rand() % (i + 1);
|
||||||
|
char temp[3];
|
||||||
|
strcpy(temp, a.mainScramble[i]);
|
||||||
|
strcpy(a.mainScramble[i], a.mainScramble[j]);
|
||||||
|
strcpy(a.mainScramble[j], temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the first 10 elements as the scramble, using only the first three elements of the dir array
|
||||||
|
for(int i = 0; i < SLEN; i++) {
|
||||||
|
a.mainScramble[i][1] = dir[furi_hal_random_get() % 3];
|
||||||
|
}
|
||||||
|
for(int i = 1; i < SLEN; i++) {
|
||||||
|
while(a.mainScramble[i][0] == a.mainScramble[i - 2][0] ||
|
||||||
|
a.mainScramble[i][0] == a.mainScramble[i - 1][0]) {
|
||||||
|
a.mainScramble[i][0] = moves[furi_hal_random_get() % 5];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let this function be here for now till I find out what is causing the extra space bug in the scrambles
|
||||||
|
void remove_double_spaces(char* str) {
|
||||||
|
int i, j;
|
||||||
|
int len = strlen(str);
|
||||||
|
for(i = 0, j = 0; i < len; i++, j++) {
|
||||||
|
if(str[i] == ' ' && str[i + 1] == ' ') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
str[j] = str[i];
|
||||||
|
}
|
||||||
|
str[j] = '\0';
|
||||||
|
}
|
||||||
|
char* printData() {
|
||||||
|
static char result[RESULT_SIZE];
|
||||||
|
int offset = 0;
|
||||||
|
for(int loop = 0; loop < SLEN; loop++) {
|
||||||
|
offset += snprintf(result + offset, RESULT_SIZE - offset, "%s ", a.mainScramble[loop]);
|
||||||
|
}
|
||||||
|
remove_double_spaces(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
3
applications/plugins/scrambler/scrambler.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
void scrambleReplace();
|
||||||
|
void genScramble();
|
||||||
|
char* printData();
|
||||||