Implement browser keyboard navigation
This commit is contained in:
parent
764312652d
commit
f1e9e47e13
174
fiv-browser.c
174
fiv-browser.c
@ -103,6 +103,7 @@ struct item {
|
||||
|
||||
struct row {
|
||||
Item *items; ///< Ends with a NULL entry
|
||||
gsize len; ///< Length of items
|
||||
int x_offset; ///< Start position outside borders
|
||||
int y_offset; ///< Start position inside borders
|
||||
};
|
||||
@ -122,11 +123,9 @@ append_row(FivBrowser *self, int *y, int x, GArray *items_array)
|
||||
*y += self->item_spacing;
|
||||
|
||||
*y += self->item_border_y;
|
||||
g_array_append_val(self->layouted_rows, ((Row) {
|
||||
.items = g_array_steal(items_array, NULL),
|
||||
.x_offset = x,
|
||||
.y_offset = *y,
|
||||
}));
|
||||
Row row = {.x_offset = x, .y_offset = *y};
|
||||
row.items = g_array_steal(items_array, &row.len),
|
||||
g_array_append_val(self->layouted_rows, row);
|
||||
|
||||
// Not trying to pack them vertically, but this would be the place to do it.
|
||||
*y += self->item_height;
|
||||
@ -1174,13 +1173,172 @@ fiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
select_closest(FivBrowser *self, const Row *row, int target)
|
||||
{
|
||||
int closest = G_MAXINT;
|
||||
for (guint i = 0; i < row->len; i++) {
|
||||
GdkRectangle extents = item_extents(self, row->items + i, row);
|
||||
int distance = ABS(extents.x + extents.width / 2 - target);
|
||||
if (distance > closest)
|
||||
break;
|
||||
self->selected = row->items[i].entry;
|
||||
closest = distance;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
scroll_to_row(FivBrowser *self, const Row *row)
|
||||
{
|
||||
if (!self->vadjustment)
|
||||
return;
|
||||
|
||||
double y1 = gtk_adjustment_get_value(self->vadjustment);
|
||||
double ph = gtk_adjustment_get_page_size(self->vadjustment);
|
||||
if (row->y_offset < y1) {
|
||||
gtk_adjustment_set_value(
|
||||
self->vadjustment, row->y_offset - self->item_border_y);
|
||||
} else if (row->y_offset + self->item_height > y1 + ph) {
|
||||
gtk_adjustment_set_value(self->vadjustment,
|
||||
row->y_offset - ph + self->item_height + self->item_border_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
move_selection(FivBrowser *self, GtkDirectionType dir)
|
||||
{
|
||||
GtkWidget *widget = GTK_WIDGET(self);
|
||||
if (!self->layouted_rows->len)
|
||||
return;
|
||||
|
||||
const Row *selected_row = NULL;
|
||||
if (!self->selected) {
|
||||
switch (dir) {
|
||||
case GTK_DIR_RIGHT:
|
||||
case GTK_DIR_DOWN:
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, 0);
|
||||
self->selected = selected_row->items->entry;
|
||||
goto adjust;
|
||||
case GTK_DIR_LEFT:
|
||||
case GTK_DIR_UP:
|
||||
selected_row = &g_array_index(
|
||||
self->layouted_rows, Row, self->layouted_rows->len - 1);
|
||||
self->selected = selected_row->items[selected_row->len - 1].entry;
|
||||
goto adjust;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
gsize x = 0, y = 0;
|
||||
int target_offset = 0;
|
||||
for (y = 0; y < self->layouted_rows->len; y++) {
|
||||
const Row *row = &g_array_index(self->layouted_rows, Row, y);
|
||||
for (x = 0; x < row->len; x++) {
|
||||
const Item *item = row->items + x;
|
||||
if (item->entry == self->selected) {
|
||||
GdkRectangle extents = item_extents(self, item, row);
|
||||
target_offset = extents.x + extents.width / 2;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
found:
|
||||
g_return_if_fail(y < self->layouted_rows->len);
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, y);
|
||||
|
||||
switch (dir) {
|
||||
case GTK_DIR_LEFT:
|
||||
if (x > 0) {
|
||||
self->selected = selected_row->items[--x].entry;
|
||||
} else if (y-- > 0) {
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, y);
|
||||
self->selected = selected_row->items[selected_row->len - 1].entry;
|
||||
}
|
||||
break;
|
||||
case GTK_DIR_RIGHT:
|
||||
if (++x < selected_row->len) {
|
||||
self->selected = selected_row->items[x].entry;
|
||||
} else if (++y < self->layouted_rows->len) {
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, y);
|
||||
self->selected = selected_row->items[0].entry;
|
||||
}
|
||||
break;
|
||||
case GTK_DIR_UP:
|
||||
if (y-- > 0) {
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, y);
|
||||
select_closest(self, selected_row, target_offset);
|
||||
}
|
||||
break;
|
||||
case GTK_DIR_DOWN:
|
||||
if (++y < self->layouted_rows->len) {
|
||||
selected_row = &g_array_index(self->layouted_rows, Row, y);
|
||||
select_closest(self, selected_row, target_offset);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
adjust:
|
||||
// TODO(p): We should also do it horizontally, although we don't use it.
|
||||
scroll_to_row(self, selected_row);
|
||||
gtk_widget_queue_draw(widget);
|
||||
}
|
||||
|
||||
static void
|
||||
move_selection_home(FivBrowser *self)
|
||||
{
|
||||
if (self->layouted_rows->len) {
|
||||
const Row *row = &g_array_index(self->layouted_rows, Row, 0);
|
||||
self->selected = row->items[0].entry;
|
||||
scroll_to_row(self, row);
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
move_selection_end(FivBrowser *self)
|
||||
{
|
||||
if (self->layouted_rows->len) {
|
||||
const Row *row = &g_array_index(
|
||||
self->layouted_rows, Row, self->layouted_rows->len - 1);
|
||||
self->selected = row->items[row->len - 1].entry;
|
||||
scroll_to_row(self, row);
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (!(event->state & gtk_accelerator_get_default_mod_mask())) {
|
||||
switch (event->keyval) {
|
||||
case GDK_KEY_Return:
|
||||
if (self->selected)
|
||||
return open_entry(widget, self->selected, FALSE);
|
||||
return TRUE;
|
||||
case GDK_KEY_Left:
|
||||
move_selection(self, GTK_DIR_LEFT);
|
||||
return TRUE;
|
||||
case GDK_KEY_Right:
|
||||
move_selection(self, GTK_DIR_RIGHT);
|
||||
return TRUE;
|
||||
case GDK_KEY_Up:
|
||||
move_selection(self, GTK_DIR_UP);
|
||||
return TRUE;
|
||||
case GDK_KEY_Down:
|
||||
move_selection(self, GTK_DIR_DOWN);
|
||||
return TRUE;
|
||||
case GDK_KEY_Home:
|
||||
move_selection_home(self);
|
||||
return TRUE;
|
||||
case GDK_KEY_End:
|
||||
move_selection_end(self);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return GTK_WIDGET_CLASS(fiv_browser_parent_class)
|
||||
->key_press_event(widget, event);
|
||||
|
Loading…
Reference in New Issue
Block a user