362 lines
9.0 KiB
C
362 lines
9.0 KiB
C
|
#include "gtkshaderstack.h"
|
||
|
|
||
|
struct _GtkShaderStack
|
||
|
{
|
||
|
GtkWidget parent_instance;
|
||
|
|
||
|
GskGLShader *shader;
|
||
|
GPtrArray *children;
|
||
|
int current;
|
||
|
int next;
|
||
|
gboolean backwards;
|
||
|
|
||
|
guint tick_id;
|
||
|
float time;
|
||
|
float duration;
|
||
|
gint64 start_time;
|
||
|
};
|
||
|
|
||
|
struct _GtkShaderStackClass
|
||
|
{
|
||
|
GtkWidgetClass parent_class;
|
||
|
};
|
||
|
|
||
|
|
||
|
enum {
|
||
|
PROP_DURATION = 1,
|
||
|
NUM_PROPERTIES
|
||
|
};
|
||
|
|
||
|
static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
|
||
|
|
||
|
G_DEFINE_TYPE (GtkShaderStack, gtk_shader_stack, GTK_TYPE_WIDGET)
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_finalize (GObject *object)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (object);
|
||
|
|
||
|
g_object_unref (self->shader);
|
||
|
|
||
|
G_OBJECT_CLASS (gtk_shader_stack_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_child_visible (GtkShaderStack *self)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < self->children->len; i++)
|
||
|
{
|
||
|
GtkWidget *child = g_ptr_array_index (self->children, i);
|
||
|
|
||
|
gtk_widget_set_child_visible (child,
|
||
|
i == self->current || i == self->next);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
transition_cb (GtkWidget *widget,
|
||
|
GdkFrameClock *clock,
|
||
|
gpointer unused)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (widget);
|
||
|
gint64 frame_time;
|
||
|
|
||
|
frame_time = gdk_frame_clock_get_frame_time (clock);
|
||
|
|
||
|
if (self->start_time == 0)
|
||
|
self->start_time = frame_time;
|
||
|
|
||
|
self->time = (frame_time - self->start_time) / (float)G_USEC_PER_SEC;
|
||
|
|
||
|
gtk_widget_queue_draw (widget);
|
||
|
|
||
|
if (self->time >= self->duration)
|
||
|
{
|
||
|
self->current = self->next;
|
||
|
self->next = -1;
|
||
|
|
||
|
update_child_visible (self);
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
else
|
||
|
return G_SOURCE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
start_transition (GtkShaderStack *self)
|
||
|
{
|
||
|
self->start_time = 0;
|
||
|
self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
|
||
|
transition_cb,
|
||
|
NULL, NULL);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
stop_transition (GtkShaderStack *self)
|
||
|
{
|
||
|
if (self->tick_id != 0)
|
||
|
{
|
||
|
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
|
||
|
self->tick_id = 0;
|
||
|
}
|
||
|
|
||
|
if (self->next != -1)
|
||
|
self->current = self->next;
|
||
|
self->next = -1;
|
||
|
|
||
|
update_child_visible (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_dispose (GObject *object)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (object);
|
||
|
|
||
|
stop_transition (self);
|
||
|
|
||
|
g_clear_pointer (&self->children, g_ptr_array_unref);
|
||
|
|
||
|
G_OBJECT_CLASS (gtk_shader_stack_parent_class)->dispose (object);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gtk_shader_stack_transition (GtkShaderStack *self,
|
||
|
gboolean forward)
|
||
|
{
|
||
|
stop_transition (self);
|
||
|
|
||
|
self->backwards = !forward;
|
||
|
if (self->backwards)
|
||
|
self->next = (self->current + self->children->len - 1) % self->children->len;
|
||
|
else
|
||
|
self->next = (self->current + 1) % self->children->len;
|
||
|
|
||
|
update_child_visible (self);
|
||
|
|
||
|
start_transition (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_init (GtkShaderStack *self)
|
||
|
{
|
||
|
self->children = g_ptr_array_new_with_free_func ((GDestroyNotify)gtk_widget_unparent);
|
||
|
self->current = -1;
|
||
|
self->next = -1;
|
||
|
self->backwards = FALSE;
|
||
|
self->duration = 1.0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_measure (GtkWidget *widget,
|
||
|
GtkOrientation orientation,
|
||
|
int for_size,
|
||
|
int *minimum,
|
||
|
int *natural,
|
||
|
int *minimum_baseline,
|
||
|
int *natural_baseline)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (widget);
|
||
|
int i;
|
||
|
|
||
|
*minimum = 0;
|
||
|
*natural = 0;
|
||
|
|
||
|
for (i = 0; i < self->children->len; i++)
|
||
|
{
|
||
|
GtkWidget *child = g_ptr_array_index (self->children, i);
|
||
|
int child_min, child_nat;
|
||
|
|
||
|
if (gtk_widget_get_visible (child))
|
||
|
{
|
||
|
gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
|
||
|
|
||
|
*minimum = MAX (*minimum, child_min);
|
||
|
*natural = MAX (*natural, child_nat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_size_allocate (GtkWidget *widget,
|
||
|
int width,
|
||
|
int height,
|
||
|
int baseline)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (widget);
|
||
|
GtkAllocation child_allocation;
|
||
|
GtkWidget *child;
|
||
|
int i;
|
||
|
|
||
|
child_allocation.x = 0;
|
||
|
child_allocation.y = 0;
|
||
|
child_allocation.width = width;
|
||
|
child_allocation.height = height;
|
||
|
|
||
|
for (i = 0; i < self->children->len; i++)
|
||
|
{
|
||
|
child = g_ptr_array_index (self->children, i);
|
||
|
if (gtk_widget_get_visible (child))
|
||
|
gtk_widget_size_allocate (child, &child_allocation, -1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_snapshot (GtkWidget *widget,
|
||
|
GtkSnapshot *snapshot)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (widget);
|
||
|
int width, height;
|
||
|
GtkWidget *current, *next;
|
||
|
|
||
|
width = gtk_widget_get_width (widget);
|
||
|
height = gtk_widget_get_height (widget);
|
||
|
|
||
|
current = g_ptr_array_index (self->children, self->current);
|
||
|
|
||
|
if (self->next == -1)
|
||
|
{
|
||
|
gtk_widget_snapshot_child (widget, current, snapshot);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GtkNative *native = gtk_widget_get_native (widget);
|
||
|
GskRenderer *renderer = gtk_native_get_renderer (native);
|
||
|
float progress;
|
||
|
|
||
|
next = g_ptr_array_index (self->children, self->next);
|
||
|
|
||
|
progress = self->time / self->duration;
|
||
|
|
||
|
if (self->backwards)
|
||
|
{
|
||
|
GtkWidget *tmp = next;
|
||
|
next = current;
|
||
|
current = tmp;
|
||
|
progress = 1. - progress;
|
||
|
}
|
||
|
|
||
|
if (gsk_gl_shader_compile (self->shader, renderer, NULL))
|
||
|
{
|
||
|
gtk_snapshot_push_gl_shader (snapshot,
|
||
|
self->shader,
|
||
|
&GRAPHENE_RECT_INIT(0, 0, width, height),
|
||
|
gsk_gl_shader_format_args (self->shader,
|
||
|
"progress", progress,
|
||
|
NULL));
|
||
|
|
||
|
gtk_widget_snapshot_child (widget, current, snapshot);
|
||
|
gtk_snapshot_gl_shader_pop_texture (snapshot); /* current child */
|
||
|
gtk_widget_snapshot_child (widget, next, snapshot);
|
||
|
gtk_snapshot_gl_shader_pop_texture (snapshot); /* next child */
|
||
|
gtk_snapshot_pop (snapshot);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Non-shader fallback */
|
||
|
gtk_widget_snapshot_child (widget, current, snapshot);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_DURATION:
|
||
|
g_value_set_float (value, self->duration);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_set_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
GtkShaderStack *self = GTK_SHADER_STACK (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_DURATION:
|
||
|
self->duration = g_value_get_float (value);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gtk_shader_stack_class_init (GtkShaderStackClass *class)
|
||
|
{
|
||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||
|
|
||
|
object_class->finalize = gtk_shader_stack_finalize;
|
||
|
object_class->dispose = gtk_shader_stack_dispose;
|
||
|
object_class->get_property = gtk_shader_stack_get_property;
|
||
|
object_class->set_property = gtk_shader_stack_set_property;
|
||
|
|
||
|
widget_class->snapshot = gtk_shader_stack_snapshot;
|
||
|
widget_class->measure = gtk_shader_stack_measure;
|
||
|
widget_class->size_allocate = gtk_shader_stack_size_allocate;
|
||
|
|
||
|
properties[PROP_DURATION] =
|
||
|
g_param_spec_float ("duration", "Duration", "Duration",
|
||
|
0.1, 3.0, 1.0,
|
||
|
G_PARAM_READWRITE);
|
||
|
|
||
|
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
|
||
|
}
|
||
|
|
||
|
GtkWidget *
|
||
|
gtk_shader_stack_new (void)
|
||
|
{
|
||
|
return g_object_new (GTK_TYPE_SHADER_STACK, NULL);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gtk_shader_stack_set_shader (GtkShaderStack *self,
|
||
|
GskGLShader *shader)
|
||
|
{
|
||
|
g_set_object (&self->shader, shader);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gtk_shader_stack_add_child (GtkShaderStack *self,
|
||
|
GtkWidget *child)
|
||
|
{
|
||
|
g_ptr_array_add (self->children, child);
|
||
|
gtk_widget_set_parent (child, GTK_WIDGET (self));
|
||
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||
|
|
||
|
if (self->current == -1)
|
||
|
self->current = 0;
|
||
|
else
|
||
|
gtk_widget_set_child_visible (child, FALSE);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gtk_shader_stack_set_active (GtkShaderStack *self,
|
||
|
int index)
|
||
|
{
|
||
|
stop_transition (self);
|
||
|
self->current = MIN (index, self->children->len);
|
||
|
update_child_visible (self);
|
||
|
}
|