[FL-2150] Dolphin animation refactoring (#938)

* Dolphin Animation Refactoring, part 1
* Remove animations from desktop
* Remove excess, first start
* Split animation_manager with callbacks
* allocate view inside animation_view
* Work on ViewComposed
* Draw white rectangles under bubble corners
* Fix bubbles sequence
* RPC: remove obsolete include "status.pb.h"
* Add animations manifest decoding
* Flipper file: add strict mode
* FFF: Animation structures parsing
* Assembling structure of animation
* Lot of view fixes:
  Add multi-line bubbles
  Add support for passive bubbles (frame_order values starts from passive now)
  Add hard-coded delay (active_shift) for active state enabling
  Fix active state handling
  Fix leaks
  Fix parsing uncorrect bubble_animation meta file
  Fix bubble rules of showing
* Animation load/unload & view freeze/unfreeze
* Blocking & system animations, fixes:
  View correct activation
  Refactoring + blocking animation
  Freeze first passive/active frames
  Many insert/eject SD tests fixes
  Add system animations
  Add Loader events app started/finished
  Add system no_sd animation
* Assets: dolphin packer. Scripts: minor refactoring.
* Desktop: update logging tags. Scripts: add metadata to dolphin bundling process, extra sorting for fs traversing. Make: phony assets rules.
* Github: rebuild assets on build
* Docker: add missing dependencies for assets compilation
* Docker: fix run command syntax
* ReadMe: update naming rules with link to source
* Assets: recompile icons
* Loader: add loader event
* Desktop, Gui, Furi Core: const shenanigans macros

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
Albert Kharisov 2022-01-03 01:39:56 +04:00 committed by GitHub
parent 065241fe5b
commit a39002ce22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
349 changed files with 3531 additions and 1912 deletions

View File

@ -71,6 +71,14 @@ jobs:
run: | run: |
tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts
- name: 'Rebuild Assets'
uses: ./.github/actions/docker
with:
run: |
set -e
make -C assets clean
make -C assets
- name: 'Build the firmware in docker' - name: 'Build the firmware in docker'
uses: ./.github/actions/docker uses: ./.github/actions/docker
with: with:
@ -142,6 +150,7 @@ jobs:
body: | body: |
[Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB.
edit-mode: replace edit-mode: replace
compact: compact:
if: ${{ !startsWith(github.ref, 'refs/tags') }} if: ${{ !startsWith(github.ref, 'refs/tags') }}
runs-on: [self-hosted,koteeq] runs-on: [self-hosted,koteeq]
@ -186,6 +195,14 @@ jobs:
echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
- name: 'Rebuild Assets'
uses: ./.github/actions/docker
with:
run: |
set -e
make -C assets clean
make -C assets
- name: 'Build the firmware in docker' - name: 'Build the firmware in docker'
uses: ./.github/actions/docker uses: ./.github/actions/docker
with: with:
@ -194,4 +211,4 @@ jobs:
for TARGET in ${TARGETS} for TARGET in ${TARGETS}
do do
make TARGET=${TARGET} DEBUG=0 COMPACT=1 make TARGET=${TARGET} DEBUG=0 COMPACT=1
done done

View File

@ -0,0 +1,436 @@
#include "animation_manager.h"
#include "furi-hal-delay.h"
#include "portmacro.h"
#include "views/bubble_animation_view.h"
#include "animation_storage.h"
#include <cmsis_os2.h>
#include <dolphin/dolphin.h>
#include <furi/check.h>
#include <furi/pubsub.h>
#include <furi/record.h>
#include <m-string.h>
#include <power/power_service/power.h>
#include <stdint.h>
#include <storage/storage.h>
#include <dolphin/dolphin_i.h>
#include <storage/filesystem-api-defines.h>
#define TAG "AnimationManager"
typedef enum {
AnimationManagerStateIdle,
AnimationManagerStateBlocked,
AnimationManagerStateFreezedIdle,
AnimationManagerStateFreezedBlocked,
} AnimationManagerState;
struct AnimationManager {
bool sd_show_url;
bool sd_shown_no_db;
bool sd_shown_sd_ok;
AnimationManagerState state;
FuriPubSubSubscription* pubsub_subscription_storage;
FuriPubSubSubscription* pubsub_subscription_dolphin;
BubbleAnimationView* animation_view;
osTimerId_t idle_animation_timer;
StorageAnimation* current_animation;
AnimationManagerInteractCallback interact_callback;
AnimationManagerSetNewIdleAnimationCallback new_idle_callback;
AnimationManagerSetNewIdleAnimationCallback check_blocking_callback;
void* context;
string_t freezed_animation_name;
int32_t freezed_animation_time_left;
};
static StorageAnimation*
animation_manager_select_idle_animation(AnimationManager* animation_manager);
static void animation_manager_replace_current_animation(
AnimationManager* animation_manager,
StorageAnimation* storage_animation);
static void animation_manager_start_new_idle(AnimationManager* animation_manager);
static bool animation_manager_check_blocking(AnimationManager* animation_manager);
void animation_manager_set_context(AnimationManager* animation_manager, void* context) {
furi_assert(animation_manager);
animation_manager->context = context;
}
void animation_manager_set_new_idle_callback(
AnimationManager* animation_manager,
AnimationManagerSetNewIdleAnimationCallback callback) {
furi_assert(animation_manager);
animation_manager->new_idle_callback = callback;
}
void animation_manager_set_check_callback(
AnimationManager* animation_manager,
AnimationManagerCheckBlockingCallback callback) {
furi_assert(animation_manager);
animation_manager->check_blocking_callback = callback;
}
void animation_manager_set_interact_callback(
AnimationManager* animation_manager,
AnimationManagerInteractCallback callback) {
furi_assert(animation_manager);
animation_manager->interact_callback = callback;
}
static void animation_manager_check_blocking_callback(const void* message, void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->check_blocking_callback) {
animation_manager->check_blocking_callback(animation_manager->context);
}
}
static void animation_manager_timer_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->new_idle_callback) {
animation_manager->new_idle_callback(animation_manager->context);
}
}
static void animation_manager_interact_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->interact_callback) {
animation_manager->interact_callback(animation_manager->context);
}
}
/* reaction to animation_manager->interact_callback() */
void animation_manager_check_blocking_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager_check_blocking(animation_manager);
}
}
/* reaction to animation_manager->new_idle_callback() */
void animation_manager_new_idle_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager_start_new_idle(animation_manager);
}
}
/* reaction to animation_manager->check_blocking_callback() */
void animation_manager_interact_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateBlocked) {
/* check if new blocking animation has to be displayed */
bool blocked = animation_manager_check_blocking(animation_manager);
if(!blocked) {
animation_manager_start_new_idle(animation_manager);
}
}
}
static void animation_manager_start_new_idle(AnimationManager* animation_manager) {
furi_assert(animation_manager);
StorageAnimation* new_animation = animation_manager_select_idle_animation(animation_manager);
animation_manager_replace_current_animation(animation_manager, new_animation);
const BubbleAnimation* bubble_animation =
animation_storage_get_bubble_animation(animation_manager->current_animation);
animation_manager->state = AnimationManagerStateIdle;
osTimerStart(animation_manager->idle_animation_timer, bubble_animation->duration * 1000);
}
static bool animation_manager_check_blocking(AnimationManager* animation_manager) {
furi_assert(animation_manager);
StorageAnimation* blocking_animation = NULL;
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
if(sd_status == FSE_INTERNAL) {
blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME);
} else if(sd_status == FSE_NOT_READY) {
animation_manager->sd_shown_sd_ok = false;
animation_manager->sd_shown_no_db = false;
} else if(sd_status == FSE_OK) {
if(!animation_manager->sd_shown_sd_ok) {
blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME);
animation_manager->sd_shown_sd_ok = true;
} else if(!animation_manager->sd_shown_no_db) {
bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK;
if(!db_exists) {
blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME);
animation_manager->sd_shown_no_db = true;
animation_manager->sd_show_url = true;
}
} else if(animation_manager->sd_show_url) {
blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME);
animation_manager->sd_show_url = false;
}
}
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
if(!blocking_animation && stats.level_up_is_pending) {
blocking_animation = animation_storage_find_animation(LEVELUP_ANIMATION_NAME);
}
if(blocking_animation) {
osTimerStop(animation_manager->idle_animation_timer);
animation_manager_replace_current_animation(animation_manager, blocking_animation);
/* no starting timer because its blocking animation */
animation_manager->state = AnimationManagerStateBlocked;
}
furi_record_close("storage");
return !!blocking_animation;
}
static void animation_manager_replace_current_animation(
AnimationManager* animation_manager,
StorageAnimation* storage_animation) {
furi_assert(storage_animation);
StorageAnimation* previous_animation = animation_manager->current_animation;
const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation);
bubble_animation_view_set_animation(animation_manager->animation_view, animation);
const char* new_name = string_get_cstr(animation_storage_get_meta(storage_animation)->name);
FURI_LOG_I(TAG, "Select \'%s\' animation", new_name);
animation_manager->current_animation = storage_animation;
if(previous_animation) {
animation_storage_free_storage_animation(&previous_animation);
}
}
AnimationManager* animation_manager_alloc(void) {
animation_storage_initialize_internal_animations();
AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager));
animation_manager->animation_view = bubble_animation_view_alloc();
string_init(animation_manager->freezed_animation_name);
animation_manager->idle_animation_timer =
osTimerNew(animation_manager_timer_callback, osTimerOnce, animation_manager, NULL);
bubble_animation_view_set_interact_callback(
animation_manager->animation_view, animation_manager_interact_callback, animation_manager);
Storage* storage = furi_record_open("storage");
animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe(
storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager);
furi_record_close("storage");
Dolphin* dolphin = furi_record_open("dolphin");
animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe(
dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager);
furi_record_close("dolphin");
animation_manager->sd_shown_sd_ok = true;
if(!animation_manager_check_blocking(animation_manager)) {
animation_manager_start_new_idle(animation_manager);
}
return animation_manager;
}
void animation_manager_free(AnimationManager* animation_manager) {
furi_assert(animation_manager);
Dolphin* dolphin = furi_record_open("dolphin");
furi_pubsub_unsubscribe(
dolphin_get_pubsub(dolphin), animation_manager->pubsub_subscription_dolphin);
furi_record_close("dolphin");
Storage* storage = furi_record_open("storage");
furi_pubsub_unsubscribe(
storage_get_pubsub(storage), animation_manager->pubsub_subscription_storage);
furi_record_close("storage");
string_clear(animation_manager->freezed_animation_name);
bubble_animation_view_free(animation_manager->animation_view);
osTimerDelete(animation_manager->idle_animation_timer);
}
View* animation_manager_get_animation_view(AnimationManager* animation_manager) {
furi_assert(animation_manager);
return bubble_animation_get_view(animation_manager->animation_view);
}
static StorageAnimation*
animation_manager_select_idle_animation(AnimationManager* animation_manager) {
StorageAnimationList_t animation_list;
StorageAnimationList_init(animation_list);
animation_storage_fill_animation_list(&animation_list);
Power* power = furi_record_open("power");
PowerInfo info;
power_get_info(power, &info);
bool battery_is_well = power_is_battery_well(&info);
furi_record_close("power");
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
furi_record_close("storage");
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
uint32_t whole_weight = 0;
StorageAnimationList_it_t it;
for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation);
bool skip_animation = false;
if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) {
skip_animation = true;
} else if((sd_status != FSE_NOT_READY) && !string_cmp_str(meta->name, NO_SD_ANIMATION_NAME)) {
skip_animation = true;
} else if((stats.butthurt < meta->min_butthurt) || (stats.butthurt > meta->max_butthurt)) {
skip_animation = true;
} else if((stats.level < meta->min_level) || (stats.level > meta->max_level)) {
skip_animation = true;
}
if(skip_animation) {
animation_storage_free_storage_animation(&storage_animation);
/* remove and increase iterator */
StorageAnimationList_remove(animation_list, it);
} else {
whole_weight += meta->weight;
StorageAnimationList_next(it);
}
}
uint32_t lucky_number = random() % whole_weight;
uint32_t weight = 0;
StorageAnimation* selected = NULL;
for
M_EACH(item, animation_list, StorageAnimationList_t) {
if(lucky_number < weight) {
break;
}
weight += animation_storage_get_meta(*item)->weight;
selected = *item;
}
for
M_EACH(item, animation_list, StorageAnimationList_t) {
if(*item != selected) {
animation_storage_free_storage_animation(item);
}
}
StorageAnimationList_clear(animation_list);
furi_record_close("dolphin");
/* cache animation, if failed - choose reliable animation */
if(!animation_storage_get_bubble_animation(selected)) {
const char* name = string_get_cstr(animation_storage_get_meta(selected)->name);
FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name);
animation_storage_free_storage_animation(&selected);
selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME);
}
furi_assert(selected);
return selected;
}
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager) {
furi_assert(animation_manager);
furi_assert(animation_manager->current_animation);
furi_assert(!string_size(animation_manager->freezed_animation_name));
furi_assert(
(animation_manager->state == AnimationManagerStateIdle) ||
(animation_manager->state == AnimationManagerStateBlocked));
if(animation_manager->state == AnimationManagerStateBlocked) {
animation_manager->state = AnimationManagerStateFreezedBlocked;
} else if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager->state = AnimationManagerStateFreezedIdle;
animation_manager->freezed_animation_time_left =
xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount();
if(animation_manager->freezed_animation_time_left < 0) {
animation_manager->freezed_animation_time_left = 0;
}
osTimerStop(animation_manager->idle_animation_timer);
} else {
furi_assert(0);
}
StorageAnimationMeta* meta = animation_storage_get_meta(animation_manager->current_animation);
/* copy str, not move, because it can be internal animation */
string_set(animation_manager->freezed_animation_name, meta->name);
bubble_animation_freeze(animation_manager->animation_view);
animation_storage_free_storage_animation(&animation_manager->current_animation);
}
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager) {
furi_assert(animation_manager);
furi_assert(!animation_manager->current_animation);
furi_assert(string_size(animation_manager->freezed_animation_name));
furi_assert(
(animation_manager->state == AnimationManagerStateFreezedIdle) ||
(animation_manager->state == AnimationManagerStateFreezedBlocked));
if(animation_manager->state == AnimationManagerStateFreezedBlocked) {
StorageAnimation* restore_animation = animation_storage_find_animation(
string_get_cstr(animation_manager->freezed_animation_name));
/* all blocked animations must be in flipper -> we can
* always find blocking animation */
furi_assert(restore_animation);
animation_manager_replace_current_animation(animation_manager, restore_animation);
animation_manager->state = AnimationManagerStateBlocked;
} else if(animation_manager->state == AnimationManagerStateFreezedIdle) {
/* check if we missed some system notifications, and set current_animation */
bool blocked = animation_manager_check_blocking(animation_manager);
if(!blocked) {
/* if no blocking - try restore last one idle */
StorageAnimation* restore_animation = animation_storage_find_animation(
string_get_cstr(animation_manager->freezed_animation_name));
if(restore_animation) {
animation_manager_replace_current_animation(animation_manager, restore_animation);
animation_manager->state = AnimationManagerStateIdle;
if(animation_manager->freezed_animation_time_left) {
osTimerStart(
animation_manager->idle_animation_timer,
animation_manager->freezed_animation_time_left);
} else {
const BubbleAnimation* animation = animation_storage_get_bubble_animation(
animation_manager->current_animation);
osTimerStart(
animation_manager->idle_animation_timer, animation->duration * 1000);
}
} else {
FURI_LOG_E(
TAG,
"Failed to restore \'%s\'",
string_get_cstr(animation_manager->freezed_animation_name));
}
}
} else {
/* Unknown state is an error. But not in release version.*/
furi_assert(0);
}
/* if can't restore previous animation - select new */
if(!animation_manager->current_animation) {
animation_manager_start_new_idle(animation_manager);
}
FURI_LOG_D(
TAG,
"Load & Continue with \'%s\'",
string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name));
bubble_animation_unfreeze(animation_manager->animation_view);
string_reset(animation_manager->freezed_animation_name);
furi_assert(animation_manager->current_animation);
}

View File

@ -0,0 +1,151 @@
#pragma once
#include "dolphin/dolphin.h"
#include <gui/view.h>
#include <stdint.h>
typedef struct AnimationManager AnimationManager;
typedef struct {
uint8_t x;
uint8_t y;
const char* str;
Align horizontal;
Align vertical;
} Bubble;
typedef struct FrameBubble {
Bubble bubble;
uint8_t starts_at_frame;
uint8_t ends_at_frame;
struct FrameBubble* next_bubble;
} FrameBubble;
typedef struct {
FrameBubble** frame_bubbles;
uint8_t frame_bubbles_count;
const Icon** icons;
uint8_t passive_frames;
uint8_t active_frames;
uint8_t active_cycles;
uint8_t frame_rate;
uint16_t duration;
uint16_t active_cooldown;
} BubbleAnimation;
typedef void (*AnimationManagerSetNewIdleAnimationCallback)(void* context);
typedef void (*AnimationManagerCheckBlockingCallback)(void* context);
typedef void (*AnimationManagerInteractCallback)(void*);
/**
* Allocate Animation Manager
*
* @return animation manager instance
*/
AnimationManager* animation_manager_alloc(void);
/**
* Free Animation Manager
*
* @animation_manager instance
*/
void animation_manager_free(AnimationManager* animation_manager);
/**
* Get View of Animation Manager
*
* @animation_manager instance
* @return view
*/
View* animation_manager_get_animation_view(AnimationManager* animation_manager);
/**
* Set context for all callbacks for Animation Manager
*
* @animation_manager instance
* @context context
*/
void animation_manager_set_context(AnimationManager* animation_manager, void* context);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_new_idle_process().
* Animation Manager doesn't have it's own thread, so main thread gives
* callbacks to A.M. to call when it should perform some inner manipulations.
* This callback is called from other threads and should notify main thread
* when to call animation_manager_new_idle_process().
* So scheme is this:
* A.M. sets callbacks,
* callbacks notifies main thread
* main thread in its own context calls appropriate *_process() function.
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_new_idle_callback(
AnimationManager* animation_manager,
AnimationManagerSetNewIdleAnimationCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_new_idle_process(AnimationManager* animation_manager);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_check_blocking_process().
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_check_callback(
AnimationManager* animation_manager,
AnimationManagerCheckBlockingCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_check_blocking_process(AnimationManager* animation_manager);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_interact_process().
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_interact_callback(
AnimationManager* animation_manager,
AnimationManagerInteractCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_interact_process(AnimationManager* animation_manager);
/**
* Unload and Stall animation actions. Draw callback in view
* paints first frame of current animation until
* animation_manager_load_and_continue_animation() is called.
* Can't be called multiple times. Every Stall has to be finished
* with Continue.
*
* @animation_manager instance
*/
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager);
/**
* Load and Contunue execution of animation manager.
*
* @animation_manager instance
*/
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager);

View File

@ -0,0 +1,482 @@
#include "animation_manager.h"
#include "file-worker.h"
#include "flipper_file.h"
#include "furi/common_defines.h"
#include "furi/memmgr.h"
#include "furi/record.h"
#include "animation_storage.h"
#include "gui/canvas.h"
#include "m-string.h"
#include "pb.h"
#include "pb_decode.h"
#include "storage/filesystem-api-defines.h"
#include "storage/storage.h"
#include "animation_storage_i.h"
#include <stdint.h>
#include <gui/icon_i.h>
#define ANIMATION_META_FILE "meta.txt"
#define ANIMATION_DIR "/ext/dolphin/animations"
#define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt"
#define TAG "AnimationStorage"
#define DEBUG_PB 0
static void animation_storage_free_bubbles(BubbleAnimation* animation);
static void animation_storage_free_frames(BubbleAnimation* animation);
static void animation_storage_free_animation(BubbleAnimation** storage_animation);
static BubbleAnimation* animation_storage_load_animation(const char* name);
void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) {
furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*));
furi_assert(!StorageAnimationList_size(*animation_list));
Storage* storage = furi_record_open("storage");
FlipperFile* file = flipper_file_alloc(storage);
/* Forbid skipping fields */
flipper_file_set_strict_mode(file, true);
string_t header;
string_init(header);
do {
uint32_t u32value;
StorageAnimation* storage_animation = NULL;
if(FSE_OK != storage_sd_status(storage)) break;
if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
if(!flipper_file_read_header(file, header, &u32value)) break;
if(string_cmp_str(header, "Flipper Animation Manifest")) break;
do {
storage_animation = furi_alloc(sizeof(StorageAnimation));
storage_animation->external = true;
storage_animation->animation = NULL;
if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break;
if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break;
storage_animation->meta.min_butthurt = u32value;
if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break;
storage_animation->meta.max_butthurt = u32value;
if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break;
storage_animation->meta.min_level = u32value;
if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break;
storage_animation->meta.max_level = u32value;
if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break;
storage_animation->meta.weight = u32value;
StorageAnimationList_push_back(*animation_list, storage_animation);
} while(1);
animation_storage_free_storage_animation(&storage_animation);
} while(0);
string_clear(header);
flipper_file_close(file);
flipper_file_free(file);
// add hard-coded animations
for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]);
}
furi_record_close("storage");
}
StorageAnimation* animation_storage_find_animation(const char* name) {
furi_assert(name);
furi_assert(strlen(name));
StorageAnimation* storage_animation = NULL;
/* look through internal animations */
for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) {
storage_animation = &StorageAnimationInternal[i];
break;
}
}
/* look through external animations */
if(!storage_animation) {
BubbleAnimation* animation = animation_storage_load_animation(name);
if(animation != NULL) {
storage_animation = furi_alloc(sizeof(StorageAnimation));
storage_animation->animation = animation;
storage_animation->external = true;
/* meta data takes part in random animation selection, so it
* doesn't need here as we exactly know which animation we need,
* that's why we can ignore reading manifest.txt file
* filling meta data by zeroes */
storage_animation->meta.min_butthurt = 0;
storage_animation->meta.max_butthurt = 0;
storage_animation->meta.min_level = 0;
storage_animation->meta.max_level = 0;
storage_animation->meta.weight = 0;
string_init_set_str(storage_animation->meta.name, name);
}
}
return storage_animation;
}
StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
return &storage_animation->meta;
}
const BubbleAnimation*
animation_storage_get_bubble_animation(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
animation_storage_cache_animation(storage_animation);
return storage_animation->animation;
}
void animation_storage_cache_animation(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
if(storage_animation->external) {
if(!storage_animation->animation) {
storage_animation->animation =
animation_storage_load_animation(string_get_cstr(storage_animation->meta.name));
}
}
}
static void animation_storage_free_animation(BubbleAnimation** animation) {
furi_assert(animation);
if(*animation) {
animation_storage_free_bubbles(*animation);
animation_storage_free_frames(*animation);
free(*animation);
*animation = NULL;
}
}
void animation_storage_free_storage_animation(StorageAnimation** storage_animation) {
furi_assert(storage_animation);
furi_assert(*storage_animation);
if((*storage_animation)->external) {
animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation);
string_clear((*storage_animation)->meta.name);
free(*storage_animation);
}
*storage_animation = NULL;
}
static bool animation_storage_cast_align(string_t align_str, Align* align) {
if(!string_cmp_str(align_str, "Bottom")) {
*align = AlignBottom;
} else if(!string_cmp_str(align_str, "Top")) {
*align = AlignTop;
} else if(!string_cmp_str(align_str, "Left")) {
*align = AlignLeft;
} else if(!string_cmp_str(align_str, "Right")) {
*align = AlignRight;
} else if(!string_cmp_str(align_str, "Center")) {
*align = AlignCenter;
} else {
return false;
}
return true;
}
static void animation_storage_free_frames(BubbleAnimation* animation) {
furi_assert(animation);
furi_assert(animation->icons);
const Icon** icons = animation->icons;
uint16_t frames = animation->active_frames + animation->passive_frames;
furi_assert(frames > 0);
for(int i = 0; i < frames; ++i) {
if(!icons[i]) continue;
const Icon* icon = icons[i];
free((void*)icon->frames[0]);
free(icon->frames);
free((void*)icon);
for(int j = i; j < frames; ++j) {
if(icons[j] == icon) {
icons[j] = NULL;
}
}
}
free(animation->icons);
animation->icons = NULL;
}
static Icon* animation_storage_alloc_icon(size_t frame_size) {
Icon* icon = furi_alloc(sizeof(Icon));
icon->frames = furi_alloc(sizeof(const uint8_t*));
icon->frames[0] = furi_alloc(frame_size);
return icon;
}
static void animation_storage_free_icon(Icon* icon) {
free((void*)icon->frames[0]);
free(icon->frames);
free(icon);
}
static bool animation_storage_load_frames(
Storage* storage,
const char* name,
BubbleAnimation* animation,
uint32_t* frame_order,
uint32_t width,
uint32_t height) {
furi_assert(!animation->icons);
uint16_t frame_order_size = animation->passive_frames + animation->active_frames;
bool frames_ok = false;
animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size);
File* file = storage_file_alloc(storage);
FileInfo file_info;
string_t filename;
string_init(filename);
size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1;
for(int i = 0; i < frame_order_size; ++i) {
if(animation->icons[i]) continue;
frames_ok = false;
string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]);
if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break;
if(file_info.size > max_filesize) {
FURI_LOG_E(
TAG,
"Filesize %d, max: %d (width %d, height %d)",
file_info.size,
max_filesize,
width,
height);
break;
}
if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename));
break;
}
Icon* icon = animation_storage_alloc_icon(file_info.size);
if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) {
FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename));
animation_storage_free_icon(icon);
break;
}
storage_file_close(file);
FURI_CONST_ASSIGN(icon->frame_count, 1);
FURI_CONST_ASSIGN(icon->frame_rate, 0);
FURI_CONST_ASSIGN(icon->height, height);
FURI_CONST_ASSIGN(icon->width, width);
/* Claim 1 allocation for 1 files blob and several links to it */
for(int j = i; j < frame_order_size; ++j) {
if(frame_order[i] == frame_order[j]) {
animation->icons[j] = icon;
}
}
frames_ok = true;
}
if(!frames_ok) {
FURI_LOG_E(
TAG,
"Load \'%s\' failed, %dx%d, size: %d",
string_get_cstr(filename),
width,
height,
file_info.size);
animation_storage_free_frames(animation);
animation->icons = NULL;
} else {
for(int i = 0; i < frame_order_size; ++i) {
furi_check(animation->icons[i]);
furi_check(animation->icons[i]->frames[0]);
}
}
storage_file_free(file);
string_clear(filename);
return frames_ok;
}
static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFile* ff) {
uint32_t u32value;
string_t str;
string_init(str);
bool success = false;
furi_assert(!animation->frame_bubbles);
do {
if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break;
if(u32value > 20) break;
animation->frame_bubbles_count = u32value;
if(animation->frame_bubbles_count == 0) {
animation->frame_bubbles = NULL;
success = true;
break;
}
animation->frame_bubbles =
furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count);
uint32_t current_slot = 0;
for(int i = 0; i < animation->frame_bubbles_count; ++i) {
animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble));
}
FrameBubble* bubble = animation->frame_bubbles[0];
int8_t index = -1;
for(;;) {
if(!flipper_file_read_uint32(ff, "Slot", &current_slot, 1)) break;
if((current_slot != 0) && (index == -1)) break;
if(current_slot == index) {
bubble->next_bubble = furi_alloc(sizeof(FrameBubble));
bubble = bubble->next_bubble;
} else if(current_slot == index + 1) {
++index;
bubble = animation->frame_bubbles[index];
} else {
/* slots have to start from 0, be ascending sorted, and
* have exact number of slots as specified in "Bubble slots" */
break;
}
if(index >= animation->frame_bubbles_count) break;
if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break;
bubble->bubble.x = u32value;
if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break;
bubble->bubble.y = u32value;
if(!flipper_file_read_string(ff, "Text", str)) break;
if(string_size(str) > 100) break;
string_replace_all_str(str, "\\n", "\n");
bubble->bubble.str = furi_alloc(string_size(str) + 1);
strcpy((char*)bubble->bubble.str, string_get_cstr(str));
if(!flipper_file_read_string(ff, "AlignH", str)) break;
if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break;
if(!flipper_file_read_string(ff, "AlignV", str)) break;
if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break;
if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break;
bubble->starts_at_frame = u32value;
if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break;
bubble->ends_at_frame = u32value;
}
success = (index + 1) == animation->frame_bubbles_count;
} while(0);
if(!success) {
if(animation->frame_bubbles) {
FURI_LOG_E(TAG, "Failed to load animation bubbles");
animation_storage_free_bubbles(animation);
}
}
string_clear(str);
return success;
}
static BubbleAnimation* animation_storage_load_animation(const char* name) {
furi_assert(name);
BubbleAnimation* animation = furi_alloc(sizeof(BubbleAnimation));
uint32_t height = 0;
uint32_t width = 0;
uint32_t* u32array = NULL;
Storage* storage = furi_record_open("storage");
FlipperFile* ff = flipper_file_alloc(storage);
/* Forbid skipping fields */
flipper_file_set_strict_mode(ff, true);
string_t str;
string_init(str);
animation->frame_bubbles = NULL;
bool success = false;
do {
uint32_t u32value;
if(FSE_OK != storage_sd_status(storage)) break;
string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name);
if(!flipper_file_open_existing(ff, string_get_cstr(str))) break;
if(!flipper_file_read_header(ff, str, &u32value)) break;
if(string_cmp_str(str, "Flipper Animation")) break;
if(!flipper_file_read_uint32(ff, "Width", &width, 1)) break;
if(!flipper_file_read_uint32(ff, "Height", &height, 1)) break;
if(!flipper_file_read_uint32(ff, "Passive frames", &u32value, 1)) break;
animation->passive_frames = u32value;
if(!flipper_file_read_uint32(ff, "Active frames", &u32value, 1)) break;
animation->active_frames = u32value;
uint8_t frames = animation->passive_frames + animation->active_frames;
u32array = furi_alloc(sizeof(uint32_t) * frames);
if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break;
/* passive and active frames must be loaded up to this point */
if(!animation_storage_load_frames(storage, name, animation, u32array, width, height))
break;
if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break;
animation->active_cycles = u32value;
if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break;
animation->frame_rate = u32value;
if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break;
animation->duration = u32value;
if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break;
animation->active_cooldown = u32value;
if(!animation_storage_load_bubbles(animation, ff)) break;
success = true;
} while(0);
string_clear(str);
flipper_file_close(ff);
flipper_file_free(ff);
if(u32array) {
free(u32array);
}
if(!success) {
free(animation);
animation = NULL;
}
return animation;
}
static void animation_storage_free_bubbles(BubbleAnimation* animation) {
if(!animation->frame_bubbles) return;
for(int i = 0; i < animation->frame_bubbles_count;) {
FrameBubble** bubble = &animation->frame_bubbles[i];
if((*bubble) == NULL) break;
while((*bubble)->next_bubble != NULL) {
bubble = &(*bubble)->next_bubble;
}
if((*bubble)->bubble.str) {
free((void*)(*bubble)->bubble.str);
}
if((*bubble) == animation->frame_bubbles[i]) {
++i;
}
free(*bubble);
*bubble = NULL;
}
free(animation->frame_bubbles);
animation->frame_bubbles = NULL;
}

View File

@ -0,0 +1,98 @@
#pragma once
#include <stdint.h>
#include <m-list.h>
#include "views/bubble_animation_view.h"
#include <m-string.h>
#define HARDCODED_ANIMATION_NAME "tv"
#define NO_SD_ANIMATION_NAME "no_sd"
#define BAD_BATTERY_ANIMATION_NAME "bad_battery"
#define NO_DB_ANIMATION_NAME "no_db"
#define BAD_SD_ANIMATION_NAME "bad_sd"
#define SD_OK_ANIMATION_NAME "sd_ok"
#define URL_ANIMATION_NAME "url"
#define LEVELUP_ANIMATION_NAME "level"
/** Main structure to handle animation data.
* Contains all, including animation playing data (BubbleAnimation),
* data for random animation selection (StorageAnimationMeta) and
* flag of location internal/external */
typedef struct StorageAnimation StorageAnimation;
typedef struct {
string_t name;
uint8_t min_butthurt;
uint8_t max_butthurt;
uint8_t min_level;
uint8_t max_level;
uint8_t weight;
} StorageAnimationMeta;
/** Container to return available animations list */
LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST)
#define M_OPL_StorageAnimationList_t() LIST_OPLIST(StorageAnimationList)
/**
* Fill list of available animations.
* List will contain all idle animations on inner flash
* and all available on SD-card, mentioned in manifest.txt.
* Performs caching of animation. If fail - falls back to
* inner animation.
* List has to be initialized.
*
* @list list to fill with animations data
*/
void animation_storage_fill_animation_list(StorageAnimationList_t* list);
/**
* Get bubble animation of storage animation.
* Bubble Animation is a structure which describes animation
* independent of it's place of storage and meta data.
* It contain all what is need to be played.
* If storage_animation is not cached - caches it.
*
* @storage_animation animation from which extract bubble animation
* @return bubble_animation, NULL if failed to cache data.
*/
const BubbleAnimation* animation_storage_get_bubble_animation(StorageAnimation* storage_animation);
/**
* Performs caching animation data (Bubble Animation)
* if this is not done yet.
*
* @storage_animation animation to cache
*/
void animation_storage_cache_animation(StorageAnimation* storage_animation);
/**
* Find animation by name.
* Search through the inner flash, and SD-card if has.
*
* @name name of animation
* @return found animation. NULL if nothing found.
*/
StorageAnimation* animation_storage_find_animation(const char* name);
/**
* Get meta information of storage animation.
* This information allows to randomly select animation.
* Also it contains name. Never returns NULL.
*
* @storage_animation item of whom we have to extract meta.
* @return meta itself
*/
StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation);
/**
* Free storage_animation, which previously acquired
* by Animation Storage.
*
* @storage_animation item to free. NULL-ed after all.
*/
void animation_storage_free_storage_animation(StorageAnimation** storage_animation);
/**
* Has to be called at least 1 time to initialize runtime structures
* of animations in inner flash.
*/
void animation_storage_initialize_internal_animations(void);

View File

@ -0,0 +1,195 @@
#pragma once
#include "animation_storage.h"
#include "assets_icons.h"
#include "animation_manager.h"
#include "gui/canvas.h"
struct StorageAnimation {
const BubbleAnimation* animation;
bool external;
StorageAnimationMeta meta;
};
// Hard-coded, always available idle animation
FrameBubble tv_bubble1 = {
.bubble =
{.x = 1,
.y = 23,
.str = "Take the red pill",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 7,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble tv_bubble2 = {
.bubble =
{.x = 1,
.y = 23,
.str = "I can joke better",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 7,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble* tv_bubbles[] = {&tv_bubble1, &tv_bubble2};
const Icon* tv_icons[] = {
&I_tv1,
&I_tv2,
&I_tv3,
&I_tv4,
&I_tv5,
&I_tv6,
&I_tv7,
&I_tv8,
};
const BubbleAnimation tv_bubble_animation = {
.icons = tv_icons,
.frame_bubbles = tv_bubbles,
.frame_bubbles_count = COUNT_OF(tv_bubbles),
.passive_frames = 6,
.active_frames = 2,
.active_cycles = 2,
.frame_rate = 2,
.duration = 3600,
.active_cooldown = 5,
};
// System animation - no SD card
const Icon* no_sd_icons[] = {
&I_no_sd1,
&I_no_sd2,
&I_no_sd1,
&I_no_sd2,
&I_no_sd1,
&I_no_sd3,
&I_no_sd4,
&I_no_sd5,
&I_no_sd4,
&I_no_sd6,
};
FrameBubble no_sd_bubble = {
.bubble =
{.x = 40,
.y = 18,
.str = "Need an\nSD card",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 0,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble* no_sd_bubbles[] = {&no_sd_bubble};
const BubbleAnimation no_sd_bubble_animation = {
.icons = no_sd_icons,
.frame_bubbles = no_sd_bubbles,
.frame_bubbles_count = COUNT_OF(no_sd_bubbles),
.passive_frames = 10,
.active_frames = 0,
.frame_rate = 2,
.duration = 3600,
.active_cooldown = 0,
.active_cycles = 0,
};
// BLOCKING ANIMATION - no_db, bad_sd, sd_ok, url
const Icon* no_db_icons[] = {
&I_no_databases1,
&I_no_databases2,
&I_no_databases3,
&I_no_databases4,
};
const BubbleAnimation no_db_bubble_animation = {
.icons = no_db_icons,
.passive_frames = COUNT_OF(no_db_icons),
.frame_rate = 2,
};
const Icon* bad_sd_icons[] = {
&I_card_bad1,
&I_card_bad2,
};
const BubbleAnimation bad_sd_bubble_animation = {
.icons = bad_sd_icons,
.passive_frames = COUNT_OF(bad_sd_icons),
.frame_rate = 2,
};
const Icon* url_icons[] = {
&I_url1,
&I_url2,
&I_url3,
&I_url4,
};
const BubbleAnimation url_bubble_animation = {
.icons = url_icons,
.passive_frames = COUNT_OF(url_icons),
.frame_rate = 2,
};
const Icon* sd_ok_icons[] = {
&I_card_ok1,
&I_card_ok2,
&I_card_ok3,
&I_card_ok4,
};
const BubbleAnimation sd_ok_bubble_animation = {
.icons = sd_ok_icons,
.passive_frames = COUNT_OF(sd_ok_icons),
.frame_rate = 2,
};
static StorageAnimation StorageAnimationInternal[] = {
{.animation = &tv_bubble_animation,
.external = false,
.meta =
{
.min_butthurt = 0,
.max_butthurt = 11,
.min_level = 1,
.max_level = 3,
.weight = 3,
}},
{.animation = &no_sd_bubble_animation,
.external = false,
.meta =
{
.min_butthurt = 0,
.max_butthurt = 14,
.min_level = 1,
.max_level = 3,
.weight = 6,
}},
{
.animation = &no_db_bubble_animation,
.external = false,
},
{
.animation = &bad_sd_bubble_animation,
.external = false,
},
{
.animation = &sd_ok_bubble_animation,
.external = false,
},
{
.animation = &url_bubble_animation,
.external = false,
},
};
void animation_storage_initialize_internal_animations(void) {
/* not in constructor - no memory pool yet */
/* called in 1 thread - no need in double check */
static bool initialized = false;
if(!initialized) {
initialized = true;
string_init_set_str(StorageAnimationInternal[0].meta.name, HARDCODED_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[1].meta.name, NO_SD_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[2].meta.name, NO_DB_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[3].meta.name, BAD_SD_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[4].meta.name, SD_OK_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[5].meta.name, URL_ANIMATION_NAME);
}
}

View File

@ -0,0 +1,410 @@
#include "cmsis_os2.h"
#include "../animation_manager.h"
#include "../animation_storage.h"
#include "furi-hal-delay.h"
#include "furi-hal-resources.h"
#include "furi/check.h"
#include "furi/memmgr.h"
#include "gui/canvas.h"
#include "gui/elements.h"
#include "gui/view.h"
#include "input/input.h"
#include <furi.h>
#include "portmacro.h"
#include <gui/icon.h>
#include <stdint.h>
#include <FreeRTOS.h>
#include <timers.h>
#include "bubble_animation_view.h"
#include <gui/icon_i.h>
#define ACTIVE_SHIFT 2
typedef struct {
const BubbleAnimation* current;
const FrameBubble* current_bubble;
uint8_t current_frame;
uint8_t active_cycle;
uint8_t active_bubbles;
uint8_t passive_bubbles;
uint8_t active_shift;
TickType_t active_ended_at;
Icon* freeze_frame;
} BubbleAnimationViewModel;
struct BubbleAnimationView {
View* view;
osTimerId_t timer;
BubbleAnimationInteractCallback interact_callback;
void* interact_callback_context;
};
static void bubble_animation_activate(BubbleAnimationView* view, bool force);
static void bubble_animation_activate_right_now(BubbleAnimationView* view);
static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) {
furi_assert(model);
uint8_t icon_index = 0;
const BubbleAnimation* animation = model->current;
if(model->current_frame < animation->passive_frames) {
icon_index = model->current_frame;
} else {
icon_index =
(model->current_frame - animation->passive_frames) % animation->active_frames +
animation->passive_frames;
}
furi_assert(icon_index < (animation->passive_frames + animation->active_frames));
return icon_index;
}
static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
furi_assert(model_);
furi_assert(canvas);
BubbleAnimationViewModel* model = model_;
const BubbleAnimation* animation = model->current;
if(model->freeze_frame) {
uint8_t y_offset = canvas_height(canvas) - icon_get_height(model->freeze_frame);
canvas_draw_icon(canvas, 0, y_offset, model->freeze_frame);
return;
}
if(!animation) {
return;
}
furi_assert(model->current_frame < 255);
const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)];
furi_assert(icon);
uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon);
canvas_draw_icon(canvas, 0, y_offset, icon);
const FrameBubble* bubble = model->current_bubble;
if(bubble) {
if((model->current_frame >= bubble->starts_at_frame) &&
(model->current_frame <= bubble->ends_at_frame)) {
const Bubble* b = &bubble->bubble;
elements_bubble_str(canvas, b->x, b->y, b->str, b->horizontal, b->vertical);
}
}
}
static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
FrameBubble* bubble = NULL;
if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
return NULL;
}
uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles);
const BubbleAnimation* animation = model->current;
for(int i = 0; i < animation->frame_bubbles_count; ++i) {
if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) {
if(!index) {
bubble = animation->frame_bubbles[i];
}
--index;
}
}
return bubble;
}
static bool bubble_animation_input_callback(InputEvent* event, void* context) {
furi_assert(context);
furi_assert(event);
BubbleAnimationView* animation_view = context;
bool consumed = false;
if(event->type == InputTypePress) {
bubble_animation_activate(animation_view, false);
}
if(event->key == InputKeyRight) {
/* Right button reserved for animation activation, so consume */
consumed = true;
if(event->type == InputTypeShort) {
if(animation_view->interact_callback) {
animation_view->interact_callback(animation_view->interact_callback_context);
}
}
} else if(event->key == InputKeyBack) {
/* Prevent back button to fall down to common handler - leaving
* application, so consume */
consumed = true;
}
return consumed;
}
static void bubble_animation_activate(BubbleAnimationView* view, bool force) {
furi_assert(view);
bool activate = true;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(!model->current) {
activate = false;
} else if(model->freeze_frame) {
activate = false;
} else if(model->current->active_frames == 0) {
activate = false;
}
if(!force) {
if((model->active_ended_at + model->current->active_cooldown * 1000) >
xTaskGetTickCount()) {
activate = false;
} else if(model->active_shift) {
activate = false;
} else if(model->current_frame >= model->current->passive_frames) {
activate = false;
}
}
view_commit_model(view->view, false);
if(!activate && !force) {
return;
}
if(ACTIVE_SHIFT > 0) {
BubbleAnimationViewModel* model = view_get_model(view->view);
model->active_shift = ACTIVE_SHIFT;
view_commit_model(view->view, false);
} else {
bubble_animation_activate_right_now(view);
}
}
static void bubble_animation_activate_right_now(BubbleAnimationView* view) {
furi_assert(view);
uint8_t frame_rate = 0;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) {
model->current_frame = model->current->passive_frames;
model->current_bubble = bubble_animation_pick_bubble(model, true);
frame_rate = model->current->frame_rate;
}
view_commit_model(view->view, true);
if(frame_rate) {
osTimerStart(view->timer, 1000 / frame_rate);
}
}
static void bubble_animation_next_frame(BubbleAnimationViewModel* model) {
furi_assert(model);
if(!model->current) {
return;
}
if(model->current_frame < model->current->passive_frames) {
model->current_frame = (model->current_frame + 1) % model->current->passive_frames;
} else {
++model->current_frame;
model->active_cycle +=
!((model->current_frame - model->current->passive_frames) %
model->current->active_frames);
if(model->active_cycle >= model->current->active_cycles) {
// switch to passive
model->active_cycle = 0;
model->current_frame = 0;
model->current_bubble = bubble_animation_pick_bubble(model, false);
model->active_ended_at = xTaskGetTickCount();
}
if(model->current_bubble) {
if(model->current_frame > model->current_bubble->ends_at_frame) {
model->current_bubble = model->current_bubble->next_bubble;
}
}
}
}
static void bubble_animation_timer_callback(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
bool activate = false;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(model->active_shift > 0) {
activate = (--model->active_shift == 0);
}
if(!model->freeze_frame && !activate) {
bubble_animation_next_frame(model);
}
view_commit_model(view->view, !activate);
if(activate) {
bubble_animation_activate_right_now(view);
}
}
static Icon* bubble_animation_clone_frame(const Icon* icon_orig) {
furi_assert(icon_orig);
furi_assert(icon_orig->frames);
furi_assert(icon_orig->frames[0]);
Icon* icon_clone = furi_alloc(sizeof(Icon));
memcpy(icon_clone, icon_orig, sizeof(Icon));
icon_clone->frames = furi_alloc(sizeof(uint8_t*));
/* icon bitmap can be either compressed or not. It is compressed if
* compressed size is less than original, so max size for bitmap is
* uncompressed (width * height) + 1 byte (in uncompressed case)
* for compressed header
*/
size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1;
icon_clone->frames[0] = furi_alloc(max_bitmap_size);
memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size);
return icon_clone;
}
static void bubble_animation_release_frame(Icon** icon) {
furi_assert(icon);
furi_assert(*icon);
free((void*)(*icon)->frames[0]);
free((*icon)->frames);
free(*icon);
*icon = NULL;
}
static void bubble_animation_enter(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
bubble_animation_activate(view, false);
BubbleAnimationViewModel* model = view_get_model(view->view);
uint8_t frame_rate = model->current->frame_rate;
view_commit_model(view->view, false);
if(frame_rate) {
osTimerStart(view->timer, 1000 / frame_rate);
}
}
static void bubble_animation_exit(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
osTimerStop(view->timer);
}
BubbleAnimationView* bubble_animation_view_alloc(void) {
BubbleAnimationView* view = furi_alloc(sizeof(BubbleAnimationView));
view->view = view_alloc();
view->interact_callback = NULL;
view->timer = osTimerNew(bubble_animation_timer_callback, osTimerPeriodic, view, NULL);
view_allocate_model(view->view, ViewModelTypeLocking, sizeof(BubbleAnimationViewModel));
view_set_context(view->view, view);
view_set_draw_callback(view->view, bubble_animation_draw_callback);
view_set_input_callback(view->view, bubble_animation_input_callback);
view_set_enter_callback(view->view, bubble_animation_enter);
view_set_exit_callback(view->view, bubble_animation_exit);
return view;
}
void bubble_animation_view_free(BubbleAnimationView* view) {
furi_assert(view);
view_set_draw_callback(view->view, NULL);
view_set_input_callback(view->view, NULL);
view_set_context(view->view, NULL);
view_free(view->view);
view->view = NULL;
free(view);
}
void bubble_animation_view_set_interact_callback(
BubbleAnimationView* view,
BubbleAnimationInteractCallback callback,
void* context) {
furi_assert(view);
view->interact_callback_context = context;
view->interact_callback = callback;
}
void bubble_animation_view_set_animation(
BubbleAnimationView* view,
const BubbleAnimation* new_animation) {
furi_assert(view);
furi_assert(new_animation);
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model);
model->current = new_animation;
model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000);
model->active_bubbles = 0;
model->passive_bubbles = 0;
for(int i = 0; i < new_animation->frame_bubbles_count; ++i) {
if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) {
++model->passive_bubbles;
} else {
++model->active_bubbles;
}
}
/* select bubble sequence */
model->current_bubble = bubble_animation_pick_bubble(model, false);
model->current_frame = 0;
model->active_cycle = 0;
view_commit_model(view->view, true);
osTimerStart(view->timer, 1000 / new_animation->frame_rate);
}
void bubble_animation_freeze(BubbleAnimationView* view) {
furi_assert(view);
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model->current);
furi_assert(!model->freeze_frame);
/* always freeze first passive frame, because
* animation is always activated at unfreezing and played
* passive frame first, and 2 frames after - active
*/
uint8_t icon_index = 0;
model->freeze_frame = bubble_animation_clone_frame(model->current->icons[icon_index]);
model->current = NULL;
view_commit_model(view->view, false);
osTimerStop(view->timer);
}
void bubble_animation_unfreeze(BubbleAnimationView* view) {
furi_assert(view);
uint8_t frame_rate;
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model->freeze_frame);
bubble_animation_release_frame(&model->freeze_frame);
furi_assert(model->current);
furi_assert(model->current->icons);
frame_rate = model->current->frame_rate;
view_commit_model(view->view, true);
osTimerStart(view->timer, 1000 / frame_rate);
bubble_animation_activate(view, false);
}
View* bubble_animation_get_view(BubbleAnimationView* view) {
furi_assert(view);
return view->view;
}

View File

@ -0,0 +1,89 @@
#pragma once
#include <gui/view.h>
#include "../animation_manager.h"
/** Bubble Animation instance */
typedef struct BubbleAnimationView BubbleAnimationView;
/** Callback type to be called when interact button pressed */
typedef void (*BubbleAnimationInteractCallback)(void*);
/**
* Allocate bubble animation view.
* This is animation with bubbles, and 2 phases:
* active and passive.
*
* @return instance of new bubble animation
*/
BubbleAnimationView* bubble_animation_view_alloc(void);
/**
* Free bubble animation view.
*
* @view bubble animation view instance
*/
void bubble_animation_view_free(BubbleAnimationView* view);
/**
* Set callback for interact action for animation.
* Currently this is right button.
*
* @view bubble animation view instance
* @callback callback to call when button pressed
* @context context
*/
void bubble_animation_view_set_interact_callback(
BubbleAnimationView* view,
BubbleAnimationInteractCallback callback,
void* context);
/**
* Set new animation.
* BubbleAnimation doesn't posses Bubble Animation object
* so it doesn't handle any memory manipulation on Bubble Animations.
*
* @view bubble animation view instance
* @new_bubble_animation new animation to set
*/
void bubble_animation_view_set_animation(
BubbleAnimationView* view,
const BubbleAnimation* new_bubble_animation);
/**
* Get view of bubble animation.
*
* @view bubble animation view instance
* @return view
*/
View* bubble_animation_get_view(BubbleAnimationView* view);
/**
* Freeze current playing animation. Saves a frame to be shown
* during next unfreeze called.
* bubble_animation_freeze() stops any reference to 'current' animation
* so it can be freed. Therefore lock unfreeze should be preceeded with
* new animation set.
*
* Freeze/Unfreeze usage example:
*
* animation_view_alloc()
* set_animation()
* ...
* freeze_animation()
* // release animation
* ...
* // allocate animation
* set_animation()
* unfreeze()
*
* @view bubble animation view instance
*/
void bubble_animation_freeze(BubbleAnimationView* view);
/**
* Starts bubble animation after freezing.
*
* @view bubble animation view instance
*/
void bubble_animation_unfreeze(BubbleAnimationView* view);

View File

@ -2,6 +2,7 @@
#include "cmsis_os2.h" #include "cmsis_os2.h"
#include "desktop/desktop.h" #include "desktop/desktop.h"
#include "desktop_i.h" #include "desktop_i.h"
#include "gui/view_composed.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <furi/pubsub.h> #include <furi/pubsub.h>
#include <furi/record.h> #include <furi/record.h>
@ -10,7 +11,7 @@
#include "storage/storage.h" #include "storage/storage.h"
#include <stdint.h> #include <stdint.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>
#include "helpers/desktop_animation.h" #include "animations/animation_manager.h"
static void desktop_lock_icon_callback(Canvas* canvas, void* context) { static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
furi_assert(canvas); furi_assert(canvas);
@ -32,11 +33,12 @@ bool desktop_back_event_callback(void* context) {
Desktop* desktop_alloc() { Desktop* desktop_alloc() {
Desktop* desktop = furi_alloc(sizeof(Desktop)); Desktop* desktop = furi_alloc(sizeof(Desktop));
desktop->unload_animation_semaphore = osSemaphoreNew(1, 0, NULL);
desktop->animation_manager = animation_manager_alloc();
desktop->gui = furi_record_open("gui"); desktop->gui = furi_record_open("gui");
desktop->scene_thread = furi_thread_alloc(); desktop->scene_thread = furi_thread_alloc();
desktop->view_dispatcher = view_dispatcher_alloc(); desktop->view_dispatcher = view_dispatcher_alloc();
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop); desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
desktop->animation = desktop_animation_alloc();
view_dispatcher_enable_queue(desktop->view_dispatcher); view_dispatcher_enable_queue(desktop->view_dispatcher);
view_dispatcher_attach_to_gui( view_dispatcher_attach_to_gui(
@ -48,16 +50,34 @@ Desktop* desktop_alloc() {
view_dispatcher_set_navigation_event_callback( view_dispatcher_set_navigation_event_callback(
desktop->view_dispatcher, desktop_back_event_callback); desktop->view_dispatcher, desktop_back_event_callback);
desktop->dolphin_view = animation_manager_get_animation_view(desktop->animation_manager);
desktop->main_view_composed = view_composed_alloc();
desktop->main_view = desktop_main_alloc(); desktop->main_view = desktop_main_alloc();
desktop->lock_menu = desktop_lock_menu_alloc(); view_composed_tie_views(
desktop->main_view_composed,
desktop->dolphin_view,
desktop_main_get_view(desktop->main_view));
view_composed_top_enable(desktop->main_view_composed, true);
desktop->locked_view_composed = view_composed_alloc();
desktop->locked_view = desktop_locked_alloc(); desktop->locked_view = desktop_locked_alloc();
view_composed_tie_views(
desktop->locked_view_composed,
desktop->dolphin_view,
desktop_locked_get_view(desktop->locked_view));
view_composed_top_enable(desktop->locked_view_composed, true);
desktop->lock_menu = desktop_lock_menu_alloc();
desktop->debug_view = desktop_debug_alloc(); desktop->debug_view = desktop_debug_alloc();
desktop->first_start_view = desktop_first_start_alloc(); desktop->first_start_view = desktop_first_start_alloc();
desktop->hw_mismatch_popup = popup_alloc(); desktop->hw_mismatch_popup = popup_alloc();
desktop->code_input = code_input_alloc(); desktop->code_input = code_input_alloc();
view_dispatcher_add_view( view_dispatcher_add_view(
desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); desktop->view_dispatcher,
DesktopViewMain,
view_composed_get_view(desktop->main_view_composed));
view_dispatcher_add_view( view_dispatcher_add_view(
desktop->view_dispatcher, desktop->view_dispatcher,
DesktopViewLockMenu, DesktopViewLockMenu,
@ -67,7 +87,7 @@ Desktop* desktop_alloc() {
view_dispatcher_add_view( view_dispatcher_add_view(
desktop->view_dispatcher, desktop->view_dispatcher,
DesktopViewLocked, DesktopViewLocked,
desktop_locked_get_view(desktop->locked_view)); view_composed_get_view(desktop->locked_view_composed));
view_dispatcher_add_view( view_dispatcher_add_view(
desktop->view_dispatcher, desktop->view_dispatcher,
DesktopViewFirstStart, DesktopViewFirstStart,
@ -91,7 +111,6 @@ Desktop* desktop_alloc() {
void desktop_free(Desktop* desktop) { void desktop_free(Desktop* desktop) {
furi_assert(desktop); furi_assert(desktop);
desktop_animation_free(desktop->animation);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked);
@ -103,6 +122,9 @@ void desktop_free(Desktop* desktop) {
view_dispatcher_free(desktop->view_dispatcher); view_dispatcher_free(desktop->view_dispatcher);
scene_manager_free(desktop->scene_manager); scene_manager_free(desktop->scene_manager);
animation_manager_free(desktop->animation_manager);
view_composed_free(desktop->main_view_composed);
view_composed_free(desktop->locked_view_composed);
desktop_main_free(desktop->main_view); desktop_main_free(desktop->main_view);
desktop_lock_menu_free(desktop->lock_menu); desktop_lock_menu_free(desktop->lock_menu);
desktop_locked_free(desktop->locked_view); desktop_locked_free(desktop->locked_view);
@ -111,6 +133,8 @@ void desktop_free(Desktop* desktop) {
popup_free(desktop->hw_mismatch_popup); popup_free(desktop->hw_mismatch_popup);
code_input_free(desktop->code_input); code_input_free(desktop->code_input);
osSemaphoreDelete(desktop->unload_animation_semaphore);
furi_record_close("gui"); furi_record_close("gui");
desktop->gui = NULL; desktop->gui = NULL;
@ -129,29 +153,9 @@ static bool desktop_is_first_start() {
return exists; return exists;
} }
static void desktop_dolphin_state_changed_callback(const void* message, void* context) {
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
static void desktop_storage_state_changed_callback(const void* message, void* context) {
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
int32_t desktop_srv(void* p) { int32_t desktop_srv(void* p) {
Desktop* desktop = desktop_alloc(); Desktop* desktop = desktop_alloc();
Dolphin* dolphin = furi_record_open("dolphin");
FuriPubSub* dolphin_pubsub = dolphin_get_pubsub(dolphin);
FuriPubSubSubscription* dolphin_subscription =
furi_pubsub_subscribe(dolphin_pubsub, desktop_dolphin_state_changed_callback, desktop);
Storage* storage = furi_record_open("storage");
FuriPubSub* storage_pubsub = storage_get_pubsub(storage);
FuriPubSubSubscription* storage_subscription =
furi_pubsub_subscribe(storage_pubsub, desktop_storage_state_changed_callback, desktop);
bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings);
if(!loaded) { if(!loaded) {
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
@ -181,8 +185,6 @@ int32_t desktop_srv(void* p) {
} }
view_dispatcher_run(desktop->view_dispatcher); view_dispatcher_run(desktop->view_dispatcher);
furi_pubsub_unsubscribe(dolphin_pubsub, dolphin_subscription);
furi_pubsub_unsubscribe(storage_pubsub, storage_subscription);
desktop_free(desktop); desktop_free(desktop);
return 0; return 0;

View File

@ -1,7 +1,10 @@
#pragma once #pragma once
#include "cmsis_os2.h"
#include "desktop.h" #include "desktop.h"
#include "animations/animation_manager.h"
#include "gui/view_composed.h"
#include <furi.h> #include <furi.h>
#include <furi-hal.h> #include <furi-hal.h>
@ -21,7 +24,6 @@
#include "views/desktop_debug.h" #include "views/desktop_debug.h"
#include "scenes/desktop_scene.h" #include "scenes/desktop_scene.h"
#include "helpers/desktop_animation.h"
#include "desktop/desktop_settings/desktop_settings.h" #include "desktop/desktop_settings/desktop_settings.h"
#include <gui/icon.h> #include <gui/icon.h>
@ -46,19 +48,29 @@ struct Desktop {
ViewDispatcher* view_dispatcher; ViewDispatcher* view_dispatcher;
SceneManager* scene_manager; SceneManager* scene_manager;
DesktopAnimation* animation;
DesktopFirstStartView* first_start_view; DesktopFirstStartView* first_start_view;
Popup* hw_mismatch_popup; Popup* hw_mismatch_popup;
DesktopMainView* main_view;
DesktopLockMenuView* lock_menu; DesktopLockMenuView* lock_menu;
DesktopLockedView* locked_view;
DesktopDebugView* debug_view; DesktopDebugView* debug_view;
CodeInput* code_input; CodeInput* code_input;
View* dolphin_view;
DesktopMainView* main_view;
DesktopLockedView* locked_view;
ViewComposed* main_view_composed;
ViewComposed* locked_view_composed;
DesktopSettings settings; DesktopSettings settings;
PinCode pincode_buffer; PinCode pincode_buffer;
ViewPort* lock_viewport; ViewPort* lock_viewport;
AnimationManager* animation_manager;
osSemaphoreId_t unload_animation_semaphore;
FuriPubSubSubscription* app_start_stop_subscription;
char* text_buffer;
}; };
Desktop* desktop_alloc(); Desktop* desktop_alloc();

View File

@ -1,382 +0,0 @@
#include "desktop/helpers/desktop_animation.h"
#include "assets_icons.h"
#include "desktop_animation_i.h"
#include "cmsis_os2.h"
#include "furi/common_defines.h"
#include "furi/record.h"
#include "storage/filesystem-api-defines.h"
#include <power/power_service/power.h>
#include <m-list.h>
#include <storage/storage.h>
#include <desktop/desktop.h>
#include <dolphin/dolphin.h>
#define KEEP_ONLY_CALM_BASIC_ANIMATIONS 1
LIST_DEF(AnimationList, const PairedAnimation*, M_PTR_OPLIST)
#define M_OPL_AnimationList_t() LIST_OPLIST(AnimationList)
#define PUSH_BACK_ANIMATIONS(listname, animations, butthurt) \
for(int i = 0; i < COUNT_OF(animations); ++i) { \
if(!(animations)[i].basic->butthurt_level_mask || \
((animations)[i].basic->butthurt_level_mask & BUTTHURT_LEVEL(butthurt))) { \
AnimationList_push_back(animation_list, &(animations)[i]); \
} \
}
#define IS_BLOCKING_ANIMATION(x) \
(((x) != DesktopAnimationStateBasic) && ((x) != DesktopAnimationStateActive))
#define IS_ONESHOT_ANIMATION(x) ((x) == DesktopAnimationStateLevelUpIsPending)
static void desktop_animation_timer_callback(void* context);
struct DesktopAnimation {
bool sd_shown_error_db;
bool sd_shown_error_card_bad;
osTimerId_t timer;
const PairedAnimation* current;
const Icon* current_blocking_icon;
const Icon** current_one_shot_icons;
uint8_t one_shot_animation_counter;
uint8_t one_shot_animation_size;
DesktopAnimationState state;
TickType_t basic_started_at;
TickType_t active_finished_at;
AnimationChangedCallback animation_changed_callback;
void* animation_changed_callback_context;
};
DesktopAnimation* desktop_animation_alloc(void) {
DesktopAnimation* animation = furi_alloc(sizeof(DesktopAnimation));
animation->timer = osTimerNew(
desktop_animation_timer_callback, osTimerPeriodic /* osTimerOnce */, animation, NULL);
animation->active_finished_at = (TickType_t)(-30);
animation->basic_started_at = 0;
animation->animation_changed_callback = NULL;
animation->animation_changed_callback_context = NULL;
desktop_start_new_idle_animation(animation);
return animation;
}
void desktop_animation_free(DesktopAnimation* animation) {
furi_assert(animation);
osTimerDelete(animation->timer);
free(animation);
}
void desktop_animation_set_animation_changed_callback(
DesktopAnimation* animation,
AnimationChangedCallback callback,
void* context) {
furi_assert(animation);
animation->animation_changed_callback = callback;
animation->animation_changed_callback_context = context;
}
void desktop_start_new_idle_animation(DesktopAnimation* animation) {
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
furi_check((stats.level >= 1) && (stats.level <= 3));
AnimationList_t animation_list;
AnimationList_init(animation_list);
#if KEEP_ONLY_CALM_BASIC_ANIMATIONS
PUSH_BACK_ANIMATIONS(animation_list, calm_animation, 0);
#else
PUSH_BACK_ANIMATIONS(animation_list, calm_animation, stats.butthurt);
PUSH_BACK_ANIMATIONS(animation_list, mad_animation, stats.butthurt);
switch(stats.level) {
case 1:
PUSH_BACK_ANIMATIONS(animation_list, level_1_animation, stats.butthurt);
break;
case 2:
PUSH_BACK_ANIMATIONS(animation_list, level_2_animation, stats.butthurt);
break;
case 3:
PUSH_BACK_ANIMATIONS(animation_list, level_3_animation, stats.butthurt);
break;
default:
furi_crash("Dolphin level is out of bounds");
}
Power* power = furi_record_open("power");
PowerInfo info;
power_get_info(power, &info);
if(!power_is_battery_well(&info)) {
PUSH_BACK_ANIMATIONS(animation_list, check_battery_animation, stats.butthurt);
}
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
animation->current = NULL;
if(sd_status == FSE_NOT_READY) {
PUSH_BACK_ANIMATIONS(animation_list, no_sd_animation, stats.butthurt);
animation->sd_shown_error_card_bad = false;
animation->sd_shown_error_db = false;
}
#endif
uint32_t whole_weight = 0;
for
M_EACH(item, animation_list, AnimationList_t) {
whole_weight += (*item)->basic->weight;
}
uint32_t lucky_number = random() % whole_weight;
uint32_t weight = 0;
const PairedAnimation* selected = NULL;
for
M_EACH(item, animation_list, AnimationList_t) {
if(lucky_number < weight) {
break;
}
weight += (*item)->basic->weight;
selected = *item;
}
animation->basic_started_at = osKernelGetTickCount();
animation->current = selected;
osTimerStart(animation->timer, animation->current->basic->duration * 1000);
animation->state = DesktopAnimationStateBasic;
furi_assert(selected);
AnimationList_clear(animation_list);
}
static void desktop_animation_timer_callback(void* context) {
furi_assert(context);
DesktopAnimation* animation = context;
TickType_t now_ms = osKernelGetTickCount();
AnimationList_t animation_list;
AnimationList_init(animation_list);
bool new_basic_animation = false;
if(animation->state == DesktopAnimationStateActive) {
animation->state = DesktopAnimationStateBasic;
TickType_t basic_lasts_ms = now_ms - animation->basic_started_at;
animation->active_finished_at = now_ms;
TickType_t basic_duration_ms = animation->current->basic->duration * 1000;
if(basic_lasts_ms > basic_duration_ms) {
// if active animation finished, and basic duration came to an end
// select new idle animation
new_basic_animation = true;
} else {
// if active animation finished, but basic duration is not finished
// play current animation for the rest of time
furi_assert(basic_duration_ms != basic_lasts_ms);
osTimerStart(animation->timer, basic_duration_ms - basic_lasts_ms);
}
} else if(animation->state == DesktopAnimationStateBasic) {
// if basic animation finished
// select new idle animation
new_basic_animation = true;
}
if(new_basic_animation) {
animation->basic_started_at = now_ms;
desktop_start_new_idle_animation(animation);
}
// for oneshot generate events every time
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
void desktop_animation_activate(DesktopAnimation* animation) {
furi_assert(animation);
if(animation->state != DesktopAnimationStateBasic) {
return;
}
if(animation->state == DesktopAnimationStateActive) {
return;
}
if(!animation->current->active) {
return;
}
TickType_t now = osKernelGetTickCount();
TickType_t time_since_last_active = now - animation->active_finished_at;
if(time_since_last_active > (animation->current->basic->active_cooldown * 1000)) {
animation->state = DesktopAnimationStateActive;
furi_assert(animation->current->active->duration > 0);
osTimerStart(animation->timer, animation->current->active->duration * 1000);
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
}
static const Icon* desktop_animation_get_current_idle_animation(
DesktopAnimation* animation,
bool* status_bar_background_black) {
const ActiveAnimation* active = animation->current->active;
const BasicAnimation* basic = animation->current->basic;
if(animation->state == DesktopAnimationStateActive && active->icon) {
*status_bar_background_black = active->black_status_bar;
return active->icon;
} else {
*status_bar_background_black = basic->black_status_bar;
return basic->icon;
}
}
// Every time somebody starts 'desktop_animation_get_animation()'
// 1) check if there is a new level
// 2) check if there is SD card corruption
// 3) check if the SD card is empty
// 4) if all false - get idle animation
const Icon* desktop_animation_get_animation(
DesktopAnimation* animation,
bool* status_bar_background_black) {
Dolphin* dolphin = furi_record_open("dolphin");
Storage* storage = furi_record_open("storage");
const Icon* icon = NULL;
furi_assert(animation);
FS_Error sd_status = storage_sd_status(storage);
if(IS_BLOCKING_ANIMATION(animation->state)) {
// don't give new animation till blocked animation
// is reseted
icon = animation->current_blocking_icon;
}
if(!icon) {
if(sd_status == FSE_INTERNAL) {
osTimerStop(animation->timer);
icon = &A_CardBad_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateSDCorrupted;
animation->sd_shown_error_card_bad = true;
animation->sd_shown_error_db = false;
} else if(sd_status == FSE_NOT_READY) {
animation->sd_shown_error_card_bad = false;
animation->sd_shown_error_db = false;
} else if(sd_status == FSE_OK) {
bool db_exists = storage_common_stat(storage, "/ext/manifest.txt", NULL) == FSE_OK;
if(db_exists && !animation->sd_shown_error_db) {
osTimerStop(animation->timer);
icon = &A_CardNoDB_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateSDEmpty;
animation->sd_shown_error_db = true;
}
}
}
DolphinStats stats = dolphin_stats(dolphin);
if(!icon && stats.level_up_is_pending) {
osTimerStop(animation->timer);
icon = &A_LevelUpPending_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateLevelUpIsPending;
}
if(!icon) {
icon =
desktop_animation_get_current_idle_animation(animation, status_bar_background_black);
} else {
status_bar_background_black = false;
}
furi_record_close("storage");
furi_record_close("dolphin");
return icon;
}
DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation) {
furi_assert(animation);
bool reset_animation = false;
bool update_animation = false;
switch(animation->state) {
case DesktopAnimationStateActive:
case DesktopAnimationStateBasic:
/* nothing */
break;
case DesktopAnimationStateLevelUpIsPending:
/* do nothing, main scene should change itself */
break;
case DesktopAnimationStateSDCorrupted:
reset_animation = true;
break;
case DesktopAnimationStateSDEmpty:
animation->state = DesktopAnimationStateSDEmptyURL;
animation->current_blocking_icon = &A_CardNoDBUrl_128x51;
update_animation = true;
break;
case DesktopAnimationStateSDEmptyURL:
reset_animation = true;
break;
default:
furi_crash("Unhandled desktop animation state");
}
if(reset_animation) {
desktop_start_new_idle_animation(animation);
update_animation = true;
}
if(update_animation) {
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
return animation->state;
}
#define LEVELUP_FRAME_RATE (0.2)
void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation) {
animation->one_shot_animation_counter = 0;
animation->state = DesktopAnimationStateLevelUpIsPending;
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
furi_assert(stats.level_up_is_pending);
if(stats.level == 1) {
animation->current_one_shot_icons = animation_level2up;
animation->one_shot_animation_size = COUNT_OF(animation_level2up);
} else if(stats.level == 2) {
animation->current_one_shot_icons = animation_level3up;
animation->one_shot_animation_size = COUNT_OF(animation_level3up);
} else {
furi_crash("Dolphin level is out of bounds");
}
osTimerStart(animation->timer, LEVELUP_FRAME_RATE * 1000);
}
const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation) {
furi_assert(IS_ONESHOT_ANIMATION(animation->state));
furi_assert(animation->one_shot_animation_size > 0);
const Icon* icon = NULL;
if(animation->one_shot_animation_counter < animation->one_shot_animation_size) {
icon = animation->current_one_shot_icons[animation->one_shot_animation_counter];
++animation->one_shot_animation_counter;
} else {
animation->state = DesktopAnimationStateBasic;
animation->one_shot_animation_size = 0;
osTimerStop(animation->timer);
icon = NULL;
}
return icon;
}

View File

@ -1,59 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <gui/icon.h>
typedef struct DesktopAnimation DesktopAnimation;
typedef struct ActiveAnimation ActiveAnimation;
typedef struct BasicAnimation BasicAnimation;
typedef enum {
DesktopAnimationStateBasic,
DesktopAnimationStateActive,
DesktopAnimationStateLevelUpIsPending,
DesktopAnimationStateSDEmpty,
DesktopAnimationStateSDEmptyURL,
DesktopAnimationStateSDCorrupted,
} DesktopAnimationState;
struct BasicAnimation {
const Icon* icon;
uint16_t duration; // sec
uint16_t active_cooldown;
uint8_t weight;
bool black_status_bar;
uint16_t butthurt_level_mask;
};
struct ActiveAnimation {
const Icon* icon;
bool black_status_bar;
uint16_t duration; // sec
};
typedef struct {
const BasicAnimation* basic;
const ActiveAnimation* active;
} PairedAnimation;
typedef void (*AnimationChangedCallback)(void*);
DesktopAnimation* desktop_animation_alloc(void);
void desktop_animation_free(DesktopAnimation*);
void desktop_animation_activate(DesktopAnimation* instance);
void desktop_animation_set_animation_changed_callback(
DesktopAnimation* instance,
AnimationChangedCallback callback,
void* context);
DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation);
void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation);
const Icon*
desktop_animation_get_animation(DesktopAnimation* animation, bool* status_bar_background_black);
const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation);
void desktop_start_new_idle_animation(DesktopAnimation* animation);

View File

@ -1,332 +0,0 @@
#include <assets_icons.h>
#include <stddef.h>
#include <stdint.h>
#include <gui/icon.h>
#include "desktop_animation.h"
// Calm/Mad Basic Idle Animations
#define COMMON_BASIC_DURATION (2 * 60 * 60)
#define COMMON_ACTIVE_CYCLES 1
#define COMMON_ACTIVE_COOLDOWN 15
#define COMMON_WEIGHT 3
#define BUTTHURT_LEVEL(x) (1UL << (x))
#define BUTTHURT_LEVEL_0 0
// frames * cycles / frame_rate
#define COMMON_ACTIVE_DURATION(x) ((x)*COMMON_ACTIVE_CYCLES / 2)
static const BasicAnimation animation_TV = {
.icon = &A_Tv_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_TV_active = {
.icon = &A_TvActive_128x52,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_sleep = {
.icon = &A_Sleep_128x52,
.black_status_bar = true,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10)};
static const ActiveAnimation animation_sleep_active = {
.icon = &A_SleepActive_128x52,
.black_status_bar = true,
.duration = COMMON_ACTIVE_DURATION(5),
};
static const BasicAnimation animation_leaving = {
.icon = &A_Leaving_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(13) | BUTTHURT_LEVEL(14),
};
static const ActiveAnimation animation_leaving_active = {
.icon = &A_LeavingActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_laptop = {
.icon = &A_Laptop_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5)};
static const ActiveAnimation animation_laptop_active = {
.icon = &A_LaptopActive_128x52,
.duration = COMMON_ACTIVE_DURATION(8),
};
static const BasicAnimation animation_knife = {
.icon = &A_Knife_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(5) | BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) |
BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) |
BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_knife_active = {
.icon = &A_KnifeActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_cry = {
.icon = &A_Cry_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) |
BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_cry_active = {
.icon = &A_CryActive_128x51,
.duration = COMMON_ACTIVE_DURATION(3),
};
static const BasicAnimation animation_box = {
.icon = &A_Box_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) |
BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) |
BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_box_active = {
.icon = &A_BoxActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_waves = {
.icon = &A_Waves_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
static const ActiveAnimation animation_waves_active = {
.icon = &A_WavesActive_128x52,
.duration = COMMON_ACTIVE_DURATION(7),
};
// Level Idle Animations
static const BasicAnimation animation_level1furippa = {
.icon = &A_Level1Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level1furippa_active = {
.icon = &A_Level1FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level1read = {
.icon = &A_Level1Read_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
static const ActiveAnimation animation_level1read_active = {
.icon = &A_Level1ReadActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level1toys = {
.icon = &A_Level1Toys_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level1toys_active = {
.icon = &A_Level1ToysActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level2furippa = {
.icon = &A_Level2Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level2furippa_active = {
.icon = &A_Level2FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level2soldering = {
.icon = &A_Level2Soldering_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9)};
static const ActiveAnimation animation_level2soldering_active = {
.icon = &A_Level2SolderingActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level2hack = {
.icon = &A_Level2Hack_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level2hack_active = {
.icon = &A_Level2HackActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level3furippa = {
.icon = &A_Level3Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level3furippa_active = {
.icon = &A_Level3FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level3hijack = {
.icon = &A_Level3Hijack_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9)};
static const ActiveAnimation animation_level3hijack_active = {
.icon = &A_Level3HijackActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level3lab = {
.icon = &A_Level3Lab_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level3lab_active = {
.icon = &A_Level3LabActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
// System Idle Animations
static const BasicAnimation animation_bad_battery = {
.icon = &A_BadBattery_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = 7,
};
static const BasicAnimation animation_no_sd_card = {
.icon = &A_NoSdCard_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = 7,
};
const Icon* animation_level2up[] = {
&I_LevelUp2_01,
&I_LevelUp2_02,
&I_LevelUp2_03,
&I_LevelUp2_04,
&I_LevelUp2_05,
&I_LevelUp2_06,
&I_LevelUp2_07};
const Icon* animation_level3up[] = {
&I_LevelUp3_01,
&I_LevelUp3_02,
&I_LevelUp3_03,
&I_LevelUp3_04,
&I_LevelUp3_05,
&I_LevelUp3_06,
&I_LevelUp3_07};
// Blocking Idle Animations & One shot Animations represented as naked Icon
static const PairedAnimation calm_animation[] = {
{.basic = &animation_TV, .active = &animation_TV_active},
{.basic = &animation_waves, .active = &animation_waves_active},
{.basic = &animation_sleep, .active = &animation_sleep_active},
{.basic = &animation_laptop, .active = &animation_laptop_active},
};
static const PairedAnimation mad_animation[] = {
{.basic = &animation_cry, .active = &animation_cry_active},
{.basic = &animation_knife, .active = &animation_knife_active},
{.basic = &animation_box, .active = &animation_box_active},
{.basic = &animation_leaving, .active = &animation_leaving_active},
};
static const PairedAnimation level_1_animation[] = {
{.basic = &animation_level1furippa, .active = &animation_level1furippa_active},
{.basic = &animation_level1read, .active = &animation_level1read_active},
{.basic = &animation_level1toys, .active = &animation_level1toys_active},
};
static const PairedAnimation level_2_animation[] = {
{.basic = &animation_level2furippa, .active = &animation_level2furippa_active},
{.basic = &animation_level2soldering, .active = &animation_level2soldering_active},
{.basic = &animation_level2hack, .active = &animation_level2hack_active},
};
static const PairedAnimation level_3_animation[] = {
{.basic = &animation_level3furippa, .active = &animation_level3furippa_active},
{.basic = &animation_level3hijack, .active = &animation_level3hijack_active},
{.basic = &animation_level3lab, .active = &animation_level3lab_active},
};
static const PairedAnimation no_sd_animation[] = {
{.basic = &animation_no_sd_card, .active = NULL},
};
static const PairedAnimation check_battery_animation[] = {
{.basic = &animation_bad_battery, .active = NULL},
};

View File

@ -5,5 +5,4 @@ ADD_SCENE(desktop, debug, Debug)
ADD_SCENE(desktop, first_start, FirstStart) ADD_SCENE(desktop, first_start, FirstStart)
ADD_SCENE(desktop, hw_mismatch, HwMismatch) ADD_SCENE(desktop, hw_mismatch, HwMismatch)
ADD_SCENE(desktop, pinsetup, PinSetup) ADD_SCENE(desktop, pinsetup, PinSetup)
ADD_SCENE(desktop, levelup, LevelUp)
ADD_SCENE(desktop, fault, Fault) ADD_SCENE(desktop, fault, Fault)

View File

@ -3,7 +3,7 @@
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <dolphin/helpers/dolphin_deed.h> #include <dolphin/helpers/dolphin_deed.h>
void desktop_scene_debug_callback(DesktopDebugEvent event, void* context) { void desktop_scene_debug_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event); view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
} }
@ -33,14 +33,12 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
case DesktopDebugEventDeed: case DesktopDebugEventDeed:
dolphin_deed(dolphin, DolphinDeedIButtonEmulate); dolphin_deed(dolphin, DolphinDeedIButtonEmulate);
desktop_debug_get_dolphin_data(desktop->debug_view); desktop_debug_get_dolphin_data(desktop->debug_view);
desktop_start_new_idle_animation(desktop->animation);
consumed = true; consumed = true;
break; break;
case DesktopDebugEventWrongDeed: case DesktopDebugEventWrongDeed:
dolphin_deed(dolphin, DolphinDeedWrong); dolphin_deed(dolphin, DolphinDeedWrong);
desktop_debug_get_dolphin_data(desktop->debug_view); desktop_debug_get_dolphin_data(desktop->debug_view);
desktop_start_new_idle_animation(desktop->animation);
consumed = true; consumed = true;
break; break;

View File

@ -1,7 +1,8 @@
#include "../desktop_i.h" #include "../desktop_i.h"
#include "../views/desktop_first_start.h" #include "../views/desktop_first_start.h"
#include "../views/desktop_events.h"
void desktop_scene_first_start_callback(DesktopFirstStartEvent event, void* context) { void desktop_scene_first_start_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event); view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
} }

View File

@ -10,18 +10,21 @@ void desktop_scene_hw_mismatch_callback(void* context) {
void desktop_scene_hw_mismatch_on_enter(void* context) { void desktop_scene_hw_mismatch_on_enter(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
furi_assert(desktop);
furi_assert(!desktop->text_buffer);
Popup* popup = desktop->hw_mismatch_popup; Popup* popup = desktop->hw_mismatch_popup;
char buffer[256]; // strange but smaller buffer not making it desktop->text_buffer = furi_alloc(256);
snprintf( snprintf(
buffer, desktop->text_buffer,
sizeof(buffer), 256,
"HW target: %d\nFW target: %d", "HW target: %d\nFW target: %d",
furi_hal_version_get_hw_target(), furi_hal_version_get_hw_target(),
version_get_target(NULL)); version_get_target(NULL));
popup_set_context(popup, desktop); popup_set_context(popup, desktop);
popup_set_header( popup_set_header(
popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_text(popup, buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_text(
popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_callback(popup, desktop_scene_hw_mismatch_callback); popup_set_callback(popup, desktop_scene_hw_mismatch_callback);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch);
} }
@ -46,9 +49,13 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event)
void desktop_scene_hw_mismatch_on_exit(void* context) { void desktop_scene_hw_mismatch_on_exit(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
furi_assert(desktop);
furi_assert(desktop->text_buffer);
Popup* popup = desktop->hw_mismatch_popup; Popup* popup = desktop->hw_mismatch_popup;
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_callback(popup, NULL); popup_set_callback(popup, NULL);
popup_set_context(popup, NULL); popup_set_context(popup, NULL);
free(desktop->text_buffer);
desktop->text_buffer = NULL;
} }

View File

@ -1,79 +0,0 @@
#include "../desktop_i.h"
#include "../views/desktop_main.h"
#include "applications.h"
#include "assets_icons.h"
#include "desktop/desktop.h"
#include "desktop/helpers/desktop_animation.h"
#include "dolphin/dolphin.h"
#include "furi/pubsub.h"
#include "furi/record.h"
#include "storage/storage-glue.h"
#include <loader/loader.h>
#include <m-list.h>
#define LEVELUP_SCENE_PLAYING 0
#define LEVELUP_SCENE_STOPPED 1
static void desktop_scene_levelup_callback(DesktopMainEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
static void desktop_scene_levelup_animation_changed_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventUpdateOneShotAnimation);
}
void desktop_scene_levelup_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
DesktopMainView* main_view = desktop->main_view;
desktop_main_set_callback(main_view, desktop_scene_levelup_callback, desktop);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_levelup_animation_changed_callback, desktop);
desktop_animation_start_oneshot_levelup(desktop->animation);
const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
desktop_main_switch_dolphin_icon(desktop->main_view, icon);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_PLAYING);
}
bool desktop_scene_levelup_on_event(void* context, SceneManagerEvent event) {
Desktop* desktop = (Desktop*)context;
bool consumed = false;
DesktopMainEvent main_event = event.event;
if(event.type == SceneManagerEventTypeCustom) {
if(main_event == DesktopMainEventUpdateOneShotAnimation) {
const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
if(icon) {
desktop_main_switch_dolphin_icon(desktop->main_view, icon);
} else {
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_STOPPED);
}
consumed = true;
} else {
if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLevelUp) ==
LEVELUP_SCENE_STOPPED) {
scene_manager_previous_scene(desktop->scene_manager);
}
}
}
return consumed;
}
void desktop_scene_levelup_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
Dolphin* dolphin = furi_record_open("dolphin");
dolphin_upgrade_level(dolphin);
furi_record_close("dolphin");
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
desktop_start_new_idle_animation(desktop->animation);
}

View File

@ -3,7 +3,7 @@
#include <toolbox/saved_struct.h> #include <toolbox/saved_struct.h>
#include <stdbool.h> #include <stdbool.h>
void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event); view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
} }

View File

@ -1,34 +1,28 @@
#include "../desktop_i.h" #include "../desktop_i.h"
#include "../views/desktop_locked.h" #include "../views/desktop_locked.h"
#include "desktop/helpers/desktop_animation.h"
#include "desktop/views/desktop_main.h" #include "desktop/views/desktop_main.h"
void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { void desktop_scene_locked_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event); view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
} }
static void desktop_scene_locked_animation_changed_callback(void* context) { static void desktop_scene_locked_new_idle_animation_callback(void* context) {
furi_assert(context); furi_assert(context);
Desktop* desktop = context; Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopLockedEventCheckAnimation);
} }
void desktop_scene_locked_on_enter(void* context) { void desktop_scene_locked_on_enter(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
DesktopLockedView* locked_view = desktop->locked_view; DesktopLockedView* locked_view = desktop->locked_view;
animation_manager_set_new_idle_callback(
desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback);
desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop); desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
desktop_locked_reset_door_pos(locked_view); desktop_locked_reset_door_pos(locked_view);
desktop_locked_update_hint_timeout(locked_view); desktop_locked_update_hint_timeout(locked_view);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_locked_animation_changed_callback, desktop);
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_locked_set_dolphin_animation(locked_view, icon, status_bar_background_black);
uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin);
@ -39,7 +33,7 @@ void desktop_scene_locked_on_enter(void* context) {
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
} }
static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) { static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopEvent event) {
bool match = false; bool match = false;
size_t length = desktop->pincode_buffer.length; size_t length = desktop->pincode_buffer.length;
@ -81,15 +75,10 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
case DesktopLockedEventInputReset: case DesktopLockedEventInputReset:
desktop->pincode_buffer.length = 0; desktop->pincode_buffer.length = 0;
break; break;
case DesktopMainEventUpdateAnimation: { case DesktopLockedEventCheckAnimation:
bool status_bar_background_black = false; animation_manager_check_blocking_process(desktop->animation_manager);
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_locked_set_dolphin_animation(
desktop->locked_view, icon, status_bar_background_black);
consumed = true; consumed = true;
break; break;
}
default: default:
if(desktop_scene_locked_check_pin(desktop, event.event)) { if(desktop_scene_locked_check_pin(desktop, event.event)) {
scene_manager_set_scene_state( scene_manager_set_scene_state(
@ -106,7 +95,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
void desktop_scene_locked_on_exit(void* context) { void desktop_scene_locked_on_exit(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL); animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
desktop_locked_reset_counter(desktop->locked_view); desktop_locked_reset_counter(desktop->locked_view);
osTimerStop(desktop->locked_view->timer); osTimerStop(desktop->locked_view->timer);
} }

View File

@ -2,14 +2,51 @@
#include "../views/desktop_main.h" #include "../views/desktop_main.h"
#include "applications.h" #include "applications.h"
#include "assets_icons.h" #include "assets_icons.h"
#include "cmsis_os2.h"
#include "desktop/desktop.h"
#include "desktop/views/desktop_events.h"
#include "dolphin/dolphin.h" #include "dolphin/dolphin.h"
#include "furi/pubsub.h" #include "furi/pubsub.h"
#include "furi/record.h" #include "furi/record.h"
#include "furi/thread.h"
#include "storage/storage-glue.h" #include "storage/storage-glue.h"
#include <loader/loader.h> #include <loader/loader.h>
#include <m-list.h> #include <m-list.h>
#define MAIN_VIEW_DEFAULT (0UL) #define MAIN_VIEW_DEFAULT (0UL)
static void desktop_scene_main_app_started_callback(const void* message, void* context) {
furi_assert(context);
Desktop* desktop = context;
const LoaderEvent* event = message;
if(event->type == LoaderEventTypeApplicationStarted) {
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventBeforeAppStarted);
osSemaphoreAcquire(desktop->unload_animation_semaphore, osWaitForever);
} else if(event->type == LoaderEventTypeApplicationStopped) {
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventAfterAppFinished);
}
}
static void desktop_scene_main_new_idle_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventNewIdleAnimation);
}
static void desktop_scene_main_check_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventCheckAnimation);
}
static void desktop_scene_main_interact_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventInteractAnimation);
}
static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
furi_assert(desktop); furi_assert(desktop);
furi_assert(flipper_app); furi_assert(flipper_app);
@ -26,23 +63,31 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl
furi_thread_set_callback(desktop->scene_thread, flipper_app->app); furi_thread_set_callback(desktop->scene_thread, flipper_app->app);
furi_thread_start(desktop->scene_thread); furi_thread_start(desktop->scene_thread);
furi_thread_join(desktop->scene_thread);
} }
void desktop_scene_main_callback(DesktopMainEvent event, void* context) { void desktop_scene_main_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event); view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
} }
static void desktop_scene_main_animation_changed_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
void desktop_scene_main_on_enter(void* context) { void desktop_scene_main_on_enter(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
DesktopMainView* main_view = desktop->main_view; DesktopMainView* main_view = desktop->main_view;
animation_manager_set_context(desktop->animation_manager, desktop);
animation_manager_set_new_idle_callback(
desktop->animation_manager, desktop_scene_main_new_idle_animation_callback);
animation_manager_set_check_callback(
desktop->animation_manager, desktop_scene_main_check_animation_callback);
animation_manager_set_interact_callback(
desktop->animation_manager, desktop_scene_main_interact_animation_callback);
furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
desktop->app_start_stop_subscription = furi_pubsub_subscribe(
loader_get_pubsub(), desktop_scene_main_app_started_callback, desktop);
desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
view_port_enabled_set(desktop->lock_viewport, false); view_port_enabled_set(desktop->lock_viewport, false);
@ -51,13 +96,6 @@ void desktop_scene_main_on_enter(void* context) {
desktop_main_unlocked(desktop->main_view); desktop_main_unlocked(desktop->main_view);
} }
desktop_animation_activate(desktop->animation);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_main_animation_changed_callback, desktop);
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_main_switch_dolphin_animation(desktop->main_view, icon, status_bar_background_black);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
} }
@ -84,48 +122,50 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
case DesktopMainEventOpenArchive: case DesktopMainEventOpenArchive:
#ifdef APP_ARCHIVE #ifdef APP_ARCHIVE
animation_manager_unload_and_stall_animation(desktop->animation_manager);
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
animation_manager_load_and_continue_animation(desktop->animation_manager);
#endif #endif
consumed = true; consumed = true;
break; break;
case DesktopMainEventOpenFavorite: case DesktopMainEventOpenFavorite:
LOAD_DESKTOP_SETTINGS(&desktop->settings); LOAD_DESKTOP_SETTINGS(&desktop->settings);
animation_manager_unload_and_stall_animation(desktop->animation_manager);
if(desktop->settings.favorite < FLIPPER_APPS_COUNT) { if(desktop->settings.favorite < FLIPPER_APPS_COUNT) {
desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]);
} else { } else {
FURI_LOG_E("DesktopSrv", "Can't find favorite application"); FURI_LOG_E("DesktopSrv", "Can't find favorite application");
} }
animation_manager_load_and_continue_animation(desktop->animation_manager);
consumed = true; consumed = true;
break; break;
case DesktopMainEventUpdateAnimation: { case DesktopMainEventCheckAnimation:
bool status_bar_background_black = false; animation_manager_check_blocking_process(desktop->animation_manager);
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_main_switch_dolphin_animation(
desktop->main_view, icon, status_bar_background_black);
consumed = true; consumed = true;
break; break;
} case DesktopMainEventNewIdleAnimation:
animation_manager_new_idle_process(desktop->animation_manager);
case DesktopMainEventRightShort: { consumed = true;
DesktopAnimationState state = desktop_animation_handle_right(desktop->animation); break;
if(state == DesktopAnimationStateLevelUpIsPending) { case DesktopMainEventInteractAnimation:
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLevelUp); animation_manager_interact_process(desktop->animation_manager);
} consumed = true;
break;
case DesktopMainEventBeforeAppStarted:
animation_manager_unload_and_stall_animation(desktop->animation_manager);
osSemaphoreRelease(desktop->unload_animation_semaphore);
consumed = true;
break;
case DesktopMainEventAfterAppFinished:
animation_manager_load_and_continue_animation(desktop->animation_manager);
consumed = true;
break; break;
}
default: default:
break; break;
} }
if(event.event != DesktopMainEventUpdateAnimation) {
desktop_animation_activate(desktop->animation);
}
} else if(event.type != SceneManagerEventTypeTick) {
desktop_animation_activate(desktop->animation);
} }
return consumed; return consumed;
@ -134,7 +174,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
void desktop_scene_main_on_exit(void* context) { void desktop_scene_main_on_exit(void* context) {
Desktop* desktop = (Desktop*)context; Desktop* desktop = (Desktop*)context;
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL); /**
* We're allowed to leave this scene only when any other app & loader
* is finished, that's why we can be sure there is no task waiting
* for start/stop semaphore
*/
furi_pubsub_unsubscribe(loader_get_pubsub(), desktop->app_start_stop_subscription);
furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
animation_manager_set_check_callback(desktop->animation_manager, NULL);
animation_manager_set_interact_callback(desktop->animation_manager, NULL);
animation_manager_set_context(desktop->animation_manager, desktop);
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
desktop_main_reset_hint(desktop->main_view); desktop_main_reset_hint(desktop->main_view);
} }

View File

@ -7,17 +7,11 @@
#include <furi.h> #include <furi.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <time.h> #include <time.h>
#include "desktop_events.h"
typedef enum {
DesktopDebugEventDeed,
DesktopDebugEventWrongDeed,
DesktopDebugEventSaveState,
DesktopDebugEventExit,
} DesktopDebugEvent;
typedef struct DesktopDebugView DesktopDebugView; typedef struct DesktopDebugView DesktopDebugView;
typedef void (*DesktopDebugViewCallback)(DesktopDebugEvent event, void* context); typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context);
// Debug info // Debug info
typedef enum { typedef enum {
@ -51,4 +45,4 @@ DesktopDebugView* desktop_debug_alloc();
void desktop_debug_free(DesktopDebugView* debug_view); void desktop_debug_free(DesktopDebugView* debug_view);
void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view); void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view);
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view); void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view);

View File

@ -0,0 +1,30 @@
#pragma once
typedef enum {
DesktopMainEventOpenLockMenu,
DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite,
DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
DesktopMainEventRightShort,
DesktopMainEventCheckAnimation,
DesktopMainEventNewIdleAnimation,
DesktopMainEventInteractAnimation,
DesktopMainEventBeforeAppStarted,
DesktopMainEventAfterAppFinished,
DesktopLockedEventUnlock,
DesktopLockedEventUpdate,
DesktopLockedEventInputReset,
DesktopLockedEventCheckAnimation,
DesktopLockedEventMax,
DesktopDebugEventDeed,
DesktopDebugEventWrongDeed,
DesktopDebugEventSaveState,
DesktopDebugEventExit,
DesktopFirstStartCompleted,
DesktopFirstStartPoweroff,
DesktopLockMenuEventLock,
DesktopLockMenuEventPinLock,
DesktopLockMenuEventExit,
} DesktopEvent;

View File

@ -5,15 +5,11 @@
#include <gui/canvas.h> #include <gui/canvas.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <furi.h> #include <furi.h>
#include "desktop_events.h"
typedef enum {
DesktopFirstStartCompleted,
DesktopFirstStartPoweroff,
} DesktopFirstStartEvent;
typedef struct DesktopFirstStartView DesktopFirstStartView; typedef struct DesktopFirstStartView DesktopFirstStartView;
typedef void (*DesktopFirstStartViewCallback)(DesktopFirstStartEvent event, void* context); typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context);
DesktopFirstStartView* desktop_first_start_alloc(); DesktopFirstStartView* desktop_first_start_alloc();

View File

@ -5,18 +5,13 @@
#include <gui/canvas.h> #include <gui/canvas.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <furi.h> #include <furi.h>
#include "desktop_events.h"
#define HINT_TIMEOUT 2 #define HINT_TIMEOUT 2
typedef enum {
DesktopLockMenuEventLock,
DesktopLockMenuEventPinLock,
DesktopLockMenuEventExit,
} DesktopLockMenuEvent;
typedef struct DesktopLockMenuView DesktopLockMenuView; typedef struct DesktopLockMenuView DesktopLockMenuView;
typedef void (*DesktopLockMenuViewCallback)(DesktopLockMenuEvent event, void* context); typedef void (*DesktopLockMenuViewCallback)(DesktopEvent event, void* context);
struct DesktopLockMenuView { struct DesktopLockMenuView {
View* view; View* view;

View File

@ -17,21 +17,6 @@ void locked_view_timer_callback(void* context) {
locked_view->callback(DesktopLockedEventUpdate, locked_view->context); locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
} }
void desktop_locked_set_dolphin_animation(
DesktopLockedView* locked_view,
const Icon* icon,
bool status_bar_background_black) {
with_view_model(
locked_view->view, (DesktopLockedViewModel * model) {
if(model->animation) icon_animation_free(model->animation);
model->animation = icon_animation_alloc(icon);
view_tie_icon_animation(locked_view->view, model->animation);
icon_animation_start(model->animation);
model->status_bar_background_black = status_bar_background_black;
return true;
});
}
void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) {
with_view_model( with_view_model(
locked_view->view, (DesktopLockedViewModel * model) { locked_view->view, (DesktopLockedViewModel * model) {
@ -95,7 +80,6 @@ void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) {
void desktop_locked_render(Canvas* canvas, void* model) { void desktop_locked_render(Canvas* canvas, void* model) {
DesktopLockedViewModel* m = model; DesktopLockedViewModel* m = model;
uint32_t now = osKernelGetTickCount(); uint32_t now = osKernelGetTickCount();
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
if(!m->animation_seq_end) { if(!m->animation_seq_end) {

View File

@ -5,6 +5,7 @@
#include <gui/canvas.h> #include <gui/canvas.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <furi.h> #include <furi.h>
#include "desktop_events.h"
#define UNLOCK_RST_TIMEOUT 300 #define UNLOCK_RST_TIMEOUT 300
#define UNLOCK_CNT 2 // 3 actually #define UNLOCK_CNT 2 // 3 actually
@ -14,12 +15,6 @@
#define DOOR_R_POS 115 #define DOOR_R_POS 115
#define DOOR_R_POS_MIN 60 #define DOOR_R_POS_MIN 60
typedef enum {
DesktopLockedEventUnlock = 10U,
DesktopLockedEventUpdate = 11U,
DesktopLockedEventInputReset = 12U,
} DesktopLockedEvent;
typedef enum { typedef enum {
DesktopLockedWithPin, DesktopLockedWithPin,
DesktopLockedNoPin, DesktopLockedNoPin,
@ -27,7 +22,7 @@ typedef enum {
typedef struct DesktopLockedView DesktopLockedView; typedef struct DesktopLockedView DesktopLockedView;
typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context);
struct DesktopLockedView { struct DesktopLockedView {
View* view; View* view;
@ -57,10 +52,6 @@ void desktop_locked_set_callback(
DesktopLockedViewCallback callback, DesktopLockedViewCallback callback,
void* context); void* context);
void desktop_locked_set_dolphin_animation(
DesktopLockedView* locked_view,
const Icon* icon,
bool status_bar_background_black);
void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
void desktop_locked_reset_counter(DesktopLockedView* locked_view); void desktop_locked_reset_counter(DesktopLockedView* locked_view);
void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);

View File

@ -1,8 +1,13 @@
#include "dolphin/dolphin.h"
#include "furi/record.h"
#include "gui/canvas.h" #include "gui/canvas.h"
#include "gui/view.h"
#include "gui/view_composed.h"
#include "input/input.h" #include "input/input.h"
#include <furi.h> #include <furi.h>
#include "../desktop_i.h" #include "../desktop_i.h"
#include "desktop_main.h" #include "desktop_main.h"
//#include "../animations/views/bubble_animation_view.h"
void desktop_main_set_callback( void desktop_main_set_callback(
DesktopMainView* main_view, DesktopMainView* main_view,
@ -49,7 +54,6 @@ void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* ic
} }
void desktop_main_render(Canvas* canvas, void* model) { void desktop_main_render(Canvas* canvas, void* model) {
canvas_clear(canvas);
DesktopMainViewModel* m = model; DesktopMainViewModel* m = model;
uint32_t now = osKernelGetTickCount(); uint32_t now = osKernelGetTickCount();
@ -78,6 +82,7 @@ bool desktop_main_input(InputEvent* event, void* context) {
furi_assert(context); furi_assert(context);
DesktopMainView* main_view = context; DesktopMainView* main_view = context;
bool consumed = false;
if(event->key == InputKeyOk && event->type == InputTypeShort) { if(event->key == InputKeyOk && event->type == InputTypeShort) {
main_view->callback(DesktopMainEventOpenMenu, main_view->context); main_view->callback(DesktopMainEventOpenMenu, main_view->context);
@ -91,11 +96,13 @@ bool desktop_main_input(InputEvent* event, void* context) {
main_view->callback(DesktopMainEventOpenFavorite, main_view->context); main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
} else if(event->key == InputKeyRight && event->type == InputTypeShort) { } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
main_view->callback(DesktopMainEventRightShort, main_view->context); main_view->callback(DesktopMainEventRightShort, main_view->context);
} else if(event->key == InputKeyBack && event->type == InputTypeShort) {
consumed = true;
} }
desktop_main_reset_hint(main_view); desktop_main_reset_hint(main_view);
return true; return consumed;
} }
void desktop_main_enter(void* context) { void desktop_main_enter(void* context) {
@ -119,6 +126,7 @@ void desktop_main_exit(void* context) {
DesktopMainView* desktop_main_alloc() { DesktopMainView* desktop_main_alloc() {
DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView));
main_view->view = view_alloc(); main_view->view = view_alloc();
view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel));
view_set_context(main_view->view, main_view); view_set_context(main_view->view, main_view);

View File

@ -1,26 +1,16 @@
#pragma once #pragma once
#include "gui/view_composed.h"
#include <gui/gui_i.h> #include <gui/gui_i.h>
#include <gui/view.h> #include <gui/view.h>
#include <gui/canvas.h> #include <gui/canvas.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <furi.h> #include <furi.h>
#include "desktop_events.h"
typedef enum {
DesktopMainEventOpenLockMenu,
DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite,
DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
DesktopMainEventRightShort,
DesktopMainEventUpdateAnimation,
DesktopMainEventUpdateOneShotAnimation,
} DesktopMainEvent;
typedef struct DesktopMainView DesktopMainView; typedef struct DesktopMainView DesktopMainView;
typedef void (*DesktopMainViewCallback)(DesktopMainEvent event, void* context); typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context);
struct DesktopMainView { struct DesktopMainView {
View* view; View* view;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "furi/pubsub.h" #include "furi/pubsub.h"
#include "gui/view.h"
#include "helpers/dolphin_deed.h" #include "helpers/dolphin_deed.h"
#include <stdbool.h> #include <stdbool.h>

View File

@ -11,6 +11,9 @@ typedef enum {
DolphinEventTypeDeed, DolphinEventTypeDeed,
DolphinEventTypeStats, DolphinEventTypeStats,
DolphinEventTypeFlush, DolphinEventTypeFlush,
DolphinEventTypeAnimationStartNewIdle,
DolphinEventTypeAnimationCheckBlocking,
DolphinEventTypeAnimationInteract,
} DolphinEventType; } DolphinEventType;
typedef struct { typedef struct {

View File

@ -348,6 +348,140 @@ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
} }
void elements_bubble_str(
Canvas* canvas,
uint8_t x,
uint8_t y,
const char* text,
Align horizontal,
Align vertical) {
furi_assert(canvas);
furi_assert(text);
uint8_t font_y = canvas_current_font_height(canvas);
uint16_t str_width = canvas_string_width(canvas, text);
// count \n's
uint8_t lines = 1;
const char* t = text;
while(*t != '\0') {
if(*t == '\n') {
lines++;
uint16_t temp_width = canvas_string_width(canvas, t + 1);
str_width = temp_width > str_width ? temp_width : str_width;
}
t++;
}
uint8_t frame_x = x;
uint8_t frame_y = y;
uint8_t frame_width = str_width + 8;
uint8_t frame_height = font_y * lines + 4;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, frame_x + 1, frame_y + 1, frame_width - 2, frame_height - 2);
canvas_set_color(canvas, ColorBlack);
canvas_draw_rframe(canvas, frame_x, frame_y, frame_width, frame_height, 1);
elements_multiline_text(canvas, x + 4, y - 1 + font_y, text);
uint8_t x1 = 0;
uint8_t x2 = 0;
uint8_t x3 = 0;
uint8_t y1 = 0;
uint8_t y2 = 0;
uint8_t y3 = 0;
if((horizontal == AlignLeft) && (vertical == AlignTop)) {
x1 = frame_x;
y1 = frame_y;
x2 = frame_x - 4;
y2 = frame_y;
x3 = frame_x;
y3 = frame_y + 4;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 + 2, y2 + 1, 2, 2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignLeft) && (vertical == AlignCenter)) {
x1 = frame_x;
y1 = frame_y + (frame_height - 1) / 2 - 4;
x2 = frame_x - 4;
y2 = frame_y + (frame_height - 1) / 2;
x3 = frame_x;
y3 = frame_y + (frame_height - 1) / 2 + 4;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 + 2, y2 - 2, 2, 5);
canvas_draw_dot(canvas, x2 + 1, y2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignLeft) && (vertical == AlignBottom)) {
x1 = frame_x;
y1 = frame_y + (frame_height - 1) - 4;
x2 = frame_x - 4;
y2 = frame_y + (frame_height - 1);
x3 = frame_x;
y3 = frame_y + (frame_height - 1);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 + 2, y2 - 2, 2, 2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignRight) && (vertical == AlignTop)) {
x1 = frame_x + (frame_width - 1);
y1 = frame_y;
x2 = frame_x + (frame_width - 1) + 4;
y2 = frame_y;
x3 = frame_x + (frame_width - 1);
y3 = frame_y + 4;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 - 3, y2 + 1, 2, 2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignRight) && (vertical == AlignCenter)) {
x1 = frame_x + (frame_width - 1);
y1 = frame_y + (frame_height - 1) / 2 - 4;
x2 = frame_x + (frame_width - 1) + 4;
y2 = frame_y + (frame_height - 1) / 2;
x3 = frame_x + (frame_width - 1);
y3 = frame_y + (frame_height - 1) / 2 + 4;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 - 3, y2 - 2, 2, 5);
canvas_draw_dot(canvas, x2 - 1, y2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignRight) && (vertical == AlignBottom)) {
x1 = frame_x + (frame_width - 1);
y1 = frame_y + (frame_height - 1) - 4;
x2 = frame_x + (frame_width - 1) + 4;
y2 = frame_y + (frame_height - 1);
x3 = frame_x + (frame_width - 1);
y3 = frame_y + (frame_height - 1);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 - 3, y2 - 2, 2, 2);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignCenter) && (vertical == AlignTop)) {
x1 = frame_x + (frame_width - 1) / 2 - 4;
y1 = frame_y;
x2 = frame_x + (frame_width - 1) / 2;
y2 = frame_y - 4;
x3 = frame_x + (frame_width - 1) / 2 + 4;
y3 = frame_y;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 - 2, y2 + 2, 5, 2);
canvas_draw_dot(canvas, x2, y2 + 1);
canvas_set_color(canvas, ColorBlack);
} else if((horizontal == AlignCenter) && (vertical == AlignBottom)) {
x1 = frame_x + (frame_width - 1) / 2 - 4;
y1 = frame_y + (frame_height - 1);
x2 = frame_x + (frame_width - 1) / 2;
y2 = frame_y + (frame_height - 1) + 4;
x3 = frame_x + (frame_width - 1) / 2 + 4;
y3 = frame_y + (frame_height - 1);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, x2 - 2, y2 - 3, 5, 2);
canvas_draw_dot(canvas, x2, y2 - 1);
canvas_set_color(canvas, ColorBlack);
}
canvas_set_color(canvas, ColorWhite);
canvas_draw_line(canvas, x3, y3, x1, y1);
canvas_set_color(canvas, ColorBlack);
canvas_draw_line(canvas, x1, y1, x2, y2);
canvas_draw_line(canvas, x2, y2, x3, y3);
}
void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {
furi_assert(canvas); furi_assert(canvas);
furi_assert(string); furi_assert(string);

18
applications/gui/elements.h Executable file → Normal file
View File

@ -160,6 +160,24 @@ void elements_slightly_rounded_box(
*/ */
void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
/** Draw bubble frame for text with corner
*
* @param canvas Canvas instance
* @param x left x coordinates
* @param y top y coordinate
* @param width bubble width
* @param height bubble height
* @param horizontal horizontal aligning
* @param vertical aligning
*/
void elements_bubble_str(
Canvas* canvas,
uint8_t x,
uint8_t y,
const char* text,
Align horizontal,
Align vertical);
/** Trim string buffer to fit width in pixels /** Trim string buffer to fit width in pixels
* *
* @param canvas Canvas instance * @param canvas Canvas instance

View File

@ -0,0 +1,166 @@
#include "gui/view.h"
#include "furi/memmgr.h"
#include "view_composed.h"
#include "view_i.h"
typedef struct {
View* bottom;
View* top;
bool top_enabled;
} ViewComposedModel;
struct ViewComposed {
View* view;
};
static void view_composed_draw(Canvas* canvas, void* model);
static bool view_composed_input(InputEvent* event, void* context);
static void view_composed_update_callback(View* view_top_or_bottom, void* context) {
furi_assert(view_top_or_bottom);
furi_assert(context);
View* view_composed_view = context;
view_composed_view->update_callback(
view_composed_view, view_composed_view->update_callback_context);
}
static void view_composed_enter(void* context) {
furi_assert(context);
ViewComposed* view_composed = context;
ViewComposedModel* model = view_get_model(view_composed->view);
/* if more than 1 composed views hold same view it has to reassign update_callback_context */
if(model->bottom) {
view_set_update_callback_context(model->bottom, view_composed->view);
if(model->bottom->enter_callback) {
model->bottom->enter_callback(model->bottom->context);
}
}
if(model->top) {
view_set_update_callback_context(model->top, view_composed->view);
if(model->top->enter_callback) {
model->top->enter_callback(model->top->context);
}
}
view_commit_model(view_composed->view, false);
}
static void view_composed_exit(void* context) {
furi_assert(context);
ViewComposed* view_composed = context;
ViewComposedModel* model = view_get_model(view_composed->view);
if(model->bottom) {
if(model->bottom->exit_callback) {
model->bottom->exit_callback(model->bottom->context);
}
}
if(model->top) {
if(model->top->exit_callback) {
model->top->exit_callback(model->top->context);
}
}
view_commit_model(view_composed->view, false);
}
ViewComposed* view_composed_alloc(void) {
ViewComposed* view_composed = furi_alloc(sizeof(ViewComposed));
view_composed->view = view_alloc();
view_allocate_model(view_composed->view, ViewModelTypeLocking, sizeof(ViewComposedModel));
view_set_draw_callback(view_composed->view, view_composed_draw);
view_set_input_callback(view_composed->view, view_composed_input);
view_set_context(view_composed->view, view_composed);
view_set_enter_callback(view_composed->view, view_composed_enter);
view_set_exit_callback(view_composed->view, view_composed_exit);
return view_composed;
}
void view_composed_free(ViewComposed* view_composed) {
furi_assert(view_composed);
ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
view_set_update_callback(view_composed_model->bottom, NULL);
view_set_update_callback_context(view_composed_model->bottom, NULL);
view_set_update_callback(view_composed_model->top, NULL);
view_set_update_callback_context(view_composed_model->top, NULL);
view_commit_model(view_composed->view, true);
view_free(view_composed->view);
free(view_composed);
}
static void view_composed_draw(Canvas* canvas, void* model) {
furi_assert(model);
ViewComposedModel* view_composed_model = model;
view_draw(view_composed_model->bottom, canvas);
if(view_composed_model->top_enabled && view_composed_model->top) {
view_draw(view_composed_model->top, canvas);
}
}
static bool view_composed_input(InputEvent* event, void* context) {
furi_assert(event);
furi_assert(context);
ViewComposed* view_composed = context;
ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
bool consumed = false;
if(view_composed_model->top_enabled && view_composed_model->top) {
consumed = view_input(view_composed_model->top, event);
}
if(!consumed) {
consumed = view_input(view_composed_model->bottom, event);
}
view_commit_model(view_composed->view, false);
return consumed;
}
void view_composed_top_enable(ViewComposed* view_composed, bool enable) {
furi_assert(view_composed);
ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
bool update = (view_composed_model->top_enabled != enable);
view_composed_model->top_enabled = enable;
view_commit_model(view_composed->view, update);
}
void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top) {
furi_assert(view_composed);
furi_assert(view_bottom);
ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
if(view_composed_model->bottom) {
view_set_update_callback(view_composed_model->bottom, NULL);
view_set_update_callback_context(view_composed_model->bottom, NULL);
}
if(view_composed_model->top) {
view_set_update_callback(view_composed_model->top, NULL);
view_set_update_callback_context(view_composed_model->top, NULL);
}
view_composed_model->bottom = view_bottom;
view_set_update_callback(view_bottom, view_composed_update_callback);
view_set_update_callback_context(view_bottom, view_composed->view);
view_composed_model->top = view_top;
view_set_update_callback(view_top, view_composed_update_callback);
view_set_update_callback_context(view_top, view_composed->view);
view_commit_model(view_composed->view, true);
}
View* view_composed_get_view(ViewComposed* view_composed) {
furi_assert(view_composed);
return view_composed->view;
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <stdbool.h>
#include "view.h"
typedef struct ViewComposed ViewComposed;
ViewComposed* view_composed_alloc(void);
void view_composed_free(ViewComposed* view_composed);
void view_composed_top_enable(ViewComposed* view_composed, bool enable);
void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top);
View* view_composed_get_view(ViewComposed* view_composed);

View File

@ -188,7 +188,7 @@ void view_dispatcher_send_to_front(ViewDispatcher* view_dispatcher) {
void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher) { void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher) {
furi_assert(view_dispatcher); furi_assert(view_dispatcher);
furi_assert(view_dispatcher->gui); furi_assert(view_dispatcher->gui);
gui_view_port_send_to_front(view_dispatcher->gui, view_dispatcher->view_port); gui_view_port_send_to_back(view_dispatcher->gui, view_dispatcher->view_port);
} }
void view_dispatcher_attach_to_gui( void view_dispatcher_attach_to_gui(

View File

@ -1,3 +1,4 @@
#include <furi/pubsub.h>
#include "loader/loader.h" #include "loader/loader.h"
#include "loader_i.h" #include "loader_i.h"
@ -21,6 +22,7 @@ static void loader_menu_callback(void* _ctx, uint32_t index) {
return; return;
} }
furi_hal_power_insomnia_enter(); furi_hal_power_insomnia_enter();
loader_instance->current_app = flipper_app; loader_instance->current_app = flipper_app;
FURI_LOG_I(TAG, "Starting: %s", loader_instance->current_app->name); FURI_LOG_I(TAG, "Starting: %s", loader_instance->current_app->name);
@ -228,9 +230,12 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
furi_assert(context); furi_assert(context);
Loader* instance = context; Loader* instance = context;
LoaderEvent event;
if(thread_state == FuriThreadStateRunning) { if(thread_state == FuriThreadStateRunning) {
instance->free_heap_size = memmgr_get_free_heap(); instance->free_heap_size = memmgr_get_free_heap();
event.type = LoaderEventTypeApplicationStarted;
furi_pubsub_publish(loader_instance->pubsub, &event);
} else if(thread_state == FuriThreadStateStopped) { } else if(thread_state == FuriThreadStateStopped) {
/* /*
* Current Leak Sanitizer assumes that memory is allocated and freed * Current Leak Sanitizer assumes that memory is allocated and freed
@ -251,6 +256,8 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
furi_thread_get_heap_size(instance->thread)); furi_thread_get_heap_size(instance->thread));
furi_hal_power_insomnia_exit(); furi_hal_power_insomnia_exit();
loader_unlock(instance); loader_unlock(instance);
event.type = LoaderEventTypeApplicationStopped;
furi_pubsub_publish(loader_instance->pubsub, &event);
} }
} }
@ -275,6 +282,7 @@ static Loader* loader_alloc() {
string_init(instance->args); string_init(instance->args);
instance->pubsub = furi_pubsub_alloc();
instance->mutex = osMutexNew(NULL); instance->mutex = osMutexNew(NULL);
#ifdef SRV_CLI #ifdef SRV_CLI
@ -334,6 +342,8 @@ static void loader_free(Loader* instance) {
osMutexDelete(instance->mutex); osMutexDelete(instance->mutex);
furi_pubsub_free(instance->pubsub);
string_clear(instance->args); string_clear(instance->args);
furi_thread_free(instance->thread); furi_thread_free(instance->thread);
@ -471,3 +481,7 @@ int32_t loader_srv(void* p) {
return 0; return 0;
} }
FuriPubSub* loader_get_pubsub() {
return loader_instance->pubsub;
}

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <furi/pubsub.h>
#include <stdbool.h> #include <stdbool.h>
typedef struct Loader Loader; typedef struct Loader Loader;
@ -11,6 +12,15 @@ typedef enum {
LoaderStatusErrorInternal, LoaderStatusErrorInternal,
} LoaderStatus; } LoaderStatus;
typedef enum {
LoaderEventTypeApplicationStarted,
LoaderEventTypeApplicationStopped
} LoaderEventType;
typedef struct {
LoaderEventType type;
} LoaderEvent;
/** Start application /** Start application
* @param name - application name * @param name - application name
* @param args - application arguments * @param args - application arguments
@ -34,3 +44,6 @@ void loader_show_menu();
/** Show primary loader */ /** Show primary loader */
void loader_update_menu(); void loader_update_menu();
/** Show primary loader */
FuriPubSub* loader_get_pubsub();

View File

@ -2,6 +2,7 @@
#include <furi.h> #include <furi.h>
#include <furi-hal.h> #include <furi-hal.h>
#include <furi/pubsub.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
@ -30,6 +31,8 @@ struct Loader {
size_t free_heap_size; size_t free_heap_size;
osMutexId_t mutex; osMutexId_t mutex;
volatile uint8_t lock_semaphore; volatile uint8_t lock_semaphore;
FuriPubSub* pubsub;
}; };
typedef enum { typedef enum {

View File

@ -4,7 +4,6 @@
#include <pb_decode.h> #include <pb_decode.h>
#include <pb_encode.h> #include <pb_encode.h>
#include <status.pb.h>
#include <storage.pb.h> #include <storage.pb.h>
#include <flipper.pb.h> #include <flipper.pb.h>
#include <portmacro.h> #include <portmacro.h>

View File

@ -1,6 +1,5 @@
#include "flipper.pb.h" #include "flipper.pb.h"
#include "furi/record.h" #include "furi/record.h"
#include "status.pb.h"
#include "rpc_i.h" #include "rpc_i.h"
#include <furi.h> #include <furi.h>
#include <loader/loader.h> #include <loader/loader.h>

View File

@ -1,6 +1,5 @@
#include "flipper.pb.h" #include "flipper.pb.h"
#include "rpc_i.h" #include "rpc_i.h"
#include "status.pb.h"
#include <furi-hal.h> #include <furi-hal.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>

View File

@ -189,4 +189,4 @@ typedef struct {
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -2,20 +2,35 @@ PROJECT_ROOT = $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))..)
include $(PROJECT_ROOT)/assets/assets.mk include $(PROJECT_ROOT)/assets/assets.mk
all: icons protobuf .PHONY: all
all: icons protobuf dolphin
$(ASSETS): $(ASSETS_SOURCES) $(ASSETS_COMPILLER) $(ASSETS): $(ASSETS_SOURCES) $(ASSETS_COMPILLER)
@echo "\tASSETS\t\t" $@ @echo "\tASSETS\t\t" $@
@$(ASSETS_COMPILLER) icons "$(ASSETS_SOURCE_DIR)" "$(ASSETS_COMPILED_DIR)" @$(ASSETS_COMPILLER) icons "$(ASSETS_SOURCE_DIR)" "$(ASSETS_COMPILED_DIR)"
.PHONY: icons
icons: $(ASSETS) icons: $(ASSETS)
$(PROTOBUF) &: $(PROTOBUF_SOURCES) $(PROTOBUF_COMPILER) $(PROTOBUF) &: $(PROTOBUF_SOURCES) $(PROTOBUF_COMPILER)
@echo "\tPROTOBUF\t" $(PROTOBUF_FILENAMES) @echo "\tPROTOBUF\t" $(PROTOBUF_FILENAMES)
@$(PROJECT_ROOT)/lib/nanopb/generator/nanopb_generator.py -q -I$(PROTOBUF_SOURCE_DIR) -D$(PROTOBUF_COMPILED_DIR) $(PROTOBUF_SOURCES) @$(PROTOBUF_COMPILER) -q -I$(PROTOBUF_SOURCE_DIR) -D$(PROTOBUF_COMPILED_DIR) $(PROTOBUF_SOURCES)
.PHONY: protobuf
protobuf: $(PROTOBUF) protobuf: $(PROTOBUF)
$(PROTOBUF_FILE_ANIMATIONS): $(PROTOBUF_SOURCES_FILE_ANIMATIONS) $(PROTOBUF_COMPILER)
@echo "\tFILE ANIMATIONS\t" $(PROTOBUF_FILE_ANIMATIONS_FILENAMES)
@$(PROTOBUF_COMPILER) -q -I$(PROTOBUF_FILE_ANIMATIONS_SOURCE_DIR) -D$(PROTOBUF_FILE_ANIMATIONS_COMPILED_DIR) $(PROTOBUF_FILE_ANIMATIONS_SOURCES)
$(DOLPHIN_OUTPUT_DIR): $(DOLPHIN_SOURCE_DIR)
@echo "\tDOLPHIN"
@$(ASSETS_COMPILLER) dolphin "$(DOLPHIN_SOURCE_DIR)" "$(DOLPHIN_OUTPUT_DIR)"
.PHONY: dolphin
dolphin: $(DOLPHIN_OUTPUT_DIR)
clean: clean:
@echo "\tCLEAN\t" @echo "\tCLEAN\t"
@$(RM) $(ASSETS_COMPILED_DIR)/* @$(RM) $(ASSETS_COMPILED_DIR)/*
@$(RM) -rf $(DOLPHIN_OUTPUT_DIR)

View File

@ -23,6 +23,11 @@ make all
Image names will be automatically prefixed with `I_`, animation names with `A_`. Image names will be automatically prefixed with `I_`, animation names with `A_`.
Icons and Animations will be gathered into `icon.h` and `icon.c`. Icons and Animations will be gathered into `icon.h` and `icon.c`.
## Dolphin and Games assets
Rules are same as for Images and Animations plus assets are grouped by level and level prepends `NAME`.
Good starting point: https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/AssetNaming/
# Important notes # Important notes
Don't include assets that you are not using, compiler is not going to strip unused assets. Don't include assets that you are not using, compiler is not going to strip unused assets.

View File

@ -6,11 +6,13 @@ ASSETS_SOURCE_DIR := $(ASSETS_DIR)/icons
ASSETS_SOURCES += $(shell find $(ASSETS_SOURCE_DIR) -type f -iname '*.png' -or -iname 'frame_rate') ASSETS_SOURCES += $(shell find $(ASSETS_SOURCE_DIR) -type f -iname '*.png' -or -iname 'frame_rate')
ASSETS += $(ASSETS_COMPILED_DIR)/assets_icons.c ASSETS += $(ASSETS_COMPILED_DIR)/assets_icons.c
DOLPHIN_SOURCE_DIR := $(ASSETS_DIR)/dolphin
DOLPHIN_OUTPUT_DIR := $(ASSETS_DIR)/resources/dolphin
PROTOBUF_SOURCE_DIR := $(ASSETS_DIR)/protobuf PROTOBUF_SOURCE_DIR := $(ASSETS_DIR)/protobuf
PROTOBUF_COMPILER := $(PROJECT_ROOT)/lib/nanopb/generator/nanopb_generator.py PROTOBUF_COMPILER := $(PROJECT_ROOT)/lib/nanopb/generator/nanopb_generator.py
PROTOBUF_COMPILED_DIR := $(ASSETS_COMPILED_DIR) PROTOBUF_COMPILED_DIR := $(ASSETS_COMPILED_DIR)
PROTOBUF_SOURCES := $(shell find $(PROTOBUF_SOURCE_DIR) -type f -iname '*.proto') PROTOBUF_SOURCES := $(shell find $(PROTOBUF_SOURCE_DIR) -type f -iname '*.proto')
#PROTOBUF_FILENAMES := $(notdir $(PROTOBUF))
PROTOBUF_FILENAMES := $(notdir $(addsuffix .pb.c,$(basename $(PROTOBUF_SOURCES)))) PROTOBUF_FILENAMES := $(notdir $(addsuffix .pb.c,$(basename $(PROTOBUF_SOURCES))))
PROTOBUF := $(addprefix $(PROTOBUF_COMPILED_DIR)/,$(PROTOBUF_FILENAMES)) PROTOBUF := $(addprefix $(PROTOBUF_COMPILED_DIR)/,$(PROTOBUF_FILENAMES))
PROTOBUF_CFLAGS += -DPB_ENABLE_MALLOC PROTOBUF_CFLAGS += -DPB_ENABLE_MALLOC

File diff suppressed because it is too large Load Diff

View File

@ -1,145 +1,118 @@
#pragma once #pragma once
#include <gui/icon.h> #include <gui/icon.h>
extern const Icon I_Certification2_119x30;
extern const Icon I_Certification1_103x23; extern const Icon I_Certification1_103x23;
extern const Icon A_BadBattery_128x51; extern const Icon I_Certification2_119x30;
extern const Icon A_BoxActive_128x51; extern const Icon I_card_bad1;
extern const Icon A_Box_128x51; extern const Icon I_card_bad2;
extern const Icon A_CardBad_128x51; extern const Icon I_card_ok1;
extern const Icon A_CardNoDBUrl_128x51; extern const Icon I_card_ok2;
extern const Icon A_CardNoDB_128x51; extern const Icon I_card_ok3;
extern const Icon A_CardOk_128x51; extern const Icon I_card_ok4;
extern const Icon A_CryActive_128x51; extern const Icon I_no_databases1;
extern const Icon A_Cry_128x51; extern const Icon I_no_databases2;
extern const Icon A_KnifeActive_128x51; extern const Icon I_no_databases3;
extern const Icon A_Knife_128x51; extern const Icon I_no_databases4;
extern const Icon A_LaptopActive_128x52; extern const Icon I_no_sd1;
extern const Icon A_Laptop_128x52; extern const Icon I_no_sd2;
extern const Icon A_LeavingActive_128x51; extern const Icon I_no_sd3;
extern const Icon A_Leaving_128x51; extern const Icon I_no_sd4;
extern const Icon A_Level1FurippaActive_128x51; extern const Icon I_no_sd5;
extern const Icon A_Level1Furippa_128x51; extern const Icon I_no_sd6;
extern const Icon A_Level1ReadActive_128x51; extern const Icon I_tv1;
extern const Icon A_Level1Read_128x51; extern const Icon I_tv2;
extern const Icon A_Level1ToysActive_128x51; extern const Icon I_tv3;
extern const Icon A_Level1Toys_128x51; extern const Icon I_tv4;
extern const Icon A_Level2FurippaActive_128x51; extern const Icon I_tv5;
extern const Icon A_Level2Furippa_128x51; extern const Icon I_tv6;
extern const Icon A_Level2HackActive_128x51; extern const Icon I_tv7;
extern const Icon A_Level2Hack_128x51; extern const Icon I_tv8;
extern const Icon A_Level2SolderingActive_128x51; extern const Icon I_url1;
extern const Icon A_Level2Soldering_128x51; extern const Icon I_url2;
extern const Icon A_Level3FurippaActive_128x51; extern const Icon I_url3;
extern const Icon A_Level3Furippa_128x51; extern const Icon I_url4;
extern const Icon A_Level3HijackActive_128x51;
extern const Icon A_Level3Hijack_128x51;
extern const Icon A_Level3LabActive_128x51;
extern const Icon A_Level3Lab_128x51;
extern const Icon I_LevelUp2_03;
extern const Icon I_LevelUp2_02;
extern const Icon I_LevelUp2_05;
extern const Icon I_LevelUp2_04;
extern const Icon I_LevelUp2_01;
extern const Icon I_LevelUp2_06;
extern const Icon I_LevelUp2_07;
extern const Icon I_LevelUp3_05;
extern const Icon I_LevelUp3_06;
extern const Icon I_LevelUp3_02;
extern const Icon I_LevelUp3_07;
extern const Icon I_LevelUp3_04;
extern const Icon I_LevelUp3_03;
extern const Icon I_LevelUp3_01;
extern const Icon A_LevelUpPending_128x51;
extern const Icon A_NoSdCard_128x51;
extern const Icon A_SleepActive_128x52;
extern const Icon A_Sleep_128x52;
extern const Icon A_TvActive_128x52;
extern const Icon A_Tv_128x52;
extern const Icon A_WavesActive_128x52;
extern const Icon A_Waves_128x52;
extern const Icon I_ble_10px;
extern const Icon I_ibutt_10px;
extern const Icon I_125_10px; extern const Icon I_125_10px;
extern const Icon I_sub1_10px;
extern const Icon I_dir_10px;
extern const Icon I_ir_10px;
extern const Icon I_Nfc_10px; extern const Icon I_Nfc_10px;
extern const Icon I_ble_10px;
extern const Icon I_dir_10px;
extern const Icon I_ibutt_10px;
extern const Icon I_ir_10px;
extern const Icon I_sub1_10px;
extern const Icon I_unknown_10px; extern const Icon I_unknown_10px;
extern const Icon I_BLE_Pairing_128x64; extern const Icon I_BLE_Pairing_128x64;
extern const Icon I_Volup_8x6; extern const Icon I_Ble_connected_38x34;
extern const Icon I_Circles_47x47;
extern const Icon I_Ble_disconnected_24x34; extern const Icon I_Ble_disconnected_24x34;
extern const Icon I_Space_65x18; extern const Icon I_Button_18x18;
extern const Icon I_Circles_47x47;
extern const Icon I_Ok_btn_9x9; extern const Icon I_Ok_btn_9x9;
extern const Icon I_Pressed_Button_13x13; extern const Icon I_Pressed_Button_13x13;
extern const Icon I_Space_65x18;
extern const Icon I_Voldwn_6x6; extern const Icon I_Voldwn_6x6;
extern const Icon I_Ble_connected_38x34; extern const Icon I_Volup_8x6;
extern const Icon I_Button_18x18; extern const Icon I_Clock_18x18;
extern const Icon I_EviSmile2_18x21; extern const Icon I_Error_18x18;
extern const Icon I_EviSmile1_18x21; extern const Icon I_EviSmile1_18x21;
extern const Icon I_UsbTree_48x22; extern const Icon I_EviSmile2_18x21;
extern const Icon I_EviWaiting1_18x21; extern const Icon I_EviWaiting1_18x21;
extern const Icon I_EviWaiting2_18x21; extern const Icon I_EviWaiting2_18x21;
extern const Icon I_Percent_10x14; extern const Icon I_Percent_10x14;
extern const Icon I_Smile_18x18; extern const Icon I_Smile_18x18;
extern const Icon I_Error_18x18; extern const Icon I_UsbTree_48x22;
extern const Icon I_Clock_18x18;
extern const Icon I_ButtonRightSmall_3x5;
extern const Icon I_ButtonLeftSmall_3x5;
extern const Icon I_ButtonCenter_7x7; extern const Icon I_ButtonCenter_7x7;
extern const Icon I_ButtonDown_7x4; extern const Icon I_ButtonDown_7x4;
extern const Icon I_ButtonRight_4x7; extern const Icon I_ButtonLeftSmall_3x5;
extern const Icon I_DFU_128x50;
extern const Icon I_ButtonUp_7x4;
extern const Icon I_Warning_30x23;
extern const Icon I_ButtonLeft_4x7; extern const Icon I_ButtonLeft_4x7;
extern const Icon I_DolphinFirstStart7_61x51; extern const Icon I_ButtonRightSmall_3x5;
extern const Icon I_DolphinOkay_41x43; extern const Icon I_ButtonRight_4x7;
extern const Icon I_DolphinFirstStart5_54x49; extern const Icon I_ButtonUp_7x4;
extern const Icon I_Flipper_young_80x60; extern const Icon I_DFU_128x50;
extern const Icon I_DolphinFirstStart2_59x51; extern const Icon I_Warning_30x23;
extern const Icon I_DolphinFirstStart8_56x51;
extern const Icon I_DolphinFirstStart3_57x48;
extern const Icon I_DolphinFirstStart0_70x53; extern const Icon I_DolphinFirstStart0_70x53;
extern const Icon I_DolphinFirstStart4_67x53;
extern const Icon I_DolphinFirstStart6_58x54;
extern const Icon I_DolphinFirstStart1_59x53; extern const Icon I_DolphinFirstStart1_59x53;
extern const Icon I_DolphinFirstStart2_59x51;
extern const Icon I_DolphinFirstStart3_57x48;
extern const Icon I_DolphinFirstStart4_67x53;
extern const Icon I_DolphinFirstStart5_54x49;
extern const Icon I_DolphinFirstStart6_58x54;
extern const Icon I_DolphinFirstStart7_61x51;
extern const Icon I_DolphinFirstStart8_56x51;
extern const Icon I_DolphinOkay_41x43;
extern const Icon I_Flipper_young_80x60;
extern const Icon I_ArrowDownEmpty_14x15;
extern const Icon I_ArrowDownFilled_14x15; extern const Icon I_ArrowDownFilled_14x15;
extern const Icon I_ArrowUpEmpty_14x15; extern const Icon I_ArrowUpEmpty_14x15;
extern const Icon I_ArrowUpFilled_14x15; extern const Icon I_ArrowUpFilled_14x15;
extern const Icon I_ArrowDownEmpty_14x15; extern const Icon I_DoorLeft_70x55;
extern const Icon I_DoorLocked_10x56; extern const Icon I_DoorLocked_10x56;
extern const Icon I_DoorRight_70x55;
extern const Icon I_LockPopup_100x49;
extern const Icon I_PassportBottom_128x17; extern const Icon I_PassportBottom_128x17;
extern const Icon I_PassportLeft_6x47; extern const Icon I_PassportLeft_6x47;
extern const Icon I_DoorLeft_70x55;
extern const Icon I_LockPopup_100x49;
extern const Icon I_DoorRight_70x55;
extern const Icon I_IrdaArrowDown_4x8;
extern const Icon I_Power_25x27;
extern const Icon I_Mute_25x27;
extern const Icon I_Down_hvr_25x27;
extern const Icon I_Vol_up_25x27;
extern const Icon I_IrdaLearnShort_128x31;
extern const Icon I_Up_25x27;
extern const Icon I_Vol_down_hvr_25x27;
extern const Icon I_Vol_down_25x27;
extern const Icon I_Vol_up_hvr_25x27;
extern const Icon I_Fill_marker_7x7;
extern const Icon I_Up_hvr_25x27;
extern const Icon I_IrdaArrowUp_4x8;
extern const Icon I_Down_25x27;
extern const Icon I_DolphinReadingSuccess_59x63;
extern const Icon I_IrdaSendShort_128x34;
extern const Icon I_IrdaLearn_128x64;
extern const Icon I_Mute_hvr_25x27;
extern const Icon I_IrdaSend_128x64;
extern const Icon I_Power_hvr_25x27;
extern const Icon I_Back_15x10; extern const Icon I_Back_15x10;
extern const Icon I_KeySaveSelected_24x11; extern const Icon I_DolphinReadingSuccess_59x63;
extern const Icon I_KeySave_24x11; extern const Icon I_Down_25x27;
extern const Icon I_Down_hvr_25x27;
extern const Icon I_Fill_marker_7x7;
extern const Icon I_IrdaArrowDown_4x8;
extern const Icon I_IrdaArrowUp_4x8;
extern const Icon I_IrdaLearnShort_128x31;
extern const Icon I_IrdaLearn_128x64;
extern const Icon I_IrdaSendShort_128x34;
extern const Icon I_IrdaSend_128x64;
extern const Icon I_Mute_25x27;
extern const Icon I_Mute_hvr_25x27;
extern const Icon I_Power_25x27;
extern const Icon I_Power_hvr_25x27;
extern const Icon I_Up_25x27;
extern const Icon I_Up_hvr_25x27;
extern const Icon I_Vol_down_25x27;
extern const Icon I_Vol_down_hvr_25x27;
extern const Icon I_Vol_up_25x27;
extern const Icon I_Vol_up_hvr_25x27;
extern const Icon I_KeyBackspaceSelected_16x9; extern const Icon I_KeyBackspaceSelected_16x9;
extern const Icon I_KeyBackspace_16x9; extern const Icon I_KeyBackspace_16x9;
extern const Icon I_KeySaveSelected_24x11;
extern const Icon I_KeySave_24x11;
extern const Icon A_125khz_14; extern const Icon A_125khz_14;
extern const Icon A_BadUsb_14; extern const Icon A_BadUsb_14;
extern const Icon A_Bluetooth_14; extern const Icon A_Bluetooth_14;
@ -157,58 +130,58 @@ extern const Icon A_Sub1ghz_14;
extern const Icon A_Tamagotchi_14; extern const Icon A_Tamagotchi_14;
extern const Icon A_U2F_14; extern const Icon A_U2F_14;
extern const Icon A_iButton_14; extern const Icon A_iButton_14;
extern const Icon I_Medium_chip_22x21;
extern const Icon I_Detailed_chip_17x13; extern const Icon I_Detailed_chip_17x13;
extern const Icon I_passport_happy3_46x49; extern const Icon I_Medium_chip_22x21;
extern const Icon I_passport_bad1_46x49; extern const Icon I_passport_bad1_46x49;
extern const Icon I_passport_left_6x46;
extern const Icon I_passport_bad2_46x49; extern const Icon I_passport_bad2_46x49;
extern const Icon I_passport_happy1_46x49;
extern const Icon I_passport_bottom_128x18;
extern const Icon I_passport_okay3_46x49;
extern const Icon I_passport_okay2_46x49;
extern const Icon I_passport_bad3_46x49; extern const Icon I_passport_bad3_46x49;
extern const Icon I_passport_okay1_46x49; extern const Icon I_passport_bottom_128x18;
extern const Icon I_passport_happy1_46x49;
extern const Icon I_passport_happy2_46x49; extern const Icon I_passport_happy2_46x49;
extern const Icon I_Health_16x16; extern const Icon I_passport_happy3_46x49;
extern const Icon I_Voltage_16x16; extern const Icon I_passport_left_6x46;
extern const Icon I_passport_okay1_46x49;
extern const Icon I_passport_okay2_46x49;
extern const Icon I_passport_okay3_46x49;
extern const Icon I_BatteryBody_52x28; extern const Icon I_BatteryBody_52x28;
extern const Icon I_FaceNormal_29x14;
extern const Icon I_FaceCharging_29x14;
extern const Icon I_Battery_16x16; extern const Icon I_Battery_16x16;
extern const Icon I_FaceCharging_29x14;
extern const Icon I_FaceConfused_29x14; extern const Icon I_FaceConfused_29x14;
extern const Icon I_Temperature_16x16;
extern const Icon I_FaceNopower_29x14; extern const Icon I_FaceNopower_29x14;
extern const Icon I_RFIDDolphinSuccess_108x57; extern const Icon I_FaceNormal_29x14;
extern const Icon I_Health_16x16;
extern const Icon I_Temperature_16x16;
extern const Icon I_Voltage_16x16;
extern const Icon I_RFIDBigChip_37x36; extern const Icon I_RFIDBigChip_37x36;
extern const Icon I_RFIDDolphinReceive_97x61; extern const Icon I_RFIDDolphinReceive_97x61;
extern const Icon I_RFIDDolphinSend_97x61; extern const Icon I_RFIDDolphinSend_97x61;
extern const Icon I_RFIDDolphinSuccess_108x57;
extern const Icon I_SDError_43x35; extern const Icon I_SDError_43x35;
extern const Icon I_SDQuestion_35x43; extern const Icon I_SDQuestion_35x43;
extern const Icon I_Cry_dolph_55x52; extern const Icon I_Cry_dolph_55x52;
extern const Icon I_Battery_19x8;
extern const Icon I_SDcardFail_11x8;
extern const Icon I_Bluetooth_5x8;
extern const Icon I_PlaceholderR_30x13;
extern const Icon I_Battery_26x8;
extern const Icon I_Lock_8x8;
extern const Icon I_SDcardMounted_11x8;
extern const Icon I_Charging_lightning_9x10;
extern const Icon I_BadUsb_9x8;
extern const Icon I_BT_Pair_9x8; extern const Icon I_BT_Pair_9x8;
extern const Icon I_Charging_lightning_mask_9x10;
extern const Icon I_PlaceholderL_11x13;
extern const Icon I_Background_128x11; extern const Icon I_Background_128x11;
extern const Icon I_BadUsb_9x8;
extern const Icon I_Battery_19x8;
extern const Icon I_Battery_26x8;
extern const Icon I_Bluetooth_5x8;
extern const Icon I_Charging_lightning_9x10;
extern const Icon I_Charging_lightning_mask_9x10;
extern const Icon I_Lock_8x8;
extern const Icon I_PlaceholderL_11x13;
extern const Icon I_PlaceholderR_30x13;
extern const Icon I_SDcardFail_11x8;
extern const Icon I_SDcardMounted_11x8;
extern const Icon I_USBConnected_15x8; extern const Icon I_USBConnected_15x8;
extern const Icon I_Quest_7x8; extern const Icon I_Lock_7x8;
extern const Icon I_MHz_25x11; extern const Icon I_MHz_25x11;
extern const Icon I_Quest_7x8;
extern const Icon I_Scanning_123x52; extern const Icon I_Scanning_123x52;
extern const Icon I_Unlock_7x8; extern const Icon I_Unlock_7x8;
extern const Icon I_Lock_7x8;
extern const Icon I_DolphinNice_96x59;
extern const Icon I_iButtonDolphinSuccess_109x60;
extern const Icon I_DolphinExcited_64x63; extern const Icon I_DolphinExcited_64x63;
extern const Icon I_iButtonKey_49x44;
extern const Icon I_iButtonDolphinVerySuccess_108x52;
extern const Icon I_DolphinWait_61x59;
extern const Icon I_DolphinMafia_115x62; extern const Icon I_DolphinMafia_115x62;
extern const Icon I_DolphinNice_96x59;
extern const Icon I_DolphinWait_61x59;
extern const Icon I_iButtonDolphinSuccess_109x60;
extern const Icon I_iButtonDolphinVerySuccess_108x52;
extern const Icon I_iButtonKey_49x44;

View File

@ -1,15 +0,0 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.5 */
#include "status.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(PB_Status_PingRequest, PB_Status_PingRequest, AUTO)
PB_BIND(PB_Status_PingResponse, PB_Status_PingResponse, AUTO)

View File

@ -1,62 +0,0 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.5 */
#ifndef PB_PB_STATUS_STATUS_PB_H_INCLUDED
#define PB_PB_STATUS_STATUS_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
typedef struct _PB_Status_PingRequest {
pb_bytes_array_t *data;
} PB_Status_PingRequest;
typedef struct _PB_Status_PingResponse {
pb_bytes_array_t *data;
} PB_Status_PingResponse;
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define PB_Status_PingRequest_init_default {NULL}
#define PB_Status_PingResponse_init_default {NULL}
#define PB_Status_PingRequest_init_zero {NULL}
#define PB_Status_PingResponse_init_zero {NULL}
/* Field tags (for use in manual encoding/decoding) */
#define PB_Status_PingRequest_data_tag 1
#define PB_Status_PingResponse_data_tag 1
/* Struct field encoding specification for nanopb */
#define PB_Status_PingRequest_FIELDLIST(X, a) \
X(a, POINTER, SINGULAR, BYTES, data, 1)
#define PB_Status_PingRequest_CALLBACK NULL
#define PB_Status_PingRequest_DEFAULT NULL
#define PB_Status_PingResponse_FIELDLIST(X, a) \
X(a, POINTER, SINGULAR, BYTES, data, 1)
#define PB_Status_PingResponse_CALLBACK NULL
#define PB_Status_PingResponse_DEFAULT NULL
extern const pb_msgdesc_t PB_Status_PingRequest_msg;
extern const pb_msgdesc_t PB_Status_PingResponse_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define PB_Status_PingRequest_fields &PB_Status_PingRequest_msg
#define PB_Status_PingResponse_fields &PB_Status_PingResponse_msg
/* Maximum encoded size of messages (where known) */
/* PB_Status_PingRequest_size depends on runtime parameters */
/* PB_Status_PingResponse_size depends on runtime parameters */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,32 @@
Filetype: Flipper Animation
Version: 1
Width: 128
Height: 51
Passive frames: 6
Active frames: 2
Frames order: 0 1 2 3 4 5 6 7
Active cycles: 3
Frame rate: 2
Duration: 3600
Active cooldown: 5
Bubble slots: 1
Slot: 0
X: 60
Y: 23
Text: I have to rest
AlignH: Left
AlignV: Bottom
StartFrame: 7
EndFrame: 9
Slot: 0
X: 60
Y: 23
Text: but not today
AlignH: Left
AlignV: Bottom
StartFrame: 10
EndFrame: 12

View File

@ -0,0 +1,34 @@
Filetype: Flipper Animation Manifest
Version: 1
# Animation 1
Name: waves
Min butthurt: 0
Max butthurt: 5
Min level: 1
Max level: 3
Weight: 3
# Animation 2
Name: laptop
Min butthurt: 0
Max butthurt: 9
Min level: 1
Max level: 3
Weight: 3
# Animation 3
Name: sleep
Min butthurt: 0
Max butthurt: 10
Min level: 1
Max level: 3
Weight: 3
# Animation 4
Name: recording
Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 1
Weight: 3

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,14 @@
Filetype: Flipper Animation
Version: 1
Width: 128
Height: 51
Passive frames: 6
Active frames: 6
Frames order: 0 1 2 3 4 5 6 7 8 9 10 11
Active cycles: 2
Frame rate: 2
Duration: 3600
Active cooldown: 5
Bubble slots: 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,41 @@
Filetype: Flipper Animation
Version: 1
Width: 128
Height: 64
Passive frames: 2
Active frames: 4
Frames order: 0 1 2 3 2 3
Active cycles: 2
Frame rate: 2
Duration: 3600
Active cooldown: 5
Bubble slots: 2
Slot: 0
X: 53
Y: 20
Text: In a lucid dream,\nI could walk...
AlignH: Left
AlignV: Bottom
StartFrame: 3
EndFrame: 9
Slot: 1
X: 53
Y: 20
Text: OH MY GOD!
AlignH: Left
AlignV: Bottom
StartFrame: 3
EndFrame: 5
Slot: 1
X: 53
Y: 31
Text: Just a dream...
AlignH: Left
AlignV: Bottom
StartFrame: 6
EndFrame: 9

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,50 @@
Filetype: Flipper Animation
Version: 1
Width: 128
Height: 50
Passive frames: 2
Active frames: 4
Frames order: 0 1 2 3 2 3
Active cycles: 2
Frame rate: 2
Duration: 3600
Active cooldown: 5
Bubble slots: 3
Slot: 0
X: 1
Y: 17
Text: I am happy,\nmy friend!
AlignH: Right
AlignV: Bottom
StartFrame: 3
EndFrame: 9
Slot: 1
X: 1
Y: 17
Text: So long and\nthanks for\nall the fish!
AlignH: Right
AlignV: Center
StartFrame: 3
EndFrame: 9
Slot: 2
X: 1
Y: 25
Text: I wish I could
AlignH: Right
AlignV: Bottom
StartFrame: 3
EndFrame: 5
Slot: 2
X: 1
Y: 25
Text: swim all day
AlignH: Right
AlignV: Bottom
StartFrame: 6
EndFrame: 9

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1 +0,0 @@
2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Some files were not shown because too many files have changed in this diff Show More