Просмотр исходного кода

Add view modules bridge for game engine

Ivan Barsukov 1 год назад
Родитель
Сommit
3ef60379ed

+ 165 - 0
src/gui_bridge/input_converter.c

@@ -0,0 +1,165 @@
+#include "input_converter.h"
+
+#include <furi/core/message_queue.h>
+
+#include "../game.h"
+
+// #define FRAME_IN_MS (1000 / 30)
+// #define CHECK_PERIOD_IN_MS 150
+
+#define CHECK_PERIOD_IN_FRAMES 5 // (int)(CHECK_PERIOD_IN_MS / FRAME_IN_MS)
+#define LONG_PRESS_IN_FRAMES 10  // (int)(CHECK_PERIOD_IN_MS * 2 / FRAME_IN_MS)
+
+const InputKey input_keys[InputKeyMAX] = { InputKeyUp,    InputKeyDown,
+                                           InputKeyRight, InputKeyLeft,
+                                           InputKeyOk,    InputKeyBack };
+
+const GameKey game_keys[InputKeyMAX] = {
+    GameKeyUp, GameKeyDown, GameKeyRight, GameKeyLeft, GameKeyOk, GameKeyBack
+};
+
+struct InputConverter
+{
+    size_t holded_frames_for_key[InputKeyMAX];
+    FuriMessageQueue* queue;
+};
+
+static void
+put_input_event(InputConverter* input_converter, InputEvent* event)
+{
+    FURI_LOG_D(GAME_NAME,
+               "Input event: %s %s",
+               input_get_key_name(event->key),
+               input_get_type_name(event->type));
+
+    FuriStatus status =
+      furi_message_queue_put(input_converter->queue, event, 0);
+    furi_check(status == FuriStatusOk);
+}
+
+static void
+process_pressed(InputConverter* input_converter,
+                InputState* input_state,
+                GameKey game_key,
+                InputKey input_key)
+{
+    if (!(input_state->pressed & game_key)) {
+        return;
+    }
+
+    ++(input_converter->holded_frames_for_key[input_key]);
+
+    InputEvent event = { .key = input_key, .type = InputTypePress };
+    put_input_event(input_converter, &event);
+}
+
+static void
+process_holded(InputConverter* input_converter,
+               InputState* input_state,
+               GameKey game_key,
+               InputKey input_key)
+{
+    size_t* holded_frames_for_key =
+      &input_converter->holded_frames_for_key[input_key];
+
+    if (!(input_state->held & game_key) || *holded_frames_for_key == 0) {
+        return;
+    }
+
+    // Add current frame
+    ++(*holded_frames_for_key);
+
+    InputEvent event = { .key = input_key };
+    if (*holded_frames_for_key == LONG_PRESS_IN_FRAMES) {
+        // Long press
+        event.type = InputTypeLong;
+        put_input_event(input_converter, &event);
+    } else if (*holded_frames_for_key ==
+               LONG_PRESS_IN_FRAMES + CHECK_PERIOD_IN_FRAMES) {
+        // Pause between events
+        input_converter->holded_frames_for_key[input_key] =
+          LONG_PRESS_IN_FRAMES;
+
+        // Repeat
+        event.type = InputTypeRepeat;
+        put_input_event(input_converter, &event);
+    }
+}
+
+static void
+process_released(InputConverter* input_converter,
+                 InputState* input_state,
+                 GameKey game_key,
+                 InputKey input_key)
+{
+    size_t* holded_frames_for_key =
+      &input_converter->holded_frames_for_key[input_key];
+
+    if (!(input_state->released & game_key) || 0 == *holded_frames_for_key) {
+        return;
+    }
+
+    InputEvent event = { .key = input_key };
+
+    // Process short press
+    if (*holded_frames_for_key < LONG_PRESS_IN_FRAMES) {
+        event.type = InputTypeShort;
+        put_input_event(input_converter, &event);
+    }
+
+    // Reset state
+    input_converter->holded_frames_for_key[input_key] = 0;
+
+    // Put release event
+    event.type = InputTypeRelease;
+    put_input_event(input_converter, &event);
+}
+
+InputConverter*
+input_converter_alloc(void)
+{
+    InputConverter* input_converter = malloc(sizeof(InputConverter));
+
+    // Init queue
+    input_converter->queue =
+      furi_message_queue_alloc(2 * InputKeyMAX, sizeof(InputEvent));
+
+    // Init counter
+    for (size_t i = 0; i < InputKeyMAX; ++i) {
+        input_converter->holded_frames_for_key[i] = 0;
+    }
+
+    return input_converter;
+}
+
+void
+input_converter_free(InputConverter* input_converter)
+{
+    furi_check(input_converter);
+    furi_message_queue_free(input_converter->queue);
+    free(input_converter);
+}
+
+void
+input_converter_process_state(InputConverter* input_converter,
+                              InputState* input_state)
+{
+    furi_check(input_converter);
+    furi_check(input_state);
+
+    // Process new state
+    for (size_t key_index = 0; key_index < sizeof(game_keys); ++key_index) {
+        GameKey game_key = game_keys[key_index];
+        InputKey input_key = input_keys[key_index];
+        process_pressed(input_converter, input_state, game_key, input_key);
+        process_holded(input_converter, input_state, game_key, input_key);
+        process_released(input_converter, input_state, game_key, input_key);
+    }
+}
+
+FuriStatus
+input_converter_get_event(InputConverter* input_converter, InputEvent* event)
+{
+    furi_check(input_converter);
+    return furi_message_queue_get(input_converter->queue, event, 0);
+}

+ 22 - 0
src/gui_bridge/input_converter.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+#include <input/input.h>
+
+#include "../engine/game_engine.h"
+
+typedef struct InputConverter InputConverter;
+
+InputConverter*
+input_converter_alloc(void);
+
+void
+input_converter_free(InputConverter* input_converter);
+
+void
+input_converter_process_state(InputConverter* input_converter,
+                              InputState* input_state);
+
+FuriStatus
+input_converter_get_event(InputConverter* input_converter, InputEvent* event);

+ 33 - 0
src/gui_bridge/view_i.h

@@ -0,0 +1,33 @@
+/**
+ * @file view_i.h
+ * GUI: internal View API
+ */
+
+#pragma once
+
+#include <gui/view.h>
+
+typedef struct
+{
+    FuriMutex* mutex;
+    uint8_t data[];
+} ViewModelLocking;
+
+struct View
+{
+    ViewDrawCallback draw_callback;
+    ViewInputCallback input_callback;
+    ViewCustomCallback custom_callback;
+
+    ViewModelType model_type;
+    ViewNavigationCallback previous_callback;
+    ViewCallback enter_callback;
+    ViewCallback exit_callback;
+    ViewOrientation orientation;
+
+    ViewUpdateCallback update_callback;
+    void* update_callback_context;
+
+    void* model;
+    void* context;
+};

+ 9 - 0
src/gui_bridge/view_module_descriptions.c

@@ -0,0 +1,9 @@
+#include "view_module_descriptions.h"
+
+#include "gui/modules/variable_item_list.h"
+
+const ViewModuleDescription variable_item_list_description = {
+    .alloc = (ViewModuleAllocCallback)variable_item_list_alloc,
+    .free = (ViewModuleFreeCallback)variable_item_list_free,
+    .get_view = (ViewModuleGetViewCallback)variable_item_list_get_view,
+};

+ 16 - 0
src/gui_bridge/view_module_descriptions.h

@@ -0,0 +1,16 @@
+#pragma once
+
+typedef struct View View;
+
+typedef void* (*ViewModuleAllocCallback)(void);
+typedef void (*ViewModuleFreeCallback)(void* view_module);
+typedef View* (*ViewModuleGetViewCallback)(void* view_module);
+
+typedef struct
+{
+    ViewModuleAllocCallback alloc;
+    ViewModuleFreeCallback free;
+    ViewModuleGetViewCallback get_view;
+} ViewModuleDescription;
+
+extern const ViewModuleDescription variable_item_list_description;

+ 125 - 0
src/gui_bridge/view_module_entity.c

@@ -0,0 +1,125 @@
+#include "view_module_entity.h"
+
+#include "../engine/game_manager.h"
+
+#include "input_converter.h"
+#include "view_i.h" // IWYU pragma: keep
+
+typedef struct
+{
+    const ViewModuleDescription* description;
+    void* view_module;
+
+    InputConverter* input_converter;
+
+    ViewModuleBackCallback back_callback;
+    void* back_callback_context;
+
+} ViewModuleContext;
+
+static void
+view_module_stop(Entity* self, GameManager* manager, void* _entity_context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+    ViewModuleContext* view_module_context = _entity_context;
+    view_module_context->description->free(view_module_context->view_module);
+    input_converter_free(view_module_context->input_converter);
+}
+
+static void
+view_module_update(Entity* self, GameManager* manager, void* _entity_context)
+{
+    UNUSED(self);
+    ViewModuleContext* entity_context = _entity_context;
+
+    InputState input = game_manager_input_get(manager);
+    input_converter_process_state(entity_context->input_converter, &input);
+
+    View* view =
+      entity_context->description->get_view(entity_context->view_module);
+
+    InputEvent event;
+    while (input_converter_get_event(entity_context->input_converter, &event) ==
+           FuriStatusOk) {
+        if (event.key == InputKeyBack &&
+            (event.type == InputTypeShort || event.type == InputTypeLong) &&
+            entity_context->back_callback != NULL) {
+            bool is_consumed = entity_context->back_callback(
+              entity_context->back_callback_context);
+
+            if (is_consumed) {
+                continue;
+            }
+        }
+        view->input_callback(&event, view->context);
+    }
+}
+
+static void
+view_module_render(Entity* self,
+                   GameManager* manager,
+                   Canvas* canvas,
+                   void* _entity_context)
+{
+    UNUSED(self);
+    UNUSED(manager);
+
+    ViewModuleContext* entity_context = _entity_context;
+    View* view =
+      entity_context->description->get_view(entity_context->view_module);
+    view->draw_callback(canvas, view_get_model(view));
+}
+
+const EntityDescription view_module_description = {
+    .start = NULL,
+    .stop = view_module_stop,
+    .update = view_module_update,
+    .render = view_module_render,
+    .collision = NULL,
+    .event = NULL,
+    .context_size = sizeof(ViewModuleContext),
+};
+
+Entity*
+view_module_add_to_level(Level* level,
+                         GameManager* manager,
+                         const ViewModuleDescription* module_description)
+{
+    UNUSED(manager);
+    furi_check(module_description);
+
+    // Alloc entity
+    Entity* entity = level_add_entity(level, &view_module_description);
+
+    // Alloc view module
+    ViewModuleContext* view_module_context = entity_context_get(entity);
+    view_module_context->description = module_description;
+    view_module_context->view_module = module_description->alloc();
+
+    // Alloc converter
+    view_module_context->input_converter = input_converter_alloc();
+
+    // Callback
+    view_module_context->back_callback = NULL;
+    view_module_context->back_callback_context = NULL;
+
+    return entity;
+}
+
+void*
+view_module_get_module(Entity* entity)
+{
+    ViewModuleContext* view_module_context = entity_context_get(entity);
+    return view_module_context->view_module;
+}
+
+void
+view_module_set_back_callback(Entity* entity,
+                              ViewModuleBackCallback back_callback,
+                              void* context)
+{
+    ViewModuleContext* view_module_context = entity_context_get(entity);
+    view_module_context->back_callback = back_callback;
+    view_module_context->back_callback_context = context;
+}

+ 21 - 0
src/gui_bridge/view_module_entity.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../engine/entity.h"
+
+#include "view_module_descriptions.h"
+
+/** Prototype for back event callback */
+typedef bool (*ViewModuleBackCallback)(void* context);
+
+Entity*
+view_module_add_to_level(Level* level,
+                         GameManager* manager,
+                         const ViewModuleDescription* module_description);
+
+void*
+view_module_get_module(Entity* entity);
+
+void
+view_module_set_back_callback(Entity* entity,
+                              ViewModuleBackCallback back_callback,
+                              void* context);