Update apps pt1
@@ -19,6 +19,7 @@ Using it is really simple:
|
||||
* Up button for Left mouse click;
|
||||
* Down button for Right mouse click;
|
||||
* Center button for Middle mouse click;
|
||||
* Left and Right buttons for scrolling;
|
||||
* Use calibration menu option if you notice significant drift (place your Flipper onto a level surface, make sure it doesn't move, run this option, wait 2 seconds, done).
|
||||
|
||||
See early prototype [in action](https://www.youtube.com/watch?v=DdxAmmsYfMA).
|
||||
@@ -41,10 +42,9 @@ Reality:
|
||||
|
||||

|
||||
|
||||
|
||||
## Software
|
||||
|
||||
The code is based on the original Bosch [driver](https://github.com/BoschSensortec/BMI160_driver/) and an orientation tracking implementation from the [Cardboard](https://github.com/googlevr/cardboard/tree/master/sdk/sensors) project
|
||||
The code is based on the original Bosch [driver](https://github.com/BoschSensortec/BMI160_driver/) and an orientation tracking implementation from the Google [Cardboard](https://github.com/googlevr/cardboard/tree/master/sdk/sensors) project
|
||||
|
||||
If you're familiar with Flipper applications, start in the [firmware](https://github.com/flipperdevices/flipperzero-firmware) checkout folder and do the following:
|
||||
```
|
||||
@@ -54,3 +54,7 @@ cd ../..
|
||||
./fbt fap_air_mouse
|
||||
```
|
||||
If you're not familiar with those, just grab a `fap` file from Releases.
|
||||
|
||||
## License
|
||||
|
||||
TL;DR: Use the code however you want, give credit where it's due, no warranty of any kind is provided.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="BMI160_Air_Mouse",
|
||||
appid="Air_Mouse",
|
||||
name="[BMI160] Air Mouse",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="air_mouse_app",
|
||||
|
||||
@@ -44,8 +44,7 @@ struct BtMouse {
|
||||
#define BT_MOUSE_FLAG_KILL_THREAD (1UL << 1)
|
||||
#define BT_MOUSE_FLAG_ALL (BT_MOUSE_FLAG_INPUT_EVENT | BT_MOUSE_FLAG_KILL_THREAD)
|
||||
|
||||
#define MOUSE_MOVE_SHORT 5
|
||||
#define MOUSE_MOVE_LONG 20
|
||||
#define MOUSE_SCROLL 2
|
||||
|
||||
static void bt_mouse_notify_event(BtMouse* bt_mouse) {
|
||||
FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread);
|
||||
@@ -100,6 +99,14 @@ static void bt_mouse_process(BtMouse* bt_mouse, InputEvent* event) {
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, false);
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
bt_mouse->wheel = MOUSE_SCROLL;
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
bt_mouse->wheel = -MOUSE_SCROLL;
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
@@ -21,6 +21,8 @@ static void usb_mouse_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
|
||||
}
|
||||
|
||||
#define MOUSE_SCROLL 2
|
||||
|
||||
static void usb_mouse_process(UsbMouse* usb_mouse, InputEvent* event) {
|
||||
with_view_model(
|
||||
usb_mouse->view,
|
||||
@@ -45,6 +47,14 @@ static void usb_mouse_process(UsbMouse* usb_mouse, InputEvent* event) {
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
furi_hal_hid_mouse_release(HID_MOUSE_BTN_WHEEL);
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
furi_hal_hid_mouse_scroll(MOUSE_SCROLL);
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
furi_hal_hid_mouse_scroll(-MOUSE_SCROLL);
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
@@ -9,6 +9,6 @@ Hit any button other than back repeatedly. Calculates based on the average of th
|
||||
## Compiling
|
||||
|
||||
```
|
||||
./fbt fap_bpm_tapper
|
||||
./fbt firmware_bpm_tapper
|
||||
```
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <m-string.h>
|
||||
#include <stdlib.h>
|
||||
#include "BPM_Tapper_icons.h"
|
||||
|
||||
@@ -91,6 +90,7 @@ static float queue_avg(queue* q) {
|
||||
//}
|
||||
//
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
int taps;
|
||||
double bpm;
|
||||
uint32_t last_stamp;
|
||||
@@ -127,42 +127,43 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
string_t tempStr;
|
||||
furi_assert(ctx);
|
||||
const BPMTapper* bpm_state = ctx;
|
||||
furi_mutex_acquire(bpm_state->mutex, FuriWaitForever);
|
||||
|
||||
const BPMTapper* bpm_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(bpm_state == NULL) {
|
||||
return;
|
||||
}
|
||||
FuriString* tempStr;
|
||||
// border
|
||||
//canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
string_init(tempStr);
|
||||
tempStr = furi_string_alloc();
|
||||
|
||||
string_printf(tempStr, "Taps: %d", bpm_state->taps);
|
||||
canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr));
|
||||
string_reset(tempStr);
|
||||
furi_string_printf(tempStr, "Taps: %d", bpm_state->taps);
|
||||
canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
|
||||
furi_string_reset(tempStr);
|
||||
|
||||
string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size);
|
||||
canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, string_get_cstr(tempStr));
|
||||
string_reset(tempStr);
|
||||
furi_string_printf(tempStr, "Queue: %d", bpm_state->tap_queue->size);
|
||||
canvas_draw_str_aligned(canvas, 70, 10, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
|
||||
furi_string_reset(tempStr);
|
||||
|
||||
string_printf(tempStr, "Interval: %dms", bpm_state->interval);
|
||||
canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, string_get_cstr(tempStr));
|
||||
string_reset(tempStr);
|
||||
furi_string_printf(tempStr, "Interval: %ldms", bpm_state->interval);
|
||||
canvas_draw_str_aligned(canvas, 5, 20, AlignLeft, AlignBottom, furi_string_get_cstr(tempStr));
|
||||
furi_string_reset(tempStr);
|
||||
|
||||
string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2);
|
||||
canvas_draw_str_aligned(canvas, 64, 60, AlignCenter, AlignCenter, string_get_cstr(tempStr));
|
||||
string_reset(tempStr);
|
||||
furi_string_printf(tempStr, "x2 %.2f /2 %.2f", bpm_state->bpm * 2, bpm_state->bpm / 2);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 60, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
|
||||
furi_string_reset(tempStr);
|
||||
|
||||
string_printf(tempStr, "%.2f", bpm_state->bpm);
|
||||
furi_string_printf(tempStr, "%.2f", bpm_state->bpm);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, string_get_cstr(tempStr));
|
||||
string_reset(tempStr);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
|
||||
furi_string_reset(tempStr);
|
||||
|
||||
string_clear(tempStr);
|
||||
furi_string_free(tempStr);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, bpm_state);
|
||||
furi_mutex_release(bpm_state->mutex);
|
||||
}
|
||||
|
||||
static void bpm_state_init(BPMTapper* const plugin_state) {
|
||||
@@ -185,8 +186,8 @@ int32_t bpm_tapper_app(void* p) {
|
||||
// setup
|
||||
bpm_state_init(bpm_state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, bpm_state, sizeof(bpm_state))) {
|
||||
bpm_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!bpm_state->mutex) {
|
||||
FURI_LOG_E("BPM-Tapper", "cannot create mutex\r\n");
|
||||
free(bpm_state);
|
||||
return 255;
|
||||
@@ -197,7 +198,7 @@ int32_t bpm_tapper_app(void* p) {
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_draw_callback_set(view_port, render_callback, bpm_state);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
@@ -207,7 +208,7 @@ int32_t bpm_tapper_app(void* p) {
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
BPMTapper* bpm_state = (BPMTapper*)acquire_mutex_block(&state_mutex);
|
||||
furi_mutex_acquire(bpm_state->mutex, FuriWaitForever);
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
@@ -240,19 +241,16 @@ int32_t bpm_tapper_app(void* p) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("BPM-Tapper", "FuriMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, bpm_state);
|
||||
furi_mutex_release(bpm_state->mutex);
|
||||
}
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
furi_mutex_free(bpm_state->mutex);
|
||||
queue* q = bpm_state->tap_queue;
|
||||
free(q);
|
||||
free(bpm_state);
|
||||
|
||||
BIN
applications/plugins/bpmtapper/images/DolphinCommon_56x48.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -11,5 +11,4 @@ App(
|
||||
fap_icon="bfico.png",
|
||||
fap_category="Misc",
|
||||
fap_icon_assets="icons",
|
||||
fap_icon_assets_symbol="brainfuck",
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ typedef unsigned char byte;
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <brainfuck_icons.h>
|
||||
#include <Brainfuck_icons.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <stream/stream.h>
|
||||
|
||||
@@ -35,9 +35,9 @@ int selectedButton = 0;
|
||||
int saveNotifyCountdown = 0;
|
||||
int execCountdown = 0;
|
||||
|
||||
char dspLine0[25] = {};
|
||||
char dspLine1[25] = {};
|
||||
char dspLine2[25] = {};
|
||||
char dspLine0[25] = {0x00};
|
||||
char dspLine1[25] = {0x00};
|
||||
char dspLine2[25] = {0x00};
|
||||
|
||||
static bMapping buttonMappings[12] = {
|
||||
{8, 8, 7, 1}, //0
|
||||
@@ -361,6 +361,10 @@ static void bf_dev_enter_callback(void* context) {
|
||||
|
||||
//read into the buffer
|
||||
appDev->dataSize = stream_size(stream);
|
||||
if(appDev->dataSize > 2000) {
|
||||
return; //BF file is too large
|
||||
}
|
||||
|
||||
stream_read(stream, (uint8_t*)appDev->dataBuffer, appDev->dataSize);
|
||||
buffered_file_stream_close(stream);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "worker.h"
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi.h>
|
||||
|
||||
bool killswitch = false;
|
||||
|
||||
@@ -207,27 +209,40 @@ static const NotificationSequence led_on = {
|
||||
};
|
||||
|
||||
static const NotificationSequence led_off = {
|
||||
&message_green_0,
|
||||
&message_blue_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void input_kill(void* _ctx) {
|
||||
UNUSED(_ctx);
|
||||
killswitch = true;
|
||||
}
|
||||
|
||||
void beginWorker() {
|
||||
status = 1;
|
||||
|
||||
//redefined from furi_hal_resources.c
|
||||
const GpioPin gpio_button_back = {.port = GPIOC, .pin = LL_GPIO_PIN_13};
|
||||
|
||||
while(inst[instPtr] != 0x00) {
|
||||
if(runOpCount % 500 == 0) {
|
||||
text_box_set_text(wrkrApp->text_box, workerGetOutput());
|
||||
notification_message(wrkrApp->notifications, &led_on);
|
||||
}
|
||||
|
||||
//status 2 indicates failure
|
||||
if(status == 2) {
|
||||
status = 0;
|
||||
break;
|
||||
}
|
||||
if(killswitch) {
|
||||
|
||||
//read back button directly to avoid weirdness in furi
|
||||
if(killswitch || !furi_hal_gpio_read(&gpio_button_back)) {
|
||||
status = 0;
|
||||
killswitch = false;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(inst[instPtr]) {
|
||||
case '>':
|
||||
rShift();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
App(
|
||||
appid="Caesar_Cipher",
|
||||
name="Caesar Cipher",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="caesar_cipher_app",
|
||||
cdefines=["APP_CAESAR_CIPHER"],
|
||||
requires=[
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef struct {
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
TextInput* text_input;
|
||||
TextBox* text_box;
|
||||
@@ -58,7 +59,10 @@ static void build_output(char* input, char* output) {
|
||||
}
|
||||
|
||||
static void text_input_callback(void* ctx) {
|
||||
CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
furi_assert(ctx);
|
||||
CaesarState* caesar_state = ctx;
|
||||
furi_mutex_acquire(caesar_state->mutex, FuriWaitForever);
|
||||
|
||||
FURI_LOG_D("caesar_cipher", "Input text: %s", caesar_state->input);
|
||||
// this is where we build the output.
|
||||
string_to_uppercase(caesar_state->input);
|
||||
@@ -67,13 +71,14 @@ static void text_input_callback(void* ctx) {
|
||||
text_box_set_text(caesar_state->text_box, caesar_state->output);
|
||||
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 1);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, caesar_state);
|
||||
furi_mutex_release(caesar_state->mutex);
|
||||
}
|
||||
|
||||
static bool back_event_callback(void* ctx) {
|
||||
const CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
const CaesarState* caesar_state = ctx;
|
||||
furi_mutex_acquire(caesar_state->mutex, FuriWaitForever);
|
||||
view_dispatcher_stop(caesar_state->view_dispatcher);
|
||||
release_mutex((ValueMutex*)ctx, caesar_state);
|
||||
furi_mutex_release(caesar_state->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,8 +104,8 @@ int32_t caesar_cipher_app() {
|
||||
FURI_LOG_D("caesar_cipher", "Running caesar_cipher_state_init");
|
||||
caesar_cipher_state_init(caesar_state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, caesar_state, sizeof(CaesarState))) {
|
||||
caesar_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!caesar_state->mutex) {
|
||||
FURI_LOG_E("caesar_cipher", "cannot create mutex\r\n");
|
||||
free(caesar_state);
|
||||
return 255;
|
||||
@@ -110,7 +115,7 @@ int32_t caesar_cipher_app() {
|
||||
text_input_set_result_callback(
|
||||
caesar_state->text_input,
|
||||
text_input_callback,
|
||||
&state_mutex,
|
||||
caesar_state,
|
||||
caesar_state->input,
|
||||
TEXT_BUFFER_SIZE,
|
||||
//clear default text
|
||||
@@ -135,12 +140,12 @@ int32_t caesar_cipher_app() {
|
||||
FURI_LOG_D("ceasar_cipher", "starting view dispatcher");
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
caesar_state->view_dispatcher, back_event_callback);
|
||||
view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, &state_mutex);
|
||||
view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, caesar_state);
|
||||
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 0);
|
||||
view_dispatcher_run(caesar_state->view_dispatcher);
|
||||
|
||||
furi_record_close("gui");
|
||||
delete_mutex(&state_mutex);
|
||||
furi_mutex_free(caesar_state->mutex);
|
||||
caesar_cipher_state_free(caesar_state);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
App(
|
||||
appid="Counter",
|
||||
appid="counter",
|
||||
name="Counter",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="counterapp",
|
||||
requires=[
|
||||
"gui",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <Counter_icons.h>
|
||||
#include <counter_icons.h>
|
||||
|
||||
#define MAX_COUNT 99
|
||||
#define BOXTIME 2
|
||||
|
||||
|
Before Width: | Height: | Size: 21 MiB |
@@ -21,6 +21,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
bool revive;
|
||||
int evo;
|
||||
FuriMutex* mutex;
|
||||
} State;
|
||||
|
||||
unsigned char new[TOTAL_PIXELS] = {};
|
||||
@@ -92,7 +93,10 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* canvas, void* ctx) {
|
||||
State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25);
|
||||
//furi_assert(ctx);
|
||||
State* state = ctx;
|
||||
furi_mutex_acquire(state->mutex, FuriWaitForever);
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
for(int i = 0; i < TOTAL_PIXELS; ++i) {
|
||||
@@ -100,7 +104,7 @@ static void render_callback(Canvas* canvas, void* ctx) {
|
||||
int y = (int)(i / SCREEN_WIDTH);
|
||||
if(fields[current][i] == 1) canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
release_mutex((ValueMutex*)ctx, state);
|
||||
furi_mutex_release(state->mutex);
|
||||
}
|
||||
|
||||
int32_t game_of_life_app(void* p) {
|
||||
@@ -112,8 +116,8 @@ int32_t game_of_life_app(void* p) {
|
||||
|
||||
State* _state = malloc(sizeof(State));
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, _state, sizeof(State))) {
|
||||
_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!_state->mutex) {
|
||||
printf("cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(_state);
|
||||
@@ -121,7 +125,7 @@ int32_t game_of_life_app(void* p) {
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_draw_callback_set(view_port, render_callback, _state);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
@@ -129,23 +133,23 @@ int32_t game_of_life_app(void* p) {
|
||||
|
||||
AppEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
State* state = (State*)acquire_mutex_block(&state_mutex);
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25);
|
||||
furi_mutex_acquire(_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk && event.type == EventTypeKey &&
|
||||
event.input.type == InputTypePress) {
|
||||
if(event.input.key == InputKeyBack) {
|
||||
// furiac_exit(NULL);
|
||||
processing = false;
|
||||
release_mutex(&state_mutex, state);
|
||||
furi_mutex_release(_state->mutex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
update_field(state);
|
||||
update_field(_state);
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, state);
|
||||
furi_mutex_release(_state->mutex);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
@@ -153,7 +157,7 @@ int32_t game_of_life_app(void* p) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
furi_mutex_free(_state->mutex);
|
||||
free(_state);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -15,6 +15,7 @@ typedef struct {
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
float xZoom;
|
||||
float yZoom;
|
||||
float xOffset;
|
||||
@@ -52,10 +53,9 @@ bool mandelbrot_pixel(int x, int y, float xZoom, float yZoom, float xOffset, flo
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
furi_assert(ctx);
|
||||
const PluginState* plugin_state = ctx;
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
@@ -75,7 +75,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
@@ -100,8 +100,8 @@ int32_t mandelbrot_app(void* p) {
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
mandelbrot_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!plugin_state->mutex) {
|
||||
FURI_LOG_E("mandelbrot", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(plugin_state);
|
||||
@@ -110,7 +110,7 @@ int32_t mandelbrot_app(void* p) {
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_draw_callback_set(view_port, render_callback, plugin_state);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
@@ -120,7 +120,7 @@ int32_t mandelbrot_app(void* p) {
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
@@ -154,12 +154,9 @@ int32_t mandelbrot_app(void* p) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("mandelbrot", "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
@@ -167,6 +164,8 @@ int32_t mandelbrot_app(void* p) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
free(plugin_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -11,9 +11,9 @@ App(
|
||||
provides=["music_beeper_start"],
|
||||
stack_size=2 * 1024,
|
||||
order=45,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon="music_10px.png",
|
||||
fap_icon_assets="icons",
|
||||
fap_category="Music",
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
BIN
applications/plugins/music_beeper/music_10px.png
Normal file
|
After Width: | Height: | Size: 142 B |
@@ -101,8 +101,11 @@ MusicBeeperWorker* music_beeper_worker_alloc() {
|
||||
|
||||
NoteBlockArray_init(instance->notes);
|
||||
|
||||
instance->thread = furi_thread_alloc_ex(
|
||||
"MusicBeeperWorker", 1024, music_beeper_worker_thread_callback, instance);
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "MusicBeeperWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 1024);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, music_beeper_worker_thread_callback);
|
||||
|
||||
instance->volume = 1.0f;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
App(
|
||||
appid="Zero_Tracker",
|
||||
appid="zero_tracker",
|
||||
name="Zero Tracker",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="zero_tracker_app",
|
||||
requires=[
|
||||
"gui",
|
||||
|
||||
@@ -40,7 +40,7 @@ void tracker_speaker_stop() {
|
||||
}
|
||||
|
||||
void tracker_speaker_init() {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(200.0f, 0.01f);
|
||||
tracker_speaker_stop();
|
||||
}
|
||||
|
||||
@@ -59,6 +59,21 @@ OK - вход в режим просмотра адресов и включен
|
||||
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/Screenshot-5.png">
|
||||
<br>
|
||||
<br>
|
||||
Схема подключения модуля nRF24l01:<br>
|
||||
Big board:<br>
|
||||
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/scheme.png">
|
||||
Gerber for full board (https://raw.githubusercontent.com/vad7/nrf24scan/master/Gerber_PCB_Flipper%20Zero%20nRF24%20board_v1_0.zip)<br>
|
||||
Easyeda source (https://oshwlab.com/vad7/flipper-zero-nrf24-board).
|
||||
<br><br>
|
||||
Mini board:<br>
|
||||
<img src="https://raw.githubusercontent.com/vad7/nRF24-Batch/main/Pics/NRF24_mini_3D.png">
|
||||
<br>
|
||||
<img src="https://raw.githubusercontent.com/vad7/nrf24scan/master/scheme2.png">
|
||||
<br>
|
||||
Gerber for mini board with 5V - 3.3V converter LM1117-3.3 (https://raw.githubusercontent.com/vad7/nrf24scan/master/Gerber_PCB_Flipper%20Zero%20nRF24%20board%20mini_v1_0.zip)<br>
|
||||
Easyeda source (https://oshwlab.com/vad7/flipper-zero-nrf24-board_copy).
|
||||
<br>
|
||||
<br>
|
||||
_________________________________________________________________________________
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <u8g2.h>
|
||||
|
||||
#define TAG "nrf24scan"
|
||||
#define VERSION "2.1"
|
||||
#define VERSION "2.2"
|
||||
#define MAX_CHANNEL 125
|
||||
#define MAX_ADDR 6
|
||||
|
||||
@@ -71,6 +71,7 @@ uint8_t NRF_Payload = 32; // Payload len in bytes or Minimum payload in sniff mo
|
||||
uint8_t NRF_Payload_sniff_min = 0;
|
||||
uint8_t NRF_AA_OFF = 0; // Disable Auto Acknowledgement
|
||||
bool NRF_ERROR = 0;
|
||||
bool NRF_BOARD_POWER_5V = false;
|
||||
|
||||
struct ADDRS {
|
||||
uint8_t addr_P0[5]; // MSB first
|
||||
@@ -485,7 +486,13 @@ void check_add_addr(uint8_t* pkt) {
|
||||
|
||||
static void prepare_nrf24(bool fsend_packet) {
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_STATUS, 0x70); // clear interrupts
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_RF_SETUP, NRF_rate);
|
||||
nrf24_write_reg(
|
||||
nrf24_HANDLE,
|
||||
REG_RF_SETUP,
|
||||
(NRF_rate == 0 ? 0b00100000 :
|
||||
NRF_rate == 1 ? 0 :
|
||||
0b00001000) |
|
||||
0b111); // +TX high power
|
||||
uint8_t erx_addr = (1 << 0); // Enable RX_P0
|
||||
struct ADDRS* adr = what_to_do == 1 ? &addrs_sniff : &addrs;
|
||||
if(!fsend_packet) {
|
||||
@@ -517,14 +524,13 @@ static void prepare_nrf24(bool fsend_packet) {
|
||||
}
|
||||
if(what_to_do == 1) { // SNIFF
|
||||
payload = 32;
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_CONFIG, 0x70); // Mask all interrupts, NO CRC
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_CONFIG, 0x70); // Mask all interrupts
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_SETUP_RETR, 0); // Automatic Retransmission
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0); // Auto acknowledgement
|
||||
nrf24_write_reg(
|
||||
nrf24_HANDLE,
|
||||
REG_FEATURE,
|
||||
0); // Enables the W_TX_PAYLOAD_NOACK command, Disable Payload with ACK, set Dynamic Payload
|
||||
nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, NRF_channel);
|
||||
} else if(setup_from_log) { // Scan
|
||||
nrf24_write_reg(
|
||||
nrf24_HANDLE,
|
||||
@@ -947,8 +953,10 @@ bool nrf24_send_packet() {
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) return;
|
||||
furi_assert(ctx);
|
||||
const PluginState* plugin_state = ctx;
|
||||
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
//canvas_draw_frame(canvas, 0, 0, 128, 64); // border around the edge of the screen
|
||||
if(what_doing == 0) {
|
||||
canvas_set_font(canvas, FontSecondary); // 8x10 font, 6 lines
|
||||
@@ -1317,7 +1325,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_draw_str(canvas, 0, 64, screen_buf);
|
||||
}
|
||||
}
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
}
|
||||
|
||||
int32_t nrf24scan_app(void* p) {
|
||||
@@ -1325,8 +1333,8 @@ int32_t nrf24scan_app(void* p) {
|
||||
APP = malloc(sizeof(Nrf24Scan));
|
||||
APP->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
APP->plugin_state = malloc(sizeof(PluginState));
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, APP->plugin_state, sizeof(PluginState))) {
|
||||
APP->plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!APP->plugin_state->mutex) {
|
||||
furi_message_queue_free(APP->event_queue);
|
||||
FURI_LOG_E(TAG, "cannot create mutex");
|
||||
free(APP->plugin_state);
|
||||
@@ -1346,11 +1354,16 @@ int32_t nrf24scan_app(void* p) {
|
||||
|
||||
memset((uint8_t*)&addrs, 0, sizeof(addrs));
|
||||
memset((uint8_t*)&addrs_sniff, 0, sizeof(addrs_sniff));
|
||||
if(!furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_enable_otg();
|
||||
NRF_BOARD_POWER_5V = true;
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
nrf24_init();
|
||||
|
||||
// Set system callbacks
|
||||
APP->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(APP->view_port, render_callback, &state_mutex);
|
||||
view_port_draw_callback_set(APP->view_port, render_callback, APP->plugin_state);
|
||||
view_port_input_callback_set(APP->view_port, input_callback, APP->event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
@@ -1391,7 +1404,7 @@ int32_t nrf24scan_app(void* p) {
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(APP->event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
furi_mutex_acquire(APP->plugin_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
@@ -1618,13 +1631,14 @@ int32_t nrf24scan_app(void* p) {
|
||||
}
|
||||
|
||||
view_port_update(APP->view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
furi_mutex_release(APP->plugin_state->mutex);
|
||||
}
|
||||
nrf24_set_idle(nrf24_HANDLE);
|
||||
if(log_arr_idx && (log_to_file == 1 || log_to_file == 2)) {
|
||||
write_to_log_file(APP->storage, false);
|
||||
}
|
||||
nrf24_deinit();
|
||||
if(NRF_BOARD_POWER_5V) furi_hal_power_disable_otg();
|
||||
|
||||
view_port_enabled_set(APP->view_port, false);
|
||||
gui_remove_view_port(APP->gui, APP->view_port);
|
||||
@@ -1632,6 +1646,7 @@ int32_t nrf24scan_app(void* p) {
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
view_port_free(APP->view_port);
|
||||
furi_mutex_free(APP->plugin_state->mutex);
|
||||
furi_message_queue_free(APP->event_queue);
|
||||
free(APP->plugin_state);
|
||||
if(APP->log_arr) free(APP->log_arr);
|
||||
|
||||
@@ -19,8 +19,7 @@ typedef struct {
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
FuriMutex* mutex;
|
||||
} PluginState;
|
||||
|
||||
struct FOUND {
|
||||
|
||||
@@ -88,27 +88,27 @@ int32_t ocarina_app(void* p) {
|
||||
if(event.type == InputTypePress) {
|
||||
switch(event.key) {
|
||||
case InputKeyUp:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_UP, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_DOWN, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_LEFT, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_RIGHT, volume);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
furi_hal_speaker_start(NOTE_OK, volume);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -12,14 +12,17 @@ typedef struct selected_position {
|
||||
} selected_position;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
selected_position selected;
|
||||
bool board[32][16];
|
||||
bool isDrawing;
|
||||
} PaintData;
|
||||
|
||||
void paint_draw_callback(Canvas* canvas, void* ctx) {
|
||||
const PaintData* paint_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
UNUSED(ctx);
|
||||
furi_assert(ctx);
|
||||
const PaintData* paint_state = ctx;
|
||||
furi_mutex_acquire(paint_state->mutex, FuriWaitForever);
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
//draw the canvas(32x16) on screen(144x64) using 4x4 tiles
|
||||
@@ -39,7 +42,7 @@ void paint_draw_callback(Canvas* canvas, void* ctx) {
|
||||
canvas, paint_state->selected.x * 4 + 1, paint_state->selected.y * 4 + 1, 2, 2);
|
||||
|
||||
//release the mutex
|
||||
release_mutex((ValueMutex*)ctx, paint_state);
|
||||
furi_mutex_release(paint_state->mutex);
|
||||
}
|
||||
|
||||
void paint_input_callback(InputEvent* input_event, void* ctx) {
|
||||
@@ -53,8 +56,9 @@ int32_t paint_app(void* p) {
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
PaintData* paint_state = malloc(sizeof(PaintData));
|
||||
ValueMutex paint_state_mutex;
|
||||
if(!init_mutex(&paint_state_mutex, paint_state, sizeof(PaintData))) {
|
||||
|
||||
paint_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!paint_state->mutex) {
|
||||
FURI_LOG_E("paint", "cannot create mutex\r\n");
|
||||
free(paint_state);
|
||||
return -1;
|
||||
@@ -62,7 +66,7 @@ int32_t paint_app(void* p) {
|
||||
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, paint_draw_callback, &paint_state_mutex);
|
||||
view_port_draw_callback_set(view_port, paint_draw_callback, paint_state);
|
||||
view_port_input_callback_set(view_port, paint_input_callback, event_queue);
|
||||
|
||||
// Register view port in GUI
|
||||
@@ -140,9 +144,10 @@ int32_t paint_app(void* p) {
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
free(paint_state);
|
||||
furi_mutex_free(paint_state->mutex);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
free(paint_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
121
applications/plugins/pomodoro/LICENSE
Normal file
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
@@ -1,12 +1,15 @@
|
||||
App(
|
||||
appid="Pomodoro",
|
||||
name="Pomodoro",
|
||||
appid="Pomodoro_Timer",
|
||||
name="Pomodoro Timer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="flipp_pomodoro_app",
|
||||
requires=["gui", "notification", "dolphin"],
|
||||
entry_point="pomodoro_app",
|
||||
stack_size=1 * 1024,
|
||||
cdefines=["APP_POMODORO"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
order=10,
|
||||
fap_icon="pomodoro_timer.png",
|
||||
fap_category="Tools",
|
||||
fap_icon_assets="images",
|
||||
fap_icon="flipp_pomodoro_10.png",
|
||||
fap_icon_assets_symbol="flipp_pomodoro",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 157 B |
@@ -1,96 +0,0 @@
|
||||
#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:
|
||||
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;
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
#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;
|
||||
@@ -1,31 +0,0 @@
|
||||
#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"
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FlippPomodoro"
|
||||
@@ -1,49 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
#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};
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
#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);
|
||||
BIN
applications/plugins/pomodoro/icons/ButtonLeft_4x7.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
applications/plugins/pomodoro/icons/Ok_btn_9x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/pomodoro/icons/Pin_back_arrow_10x8.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/pomodoro/icons/Space_65x18.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,94 +0,0 @@
|
||||
#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;
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
#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);
|
||||
164
applications/plugins/pomodoro/pomodoro.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "pomodoro.h"
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "PomodoroApp"
|
||||
|
||||
enum PomodoroDebugSubmenuIndex {
|
||||
PomodoroSubmenuIndex10,
|
||||
PomodoroSubmenuIndex25,
|
||||
PomodoroSubmenuIndex50,
|
||||
};
|
||||
|
||||
void pomodoro_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
Pomodoro* app = context;
|
||||
if(index == PomodoroSubmenuIndex10) {
|
||||
app->view_id = PomodoroView10;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView10);
|
||||
}
|
||||
if(index == PomodoroSubmenuIndex25) {
|
||||
app->view_id = PomodoroView25;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView25);
|
||||
}
|
||||
if(index == PomodoroSubmenuIndex50) {
|
||||
app->view_id = PomodoroView50;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView50);
|
||||
}
|
||||
}
|
||||
|
||||
void pomodoro_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
Pomodoro* app = context;
|
||||
if(result == DialogExResultLeft) {
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
} else if(result == DialogExResultRight) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
|
||||
} else if(result == DialogExResultCenter) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroViewSubmenu);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t pomodoro_exit_confirm_view(void* context) {
|
||||
UNUSED(context);
|
||||
return PomodoroViewExitConfirm;
|
||||
}
|
||||
|
||||
uint32_t pomodoro_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
Pomodoro* pomodoro_app_alloc() {
|
||||
Pomodoro* app = malloc(sizeof(Pomodoro));
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
// Notifications
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Submenu view
|
||||
app->submenu = submenu_alloc();
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Classic: 25 work 5 rest",
|
||||
PomodoroSubmenuIndex25,
|
||||
pomodoro_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Long: 50 work 10 rest",
|
||||
PomodoroSubmenuIndex50,
|
||||
pomodoro_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Sprint: 10 work 2 rest",
|
||||
PomodoroSubmenuIndex10,
|
||||
pomodoro_submenu_callback,
|
||||
app);
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), pomodoro_exit);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, PomodoroViewSubmenu, submenu_get_view(app->submenu));
|
||||
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(app->dialog, pomodoro_dialog_callback);
|
||||
dialog_ex_set_context(app->dialog, app);
|
||||
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
||||
dialog_ex_set_center_button_text(app->dialog, "Menu");
|
||||
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, PomodoroViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// 25 minutes view
|
||||
app->pomodoro_25 = pomodoro_25_alloc();
|
||||
view_set_previous_callback(pomodoro_25_get_view(app->pomodoro_25), pomodoro_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, PomodoroView25, pomodoro_25_get_view(app->pomodoro_25));
|
||||
|
||||
// 50 minutes view
|
||||
app->pomodoro_50 = pomodoro_50_alloc();
|
||||
view_set_previous_callback(pomodoro_50_get_view(app->pomodoro_50), pomodoro_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, PomodoroView50, pomodoro_50_get_view(app->pomodoro_50));
|
||||
|
||||
// 10 minutes view
|
||||
app->pomodoro_10 = pomodoro_10_alloc();
|
||||
view_set_previous_callback(pomodoro_10_get_view(app->pomodoro_10), pomodoro_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, PomodoroView10, pomodoro_10_get_view(app->pomodoro_10));
|
||||
|
||||
// TODO switch to menu after Media is done
|
||||
app->view_id = PomodoroViewSubmenu;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void pomodoro_app_free(Pomodoro* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Reset notification
|
||||
notification_internal_message(app->notifications, &sequence_reset_blue);
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewExitConfirm);
|
||||
dialog_ex_free(app->dialog);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView25);
|
||||
pomodoro_25_free(app->pomodoro_25);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView50);
|
||||
pomodoro_50_free(app->pomodoro_50);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView10);
|
||||
pomodoro_10_free(app->pomodoro_10);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
app->notifications = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t pomodoro_app(void* p) {
|
||||
UNUSED(p);
|
||||
// Switch profile to Hid
|
||||
Pomodoro* app = pomodoro_app_alloc();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
pomodoro_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
34
applications/plugins/pomodoro/pomodoro.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <notification/notification.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "pomodoro_timer.h"
|
||||
#include "views/pomodoro_10.h"
|
||||
#include "views/pomodoro_25.h"
|
||||
#include "views/pomodoro_50.h"
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog;
|
||||
PomodoroTimer* pomodoro_10;
|
||||
PomodoroTimer* pomodoro_25;
|
||||
PomodoroTimer* pomodoro_50;
|
||||
uint32_t view_id;
|
||||
} Pomodoro;
|
||||
|
||||
typedef enum {
|
||||
PomodoroViewSubmenu,
|
||||
PomodoroView10,
|
||||
PomodoroView25,
|
||||
PomodoroView50,
|
||||
PomodoroViewExitConfirm,
|
||||
} PomodoroView;
|
||||
242
applications/plugins/pomodoro/pomodoro_timer.c
Normal file
@@ -0,0 +1,242 @@
|
||||
#include "pomodoro_timer.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <Pomodoro_Timer_icons.h>
|
||||
|
||||
const NotificationSequence sequence_finish = {
|
||||
&message_display_backlight_on,
|
||||
&message_green_255,
|
||||
&message_vibro_on,
|
||||
&message_note_c5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_e5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_g5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_b5,
|
||||
&message_delay_250,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_c6,
|
||||
&message_delay_250,
|
||||
&message_vibro_off,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_rest = {
|
||||
&message_display_backlight_on,
|
||||
&message_red_255,
|
||||
&message_vibro_on,
|
||||
&message_note_c6,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_b5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_g5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_e5,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_vibro_on,
|
||||
&message_note_c5,
|
||||
&message_delay_250,
|
||||
&message_vibro_off,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event) {
|
||||
with_view_model(
|
||||
pomodoro_timer->view,
|
||||
PomodoroTimerModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->reset_pressed = true;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = true;
|
||||
}
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
|
||||
// START/STOP TIMER
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
|
||||
// STARTED -> PAUSED
|
||||
if(model->timer_running) {
|
||||
// Update stopped seconds
|
||||
model->timer_stopped_seconds =
|
||||
current_timestamp - model->timer_start_timestamp;
|
||||
} else if(!model->time_passed) {
|
||||
// INITIAL -> STARTED
|
||||
model->timer_start_timestamp = current_timestamp;
|
||||
model->rest_running = false;
|
||||
} else {
|
||||
// PAUSED -> STARTED
|
||||
model->timer_start_timestamp =
|
||||
current_timestamp - model->timer_stopped_seconds;
|
||||
}
|
||||
model->timer_running = !model->timer_running;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(!model->timer_running) {
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
model->timer_stopped_seconds = 0;
|
||||
model->timer_start_timestamp = 0;
|
||||
model->time_passed = 0;
|
||||
model->timer_running = false;
|
||||
}
|
||||
model->reset_pressed = false;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest) {
|
||||
furi_assert(context);
|
||||
PomodoroTimerModel* model = context;
|
||||
FuriHalRtcDateTime curr_dt;
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Pomodoro");
|
||||
|
||||
canvas_draw_icon(canvas, 68, 1, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 127, 1, AlignRight, AlignTop, "Hold to exit");
|
||||
|
||||
// Start/Pause/Continue
|
||||
int txt_main_y = 34;
|
||||
canvas_draw_icon(canvas, 63, 23, &I_Space_65x18); // button
|
||||
if(model->ok_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 25, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
if(model->timer_running) {
|
||||
model->time_passed = current_timestamp - model->timer_start_timestamp;
|
||||
elements_multiline_text_aligned(canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Pause");
|
||||
canvas_draw_box(canvas, 71, 27, 2, 8);
|
||||
canvas_draw_box(canvas, 75, 27, 2, 8);
|
||||
} else {
|
||||
if(model->time_passed) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Continue");
|
||||
} else {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Start");
|
||||
}
|
||||
canvas_draw_icon(canvas, 70, 26, &I_Ok_btn_9x9); // OK icon
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Reset
|
||||
if(!model->timer_running && model->time_passed) {
|
||||
canvas_draw_icon(canvas, 63, 46, &I_Space_65x18);
|
||||
if(model->reset_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 48, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 72, 50, &I_ButtonLeft_4x7);
|
||||
elements_multiline_text_aligned(canvas, 83, 57, AlignLeft, AlignBottom, "Reset");
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
char buffer[64];
|
||||
|
||||
// Time to work
|
||||
int total_time_left = (max_seconds - (uint32_t)model->time_passed);
|
||||
int minutes_left = total_time_left / 60;
|
||||
int seconds_left = total_time_left % 60;
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
|
||||
// Play sound
|
||||
if(total_time_left == 0 && !model->sound_playing) {
|
||||
model->sound_playing = true;
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_finish);
|
||||
}
|
||||
if(total_time_left < 0) {
|
||||
model->timer_running = false;
|
||||
model->time_passed = 0;
|
||||
model->sound_playing = false;
|
||||
|
||||
model->rest_running = true;
|
||||
model->rest_start_timestamp = current_timestamp;
|
||||
seconds_left = 0;
|
||||
model->counter += 1;
|
||||
}
|
||||
if(!model->rest_running) {
|
||||
snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes_left, seconds_left);
|
||||
canvas_draw_str(canvas, 0, 39, buffer);
|
||||
}
|
||||
if(model->timer_running) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 0, 50, AlignLeft, AlignTop, "Time to work");
|
||||
}
|
||||
|
||||
// Time to rest
|
||||
if(model->rest_running && !model->timer_running) {
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
int rest_passed = current_timestamp - model->rest_start_timestamp;
|
||||
int rest_total_time_left = (max_seconds_rest - rest_passed);
|
||||
int rest_minutes_left = rest_total_time_left / 60;
|
||||
int rest_seconds_left = rest_total_time_left % 60;
|
||||
|
||||
// Play sound
|
||||
if(rest_total_time_left == 0 && !model->sound_playing) {
|
||||
model->sound_playing = true;
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_rest);
|
||||
}
|
||||
if(rest_total_time_left < 0) {
|
||||
rest_seconds_left = 0;
|
||||
model->rest_running = false;
|
||||
model->sound_playing = false;
|
||||
}
|
||||
snprintf(buffer, sizeof(buffer), "%02d:%02d", rest_minutes_left, rest_seconds_left);
|
||||
canvas_draw_str(canvas, 0, 60, buffer);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, "Have a rest");
|
||||
}
|
||||
|
||||
// Clocks
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%02ld:%02ld:%02ld",
|
||||
((uint32_t)current_timestamp % (60 * 60 * 24)) / (60 * 60),
|
||||
((uint32_t)current_timestamp % (60 * 60)) / 60,
|
||||
(uint32_t)current_timestamp % 60);
|
||||
canvas_draw_str(canvas, 0, 20, buffer);
|
||||
|
||||
// Tomato counter
|
||||
if(model->counter > 5) {
|
||||
model->counter = 1;
|
||||
}
|
||||
for(int i = 0; i < model->counter; ++i) {
|
||||
canvas_draw_disc(canvas, 122 - i * 10, 15, 4);
|
||||
}
|
||||
}
|
||||
33
applications/plugins/pomodoro/pomodoro_timer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
typedef struct PomodoroTimer PomodoroTimer;
|
||||
|
||||
struct PomodoroTimer {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct PomodoroTimerModel PomodoroTimerModel;
|
||||
|
||||
struct PomodoroTimerModel {
|
||||
bool ok_pressed;
|
||||
bool reset_pressed;
|
||||
bool back_pressed;
|
||||
bool connected;
|
||||
bool timer_running;
|
||||
bool rest_running;
|
||||
bool sound_playing;
|
||||
uint32_t timer_start_timestamp;
|
||||
uint32_t timer_stopped_seconds;
|
||||
uint32_t time_passed;
|
||||
uint32_t rest_start_timestamp;
|
||||
int counter;
|
||||
};
|
||||
|
||||
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event);
|
||||
|
||||
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest);
|
||||
BIN
applications/plugins/pomodoro/pomodoro_timer.png
Normal file
|
After Width: | Height: | Size: 306 B |
@@ -1 +0,0 @@
|
||||
ADD_SCENE(flipp_pomodoro, timer, Timer)
|
||||
@@ -1,30 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
#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
|
||||
@@ -1,71 +0,0 @@
|
||||
#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);
|
||||
};
|
||||
@@ -1,195 +0,0 @@
|
||||
#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);
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
#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);
|
||||
46
applications/plugins/pomodoro/views/pomodoro_10.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "../pomodoro_timer.h"
|
||||
#include "pomodoro_10.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static void pomodoro_10_draw_callback(Canvas* canvas, void* context) {
|
||||
int max_seconds = 60 * 10;
|
||||
int max_seconds_rest = 60 * 2;
|
||||
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||
}
|
||||
|
||||
static bool pomodoro_10_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
PomodoroTimer* pomodoro_10 = context;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
return false;
|
||||
} else {
|
||||
pomodoro_timer_process(pomodoro_10, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PomodoroTimer* pomodoro_10_alloc() {
|
||||
PomodoroTimer* pomodoro_10 = malloc(sizeof(PomodoroTimer));
|
||||
pomodoro_10->view = view_alloc();
|
||||
view_set_context(pomodoro_10->view, pomodoro_10);
|
||||
view_allocate_model(pomodoro_10->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||
view_set_draw_callback(pomodoro_10->view, pomodoro_10_draw_callback);
|
||||
view_set_input_callback(pomodoro_10->view, pomodoro_10_input_callback);
|
||||
|
||||
return pomodoro_10;
|
||||
}
|
||||
|
||||
void pomodoro_10_free(PomodoroTimer* pomodoro_10) {
|
||||
furi_assert(pomodoro_10);
|
||||
view_free(pomodoro_10->view);
|
||||
free(pomodoro_10);
|
||||
}
|
||||
|
||||
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10) {
|
||||
furi_assert(pomodoro_10);
|
||||
return pomodoro_10->view;
|
||||
}
|
||||
10
applications/plugins/pomodoro/views/pomodoro_10.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../pomodoro_timer.h"
|
||||
|
||||
PomodoroTimer* pomodoro_10_alloc();
|
||||
|
||||
void pomodoro_10_free(PomodoroTimer* pomodoro_10);
|
||||
|
||||
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10);
|
||||
46
applications/plugins/pomodoro/views/pomodoro_25.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "../pomodoro_timer.h"
|
||||
#include "pomodoro_25.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static void pomodoro_25_draw_callback(Canvas* canvas, void* context) {
|
||||
int max_seconds = 60 * 25;
|
||||
int max_seconds_rest = 60 * 5;
|
||||
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||
}
|
||||
|
||||
static bool pomodoro_25_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
PomodoroTimer* pomodoro_25 = context;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
return false;
|
||||
} else {
|
||||
pomodoro_timer_process(pomodoro_25, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PomodoroTimer* pomodoro_25_alloc() {
|
||||
PomodoroTimer* pomodoro_25 = malloc(sizeof(PomodoroTimer));
|
||||
pomodoro_25->view = view_alloc();
|
||||
view_set_context(pomodoro_25->view, pomodoro_25);
|
||||
view_allocate_model(pomodoro_25->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||
view_set_draw_callback(pomodoro_25->view, pomodoro_25_draw_callback);
|
||||
view_set_input_callback(pomodoro_25->view, pomodoro_25_input_callback);
|
||||
|
||||
return pomodoro_25;
|
||||
}
|
||||
|
||||
void pomodoro_25_free(PomodoroTimer* pomodoro_25) {
|
||||
furi_assert(pomodoro_25);
|
||||
view_free(pomodoro_25->view);
|
||||
free(pomodoro_25);
|
||||
}
|
||||
|
||||
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25) {
|
||||
furi_assert(pomodoro_25);
|
||||
return pomodoro_25->view;
|
||||
}
|
||||
10
applications/plugins/pomodoro/views/pomodoro_25.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../pomodoro_timer.h"
|
||||
|
||||
PomodoroTimer* pomodoro_25_alloc();
|
||||
|
||||
void pomodoro_25_free(PomodoroTimer* pomodoro_25);
|
||||
|
||||
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25);
|
||||
46
applications/plugins/pomodoro/views/pomodoro_50.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "../pomodoro_timer.h"
|
||||
#include "pomodoro_50.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static void pomodoro_50_draw_callback(Canvas* canvas, void* context) {
|
||||
int max_seconds = 60 * 50;
|
||||
int max_seconds_rest = 60 * 10;
|
||||
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||
}
|
||||
|
||||
static bool pomodoro_50_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
PomodoroTimer* pomodoro_50 = context;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
return false;
|
||||
} else {
|
||||
pomodoro_timer_process(pomodoro_50, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PomodoroTimer* pomodoro_50_alloc() {
|
||||
PomodoroTimer* pomodoro_50 = malloc(sizeof(PomodoroTimer));
|
||||
pomodoro_50->view = view_alloc();
|
||||
view_set_context(pomodoro_50->view, pomodoro_50);
|
||||
view_allocate_model(pomodoro_50->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||
view_set_draw_callback(pomodoro_50->view, pomodoro_50_draw_callback);
|
||||
view_set_input_callback(pomodoro_50->view, pomodoro_50_input_callback);
|
||||
|
||||
return pomodoro_50;
|
||||
}
|
||||
|
||||
void pomodoro_50_free(PomodoroTimer* pomodoro_50) {
|
||||
furi_assert(pomodoro_50);
|
||||
view_free(pomodoro_50->view);
|
||||
free(pomodoro_50);
|
||||
}
|
||||
|
||||
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50) {
|
||||
furi_assert(pomodoro_50);
|
||||
return pomodoro_50->view;
|
||||
}
|
||||
10
applications/plugins/pomodoro/views/pomodoro_50.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../pomodoro_timer.h"
|
||||
|
||||
PomodoroTimer* pomodoro_50_alloc();
|
||||
|
||||
void pomodoro_50_free(PomodoroTimer* pomodoro_50);
|
||||
|
||||
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50);
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="RC2014_Coleco",
|
||||
appid="coleco",
|
||||
name="[RC2014] ColecoVision",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="coleco_app",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <gui/gui.h>
|
||||
#include "RC2014_Coleco_icons.h"
|
||||
#include "coleco_icons.h"
|
||||
|
||||
#define CODE_0 0x0A
|
||||
#define CODE_1 0x0D
|
||||
@@ -40,16 +40,16 @@ typedef struct {
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
bool dpad;
|
||||
int row;
|
||||
int column;
|
||||
} Coleco;
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* context) {
|
||||
Coleco* coleco = acquire_mutex((ValueMutex*)context, 25);
|
||||
if(coleco == NULL) {
|
||||
return;
|
||||
}
|
||||
furi_assert(context);
|
||||
Coleco* coleco = context;
|
||||
furi_mutex_acquire(coleco->mutex, FuriWaitForever);
|
||||
|
||||
if(coleco->dpad) {
|
||||
canvas_draw_icon(canvas, 4, 16, &I_ColecoJoystick_sel_33x33);
|
||||
@@ -128,7 +128,7 @@ static void render_callback(Canvas* const canvas, void* context) {
|
||||
(coleco->row == 4 && coleco->column == 2) ? &I_ColecoPound_hvr_17x17 :
|
||||
&I_ColecoPound_17x17);
|
||||
|
||||
release_mutex((ValueMutex*)context, coleco);
|
||||
furi_mutex_release(coleco->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
@@ -191,8 +191,8 @@ int32_t coleco_app(void* p) {
|
||||
|
||||
Coleco* coleco = coleco_alloc();
|
||||
|
||||
ValueMutex coleco_mutex;
|
||||
if(!init_mutex(&coleco_mutex, coleco, sizeof(Coleco))) {
|
||||
coleco->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!coleco->mutex) {
|
||||
FURI_LOG_E("Coleco", "cannot create mutex\r\n");
|
||||
coleco_free(coleco);
|
||||
return 255;
|
||||
@@ -200,7 +200,7 @@ int32_t coleco_app(void* p) {
|
||||
|
||||
// set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &coleco_mutex);
|
||||
view_port_draw_callback_set(view_port, render_callback, coleco);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// open GUI and register view_port
|
||||
@@ -214,7 +214,7 @@ int32_t coleco_app(void* p) {
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
||||
Coleco* coleco = (Coleco*)acquire_mutex_block(&coleco_mutex);
|
||||
furi_mutex_acquire(coleco->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
@@ -346,11 +346,9 @@ int32_t coleco_app(void* p) {
|
||||
|
||||
view_port_update(view_port);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("Coleco", "FuriMessageQueue: event timeout");
|
||||
}
|
||||
|
||||
release_mutex(&coleco_mutex, coleco);
|
||||
furi_mutex_release(coleco->mutex);
|
||||
}
|
||||
|
||||
furi_hal_power_disable_otg();
|
||||
@@ -360,7 +358,7 @@ int32_t coleco_app(void* p) {
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&coleco_mutex);
|
||||
furi_mutex_free(coleco->mutex);
|
||||
coleco_free(coleco);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
App(
|
||||
appid="SAM",
|
||||
name="SAM AYBABTU",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="sam_app",
|
||||
requires=[
|
||||
"gui",
|
||||
@@ -9,7 +9,7 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
@@ -24,7 +24,7 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
@@ -39,7 +39,7 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
@@ -54,7 +54,7 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_icon="music_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
BIN
applications/plugins/sam/music_10px.png
Normal file
|
After Width: | Height: | Size: 142 B |
@@ -6,7 +6,7 @@ STM32SAM voice;
|
||||
|
||||
extern "C" int32_t sam_app(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say(
|
||||
"All your base are belong to us. You have no chance to survive make your time. ha. ha. ha. GOOD BYE. ");
|
||||
@@ -17,7 +17,7 @@ extern "C" int32_t sam_app(void* p) {
|
||||
|
||||
extern "C" int32_t sam_app_yes(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("Yes");
|
||||
furi_hal_speaker_release();
|
||||
@@ -27,7 +27,7 @@ extern "C" int32_t sam_app_yes(void* p) {
|
||||
|
||||
extern "C" int32_t sam_app_no(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("No");
|
||||
furi_hal_speaker_release();
|
||||
@@ -37,7 +37,7 @@ extern "C" int32_t sam_app_no(void* p) {
|
||||
|
||||
extern "C" int32_t sam_app_wtf(void* p) {
|
||||
UNUSED(p);
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("What The Fuck");
|
||||
furi_hal_speaker_release();
|
||||
|
||||
@@ -8,27 +8,32 @@ This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://gith
|
||||
How to play
|
||||
-----------
|
||||
Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`.
|
||||
Use a search engine to find the Tamagotchi ROM. There is a file named `a`.
|
||||
Use a search engine to find the Tamagotchi ROM. There is a file named `tama.b`.
|
||||
Rename this to `rom.bin`.
|
||||
|
||||
*Controls in portrait mode are the same as landscape mode, but turned 90 degrees.*
|
||||
- Left button is A.
|
||||
- OK is B.
|
||||
- Down or OK is B.
|
||||
- Right button is C.
|
||||
- Holding the Up button functions the same as press both A and C, which mutes the volume.
|
||||
- Up button takes you to the emulator menu.
|
||||
- Hold the Back button to save and exit.
|
||||
|
||||

|
||||

|
||||
|
||||
Building
|
||||
--------
|
||||
Move this folder into flippers applications/plugins/tama_p1.
|
||||
Move this folder into flippers `applications/plugins/tama_p1`.
|
||||
|
||||
|
||||
Launching the app, directly from console to flipper:
|
||||
`./fbt launch_app APPSRC=applications\plugins\tama_p1`
|
||||
```
|
||||
./fbt launch_app APPSRC=applications/plugins/tama_p1
|
||||
```
|
||||
|
||||
Run the following to compile icons:
|
||||
```
|
||||
scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
|
||||
scripts/assets.py icons applications/plugins/tama_p1/icons applications/plugins/tama_p1/compiled
|
||||
```
|
||||
|
||||
Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in
|
||||
@@ -38,22 +43,34 @@ Debugging
|
||||
---------
|
||||
Using the serial script from [FlipperScripts](https://github.com/DroomOne/FlipperScripts/blob/main/serial_logger.py)
|
||||
it is easy to add direct logging after running the application:
|
||||
```
|
||||
`python .\serial_logger.py`
|
||||
|
||||
`./fbt launch_app APPSRC=applications\plugins\tama_p1; python .\serial_logger.py`
|
||||
|
||||
`./fbt launch_app APPSRC=applications/plugins/tama_p1; python .\serial_logger.py`
|
||||
```
|
||||
Alternatively, follow the directions here: https://flipper.atmanos.com/docs/debugging/viewing/
|
||||
|
||||
Implemented
|
||||
-----------
|
||||
- Basic emulation
|
||||
- Input
|
||||
- Sound
|
||||
- Saving/Loading emulator state (stored in `/ext/tama_p1/save.bin`)
|
||||
- Mute button combo shortcut (Up = A+C)
|
||||
- Menu options:
|
||||
- Switch between portrait and landscape
|
||||
- A+C shortcut (mute/change in-game time)
|
||||
- Double / quadruple speed
|
||||
|
||||
To-do
|
||||

|
||||
|
||||
To-Do
|
||||
-----
|
||||
- more than one save slot
|
||||
- In-game reset
|
||||
- Test mode?
|
||||
- Volume adjustment
|
||||
- Fix bugs:
|
||||
- When not on 1x speed, after mashing buttons in quick succession, buttons stop responding for a few seconds. But the rom still runs.
|
||||
- Stuff to do when bored:
|
||||
- optimization and bug fixing (see above)
|
||||
- add to this list
|
||||
- portrait menu
|
||||
- Add "loading bar" when saving
|
||||
- "Advanced" settings
|
||||
- saving and loading, multiple save states, with the date and time of of each save.
|
||||
- Autosave and changing autosave frequency
|
||||
- Save settings to /tama_p1/settings.txt
|
||||
|
||||

|
||||
|
||||
@@ -5,7 +5,7 @@ App(
|
||||
entry_point="tama_p1_app",
|
||||
cdefines=["APP_TAMA_P1"],
|
||||
requires=["gui", "storage"],
|
||||
stack_size=1 * 1024,
|
||||
stack_size=2 * 1024,
|
||||
order=215,
|
||||
fap_icon="tamaIcon.png",
|
||||
fap_category="Games",
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
|
||||
TamaApp* g_ctx;
|
||||
FuriMutex* g_state_mutex;
|
||||
uint8_t layout_mode = 0; // 3: portrait => 4: portrait <=
|
||||
// 0: landscape (small) 1: landscape (big) 2: landscape (full)
|
||||
bool in_menu = false;
|
||||
|
||||
uint8_t speed = 1;
|
||||
const uint8_t speed_options[] = {1, 2, 4};
|
||||
const uint8_t min_speed = 1;
|
||||
const uint8_t max_speed = 4;
|
||||
const uint8_t speed_options_size = 3;
|
||||
// = sizeof(speed_options) / sizeof(speed_options[0]);
|
||||
|
||||
uint8_t menu_cursor = 3; // 0: layout mode; 1: speed; 2: A+C;
|
||||
const uint8_t menu_items = 4; // 3: Close menu, Save & Exit
|
||||
uint8_t sub_menu_buttons = 0;
|
||||
uint8_t sub_menu_last = 0;
|
||||
|
||||
static const Icon* icons_list[] = {
|
||||
&I_icon_0,
|
||||
@@ -22,6 +37,384 @@ static const Icon* icons_list[] = {
|
||||
&I_icon_7,
|
||||
};
|
||||
|
||||
static InputKey m = InputKeyUp;
|
||||
static InputKey a = InputKeyLeft;
|
||||
static InputKey b = InputKeyDown;
|
||||
static InputKey c = InputKeyRight;
|
||||
|
||||
static void speed_up() {
|
||||
switch(speed) {
|
||||
case max_speed:
|
||||
speed = speed_options[0];
|
||||
break;
|
||||
default:
|
||||
for(uint8_t i = 0; i < speed_options_size - 1; i++) {
|
||||
if(speed == speed_options[i]) {
|
||||
speed = speed_options[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
tamalib_set_speed(speed);
|
||||
}
|
||||
static void speed_down() {
|
||||
switch(speed) {
|
||||
case min_speed:
|
||||
speed = speed_options[speed_options_size - 1];
|
||||
break;
|
||||
default:
|
||||
for(uint8_t i = speed_options_size - 1; i > 0; i--) {
|
||||
if(speed == speed_options[i]) {
|
||||
speed = speed_options[i - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
tamalib_set_speed(speed);
|
||||
}
|
||||
|
||||
// static void draw_landscape(Canvas* const canvas, void* cb_ctx)
|
||||
static void draw_landscape(Canvas* const canvas, uint8_t scale) {
|
||||
// FURI_LOG_D(TAG, "Drawing frame");
|
||||
// Calculate positioning
|
||||
uint16_t canv_width = canvas_width(canvas);
|
||||
uint16_t canv_height = canvas_height(canvas);
|
||||
uint16_t lcd_matrix_scaled_width = 32 * scale;
|
||||
uint16_t lcd_matrix_scaled_height = 16 * scale;
|
||||
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2; // 0
|
||||
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
|
||||
|
||||
// uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
|
||||
// uint16_t lcd_icon_lower_top = lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN;
|
||||
uint16_t lcd_icon_upper_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_lower_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_spacing_horiz =
|
||||
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
|
||||
|
||||
uint16_t y = lcd_matrix_top;
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
uint16_t x = lcd_matrix_left;
|
||||
uint32_t row_pixels = g_ctx->framebuffer[row];
|
||||
for(uint8_t col = 0; col < 32; ++col) {
|
||||
if(row_pixels & 1) {
|
||||
canvas_draw_box(canvas, x, y, scale, scale);
|
||||
}
|
||||
x += scale;
|
||||
row_pixels >>= 1;
|
||||
}
|
||||
y += scale;
|
||||
}
|
||||
|
||||
// Start drawing icons
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
|
||||
// Draw top icons
|
||||
y = 0;
|
||||
uint16_t x_ic = lcd_icon_upper_left;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
// x_ic += TAMA_LCD_ICON_SIZE + 4;
|
||||
// if(scale == 3) {
|
||||
// y += 16;
|
||||
// } else {
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
// }
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Draw bottom icons
|
||||
y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
// if(scale == 3) {
|
||||
// y = 0;
|
||||
// x_ic = 128 - TAMA_LCD_ICON_SIZE;
|
||||
// x_ic = 0;
|
||||
// } else {
|
||||
// y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
x_ic = lcd_icon_lower_left;
|
||||
// }
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
// if(scale == 3) {
|
||||
// y += 16;
|
||||
// } else {
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
// }
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
}
|
||||
// static void draw_portrait_right(Canvas* const canvas, void* cb_ctx)
|
||||
static void draw_portrait_right(Canvas* const canvas, uint8_t scale) {
|
||||
// FURI_LOG_D(TAG, "Drawing frame");
|
||||
// Calculate positioning
|
||||
// uint16_t canv_width = canvas_width(canvas);
|
||||
uint16_t canv_height = canvas_height(canvas);
|
||||
uint16_t lcd_matrix_scaled_width = 32 * scale;
|
||||
uint16_t lcd_matrix_scaled_height = 16 * scale;
|
||||
// uint16_t lcd_matrix_top = 0;
|
||||
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
|
||||
// uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
|
||||
uint16_t lcd_matrix_left = 64 - TAMA_LCD_ICON_SIZE;
|
||||
uint16_t lcd_icon_upper_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_lower_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_spacing_horiz =
|
||||
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
|
||||
|
||||
uint16_t y = lcd_matrix_top; // 64
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
uint16_t x = 128; // lcd_matrix_left
|
||||
uint32_t row_pixels = g_ctx->framebuffer[row];
|
||||
for(uint8_t col = 0; col < 32; ++col) {
|
||||
if(row_pixels & 1) {
|
||||
canvas_draw_box(canvas, y + 32, x - 66, scale, scale);
|
||||
}
|
||||
x -= scale;
|
||||
row_pixels >>= 1;
|
||||
}
|
||||
y += scale;
|
||||
}
|
||||
|
||||
// Start drawing icons
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
|
||||
// Draw top icons
|
||||
// y = lcd_icon_upper_top;
|
||||
y = 30;
|
||||
// y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
uint16_t x_ic = lcd_icon_upper_left;
|
||||
// uint16_t x_ic = 64 - TAMA_LCD_ICON_SIZE;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
|
||||
}
|
||||
x_ic -= lcd_icon_spacing_horiz; // TAMA_LCD_ICON_SIZE + 4;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Draw bottom icons
|
||||
y = 84; // lcd_icon_lower_top
|
||||
x_ic = lcd_icon_lower_left; // 64 - TAMA_LCD_ICON_SIZE
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
|
||||
}
|
||||
x_ic -= lcd_icon_spacing_horiz;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
}
|
||||
static void draw_portrait_left(Canvas* const canvas, uint8_t scale) {
|
||||
// FURI_LOG_D(TAG, "Drawing frame");
|
||||
// Calculate positioning
|
||||
// uint16_t canv_width = canvas_width(canvas);
|
||||
// uint16_t canv_height = canvas_height(canvas);
|
||||
uint16_t lcd_matrix_scaled_width = 32 * scale;
|
||||
// uint16_t lcd_matrix_scaled_height = 16 * scale;
|
||||
// uint16_t lcd_matrix_top = 0;
|
||||
// uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
|
||||
// uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
|
||||
uint16_t lcd_matrix_left = 0;
|
||||
uint16_t lcd_icon_upper_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_lower_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_spacing_horiz =
|
||||
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
|
||||
|
||||
// uint16_t y = 64 + lcd_matrix_top;
|
||||
uint16_t y = 64;
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
uint16_t x = 0; // lcd_matrix_left
|
||||
uint32_t row_pixels = g_ctx->framebuffer[row];
|
||||
for(uint8_t col = 0; col < 32; ++col) {
|
||||
if(row_pixels & 1) {
|
||||
canvas_draw_box(canvas, y, x, scale, scale);
|
||||
}
|
||||
x += scale;
|
||||
row_pixels >>= 1;
|
||||
}
|
||||
y -= scale;
|
||||
}
|
||||
|
||||
// Start drawing icons
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
|
||||
// Draw top icons
|
||||
// y = lcd_icon_upper_top;
|
||||
y = 70;
|
||||
// y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
uint16_t x_ic = lcd_icon_upper_left;
|
||||
// uint16_t x_ic = 64 - TAMA_LCD_ICON_SIZE;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
|
||||
}
|
||||
x_ic += lcd_icon_spacing_horiz; // TAMA_LCD_ICON_SIZE + 4;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Draw bottom icons
|
||||
y = 16; // lcd_icon_lower_top
|
||||
x_ic = lcd_icon_lower_left; // 64 - TAMA_LCD_ICON_SIZE
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, y, x_ic, icons_list[i]);
|
||||
}
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
}
|
||||
// static void draw_mini(Canvas* const canvas, uint16_t inX, uint16_t inY)
|
||||
static void draw_mini(Canvas* const canvas) {
|
||||
// Calculate positioning
|
||||
// uint16_t y = inY;
|
||||
const uint16_t y = 34;
|
||||
const uint16_t x = 84;
|
||||
|
||||
uint16_t y1 = y;
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
// uint16_t x = inX;
|
||||
uint16_t x1 = x;
|
||||
uint32_t row_pixels = g_ctx->framebuffer[row];
|
||||
for(uint8_t col = 0; col < 32; ++col) {
|
||||
if(row_pixels & 1) {
|
||||
canvas_draw_dot(canvas, x1, y1);
|
||||
}
|
||||
x1 += 1;
|
||||
row_pixels >>= 1;
|
||||
}
|
||||
y1 += 1;
|
||||
}
|
||||
|
||||
// Start drawing icons
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
|
||||
// Draw top icons
|
||||
uint16_t y2 = y - 2;
|
||||
uint16_t x_ic = x;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
// canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
canvas_draw_line(canvas, x_ic, y2, x_ic + 6, y2);
|
||||
}
|
||||
x_ic += 8;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Draw bottom icons
|
||||
y2 = y + 17;
|
||||
x_ic = x;
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
// canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
canvas_draw_line(canvas, x_ic, y2, x_ic + 6, y2);
|
||||
}
|
||||
x_ic += 8;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_menu(Canvas* const canvas) {
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "Menu");
|
||||
canvas_draw_line(canvas, 0, 10, 128, 10);
|
||||
draw_mini(canvas);
|
||||
// draw_mini(canvas, 34, 84);
|
||||
|
||||
switch(menu_cursor) {
|
||||
case 0:
|
||||
canvas_draw_triangle(canvas, 4, 16, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
case 1:
|
||||
canvas_draw_triangle(canvas, 4, 26, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
case 2:
|
||||
switch(sub_menu_buttons) {
|
||||
case 0:
|
||||
canvas_draw_triangle(canvas, 4, 36, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
case 1:
|
||||
canvas_draw_triangle(canvas, 47, 44, 6, 4, CanvasDirectionBottomToTop);
|
||||
break;
|
||||
case 2:
|
||||
canvas_draw_triangle(canvas, 57, 44, 6, 4, CanvasDirectionBottomToTop);
|
||||
break;
|
||||
case 3:
|
||||
canvas_draw_triangle(canvas, 67, 44, 6, 4, CanvasDirectionBottomToTop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case menu_items - 1:
|
||||
switch(sub_menu_last) {
|
||||
case 0:
|
||||
canvas_draw_triangle(canvas, 4, 56, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
case 1:
|
||||
canvas_draw_triangle(canvas, 36, 56, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
case 2:
|
||||
canvas_draw_triangle(canvas, 67, 56, 6, 6, CanvasDirectionLeftToRight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch(layout_mode) {
|
||||
case 0:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (small)");
|
||||
break;
|
||||
case 1:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (big)");
|
||||
break;
|
||||
case 2:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: Landscape (full)");
|
||||
break;
|
||||
case 3:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: Portrait =>");
|
||||
break;
|
||||
case 4:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: Portrait <=");
|
||||
break;
|
||||
default:
|
||||
canvas_draw_str(canvas, 12, 20, "Layout: ???");
|
||||
break;
|
||||
}
|
||||
switch(speed) { // match with speed_options
|
||||
// case 0: // freeze menu too
|
||||
// canvas_draw_str(canvas, 12, 30, "Speed: 0x");
|
||||
// break;
|
||||
case 1:
|
||||
canvas_draw_str(canvas, 12, 30, "Speed: 1x");
|
||||
break;
|
||||
case 2:
|
||||
canvas_draw_str(canvas, 12, 30, "Speed: 2x");
|
||||
break;
|
||||
case 4:
|
||||
canvas_draw_str(canvas, 12, 30, "Speed: 4x (max)");
|
||||
break;
|
||||
default:
|
||||
canvas_draw_str(canvas, 12, 30, "Speed ??x");
|
||||
break;
|
||||
}
|
||||
canvas_draw_str(canvas, 12, 40, "A+C");
|
||||
canvas_draw_str(canvas, 45, 40, "A");
|
||||
canvas_draw_str(canvas, 55, 40, "B");
|
||||
canvas_draw_str(canvas, 65, 40, "C");
|
||||
|
||||
canvas_draw_str(canvas, 12, 60, "Close");
|
||||
canvas_draw_str(canvas, 44, 60, "Save");
|
||||
canvas_draw_str(canvas, 75, 60, "Save & Exit");
|
||||
}
|
||||
|
||||
static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
|
||||
furi_assert(cb_ctx);
|
||||
|
||||
@@ -35,68 +428,32 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 30, 30, "Halted");
|
||||
} else {
|
||||
// FURI_LOG_D(TAG, "Drawing frame");
|
||||
// Calculate positioning
|
||||
uint16_t canv_width = canvas_width(canvas);
|
||||
uint16_t canv_height = canvas_height(canvas);
|
||||
uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR;
|
||||
uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR;
|
||||
// uint16_t lcd_matrix_top = 0;
|
||||
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
|
||||
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
|
||||
|
||||
uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
|
||||
uint16_t lcd_icon_upper_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_lower_top =
|
||||
lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN;
|
||||
uint16_t lcd_icon_lower_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_spacing_horiz =
|
||||
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
|
||||
|
||||
uint16_t y = lcd_matrix_top;
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
uint16_t x = lcd_matrix_left;
|
||||
uint32_t row_pixels = g_ctx->framebuffer[row];
|
||||
for(uint8_t col = 0; col < 32; ++col) {
|
||||
if(row_pixels & 1) {
|
||||
canvas_draw_box(
|
||||
canvas, x, y, TAMA_SCREEN_SCALE_FACTOR, TAMA_SCREEN_SCALE_FACTOR);
|
||||
}
|
||||
x += TAMA_SCREEN_SCALE_FACTOR;
|
||||
row_pixels >>= 1;
|
||||
}
|
||||
y += TAMA_SCREEN_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
// Start drawing icons
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
|
||||
// Draw top icons
|
||||
y = lcd_icon_upper_top;
|
||||
// y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
uint16_t x_ic = lcd_icon_upper_left;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
// x_ic += TAMA_LCD_ICON_SIZE + 4;
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Draw bottom icons
|
||||
y = lcd_icon_lower_top;
|
||||
x_ic = lcd_icon_lower_left;
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
lcd_icons >>= 1;
|
||||
if(in_menu) {
|
||||
// switch(layout_mode)
|
||||
// draw_menu_landscape(canvas);
|
||||
draw_menu(canvas);
|
||||
} else {
|
||||
switch(layout_mode) {
|
||||
case 0:
|
||||
draw_landscape(canvas, TAMA_SCREEN_SCALE_FACTOR); // 2
|
||||
break;
|
||||
case 1:
|
||||
draw_landscape(canvas, 3);
|
||||
break;
|
||||
case 2:
|
||||
draw_landscape(canvas, 4);
|
||||
break;
|
||||
case 3:
|
||||
draw_portrait_right(canvas, TAMA_SCREEN_SCALE_FACTOR);
|
||||
break;
|
||||
case 4:
|
||||
draw_portrait_left(canvas, TAMA_SCREEN_SCALE_FACTOR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(mutex);
|
||||
}
|
||||
|
||||
@@ -345,9 +702,9 @@ static int32_t tama_p1_worker(void* context) {
|
||||
if(furi_thread_flags_get()) {
|
||||
running = false;
|
||||
} else {
|
||||
// FURI_LOG_D(TAG, "Stepping");
|
||||
// FURI_LOG_D(TAG, "Stepping"); // enabling this cause blank screen somehow
|
||||
// for (int i = 0; i < 100; ++i)
|
||||
tamalib_step();
|
||||
tamalib_step(); // tamalib_mainloop();
|
||||
}
|
||||
}
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
@@ -406,15 +763,15 @@ static void tama_p1_init(TamaApp* const ctx) {
|
||||
// Init TamaLIB
|
||||
tamalib_register_hal(&ctx->hal);
|
||||
tamalib_init((u12_t*)ctx->rom, NULL, 64000);
|
||||
tamalib_set_speed(1);
|
||||
tamalib_set_speed(speed);
|
||||
|
||||
// TODO: implement fast forwarding
|
||||
ctx->fast_forward_done = true;
|
||||
// ctx->fast_forward_done = true;
|
||||
|
||||
// Start stepping thread
|
||||
ctx->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(ctx->thread, "TamaLIB");
|
||||
furi_thread_set_stack_size(ctx->thread, 1024);
|
||||
furi_thread_set_stack_size(ctx->thread, 2 * 1024);
|
||||
furi_thread_set_callback(ctx->thread, tama_p1_worker);
|
||||
furi_thread_set_context(ctx->thread, g_state_mutex);
|
||||
furi_thread_start(ctx->thread);
|
||||
@@ -449,6 +806,9 @@ int32_t tama_p1_app(void* p) {
|
||||
furi_timer_alloc(tama_p1_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
|
||||
|
||||
// in_menu = false;
|
||||
// menu_cursor = 2;
|
||||
|
||||
for(bool running = true; running;) {
|
||||
TamaEvent event;
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
|
||||
@@ -466,51 +826,353 @@ int32_t tama_p1_app(void* p) {
|
||||
event.input.sequence,
|
||||
event.input.key,
|
||||
event.input.type);
|
||||
InputType input_type = event.input.type;
|
||||
if(input_type == InputTypePress || input_type == InputTypeRelease) {
|
||||
btn_state_t tama_btn_state = 0;
|
||||
if(input_type == InputTypePress)
|
||||
// InputType input_type = event.input.type; // idk why this is a variable
|
||||
btn_state_t tama_btn_state = 0; // BTN_STATE_RELEASED is 0
|
||||
|
||||
if(in_menu) {
|
||||
// if(menu_cursor == 2 &&
|
||||
// (event.input.key == InputKeyUp || event.input.key == InputKeyDown)) {
|
||||
// tama_btn_state = BTN_STATE_RELEASED;
|
||||
// }
|
||||
if(event.input.key == InputKeyBack) {
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
in_menu = false;
|
||||
} else if(event.input.key == InputKeyUp && event.input.type == InputTypePress) {
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
if(menu_cursor > 0) {
|
||||
menu_cursor -= 1;
|
||||
} else {
|
||||
menu_cursor = menu_items - 1;
|
||||
}
|
||||
if(menu_cursor >= menu_items - 2 && sub_menu_last > 0) {
|
||||
sub_menu_buttons = 1;
|
||||
sub_menu_last = 1;
|
||||
} else {
|
||||
sub_menu_buttons = 0;
|
||||
sub_menu_last = 0;
|
||||
}
|
||||
} else if(event.input.key == InputKeyDown && event.input.type == InputTypePress) {
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
if(menu_cursor < menu_items - 1) {
|
||||
menu_cursor += 1;
|
||||
} else {
|
||||
menu_cursor = 0;
|
||||
}
|
||||
if(menu_cursor >= menu_items - 2 && sub_menu_buttons > 0) {
|
||||
sub_menu_buttons = 1;
|
||||
sub_menu_last = 1;
|
||||
} else {
|
||||
sub_menu_buttons = 0;
|
||||
sub_menu_last = 0;
|
||||
}
|
||||
} else if(event.input.key == InputKeyLeft && event.input.type == InputTypePress) {
|
||||
switch(menu_cursor) {
|
||||
case 0:
|
||||
switch(layout_mode) {
|
||||
case 0:
|
||||
layout_mode = 4;
|
||||
m = InputKeyRight;
|
||||
a = InputKeyUp;
|
||||
b = InputKeyLeft;
|
||||
c = InputKeyDown;
|
||||
break;
|
||||
case 1:
|
||||
layout_mode -= 1; // 0
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 2:
|
||||
layout_mode -= 1; // 1
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 3:
|
||||
layout_mode -= 1; // 2
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 4:
|
||||
layout_mode -= 1; // 3
|
||||
m = InputKeyLeft;
|
||||
a = InputKeyDown;
|
||||
b = InputKeyRight;
|
||||
c = InputKeyUp;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
speed_down();
|
||||
break;
|
||||
case 2:
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
switch(sub_menu_buttons) {
|
||||
case 0:
|
||||
sub_menu_buttons = 3;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
sub_menu_buttons -= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case menu_items - 1:
|
||||
switch(sub_menu_last) {
|
||||
case 0:
|
||||
sub_menu_last = 2;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
sub_menu_last -= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.input.key == InputKeyRight && event.input.type == InputTypePress) {
|
||||
switch(menu_cursor) {
|
||||
case 0:
|
||||
switch(layout_mode) {
|
||||
case 0:
|
||||
layout_mode += 1; // 1
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 1:
|
||||
layout_mode += 1; // 2
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 2:
|
||||
layout_mode += 1; // 3
|
||||
m = InputKeyLeft;
|
||||
a = InputKeyDown;
|
||||
b = InputKeyRight;
|
||||
c = InputKeyUp;
|
||||
break;
|
||||
case 3:
|
||||
layout_mode += 1; // 4
|
||||
m = InputKeyRight;
|
||||
a = InputKeyUp;
|
||||
b = InputKeyLeft;
|
||||
c = InputKeyDown;
|
||||
break;
|
||||
case 4:
|
||||
layout_mode = 0;
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
speed_up();
|
||||
break;
|
||||
case 2:
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
switch(sub_menu_buttons) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
sub_menu_buttons += 1;
|
||||
break;
|
||||
case 3:
|
||||
sub_menu_buttons = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case menu_items - 1:
|
||||
switch(sub_menu_last) {
|
||||
case 0:
|
||||
case 1:
|
||||
sub_menu_last += 1;
|
||||
break;
|
||||
case 2:
|
||||
sub_menu_last = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.input.key == InputKeyOk) {
|
||||
switch(menu_cursor) {
|
||||
case 0:
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(layout_mode) {
|
||||
case 0:
|
||||
layout_mode += 1; // 1
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 1:
|
||||
layout_mode += 1; // 2
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
case 2:
|
||||
layout_mode += 1; // 3
|
||||
m = InputKeyLeft;
|
||||
a = InputKeyDown;
|
||||
b = InputKeyRight;
|
||||
c = InputKeyUp;
|
||||
break;
|
||||
case 3:
|
||||
layout_mode += 1; // 4
|
||||
m = InputKeyRight;
|
||||
a = InputKeyUp;
|
||||
b = InputKeyLeft;
|
||||
c = InputKeyDown;
|
||||
break;
|
||||
case 4:
|
||||
layout_mode = 0;
|
||||
m = InputKeyUp;
|
||||
a = InputKeyLeft;
|
||||
b = InputKeyDown;
|
||||
c = InputKeyRight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(event.input.type == InputTypePress) {
|
||||
speed_up();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(event.input.type == InputTypePress)
|
||||
tama_btn_state = BTN_STATE_PRESSED;
|
||||
else if(input_type == InputTypeRelease)
|
||||
else if(event.input.type == InputTypeRelease)
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
|
||||
if(event.input.key == InputKeyLeft) {
|
||||
switch(sub_menu_buttons) {
|
||||
case 0: // A+C
|
||||
tamalib_set_button(BTN_LEFT, tama_btn_state);
|
||||
tamalib_set_button(BTN_RIGHT, tama_btn_state);
|
||||
break;
|
||||
case 1: // A
|
||||
tamalib_set_button(BTN_LEFT, tama_btn_state);
|
||||
break;
|
||||
case 2: // B
|
||||
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
|
||||
break;
|
||||
case 3: // C
|
||||
tamalib_set_button(BTN_RIGHT, tama_btn_state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case menu_items - 1:
|
||||
default:
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(sub_menu_last) {
|
||||
case 0: // close menu
|
||||
in_menu = false;
|
||||
break;
|
||||
case 1: // Save
|
||||
if(speed != 1) {
|
||||
uint8_t temp = speed;
|
||||
speed = 1;
|
||||
tamalib_set_speed(speed);
|
||||
furi_timer_stop(timer);
|
||||
tama_p1_save_state();
|
||||
furi_timer_start(
|
||||
timer, furi_kernel_get_tick_frequency() / 30);
|
||||
speed = temp;
|
||||
tamalib_set_speed(speed);
|
||||
} else {
|
||||
furi_timer_stop(timer);
|
||||
tama_p1_save_state();
|
||||
furi_timer_start(
|
||||
timer, furi_kernel_get_tick_frequency() / 30);
|
||||
}
|
||||
break;
|
||||
case 2: // Save & Exit
|
||||
if(speed != 1) {
|
||||
speed = 1;
|
||||
tamalib_set_speed(speed);
|
||||
}
|
||||
furi_timer_stop(timer);
|
||||
tama_p1_save_state();
|
||||
running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { // out of menu // TODO: clean up code -.-
|
||||
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
|
||||
if(speed != 1) {
|
||||
speed = 1;
|
||||
tamalib_set_speed(speed);
|
||||
}
|
||||
furi_timer_stop(timer);
|
||||
tama_p1_save_state();
|
||||
running = false;
|
||||
} else if(
|
||||
event.input.type == InputTypePress ||
|
||||
event.input.type == InputTypeRelease) {
|
||||
if(event.input.key != InputKeyBack && event.input.key != m) {
|
||||
if(event.input.type == InputTypePress)
|
||||
tama_btn_state = BTN_STATE_PRESSED;
|
||||
else if(event.input.type == InputTypeRelease)
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
} else {
|
||||
tama_btn_state = BTN_STATE_RELEASED;
|
||||
}
|
||||
if(event.input.key == m && event.input.type == InputTypePress) {
|
||||
in_menu = true;
|
||||
} else if(event.input.key == a) {
|
||||
tamalib_set_button(BTN_LEFT, tama_btn_state);
|
||||
} else if(event.input.key == b) {
|
||||
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
|
||||
} else if(event.input.key == c) {
|
||||
tamalib_set_button(BTN_RIGHT, tama_btn_state);
|
||||
} else if(event.input.key == InputKeyOk) {
|
||||
tamalib_set_button(BTN_MIDDLE, tama_btn_state);
|
||||
} else if(event.input.key == InputKeyRight) {
|
||||
tamalib_set_button(BTN_RIGHT, tama_btn_state);
|
||||
} else if(event.input.key == InputKeyDown && event.input.type == InputTypeShort) {
|
||||
// TODO: pause or fast-forward tamagotchi
|
||||
tama_p1_save_state();
|
||||
} else if(event.input.key == InputKeyUp) { // mute tamagotchi
|
||||
tamalib_set_button(BTN_LEFT, tama_btn_state);
|
||||
tamalib_set_button(BTN_RIGHT, tama_btn_state);
|
||||
} else if(event.input.key == InputKeyBack && event.input.type == InputTypeShort) {
|
||||
tama_p1_save_state();
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
|
||||
furi_timer_stop(timer);
|
||||
running = false;
|
||||
|
||||
tama_p1_save_state();
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(g_state_mutex);
|
||||
} else {
|
||||
// Timeout
|
||||
}
|
||||
// else {
|
||||
// // Timeout
|
||||
// FURI_LOG_D(TAG, "Timed out");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx->rom != NULL) {
|
||||
furi_thread_flags_set(furi_thread_get_id(ctx->thread), 1);
|
||||
furi_thread_join(ctx->thread);
|
||||
}
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
@@ -520,6 +1182,5 @@ int32_t tama_p1_app(void* p) {
|
||||
furi_mutex_free(g_state_mutex);
|
||||
tama_p1_deinit(ctx);
|
||||
free(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ typedef struct {
|
||||
uint8_t sent;
|
||||
PlayerState* p1;
|
||||
PlayerState* p2;
|
||||
FuriMutex* mutex;
|
||||
} TanksState;
|
||||
|
||||
typedef enum {
|
||||
@@ -388,10 +389,9 @@ void tanks_game_deserialize_and_render(unsigned char* data, Canvas* const canvas
|
||||
}
|
||||
|
||||
static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
const TanksState* tanks_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(tanks_state == NULL) {
|
||||
return;
|
||||
}
|
||||
furi_assert(ctx);
|
||||
const TanksState* tanks_state = ctx;
|
||||
furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
|
||||
|
||||
// Before the function is called, the state is set with the canvas_reset(canvas)
|
||||
if(tanks_state->state == GameStateMenu) {
|
||||
@@ -415,7 +415,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, tanks_state);
|
||||
furi_mutex_release(tanks_state->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
|
||||
tanks_game_render_constant_cells(canvas);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, tanks_state);
|
||||
furi_mutex_release(tanks_state->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -615,7 +615,7 @@ static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
tanks_game_deserialize_and_render(data, canvas);
|
||||
// TEST enf
|
||||
|
||||
release_mutex((ValueMutex*)ctx, tanks_state);
|
||||
furi_mutex_release(tanks_state->mutex);
|
||||
}
|
||||
|
||||
static void tanks_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
@@ -1188,8 +1188,8 @@ int32_t tanks_game_app(void* p) {
|
||||
tanks_state->state = GameStateMenu;
|
||||
tanks_state->menu_state = MenuStateSingleMode;
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, tanks_state, sizeof(TanksState))) {
|
||||
tanks_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!tanks_state->mutex) {
|
||||
FURI_LOG_E("Tanks", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(tanks_state);
|
||||
@@ -1197,7 +1197,7 @@ int32_t tanks_game_app(void* p) {
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, tanks_game_render_callback, &state_mutex);
|
||||
view_port_draw_callback_set(view_port, tanks_game_render_callback, tanks_state);
|
||||
view_port_input_callback_set(view_port, tanks_game_input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer =
|
||||
@@ -1221,7 +1221,7 @@ int32_t tanks_game_app(void* p) {
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
||||
TanksState* tanks_state = (TanksState*)acquire_mutex_block(&state_mutex);
|
||||
furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
@@ -1422,7 +1422,7 @@ int32_t tanks_game_app(void* p) {
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, tanks_state);
|
||||
furi_mutex_release(tanks_state->mutex);
|
||||
furi_delay_ms(1);
|
||||
}
|
||||
|
||||
@@ -1440,7 +1440,7 @@ int32_t tanks_game_app(void* p) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
furi_mutex_free(tanks_state->mutex);
|
||||
|
||||
if(tanks_state->p1 != NULL) {
|
||||
free(tanks_state->p1);
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
english version [below](#eng)
|
||||
|
||||
|
||||
Blog: [theageoflove.ru](https://theageoflove.ru)
|
||||
|
||||
TG: [t.me/scuko_bled](https://t.me/scuko_bled)
|
||||
|
||||
|
||||

|
||||
Видео работы: https://youtu.be/VPSpRLJXYAc
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="GPIO_Intervalometer",
|
||||
name="[GPIO] Intervalometer",
|
||||
appid="GPIO_Timelapse",
|
||||
name="[GPIO] Timelapse",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="zeitraffer_app",
|
||||
cdefines=["APP_ZEITRAFFER"],
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include <notification/notification_messages.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "gpio_item.h"
|
||||
#include "GPIO_Intervalometer_icons.h"
|
||||
#include "GPIO_Timelapse_icons.h"
|
||||
|
||||
#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/intravelometer"
|
||||
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/intravelometer.conf"
|
||||
#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps_data/timelapse"
|
||||
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/timelapse.conf"
|
||||
|
||||
// Часть кода покрадена из https://github.com/zmactep/flipperzero-hello-world
|
||||
|
||||
@@ -390,7 +390,7 @@ int32_t zeitraffer_app(void* p) {
|
||||
}
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
save,
|
||||
"Zeitraffer app settings: № of frames, interval time, backlight type, Delay")) {
|
||||
"Zeitraffer app settings: n of frames, interval time, backlight type, Delay")) {
|
||||
notification_message(notifications, &sequence_error);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5
|
||||
- Fix compatibility with Flipper Zero firmware 0.74.2
|
||||
|
||||
## 0.4
|
||||
- Show active/inactive state in primary font (bold)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define VERSION "0.5"
|
||||
#define VERSION "0.4"
|
||||
|
||||
21
applications/plugins/wii_ec_anal/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 BlueChip
|
||||
|
||||
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.
|
||||
0
applications/plugins/wii_ec_anal/_image_tool/_convert.sh
Normal file → Executable file
0
applications/plugins/wii_ec_anal/info.sh
Normal file → Executable file
@@ -94,10 +94,9 @@ static void cbDraw(Canvas* const canvas, void* ctx) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(ctx);
|
||||
|
||||
state_t* state = NULL;
|
||||
|
||||
// Try to acquire the mutex for the plugin state variables, timeout = 25mS
|
||||
if(!(state = (state_t*)acquire_mutex((ValueMutex*)ctx, 25))) return;
|
||||
state_t* state = ctx;
|
||||
furi_mutex_acquire(state->mutex, FuriWaitForever);
|
||||
|
||||
switch(state->scene) {
|
||||
//---------------------------------------------------------------------
|
||||
@@ -182,7 +181,7 @@ static void cbDraw(Canvas* const canvas, void* ctx) {
|
||||
}
|
||||
|
||||
// Release the mutex
|
||||
release_mutex((ValueMutex*)ctx, state);
|
||||
furi_mutex_release(state->mutex);
|
||||
|
||||
LEAVE;
|
||||
return;
|
||||
@@ -308,7 +307,6 @@ int32_t wii_ec_anal(void) {
|
||||
Gui* gui = NULL;
|
||||
ViewPort* vpp = NULL;
|
||||
state_t* state = NULL;
|
||||
ValueMutex mutex = {0};
|
||||
FuriMessageQueue* queue = NULL;
|
||||
const uint32_t queueSz = 20; // maximum messages in queue
|
||||
uint32_t tmo = (3.5f * 1000); // timeout splash screen after N seconds
|
||||
@@ -346,7 +344,8 @@ int32_t wii_ec_anal(void) {
|
||||
goto bail;
|
||||
}
|
||||
// 5. Create a mutex for (reading/writing) the plugin state variables
|
||||
if(!init_mutex(&mutex, state, sizeof(state))) {
|
||||
state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(!state->mutex) {
|
||||
ERROR(wii_errs[(error = ERR_NO_MUTEX)]);
|
||||
goto bail;
|
||||
}
|
||||
@@ -360,7 +359,7 @@ int32_t wii_ec_anal(void) {
|
||||
// 7a. Register a callback for input events
|
||||
view_port_input_callback_set(vpp, cbInput, queue);
|
||||
// 7b. Register a callback for draw events
|
||||
view_port_draw_callback_set(vpp, cbDraw, &mutex);
|
||||
view_port_draw_callback_set(vpp, cbDraw, state);
|
||||
|
||||
// ===== Start GUI Interface =====
|
||||
// 8. Attach the viewport to the GUI
|
||||
@@ -435,10 +434,7 @@ int32_t wii_ec_anal(void) {
|
||||
// Read successful
|
||||
|
||||
// *** Try to lock the plugin state variables ***
|
||||
if(!(state = (state_t*)acquire_mutex_block(&mutex))) {
|
||||
ERROR(wii_errs[(error = ERR_MUTEX_BLOCK)]);
|
||||
goto bail;
|
||||
}
|
||||
furi_mutex_acquire(state->mutex, FuriWaitForever);
|
||||
|
||||
// *** Handle events ***
|
||||
switch(msg.id) {
|
||||
@@ -476,10 +472,7 @@ int32_t wii_ec_anal(void) {
|
||||
if(redraw) view_port_update(vpp);
|
||||
|
||||
// *** Try to release the plugin state variables ***
|
||||
if(!release_mutex(&mutex, state)) {
|
||||
ERROR(wii_errs[(error = ERR_MUTEX_RELEASE)]);
|
||||
goto bail;
|
||||
}
|
||||
furi_mutex_release(state->mutex);
|
||||
} while(state->run);
|
||||
|
||||
// ===== Game Over =====
|
||||
@@ -514,10 +507,7 @@ bail:
|
||||
}
|
||||
|
||||
// 5. Free the mutex
|
||||
if(mutex.mutex) {
|
||||
delete_mutex(&mutex);
|
||||
mutex.mutex = NULL;
|
||||
}
|
||||
furi_mutex_free(state->mutex);
|
||||
|
||||
// 4. Free up state pointer(s)
|
||||
// none
|
||||
|
||||
@@ -56,6 +56,7 @@ typedef struct eventMsg {
|
||||
// Access to this memory is controlled by mutex
|
||||
//
|
||||
typedef struct state {
|
||||
FuriMutex* mutex;
|
||||
bool run; // true : plugin is running
|
||||
|
||||
bool timerEn; // controller scanning enabled
|
||||
|
||||