Commit 5178daf1 authored by Francois Techene's avatar Francois Techene

Implementing fade animations + some refactoring

parent 121edcdf
......@@ -26,7 +26,18 @@ static void
on_startup (GtkApplication *app)
{
//g_print ("Init Libhandy\n");
hdy_init();
//
GtkCssProvider *css_provider = gtk_css_provider_new ();
hdy_init ();
gtk_css_provider_load_from_resource (css_provider, "/sm/puri/Randomizer/style.css");
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (css_provider);
}
static void
......
randomizer_sources = [
'main.c',
'randomizer-animation.c',
'randomizer-window.c',
]
......
/* Copyright YEAR Copyright Holder
*
* Licence text goes here
*
* Author: you <your@email>
*/
#include "randomizer-animation.h"
#include <math.h>
struct _RandomizerAnimation
{
GInitiallyUnowned parent_instance;
/* Instance variables */
animated_widget_property animated_property;
animation_curve_type curve_type;
GtkWidget *widget;
gint64 start_time; /* ms */
gint64 end_time; /* ms */
gint64 duration; /* ms */
guint tick_cb_id;
guint animation_id;
gdouble value_from;
gdouble value_to;
RandomizerAnimationCompleteCallback on_complete_cb;
gpointer user_data;
gboolean can_animate;
gboolean is_running;
};
G_DEFINE_TYPE(RandomizerAnimation, randomizer_animation, G_TYPE_INITIALLY_UNOWNED)
/***********************************************************
* Static functions
*
*/
static gboolean
is_widget_animatable (GtkWidget *widget)
{
gboolean enable_animations = TRUE;
g_assert (GTK_IS_WIDGET (widget));
g_object_get (gtk_widget_get_settings (widget),
"gtk-enable-animations", &enable_animations,
NULL);
return enable_animations;
}
static gdouble
get_value_for_current_time (RandomizerAnimation *self,
gdouble current_time)
{
gdouble value;
gdouble t;
gdouble low_value;
gdouble high_value;
gboolean decrease = (self->value_from > self->value_to);
if (decrease) {
low_value = self->value_to;
high_value = self->value_from;
}
else {
low_value = self->value_from;
high_value = self->value_to;
}
// Logic from http://gizma.com/easing/#cub2
//
switch (self->curve_type)
{
case LINEAR_ANIMATION:
t = current_time / self->duration;
value = low_value * (1.0 - t) + high_value * t;
break;
case EASE_IN_ANIMATION:
t = current_time / self->duration;
value = high_value * t * t * t + low_value;
break;
case EASE_OUT_ANIMATION:
t = (current_time / self->duration) - 1;
value = high_value * (t * t * t + 1) + low_value;
break;
case EASE_IN_AND_OUT_ANIMATION:
t = current_time / (self->duration / 2) ;
if (t < 1) {
value = high_value/2*t*t*t + low_value;
}
else {
t -= 2;
value = high_value/2*(t*t*t + 2) + low_value;
}
break;
}
if (decrease) {
value = self->value_from - value;
}
return value;
}
static gdouble
get_animation_value (RandomizerAnimation *self,
GdkFrameClock *frame_clock)
{
gint64 frame_time;
gdouble current_time;
frame_time = gdk_frame_clock_get_frame_time (frame_clock) / 1000;
frame_time = MIN (frame_time, self->end_time);
current_time = frame_time - self->start_time;
return get_value_for_current_time (self, current_time);
}
static void
set_animation_value(RandomizerAnimation *self,
gdouble value)
{
switch (self->animated_property)
{
case OPACITY_ANIMATED_PROPERTY:
gtk_widget_set_opacity (self->widget, value);
break;
case SIZE_ANIMATED_PROPERTY:
// TODO : Implement size animation
break;
case X_TRANSLATION_ANIMATED_PROPERTY:
// TODO : Implement translation animation
break;
case Y_TRANSLATION_ANIMATED_PROPERTY:
// TODO : Implement translation animation
break;
default :
g_print("There is no animation implemented for this property\n");
}
}
static gboolean
update_animation (GtkWidget *widget,
GdkFrameClock *frame_clock,
RandomizerAnimation *self)
{
gboolean should_continue = TRUE;
gint64 frame_time;
gdouble value;
frame_time = gdk_frame_clock_get_frame_time (frame_clock) / 1000;
if (frame_time >= self->end_time) {
value = self->value_to;
should_continue = FALSE;
}
else {
value = get_animation_value(self, frame_clock);
}
//g_print("Value : %f\n", value);
set_animation_value(self, value);
return should_continue;
}
static void
on_update_animation_complete_cb(RandomizerAnimation *self)
{
randomizer_animation_stop(self);
}
/***********************************************************
* Public functions
*
*/
void
randomizer_animation_start(RandomizerAnimation *self,
animation_curve_type curve_type,
gdouble value_from,
gdouble value_to,
gint64 duration,
RandomizerAnimationCompleteCallback on_complete_cb,
gpointer user_data)
{
g_return_if_fail (self != NULL);
self->curve_type = curve_type;
self->value_from = value_from;
self->value_to = value_to;
self->duration = duration;
self->on_complete_cb = on_complete_cb;
self->user_data = user_data;
self->can_animate = TRUE;
// If there cannot be an animation,
// complete the animation.
//
if (!is_widget_animatable (self->widget) ||
!gtk_widget_get_mapped (self->widget) ||
self->duration <= 0) {
self->can_animate = FALSE;
self->animation_id = 0;
set_animation_value (self, self->value_to);
randomizer_animation_stop (self);
return;
}
GdkFrameClock *frame_clock;
gint64 frame_time;
frame_clock = gtk_widget_get_frame_clock (self->widget);
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
self->start_time = frame_time / 1000;
self->end_time = self->start_time + self->duration;
// Don't register a new animation callback if one is already running.
//
if (!self->is_running) {
self->tick_cb_id = gtk_widget_add_tick_callback (self->widget, update_animation, self, on_update_animation_complete_cb);
self->animation_id = self->tick_cb_id;
}
self->is_running = TRUE;
}
void
randomizer_animation_stop (RandomizerAnimation *self)
{
g_return_if_fail (self != NULL);
self->is_running = FALSE;
if (self->tick_cb_id != 0) {
gtk_widget_remove_tick_callback (self->widget, self->tick_cb_id);
self->tick_cb_id = 0;
}
self->start_time = 0;
self->end_time = 0;
if (self->on_complete_cb != NULL) {
self->on_complete_cb (self, self->user_data);
}
}
guint
randomizer_animation_get_id (RandomizerAnimation *self)
{
return self->animation_id;
}
/***********************************************************
* Init
*
*/
static void
randomizer_animation_init (RandomizerAnimation *self)
{
// initialisation goes here
self->tick_cb_id = 0;
self->animation_id = 0;
self->start_time = 0;
self->end_time = 0;
}
static void
randomizer_animation_class_init (RandomizerAnimationClass *class)
{
// virtual function overrides go here
// property and signal definitions go here
}
RandomizerAnimation *
randomizer_animation_new (GtkWidget *widget,
animated_widget_property property)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
RandomizerAnimation *self;
self = g_object_new (RANDOMIZER_TYPE_ANIMATION, NULL);
self->widget = widget;
self->animated_property = property;
return self;
}
/* Copyright YEAR Copyright Holder
*
* Licence text goes here
*
* Author: you <your@email>
*/
#ifndef _my_app_window_h_
#define _my_app_window_h_
#include <gtk/gtk.h>
/*
* Animation types
*/
typedef enum {
NO_ANIMATED_PROPERTY,
OPACITY_ANIMATED_PROPERTY,
SIZE_ANIMATED_PROPERTY,
X_TRANSLATION_ANIMATED_PROPERTY,
Y_TRANSLATION_ANIMATED_PROPERTY
} animated_widget_property;
typedef enum {
LINEAR_ANIMATION,
EASE_IN_ANIMATION,
EASE_OUT_ANIMATION,
EASE_IN_AND_OUT_ANIMATION
} animation_curve_type;
G_BEGIN_DECLS
#define RANDOMIZER_TYPE_ANIMATION (randomizer_animation_get_type ())
G_DECLARE_FINAL_TYPE(RandomizerAnimation, randomizer_animation, RANDOMIZER, ANIMATION, GInitiallyUnowned)
typedef void (*RandomizerAnimationCompleteCallback) (RandomizerAnimation *self,
gpointer user_data);
RandomizerAnimation * randomizer_animation_new (GtkWidget *widget,
animated_widget_property property);
void randomizer_animation_start (RandomizerAnimation *self,
animation_curve_type curve_type,
gdouble start_value,
gdouble end_value,
gint64 duration,
RandomizerAnimationCompleteCallback on_complete_cb,
gpointer user_data);
void randomizer_animation_stop (RandomizerAnimation *self);
guint randomizer_animation_get_id (RandomizerAnimation *self);
G_END_DECLS
#endif /* _my_app_window_h_ */
......@@ -18,6 +18,7 @@
#include "randomizer-config.h"
#include "randomizer-window.h"
#include "randomizer-animation.h"
#include <sys/time.h>
struct _RandomizerWindow
......@@ -25,21 +26,27 @@ struct _RandomizerWindow
HdyWindow parent_instance;
/* Template widgets */
GtkStack *stack;
HdyViewSwitcherTitle *switcher_title;
GtkLabel *boolean_label;
GtkImage *dice_image;
GtkSpinner *yes_no_spinner;
GtkLabel *answer_label;
GtkImage *dice_image_1;
GtkImage *dice_image_2;
RandomizerAnimation *fade_answer_animation;
gboolean answer_is_displayed;
};
G_DEFINE_TYPE (RandomizerWindow, randomizer_window, HDY_TYPE_WINDOW)
/***********************************************************
* Randomize function
* Randomize functions
*
*/
static int
get_randomized_number_with_range (int lower,
int upper)
......@@ -52,46 +59,126 @@ get_randomized_number_with_range (int lower,
return num;
}
/***********************************************************
* Yes / NO Answer tab
*
*/
static void
randomize_dice_image (GtkImage *dice_image)
randomize_answer (GtkLabel *label)
{
int num = get_randomized_number_with_range(1,6);
char arr[6][30] =
{ "Yes",
"No",
"Definitely",
"Don't count on it.",
"Only you can find out.",
"Maybe"
};
char image_name[6];
sprintf(image_name, "dice-%d", num);
int num = get_randomized_number_with_range(0,5);
gtk_label_set_text(label, arr[num]);
gtk_image_set_from_icon_name (dice_image,
image_name,
GTK_ICON_SIZE_INVALID);
}
static void
hide_answer_cb(RandomizerAnimation *animation,
RandomizerWindow *self)
{
gtk_label_set_text(self->answer_label, "Yes or No?");
randomizer_animation_start (self->fade_answer_animation,
EASE_IN_ANIMATION,
0,
1,
500,
NULL,
self);
}
static gboolean
hide_answer(RandomizerWindow *self)
{
if(self->answer_is_displayed) {
randomizer_animation_start (self->fade_answer_animation,
EASE_IN_ANIMATION,
1,
0,
2000,
hide_answer_cb,
self);
self->answer_is_displayed = FALSE;
}
return FALSE;
}
static void
on_update_answer_animation_complete_cb(RandomizerAnimation *animation,
RandomizerWindow *self)
{
g_timeout_add(5000, G_SOURCE_FUNC(hide_answer), self);
}
static void
answer_randomize_clicked_cb (GtkButton *btn,
RandomizerWindow *self)
{
randomize_answer(self->answer_label);
randomizer_animation_start (self->fade_answer_animation,
LINEAR_ANIMATION,
0,
1,
1000,
on_update_answer_animation_complete_cb,
self);
self->answer_is_displayed = TRUE;
}
/***********************************************************
* Callbacks
* Dices tab
*
*/
static void
boolean_randomize_clicked_cb (GtkButton *btn,
RandomizerWindow *self)
set_dice_image_with_value(GtkImage *dice_image,
int value)
{
int num = get_randomized_number_with_range(0,1);
char image_name[6];
sprintf(image_name, "dice-%d", value);
if (num == 0) {
gtk_label_set_text(self->boolean_label, "No");
}
else {
gtk_label_set_text(self->boolean_label, "Yes");
}
gtk_image_set_from_icon_name (dice_image,
image_name,
GTK_ICON_SIZE_INVALID);
}
static void
dice_randomize_clicked_cb (GtkButton *btn,
randomize_dice_image (GtkImage *dice_image)
{
set_dice_image_with_value (dice_image,
get_randomized_number_with_range(1,6));
}
static void
dice_randomize_clicked_cb (GtkButton *btn,
RandomizerWindow *self)
{
randomize_dice_image(self->dice_image);
randomize_dice_image(self->dice_image_1);
randomize_dice_image(self->dice_image_2);
}
......@@ -110,6 +197,42 @@ dice_number_changed_cb (GtkSpinButton *spinbutton,
}
}
/***********************************************************
* Init Tabs
*
*/
static void
init_answer_tab(RandomizerWindow *self)
{
gtk_widget_set_opacity (GTK_WIDGET (self->answer_label), 1);
}
static void
init_dice_tab(RandomizerWindow *self)
{
set_dice_image_with_value (self->dice_image_1, 1);
set_dice_image_with_value (self->dice_image_2, 1);
}
static void
switched_tab_cb (GtkStack *current_stack,
RandomizerWindow *self)
{
const char * page_name = gtk_stack_get_visible_child_name(current_stack);
g_print("on new tab : %s \n", page_name);
if (strcmp(page_name, "dice") == 0) {
//init_dice_tab(self);
}
else {
//init_answer_tab(self);
}
}
/***********************************************************
* Init
*
......@@ -121,13 +244,17 @@ randomizer_window_class_init (RandomizerWindowClass *klass)
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/Randomizer/randomizer-window.ui");
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, stack);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, switcher_title);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, boolean_label);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, dice_image);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, yes_no_spinner);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, answer_label);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, dice_image_1);
gtk_widget_class_bind_template_child (widget_class, RandomizerWindow, dice_image_2);
gtk_widget_class_bind_template_callback (widget_class, boolean_randomize_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, answer_randomize_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, dice_randomize_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, dice_number_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, switched_tab_cb);
}
static void
......@@ -135,7 +262,15 @@ randomizer_window_init (RandomizerWindow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
/* Init UI */
self->fade_answer_animation = randomizer_animation_new(GTK_WIDGET (self->answer_label),
OPACITY_ANIMATED_PROPERTY);
init_answer_tab(self);
gtk_widget_hide( GTK_WIDGET(self->dice_image_2) );
}
RandomizerWindow *
......
......@@ -31,6 +31,7 @@
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="notify::visible-child" handler="switched_tab_cb" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
......@@ -41,49 +42,75 @@
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="spacing">24</property>
<child>
<object class="GtkLabel" id="boolean_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes"></property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="opacity">1</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="2"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel" id="boolean_description_label">
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Yes or No ?</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="opacity">0.5</property>
<attributes>
<attribute name="weight" value="normal"/>
<attribute name="scale" value="1"/>
</attributes>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="spacing">24</property>
<child>
<object class="GtkSpinner" id="yes_no_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkLabel" id="answer_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Yes or No ?</property>
<property name="justify">center</property>