Pre-layout the browser
Now the widget is scrollable.
This commit is contained in:
parent
a346ff8d02
commit
d2ef5c9c95
179
fastiv-browser.c
179
fastiv-browser.c
@ -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 &&
|
cairo_save(cr);
|
||||||
occupied_width + width > allocation.width) {
|
cairo_translate(cr, x, y);
|
||||||
occupied_width = 0;
|
cairo_set_source_surface(cr, thumbnail, 0, 0);
|
||||||
y += g_row_height;
|
cairo_paint(cr);
|
||||||
|
cairo_restore(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
cairo_save(cr);
|
|
||||||
cairo_translate(cr, occupied_width, y + g_row_height - height);
|
|
||||||
cairo_set_source_surface(cr, entry->thumbnail, 0, 0);
|
|
||||||
cairo_paint(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;
|
||||||
|
3
fastiv.c
3
fastiv.c
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user