From e663f027548a862c051603cf4c626001165e27f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Mon, 10 Jan 2022 17:54:41 +0100 Subject: [PATCH] Implement selection in the browser Keyboard controls are missing so far. --- fiv-browser.c | 73 ++++++++++++++++++++++++++++++++++++++++++--------- fiv.c | 31 +++++++++++++++++++--- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/fiv-browser.c b/fiv-browser.c index e184fa8..e370ad6 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -45,7 +45,7 @@ typedef struct entry Entry; typedef struct item Item; typedef struct row Row; -typedef struct _Thumbnailer { +typedef struct { FivBrowser *self; ///< Parent browser Entry *target; ///< Currently processed Entry pointer GSubprocess *minion; ///< A slave for the current queue head @@ -66,7 +66,7 @@ struct _FivBrowser { FivIoModel *model; ///< Filesystem model GArray *entries; ///< []Entry GArray *layouted_rows; ///< []Row - int selected; + const Entry *selected; ///< Selected entry or NULL Thumbnailer *thumbnailers; ///< Parallelized thumbnailers size_t thumbnailers_len; ///< Thumbnailers array size @@ -244,7 +244,7 @@ static const Entry * entry_at(FivBrowser *self, int x, int y) { if (self->vadjustment) - y += gtk_adjustment_get_value(self->vadjustment); + y += round(gtk_adjustment_get_value(self->vadjustment)); for (guint i = 0; i < self->layouted_rows->len; i++) { const Row *row = &g_array_index(self->layouted_rows, Row, i); @@ -267,21 +267,25 @@ draw_row(FivBrowser *self, cairo_t *cr, const Row *row) gtk_style_context_save(style); gtk_style_context_add_class(style, "item"); - GdkRGBA glow_color = {}; - GtkStateFlags state = gtk_style_context_get_state (style); - gtk_style_context_get_color(style, state, &glow_color); - GtkBorder border; - gtk_style_context_get_border(style, state, &border); + GtkStateFlags common_state = gtk_style_context_get_state(style); + gtk_style_context_get_border(style, common_state, &border); for (Item *item = row->items; item->entry; item++) { cairo_save(cr); GdkRectangle extents = item_extents(self, item, row); cairo_translate(cr, extents.x - border.left, extents.y - border.top); + GtkStateFlags state = common_state; + if (item->entry == self->selected) + state |= GTK_STATE_FLAG_SELECTED; + gtk_style_context_save(style); + gtk_style_context_set_state(style, state); if (item->entry->icon) { gtk_style_context_add_class(style, "symbolic"); } else { + GdkRGBA glow_color = {}; + gtk_style_context_get_color(style, state, &glow_color); gdk_cairo_set_source_rgba(cr, &glow_color); draw_outer_border(self, cr, border.left + extents.width + border.right, @@ -309,6 +313,9 @@ draw_row(FivBrowser *self, cairo_t *cr, const Row *row) cairo_set_source_surface( cr, item->entry->thumbnail, border.left, border.top); cairo_paint(cr); + + // Here, we could consider multiplying + // the whole rectangle with the selection color. } cairo_restore(cr); @@ -1010,7 +1017,7 @@ fiv_browser_draw(GtkWidget *widget, cairo_t *cr) // TODO(p): self->hadjustment as well, and test it. if (self->vadjustment) { - gdouble y = gtk_adjustment_get_value(self->vadjustment); + gdouble y = round(gtk_adjustment_get_value(self->vadjustment)); cairo_translate(cr, 0, -y); } @@ -1057,8 +1064,21 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event) gtk_widget_grab_focus(widget); const Entry *entry = entry_at(self, event->x, event->y); - if (!entry && event->button == GDK_BUTTON_SECONDARY) { - show_context_menu(widget, fiv_io_model_get_location(self->model)); + if (!entry && state == 0) { + switch (event->button) { + case GDK_BUTTON_PRIMARY: + break; + case GDK_BUTTON_SECONDARY: + show_context_menu(widget, fiv_io_model_get_location(self->model)); + break; + default: + return FALSE; + } + + if (self->selected) { + self->selected = NULL; + gtk_widget_queue_draw(widget); + } return TRUE; } if (!entry) @@ -1076,6 +1096,9 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event) return open_entry(widget, entry, TRUE); return FALSE; case GDK_BUTTON_SECONDARY: + self->selected = entry; + gtk_widget_queue_draw(widget); + // On X11, after closing the menu, the pointer otherwise remains, // no matter what its new location is. gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); @@ -1127,6 +1150,18 @@ fiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event) } } +static gboolean +fiv_browser_key_press_event(GtkWidget *widget, GdkEventKey *event) +{ + FivBrowser *self = FIV_BROWSER(widget); + if (!(event->state & gtk_accelerator_get_default_mod_mask()) && + event->keyval == GDK_KEY_Return && self->selected) + return open_entry(widget, self->selected, FALSE); + + return GTK_WIDGET_CLASS(fiv_browser_parent_class) + ->key_press_event(widget, event); +} + static gboolean fiv_browser_query_tooltip(GtkWidget *widget, gint x, gint y, G_GNUC_UNUSED gboolean keyboard_tooltip, GtkTooltip *tooltip) @@ -1253,6 +1288,7 @@ fiv_browser_class_init(FivBrowserClass *klass) widget_class->button_press_event = fiv_browser_button_press_event; widget_class->motion_notify_event = fiv_browser_motion_notify_event; widget_class->scroll_event = fiv_browser_scroll_event; + widget_class->key_press_event = fiv_browser_key_press_event; widget_class->query_tooltip = fiv_browser_query_tooltip; widget_class->style_updated = fiv_browser_style_updated; @@ -1277,7 +1313,6 @@ fiv_browser_init(FivBrowser *self) g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free); self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row)); g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free); - self->selected = -1; self->thumbnailers_len = g_get_num_processors(); self->thumbnailers = @@ -1299,6 +1334,13 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self) { g_return_if_fail(model == self->model); + int selected = -1; + gchar *selected_uri = NULL; + if (self->selected) { + selected_uri = g_strdup(self->selected->uri); + self->selected = NULL; + } + // TODO(p): Later implement arguments. thumbnailers_abort(self); g_array_set_size(self->entries, 0); @@ -1308,10 +1350,17 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self) for (guint i = 0; i < files->len; i++) { g_array_append_val(self->entries, ((Entry) {.thumbnail = NULL, .uri = files->pdata[i]})); + if (!g_strcmp0(selected_uri, files->pdata[i])) + selected = i; files->pdata[i] = NULL; } g_ptr_array_free(files, TRUE); + // Beware that the pointer may shift with the storage. + g_free(selected_uri); + if (selected >= 0) + self->selected = &g_array_index(self->entries, Entry, selected); + reload_thumbnails(self); thumbnailers_start(self); } diff --git a/fiv.c b/fiv.c index c326fdf..0c62c11 100644 --- a/fiv.c +++ b/fiv.c @@ -567,7 +567,7 @@ switch_to_browser(void) { set_window_title(g.directory); gtk_stack_set_visible_child(GTK_STACK(g.stack), g.browser_paned); - gtk_widget_grab_focus(g.browser_scroller); + gtk_widget_grab_focus(g.browser); } static void @@ -1567,6 +1567,8 @@ make_browser_sidebar(FivIoModel *model) // thus resolving the problem using overlaps. // We're trying to be universal for light and dark themes both. It's hard. static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \ + @define-color fiv-semiselected \ + mix(@theme_selected_bg_color, @content_view_bg, 0.5); \ fiv-view, fiv-browser { background: @content_view_bg; } \ placessidebar.fiv .toolbar { padding: 2px 6px; } \ placessidebar.fiv box > separator { margin: 4px 0; } \ @@ -1579,6 +1581,7 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \ } \ fiv-browser { padding: 5px; } \ fiv-browser.item { \ + /* For non-symbolic, color is applied to the glowing margin. */ \ color: mix(#000, @content_view_bg, 0.625); margin: 8px; \ border: 2px solid #fff; \ } \ @@ -1591,14 +1594,36 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \ background-size: 40px 40px; \ background-position: 0 0, 0 20px, 20px -20px, -20px 0px; \ } \ - fiv-browser.item:backdrop { \ + fiv-browser.item:selected { \ + color: @theme_selected_bg_color; \ + border-color: @theme_selected_bg_color; \ + } \ + fiv-browser.item:selected:not(:focus) { \ + color: @fiv-semiselected; \ + border-color: @fiv-semiselected; \ + } \ + fiv-browser.item:backdrop:not(:selected) { \ color: mix(#000, @content_view_bg, 0.875); \ border-color: mix(#fff, @content_view_bg, 0.5); \ } \ + fiv-browser.item.symbolic, \ + fiv-browser.item.symbolic:selected, \ + fiv-browser.item.symbolic:backdrop { \ + color: shade(@theme_bg_color, 0.875); \ + border-color: transparent; \ + } \ fiv-browser.item.symbolic { \ - border-color: transparent; color: shade(@theme_bg_color, 0.875); \ + background-blend-mode: color; \ background: @theme_bg_color; background-image: none; \ } \ + fiv-browser.item.symbolic:selected { \ + color: @theme_selected_bg_color; background-image: linear-gradient(0, \ + @theme_selected_bg_color, @theme_selected_bg_color); \ + } \ + fiv-browser.item.symbolic:selected:not(:focus) { \ + color: @fiv-semiselected; background-image: linear-gradient(0, \ + @fiv-semiselected, @fiv-semiselected); \ + } \ .fiv-information label { padding: 0 4px; }"; int