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

+ 16 - 0
src/gui_bridge/input_converter.c

@@ -22,6 +22,7 @@ struct InputConverter
 {
     size_t holded_frames_for_key[InputKeyMAX];
     FuriMessageQueue* queue;
+    bool skip_next_input;
 };
 
 static void
@@ -140,6 +141,16 @@ input_converter_free(InputConverter* input_converter)
     free(input_converter);
 }
 
+void
+input_converter_reset(InputConverter* input_converter)
+{
+    for (InputKey i = 0; i < InputKeyMAX; ++i) {
+        input_converter->holded_frames_for_key[i] = 0;
+    }
+    furi_message_queue_reset(input_converter->queue);
+    input_converter->skip_next_input = true;
+}
+
 void
 input_converter_process_state(InputConverter* input_converter,
                               InputState* input_state)
@@ -147,6 +158,11 @@ input_converter_process_state(InputConverter* input_converter,
     furi_check(input_converter);
     furi_check(input_state);
 
+    if (input_converter->skip_next_input) {
+        input_converter->skip_next_input = false;
+        return;
+    }
+
     // Process new state
     for (size_t key_index = 0; key_index < sizeof(game_keys); ++key_index) {
         GameKey game_key = game_keys[key_index];

+ 3 - 0
src/gui_bridge/input_converter.h

@@ -14,6 +14,9 @@ input_converter_alloc(void);
 void
 input_converter_free(InputConverter* input_converter);
 
+void
+input_converter_reset(InputConverter* input_converter);
+
 void
 input_converter_process_state(InputConverter* input_converter,
                               InputState* input_state);

+ 303 - 13
src/levels/level_game/context_menu.c

@@ -2,36 +2,144 @@
 
 #include <stddef.h>
 
+#include <m-array.h>
+
 #include <furi.h>
+#include <gui/elements.h>
 #include <gui/modules/variable_item_list.h>
 
-#include "../../engine/game_manager.h"
-#include "../../gui_bridge/input_converter.h"
+#include "src/engine/game_manager.h"
+#include "src/game.h"
+#include "src/gui_bridge/input_converter.h"
+#include "src/levels/level_game/level_game.h"
+
+#define ITEM_TEXT_PADDING_X 6
+#define ITEM_TEXT_PADDING_Y 4
+#define ITEM_SELECTION_PADDING 1
+#define ITEMS_ON_SCREEN 2
+// BOX_WIDTH - BOX_PADDING_X * 2 - SCROLLBAR_WIDTH - BOX_SPACING;
+#define ITEM_WIDTH 70
+#define ITEM_HEIGHT 16
+
+#define SCROLLBAR_WIDTH 3
+
+#define BOX_PADDING_X 4
+#define BOX_PADDING_Y 3
+#define BOX_SPACING 3
+
+#define BOX_WIDTH 84
+#define BOX_HEIGHT 38 // (ITEM_HEIGHT * ITEMS_ON_SCREEN + BOX_PADDING_Y * 2)
+#define BOX_X 22      // (SCREEN_WIDTH - BOX_WIDTH) / 2)
+#define BOX_Y 13      // (SCREEN_HEIGHT - BOX_HEIGHT) / 2)
+#define BOX_RADIUS 4
+#define BOX_OFFSET_X 26 // BOX_X + BOX_PADDING_X;
+#define BOX_OFFSET_Y 16 // BOX_Y + BOX_PADDING_Y;
+
+typedef struct
+{
+    FuriString* label;
+    uint32_t index;
+    ContextMenuItemCallback callback;
+    void* callback_context;
+} ContextMenuItem;
+
+static void
+ContextMenuItem_init(ContextMenuItem* item)
+{
+    item->label = furi_string_alloc();
+    item->index = 0;
+    item->callback = NULL;
+    item->callback_context = NULL;
+}
+
+static void
+ContextMenuItem_init_set(ContextMenuItem* item, const ContextMenuItem* src)
+{
+    item->label = furi_string_alloc_set(src->label);
+    item->index = src->index;
+    item->callback = src->callback;
+    item->callback_context = src->callback_context;
+}
 
-// #include "../../game.h"
-// #include "../../game_settings.h"
+static void
+ContextMenuItem_set(ContextMenuItem* item, const ContextMenuItem* src)
+{
+    furi_string_set(item->label, src->label);
+    item->index = src->index;
+    item->callback = src->callback;
+    item->callback_context = src->callback_context;
+}
+
+static void
+ContextMenuItem_clear(ContextMenuItem* item)
+{
+    furi_string_free(item->label);
+}
+
+ARRAY_DEF(ContextMenuItemArray,
+          ContextMenuItem,
+          (INIT(API_2(ContextMenuItem_init)),
+           SET(API_6(ContextMenuItem_set)),
+           INIT_SET(API_6(ContextMenuItem_init_set)),
+           CLEAR(API_2(ContextMenuItem_clear))))
 
 typedef struct
 {
     InputConverter* input_converter;
 
+    ContextMenuItemArray_t items;
+    size_t position;
+    size_t window_position;
+
     ContextMenuBackCallback back_callback;
     void* back_callback_context;
 
-    size_t current_index;
-
 } ContextMenuContext;
 
+void
+context_menu_add_item(Entity* self,
+                      const char* label,
+                      uint32_t index,
+                      ContextMenuItemCallback callback,
+                      void* callback_context)
+{
+    furi_check(self);
+    furi_check(label);
+
+    ContextMenuContext* entity_context = entity_context_get(self);
+
+    ContextMenuItem* item;
+    item = ContextMenuItemArray_push_new(entity_context->items);
+    furi_string_set_str(item->label, label);
+    item->index = index;
+    item->callback = callback;
+    item->callback_context = callback_context;
+}
+
 void
 context_menu_back_callback_set(Entity* self,
                                ContextMenuBackCallback back_callback,
                                void* callback_context)
 {
+    furi_check(self);
+    furi_check(back_callback);
+
     ContextMenuContext* entity_context = entity_context_get(self);
     entity_context->back_callback = back_callback;
     entity_context->back_callback_context = callback_context;
 }
 
+void
+context_menu_reset_state(Entity* self)
+{
+    furi_check(self);
+
+    ContextMenuContext* entity_context = entity_context_get(self);
+    input_converter_reset(entity_context->input_converter);
+    entity_context->position = 0;
+    entity_context->window_position = 0;
+}
+
 static void
 context_menu_start(Entity* self, GameManager* manager, void* _entity_context)
 {
@@ -43,6 +151,11 @@ context_menu_start(Entity* self, GameManager* manager, void* _entity_context)
     // Alloc converter
     entity_context->input_converter = input_converter_alloc();
 
+    // Menu
+    ContextMenuItemArray_init(entity_context->items);
+    entity_context->position = 0;
+    entity_context->window_position = 0;
+
     // Callback
     entity_context->back_callback = NULL;
     entity_context->back_callback_context = NULL;
@@ -55,13 +168,113 @@ context_menu_stop(Entity* self, GameManager* manager, void* _entity_context)
     UNUSED(manager);
 
     ContextMenuContext* entity_context = _entity_context;
+    ContextMenuItemArray_clear(entity_context->items);
     input_converter_free(entity_context->input_converter);
 }
 
+static void
+context_menu_process_up(ContextMenuContext* context_menu)
+{
+    const size_t items_size = ContextMenuItemArray_size(context_menu->items);
+
+    if (context_menu->position == 0) {
+        context_menu->position = items_size - 1;
+        if (context_menu->position > ITEMS_ON_SCREEN - 1) {
+            context_menu->window_position =
+              context_menu->position - (ITEMS_ON_SCREEN - 1);
+        }
+    } else {
+        context_menu->position--;
+        if (context_menu->position < context_menu->window_position) {
+            context_menu->window_position--;
+        }
+    }
+}
+
+static void
+context_menu_process_down(ContextMenuContext* context_menu)
+{
+    const size_t items_size = ContextMenuItemArray_size(context_menu->items);
+
+    if (context_menu->position == items_size - 1) {
+        context_menu->position = 0;
+        context_menu->window_position = 0;
+    } else {
+        context_menu->position++;
+
+        if ((context_menu->position - context_menu->window_position ==
+             ITEMS_ON_SCREEN) &&
+            (context_menu->window_position < items_size - ITEMS_ON_SCREEN)) {
+            context_menu->window_position++;
+        }
+    }
+}
+
+static void
+context_menu_process_ok(ContextMenuContext* context_menu)
+{
+    ContextMenuItem* item = NULL;
+
+    const size_t items_size = ContextMenuItemArray_size(context_menu->items);
+    if (context_menu->position < items_size) {
+        item =
+          ContextMenuItemArray_get(context_menu->items, context_menu->position);
+    }
+
+    if (item && item->callback) {
+        item->callback(item->callback_context, item->index);
+    }
+}
+
+static bool
+context_menu_input_process(ContextMenuContext* context_menu, InputEvent* event)
+{
+    furi_assert(context_menu);
+    furi_assert(event);
+
+    bool consumed = false;
+
+    if (event->type == InputTypeShort) {
+        switch (event->key) {
+            case InputKeyUp:
+                consumed = true;
+                context_menu_process_up(context_menu);
+                break;
+            case InputKeyDown:
+                consumed = true;
+                context_menu_process_down(context_menu);
+                break;
+            case InputKeyOk:
+                consumed = true;
+                context_menu_process_ok(context_menu);
+                break;
+            default:
+                break;
+        }
+    } else if (event->type == InputTypeRepeat) {
+        if (event->key == InputKeyUp) {
+            consumed = true;
+            context_menu_process_up(context_menu);
+        } else if (event->key == InputKeyDown) {
+            consumed = true;
+            context_menu_process_down(context_menu);
+        }
+    }
+
+    return consumed;
+}
+
 static void
 context_menu_update(Entity* self, GameManager* manager, void* _entity_context)
 {
     UNUSED(self);
+
+    Level* level = game_manager_current_level_get(manager);
+    GameLevelContext* level_context = level_context_get(level);
+    if (!level_context->is_paused) {
+        return;
+    }
+
     ContextMenuContext* entity_context = _entity_context;
 
     InputState input = game_manager_input_get(manager);
@@ -76,20 +289,88 @@ context_menu_update(Entity* self, GameManager* manager, void* _entity_context)
               entity_context->back_callback_context);
             return;
         }
+        context_menu_input_process(entity_context, &event);
     }
 }
 
 static void
 gray_canvas(Canvas* const canvas)
 {
-    // canvas_set_color(canvas, ColorWhite);
-    canvas_clear(canvas);
-    for (size_t x = 0; x < 128; x += 2) {
-        for (size_t y = 0; y < 64; y++) {
+    canvas_set_color(canvas, ColorWhite);
+    for (size_t x = 0; x < SCREEN_WIDTH; x += 2) {
+        for (size_t y = 0; y < SCREEN_HEIGHT; y++) {
             canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y);
         }
     }
-    // canvas_set_color(canvas, ColorBlack);
+    canvas_set_color(canvas, ColorBlack);
+}
+
+static void
+draw_box(Canvas* const canvas)
+{
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_rbox(canvas, BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT, BOX_RADIUS);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_rframe(canvas, BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT, BOX_RADIUS);
+}
+
+static void
+draw_items(Canvas* const canvas, ContextMenuContext* menu_context)
+{
+    canvas_set_font(canvas, FontSecondary);
+
+    size_t index = 0;
+    ContextMenuItemArray_it_t it;
+    for (ContextMenuItemArray_it(it, menu_context->items);
+         !ContextMenuItemArray_end_p(it);
+         ContextMenuItemArray_next(it)) {
+
+        const size_t item_position = index - menu_context->window_position;
+
+        if (item_position < ITEMS_ON_SCREEN) {
+            if (index == menu_context->position) {
+                canvas_set_color(canvas, ColorBlack);
+                elements_slightly_rounded_box(
+                  canvas,
+                  BOX_OFFSET_X,
+                  BOX_OFFSET_Y + (item_position * ITEM_HEIGHT) + 1,
+                  ITEM_WIDTH,
+                  ITEM_HEIGHT - 2);
+                canvas_set_color(canvas, ColorWhite);
+            } else {
+                canvas_set_color(canvas, ColorBlack);
+            }
+
+            FuriString* display_str;
+            display_str =
+              furi_string_alloc_set(ContextMenuItemArray_cref(it)->label);
+
+            elements_string_fit_width(
+              canvas, display_str, ITEM_WIDTH - (ITEM_TEXT_PADDING_X * 2));
+
+            canvas_draw_str(canvas,
+                            BOX_OFFSET_X + ITEM_TEXT_PADDING_X,
+                            BOX_OFFSET_Y + (item_position * ITEM_HEIGHT) +
+                              ITEM_HEIGHT - ITEM_TEXT_PADDING_Y,
+                            furi_string_get_cstr(display_str));
+
+            furi_string_free(display_str);
+        }
+
+        ++index;
+    }
+}
+
+static void
+draw_scrollbar(Canvas* const canvas, ContextMenuContext* menu_context)
+{
+    elements_scrollbar_pos(canvas,
+                           BOX_X + BOX_WIDTH - BOX_PADDING_X,
+                           BOX_Y + BOX_PADDING_Y + ITEM_SELECTION_PADDING,
+                           BOX_HEIGHT - BOX_PADDING_Y * 2 -
+                             ITEM_SELECTION_PADDING * 2,
+                           menu_context->position,
+                           ContextMenuItemArray_size(menu_context->items));
 }
 
 static void
@@ -99,10 +380,19 @@ context_menu_render(Entity* self,
                     void* _entity_context)
 {
     UNUSED(self);
-    UNUSED(manager);
-    UNUSED(_entity_context);
+
+    Level* level = game_manager_current_level_get(manager);
+    GameLevelContext* level_context = level_context_get(level);
+    if (!level_context->is_paused) {
+        return;
+    }
+
+    ContextMenuContext* menu_context = _entity_context;
 
     gray_canvas(canvas);
+    draw_box(canvas);
+    draw_items(canvas, menu_context);
+    draw_scrollbar(canvas, menu_context);
 }
 
 const EntityDescription context_menu_description = {

+ 12 - 1
src/levels/level_game/context_menu.h

@@ -1,12 +1,23 @@
 #pragma once
 
-#include "../../engine/entity.h"
+#include "src/engine/entity.h"
 
 typedef void (*ContextMenuBackCallback)(void* context);
+typedef void (*ContextMenuItemCallback)(void* context, uint32_t index);
+
+void
+context_menu_add_item(Entity* self,
+                      const char* label,
+                      uint32_t index,
+                      ContextMenuItemCallback callback,
+                      void* callback_context);
 
 void
 context_menu_back_callback_set(Entity* entity,
                                ContextMenuBackCallback back_callback,
                                void* callback_context);
 
+void
+context_menu_reset_state(Entity* entity);
+
 extern const EntityDescription context_menu_description;