Pre-layout the browser

Now the widget is scrollable.
This commit is contained in:
Přemysl Eric Janouch 2021-11-04 19:35:08 +01:00
parent a346ff8d02
commit d2ef5c9c95
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 141 additions and 41 deletions

View File

@ -22,11 +22,32 @@
#include "fastiv-io.h" #include "fastiv-io.h"
#include "fastiv-view.h" #include "fastiv-view.h"
// --- Widget ------------------------------------------------------------------
struct _FastivBrowser {
GtkWidget parent_instance;
GArray *entries; ///< [Entry]
GArray *layouted_rows; ///< [Row]
int selected;
};
typedef struct entry Entry; typedef struct entry Entry;
typedef struct item Item;
typedef struct row Row;
static const double g_row_height = 256;
static const double g_permitted_width_multiplier = 2;
// Could be split out to also-idiomatic row-spacing/column-spacing properties.
// TODO(p): Make a property for this.
static const int g_item_spacing = 5;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct entry { struct entry {
char *filename; char *filename; ///< Absolute path
cairo_surface_t *thumbnail; cairo_surface_t *thumbnail; ///< Prescaled thumbnail
}; };
static void static void
@ -37,19 +58,87 @@ entry_free(Entry *self)
cairo_surface_destroy(self->thumbnail); cairo_surface_destroy(self->thumbnail);
} }
static const double g_row_height = 256; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct item {
const Entry *entry;
int x_offset; ///< Offset within the row
};
struct row {
Item *items; ///< Ends with a NULL entry
int x_offset; ///< Start position including padding
int y_offset; ///< Start position including padding
};
static void
row_free(Row *self)
{
g_free(self->items);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
append_row(FastivBrowser *self, int *y, int x, GArray *items_array)
{
if (self->layouted_rows->len)
*y += g_item_spacing;
g_array_append_val(self->layouted_rows, ((Row) {
.items = g_array_steal(items_array, NULL),
.x_offset = x,
.y_offset = *y,
}));
// Not trying to pack them vertically, but this would be the place to do it.
*y += g_row_height;
}
static int
relayout(FastivBrowser *self, int width)
{
GtkWidget *widget = GTK_WIDGET(self);
GtkStyleContext *context = gtk_widget_get_style_context(widget);
GtkBorder padding = {};
gtk_style_context_get_padding(context, GTK_STATE_FLAG_NORMAL, &padding);
int available_width = width - padding.left - padding.right;
g_array_set_size(self->layouted_rows, 0);
GArray *items = g_array_new(TRUE, TRUE, sizeof(Item));
int x = 0, y = padding.top;
for (guint i = 0; i < self->entries->len; i++) {
const Entry *entry = &g_array_index(self->entries, Entry, i);
if (!entry->thumbnail)
continue;
int width = cairo_image_surface_get_width(entry->thumbnail);
if (!items->len) {
// Just insert it, whether or not there's any space.
} else if (x + g_item_spacing + width <= available_width) {
x += g_item_spacing;
} else {
append_row(self, &y,
padding.left + MAX(0, available_width - x) / 2, items);
x = 0;
}
g_array_append_val(items, ((Item) {.entry = entry, .x_offset = x}));
x += width;
}
if (items->len) {
append_row(self, &y,
padding.left + MAX(0, available_width - x) / 2, items);
}
g_array_free(items, TRUE);
return y + padding.bottom;
}
// --- Boilerplate ------------------------------------------------------------- // --- Boilerplate -------------------------------------------------------------
struct _FastivBrowser {
GtkWidget parent_instance;
// TODO(p): We probably want to pre-arrange everything into rows.
// - All rows are the same height.
GArray *entries;
int selected;
};
// TODO(p): For proper navigation, we need to implement GtkScrollable. // TODO(p): For proper navigation, we need to implement GtkScrollable.
G_DEFINE_TYPE_EXTENDED(FastivBrowser, fastiv_browser, GTK_TYPE_WIDGET, 0, G_DEFINE_TYPE_EXTENDED(FastivBrowser, fastiv_browser, GTK_TYPE_WIDGET, 0,
/* G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, /* G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE,
@ -80,18 +169,19 @@ static void
fastiv_browser_get_preferred_width( fastiv_browser_get_preferred_width(
GtkWidget *widget, gint *minimum, gint *natural) GtkWidget *widget, gint *minimum, gint *natural)
{ {
G_GNUC_UNUSED FastivBrowser *self = FASTIV_BROWSER(widget); GtkBorder padding = {};
// TODO(p): Set it to the width of the widget with one wide item within. GtkStyleContext *context = gtk_widget_get_style_context(widget);
*minimum = *natural = 0; gtk_style_context_get_padding(context, GTK_STATE_FLAG_NORMAL, &padding);
*minimum = *natural = g_permitted_width_multiplier * g_row_height +
padding.left + padding.right;;
} }
static void static void
fastiv_browser_get_preferred_height_for_width( fastiv_browser_get_preferred_height_for_width(
GtkWidget *widget, G_GNUC_UNUSED gint width, gint *minimum, gint *natural) GtkWidget *widget, gint width, gint *minimum, gint *natural)
{ {
G_GNUC_UNUSED FastivBrowser *self = FASTIV_BROWSER(widget); // XXX: This is rather ugly, the caller is only asking.
// TODO(p): Re-layout, figure it out. *minimum = *natural = relayout(FASTIV_BROWSER(widget), width);
*minimum = *natural = 0;
} }
static void static void
@ -125,6 +215,15 @@ fastiv_browser_realize(GtkWidget *widget)
gtk_widget_set_realized(widget, TRUE); gtk_widget_set_realized(widget, TRUE);
} }
static void
fastiv_browser_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
GTK_WIDGET_CLASS(fastiv_browser_parent_class)
->size_allocate(widget, allocation);
relayout(FASTIV_BROWSER(widget), allocation->width);
}
static gboolean static gboolean
fastiv_browser_draw(GtkWidget *widget, cairo_t *cr) fastiv_browser_draw(GtkWidget *widget, cairo_t *cr)
{ {
@ -137,27 +236,23 @@ fastiv_browser_draw(GtkWidget *widget, cairo_t *cr)
gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0, gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0,
allocation.width, allocation.height); allocation.width, allocation.height);
gint occupied_width = 0, y = 0; for (guint i = 0; i < self->layouted_rows->len; i++) {
for (guint i = 0; i < self->entries->len; i++) { const Row *row = &g_array_index(self->layouted_rows, Row, i);
const Entry *entry = &g_array_index(self->entries, Entry, i); for (Item *item = row->items; item->entry; item++) {
if (!entry->thumbnail) cairo_surface_t *thumbnail = item->entry->thumbnail;
continue; int width = cairo_image_surface_get_width(thumbnail);
int height = cairo_image_surface_get_height(thumbnail);
int x = row->x_offset + item->x_offset;
int y = row->y_offset + g_row_height - height;
int width = cairo_image_surface_get_width(entry->thumbnail); // TODO(p): Test whether we need to render this first.
int height = cairo_image_surface_get_height(entry->thumbnail);
if (occupied_width != 0 &&
occupied_width + width > allocation.width) {
occupied_width = 0;
y += g_row_height;
}
cairo_save(cr); cairo_save(cr);
cairo_translate(cr, occupied_width, y + g_row_height - height); cairo_translate(cr, x, y);
cairo_set_source_surface(cr, entry->thumbnail, 0, 0); cairo_set_source_surface(cr, thumbnail, 0, 0);
cairo_paint(cr); cairo_paint(cr);
cairo_restore(cr); cairo_restore(cr);
}
occupied_width += width;
} }
return TRUE; return TRUE;
} }
@ -175,6 +270,7 @@ fastiv_browser_class_init(FastivBrowserClass *klass)
fastiv_browser_get_preferred_height_for_width; fastiv_browser_get_preferred_height_for_width;
widget_class->realize = fastiv_browser_realize; widget_class->realize = fastiv_browser_realize;
widget_class->draw = fastiv_browser_draw; widget_class->draw = fastiv_browser_draw;
widget_class->size_allocate = fastiv_browser_size_allocate;
// TODO(p): Connect to this and emit it. // TODO(p): Connect to this and emit it.
browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated", browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated",
@ -192,6 +288,9 @@ fastiv_browser_init(FastivBrowser *self)
self->entries = g_array_new(FALSE, TRUE, sizeof(Entry)); self->entries = g_array_new(FALSE, TRUE, sizeof(Entry));
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free); 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->selected = -1;
} }
@ -206,8 +305,8 @@ rescale_thumbnail(cairo_surface_t *thumbnail)
double scale_x = 1; double scale_x = 1;
double scale_y = 1; double scale_y = 1;
if (width > 2 * height) { if (width > g_permitted_width_multiplier * height) {
scale_x = 2 * g_row_height / width; scale_x = g_permitted_width_multiplier * g_row_height / width;
scale_y = round(scale_x * height) / height; scale_y = round(scale_x * height) / height;
} else { } else {
scale_y = g_row_height / height; scale_y = g_row_height / height;

View File

@ -304,7 +304,8 @@ main(int argc, char *argv[])
gtk_window_set_default_icon_name(PROJECT_NAME); gtk_window_set_default_icon_name(PROJECT_NAME);
const char *style = "fastiv-view, fastiv-browser { background: black; }"; const char *style = "fastiv-view, fastiv-browser { background: black; }"
"fastiv-browser { padding: 5px; }";
GtkCssProvider *provider = gtk_css_provider_new(); GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_data(provider, style, strlen(style), NULL); gtk_css_provider_load_from_data(provider, style, strlen(style), NULL);
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),