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

Gui: scrollable long file names in FileBrowser and Archive Browser (#2159)

* Gui: scrollable long file names in FileBrowser
* Archive: scroll long file names
* Gui: elements code cleanup
あく 3 лет назад
Родитель
Сommit
e7107e39f7

+ 48 - 4
applications/main/archive/views/archive_browser_view.c

@@ -5,6 +5,9 @@
 #include "archive_browser_view.h"
 #include "../helpers/archive_browser.h"
 
+#define SCROLL_INTERVAL (333)
+#define SCROLL_DELAY (2)
+
 static const char* ArchiveTabNames[] = {
     [ArchiveTabFavorites] = "Favorites",
     [ArchiveTabIButton] = "iButton",
@@ -146,13 +149,18 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
             furi_string_set(str_buf, "---");
         }
 
-        elements_string_fit_width(
-            canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset);
+        size_t scroll_counter = model->scroll_counter;
 
         if(model->item_idx == idx) {
             archive_draw_frame(canvas, i, scrollbar, model->move_fav);
+            if(scroll_counter < SCROLL_DELAY) {
+                scroll_counter = 0;
+            } else {
+                scroll_counter -= SCROLL_DELAY;
+            }
         } else {
             canvas_set_color(canvas, ColorBlack);
+            scroll_counter = 0;
         }
 
         if(custom_icon_data) {
@@ -162,8 +170,15 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
             canvas_draw_icon(
                 canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]);
         }
-        canvas_draw_str(
-            canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf));
+
+        elements_scrollable_text_line(
+            canvas,
+            15 + x_offset,
+            24 + i * FRAME_HEIGHT,
+            ((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset),
+            str_buf,
+            scroll_counter,
+            (model->item_idx != idx));
 
         furi_string_free(str_buf);
     }
@@ -329,6 +344,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
                         if(move_fav_mode) {
                             browser->callback(ArchiveBrowserEventFavMoveUp, browser->context);
                         }
+                        model->scroll_counter = 0;
                     } else if(event->key == InputKeyDown) {
                         model->item_idx = (model->item_idx + 1) % model->item_cnt;
                         if(is_file_list_load_required(model)) {
@@ -338,6 +354,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
                         if(move_fav_mode) {
                             browser->callback(ArchiveBrowserEventFavMoveDown, browser->context);
                         }
+                        model->scroll_counter = 0;
                     }
                 },
                 true);
@@ -377,6 +394,27 @@ static bool archive_view_input(InputEvent* event, void* context) {
     return true;
 }
 
+static void browser_scroll_timer(void* context) {
+    furi_assert(context);
+    ArchiveBrowserView* browser = context;
+    with_view_model(
+        browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter++; }, true);
+}
+
+static void browser_view_enter(void* context) {
+    furi_assert(context);
+    ArchiveBrowserView* browser = context;
+    with_view_model(
+        browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter = 0; }, true);
+    furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
+}
+
+static void browser_view_exit(void* context) {
+    furi_assert(context);
+    ArchiveBrowserView* browser = context;
+    furi_timer_stop(browser->scroll_timer);
+}
+
 ArchiveBrowserView* browser_alloc() {
     ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView));
     browser->view = view_alloc();
@@ -384,6 +422,10 @@ ArchiveBrowserView* browser_alloc() {
     view_set_context(browser->view, browser);
     view_set_draw_callback(browser->view, archive_view_render);
     view_set_input_callback(browser->view, archive_view_input);
+    view_set_enter_callback(browser->view, browser_view_enter);
+    view_set_exit_callback(browser->view, browser_view_exit);
+
+    browser->scroll_timer = furi_timer_alloc(browser_scroll_timer, FuriTimerTypePeriodic, browser);
 
     browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT));
 
@@ -402,6 +444,8 @@ ArchiveBrowserView* browser_alloc() {
 void browser_free(ArchiveBrowserView* browser) {
     furi_assert(browser);
 
+    furi_timer_free(browser->scroll_timer);
+
     if(browser->worker_running) {
         file_browser_worker_free(browser->worker);
     }

+ 2 - 0
applications/main/archive/views/archive_browser_view.h

@@ -81,6 +81,7 @@ struct ArchiveBrowserView {
     FuriString* path;
     InputKey last_tab_switch_dir;
     bool is_root;
+    FuriTimer* scroll_timer;
 };
 
 typedef struct {
@@ -97,6 +98,7 @@ typedef struct {
     int32_t item_idx;
     int32_t array_offset;
     int32_t list_offset;
+    size_t scroll_counter;
 } ArchiveBrowserViewModel;
 
 void archive_browser_set_callback(

+ 8 - 0
applications/services/dialogs/view_holder.c

@@ -50,6 +50,10 @@ void view_holder_free(ViewHolder* view_holder) {
 void view_holder_set_view(ViewHolder* view_holder, View* view) {
     furi_assert(view_holder);
     if(view_holder->view) {
+        if(view_holder->view->exit_callback) {
+            view_holder->view->exit_callback(view_holder->view->context);
+        }
+
         view_set_update_callback(view_holder->view, NULL);
         view_set_update_callback_context(view_holder->view, NULL);
     }
@@ -59,6 +63,10 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
     if(view_holder->view) {
         view_set_update_callback(view_holder->view, view_holder_update);
         view_set_update_callback_context(view_holder->view, view_holder);
+
+        if(view_holder->view->enter_callback) {
+            view_holder->view->enter_callback(view_holder->view->context);
+        }
     }
 }
 

+ 46 - 0
applications/services/gui/elements.c

@@ -547,6 +547,52 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width
     }
 }
 
+void elements_scrollable_text_line(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    FuriString* string,
+    size_t scroll,
+    bool ellipsis) {
+    FuriString* line = furi_string_alloc_set(string);
+
+    size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
+    if(len_px > width) {
+        if(ellipsis) {
+            width -= canvas_string_width(canvas, "...");
+        }
+
+        // Calculate scroll size
+        size_t scroll_size = furi_string_size(string);
+        size_t right_width = 0;
+        for(size_t i = scroll_size; i > 0; i--) {
+            right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i));
+            if(right_width > width) break;
+            scroll_size--;
+            if(!scroll_size) break;
+        }
+        // Ensure that we have something to scroll
+        if(scroll_size) {
+            scroll_size += 3;
+            scroll = scroll % scroll_size;
+            furi_string_right(line, scroll);
+        }
+
+        do {
+            furi_string_left(line, furi_string_size(line) - 1);
+            len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
+        } while(len_px > width);
+
+        if(ellipsis) {
+            furi_string_cat(line, "...");
+        }
+    }
+
+    canvas_draw_str(canvas, x, y, furi_string_get_cstr(line));
+    furi_string_free(line);
+}
+
 void elements_text_box(
     Canvas* canvas,
     uint8_t x,

+ 19 - 0
applications/services/gui/elements.h

@@ -192,6 +192,25 @@ void elements_bubble_str(
  */
 void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width);
 
+/** Draw scrollable text line
+ *
+ * @param      canvas    The canvas
+ * @param[in]  x         X coordinate
+ * @param[in]  y         Y coordinate
+ * @param[in]  width     The width
+ * @param      string    The string
+ * @param[in]  scroll    The scroll counter: 0 - no scroll, any other number - scroll. Just count up, everything else will be calculated on the inside.
+ * @param[in]  ellipsis  The ellipsis flag: true to add ellipse
+ */
+void elements_scrollable_text_line(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    FuriString* string,
+    size_t scroll,
+    bool ellipsis);
+
 /** Draw text box element
  *
  * @param       canvas          Canvas instance

+ 50 - 5
applications/services/gui/modules/file_browser.c

@@ -19,6 +19,9 @@
 
 #define CUSTOM_ICON_MAX_SIZE 32
 
+#define SCROLL_INTERVAL (333)
+#define SCROLL_DELAY (2)
+
 typedef enum {
     BrowserItemTypeLoading,
     BrowserItemTypeBack,
@@ -95,6 +98,7 @@ struct FileBrowser {
     void* item_context;
 
     FuriString* result_path;
+    FuriTimer* scroll_timer;
 };
 
 typedef struct {
@@ -110,6 +114,7 @@ typedef struct {
 
     const Icon* file_icon;
     bool hide_ext;
+    size_t scroll_counter;
 } FileBrowserModel;
 
 static const Icon* BrowserItemIcons[] = {
@@ -129,6 +134,27 @@ static void
     browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last);
 static void browser_long_load_cb(void* context);
 
+static void file_browser_scroll_timer_callback(void* context) {
+    furi_assert(context);
+    FileBrowser* browser = context;
+    with_view_model(
+        browser->view, FileBrowserModel * model, { model->scroll_counter++; }, true);
+}
+
+static void file_browser_view_enter_callback(void* context) {
+    furi_assert(context);
+    FileBrowser* browser = context;
+    with_view_model(
+        browser->view, FileBrowserModel * model, { model->scroll_counter = 0; }, true);
+    furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
+}
+
+static void file_browser_view_exit_callback(void* context) {
+    furi_assert(context);
+    FileBrowser* browser = context;
+    furi_timer_stop(browser->scroll_timer);
+}
+
 FileBrowser* file_browser_alloc(FuriString* result_path) {
     furi_assert(result_path);
     FileBrowser* browser = malloc(sizeof(FileBrowser));
@@ -137,6 +163,11 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
     view_set_context(browser->view, browser);
     view_set_draw_callback(browser->view, file_browser_view_draw_callback);
     view_set_input_callback(browser->view, file_browser_view_input_callback);
+    view_set_enter_callback(browser->view, file_browser_view_enter_callback);
+    view_set_exit_callback(browser->view, file_browser_view_exit_callback);
+
+    browser->scroll_timer =
+        furi_timer_alloc(file_browser_scroll_timer_callback, FuriTimerTypePeriodic, browser);
 
     browser->result_path = result_path;
 
@@ -149,6 +180,8 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
 void file_browser_free(FileBrowser* browser) {
     furi_assert(browser);
 
+    furi_timer_free(browser->scroll_timer);
+
     with_view_model(
         browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false);
 
@@ -468,13 +501,17 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
             furi_string_set(filename, ". .");
         }
 
-        elements_string_fit_width(
-            canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX));
-
+        size_t scroll_counter = model->scroll_counter;
         if(model->item_idx == idx) {
             browser_draw_frame(canvas, i, show_scrollbar);
+            if(scroll_counter < SCROLL_DELAY) {
+                scroll_counter = 0;
+            } else {
+                scroll_counter -= SCROLL_DELAY;
+            }
         } else {
             canvas_set_color(canvas, ColorBlack);
+            scroll_counter = 0;
         }
 
         if(custom_icon_data) {
@@ -487,8 +524,14 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
             canvas_draw_icon(
                 canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]);
         }
-        canvas_draw_str(
-            canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, furi_string_get_cstr(filename));
+        elements_scrollable_text_line(
+            canvas,
+            15,
+            Y_OFFSET + 9 + i * FRAME_HEIGHT,
+            (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX),
+            filename,
+            scroll_counter,
+            (model->item_idx != idx));
     }
 
     if(show_scrollbar) {
@@ -543,6 +586,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
                             file_browser_worker_load(
                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX);
                         }
+                        model->scroll_counter = 0;
                     } else if(event->key == InputKeyDown) {
                         model->item_idx = (model->item_idx + 1) % model->item_cnt;
                         if(browser_is_list_load_required(model)) {
@@ -554,6 +598,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
                             file_browser_worker_load(
                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX);
                         }
+                        model->scroll_counter = 0;
                     }
                 },
                 true);

+ 1 - 0
firmware/targets/f7/api_symbols.csv

@@ -752,6 +752,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*"
 Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*"
 Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*"
 Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float"
+Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool"
 Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t"
 Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t"
 Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"