shell: Add animated notebook

Powered by gtk-clutter.
This commit is contained in:
Bastien Nocera 2012-04-30 19:24:14 +01:00
parent 8a01c7caf1
commit 74aa98b7c8
6 changed files with 639 additions and 1 deletions

View file

@ -91,7 +91,7 @@ COMMON_MODULES="gtk+-3.0 >= $GTK_REQUIRED_VERSION
PKG_CHECK_MODULES(LIBGNOME_CONTROL_CENTER, $COMMON_MODULES)
PKG_CHECK_MODULES(LIBLANGUAGE, $COMMON_MODULES gnome-desktop-3.0 fontconfig)
PKG_CHECK_MODULES(LIBSHORTCUTS, $COMMON_MODULES x11)
PKG_CHECK_MODULES(SHELL, $COMMON_MODULES libgnome-menu-3.0 gio-unix-2.0 x11)
PKG_CHECK_MODULES(SHELL, $COMMON_MODULES libgnome-menu-3.0 gio-unix-2.0 x11 clutter-gtk-1.0)
PKG_CHECK_MODULES(BACKGROUND_PANEL, $COMMON_MODULES libxml-2.0 gnome-desktop-3.0
gdk-pixbuf-2.0 >= $GDKPIXBUF_REQUIRED_VERSION)
PKG_CHECK_MODULES(DATETIME_PANEL, $COMMON_MODULES

View file

@ -19,6 +19,8 @@ gnome_control_center_SOURCES = \
cc-shell-log.h \
gnome-control-center.c \
gnome-control-center.h \
cc-notebook.c \
cc-notebook.h \
shell-search-renderer.c \
shell-search-renderer.h \
cc-shell-category-view.c \
@ -67,6 +69,11 @@ directory_in_files = gnomecc.directory.in
directory_DATA = $(directory_in_files:.directory.in=.directory)
@INTLTOOL_DIRECTORY_RULE@
noinst_PROGRAMS = test-notebook
test_notebook_SOURCES = test-notebook.c cc-notebook.c cc-notebook.h
test_notebook_CFLAGS = $(SHELL_CFLAGS)
test_notebook_LDADD = $(SHELL_LIBS)
EXTRA_DIST = \
$(ui_DATA) \
gnome-control-center.desktop.in.in \

4
shell/TODO Normal file
View file

@ -0,0 +1,4 @@
- Make transitions between pages always be contiguous
the children list order affects the paint order - so you can use set_child_above_sibling/set_child_below_sibling
- Add a scroll direction to the animation
(back to parent to the left, to a child to the right)

466
shell/cc-notebook.c Normal file
View file

@ -0,0 +1,466 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright © 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Written by:
* Bastien Nocera <hadess@hadess.net>
* Emmanuele Bassi <ebassi@linux.intel.com>
*/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "cc-notebook.h"
#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CC_TYPE_NOTEBOOK, CcNotebookPrivate))
/*
* Structure:
*
* Notebook
* +---- GtkClutterEmbed
* +---- ClutterStage
* +---- ClutterScrollActor:scroll
* +---- ClutterActor:bin
* +---- ClutterActor:frame<ClutterBinLayout>
* +---- GtkClutterActor:embed<GtkWidget>
*
* the frame element is needed to make the GtkClutterActor contents fill the allocation
*/
struct _CcNotebookPrivate
{
GtkWidget *embed;
ClutterActor *stage;
ClutterActor *scroll;
ClutterActor *bin;
int last_width;
GtkWidget *selected_page;
GList *pages; /* GList of GtkWidgets */
};
enum
{
PROP_0,
PROP_CURRENT_PAGE,
LAST_PROP
};
static GParamSpec *obj_props[LAST_PROP] = { NULL, };
G_DEFINE_TYPE (CcNotebook, cc_notebook, GTK_TYPE_BOX)
static void
cc_notebook_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcNotebookPrivate *priv = CC_NOTEBOOK (gobject)->priv;
switch (prop_id) {
case PROP_CURRENT_PAGE:
g_value_set_pointer (value, priv->selected_page);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
cc_notebook_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcNotebook *self = CC_NOTEBOOK (gobject);
switch (prop_id) {
case PROP_CURRENT_PAGE:
cc_notebook_select_page (self, g_value_get_pointer (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static GtkSizeRequestMode
cc_notebook_get_request_mode (GtkWidget *widget)
{
CcNotebook *notebook;
GtkWidget *target;
notebook = CC_NOTEBOOK (widget);
target = notebook->priv->selected_page ? notebook->priv->selected_page : notebook->priv->embed;
return gtk_widget_get_request_mode (target);
}
static void
cc_notebook_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height (notebook->priv->selected_page, minimum_height, natural_height);
return;
}
*minimum_height = 0;
*natural_height = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_height (page, &page_min, &page_nat);
*minimum_height = MAX(page_min, *minimum_height);
*natural_height = MAX(page_nat, *natural_height);
}
}
static void
cc_notebook_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_width_for_height (notebook->priv->selected_page, height, minimum_width, natural_width);
return;
}
*minimum_width = 0;
*natural_width = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_width_for_height (page, height, &page_min, &page_nat);
*minimum_width = MAX(page_min, *minimum_width);
*natural_width = MAX(page_nat, *natural_width);
}
}
static void
cc_notebook_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height (notebook->priv->selected_page, minimum_width, natural_width);
return;
}
*minimum_width = 0;
*natural_width = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_width (page, &page_min, &page_nat);
*minimum_width = MAX(page_min, *minimum_width);
*natural_width = MAX(page_nat, *natural_width);
}
}
static void
cc_notebook_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height,
gint *natural_height)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height_for_width (notebook->priv->selected_page, width, minimum_height, natural_height);
return;
}
*minimum_height = 0;
*natural_height = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_height_for_width (page, width, &page_min, &page_nat);
*minimum_height = MAX(page_min, *minimum_height);
*natural_height = MAX(page_nat, *natural_height);
}
}
static void
cc_notebook_class_init (CcNotebookClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
g_type_class_add_private (klass, sizeof (CcNotebookPrivate));
obj_props[PROP_CURRENT_PAGE] =
g_param_spec_pointer (g_intern_static_string ("current-page"),
"Current Page",
"The currently selected page widget",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
gobject_class->get_property = cc_notebook_get_property;
gobject_class->set_property = cc_notebook_set_property;
g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
widget_class->get_request_mode = cc_notebook_get_request_mode;
widget_class->get_preferred_height = cc_notebook_get_preferred_height;
widget_class->get_preferred_width_for_height = cc_notebook_get_preferred_width_for_height;
widget_class->get_preferred_width = cc_notebook_get_preferred_width;
widget_class->get_preferred_height_for_width = cc_notebook_get_preferred_height_for_width;
}
static void
on_embed_size_allocate (GtkWidget *embed,
GtkAllocation *allocation,
CcNotebook *self)
{
ClutterActorIter iter;
ClutterActor *child;
ClutterActor *frame;
float page_w, page_h;
float offset = 0.f;
ClutterPoint pos;
if (self->priv->selected_page == NULL)
return;
page_w = allocation->width;
page_h = allocation->height;
clutter_actor_iter_init (&iter, self->priv->bin);
while (clutter_actor_iter_next (&iter, &child)) {
clutter_actor_set_x (child, offset);
clutter_actor_set_size (child, page_w, page_h);
offset += page_w;
}
/* This stops the non-animated scrolling from happening
* if we're still scrolling there */
if (self->priv->last_width == allocation->width)
return;
self->priv->last_width = allocation->width;
frame = g_object_get_data (G_OBJECT (self->priv->selected_page),
"cc-notebook-frame");
pos.y = 0;
pos.x = clutter_actor_get_x (frame);
clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (self->priv->scroll), &pos);
}
static void
cc_notebook_init (CcNotebook *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CC_TYPE_NOTEBOOK, CcNotebookPrivate);
self->priv->embed = gtk_clutter_embed_new ();
gtk_widget_push_composite_child ();
gtk_container_add (GTK_CONTAINER (self), self->priv->embed);
gtk_widget_pop_composite_child ();
g_signal_connect (self->priv->embed, "size-allocate", G_CALLBACK (on_embed_size_allocate), self);
gtk_widget_show (self->priv->embed);
self->priv->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (self->priv->embed));
self->priv->scroll = clutter_scroll_actor_new ();
clutter_scroll_actor_set_scroll_mode (CLUTTER_SCROLL_ACTOR (self->priv->scroll),
CLUTTER_SCROLL_HORIZONTALLY);
clutter_actor_add_constraint (self->priv->scroll, clutter_bind_constraint_new (self->priv->stage, CLUTTER_BIND_SIZE, 0.f));
clutter_actor_add_child (self->priv->stage, self->priv->scroll);
self->priv->bin = clutter_actor_new ();
clutter_actor_add_child (self->priv->scroll, self->priv->bin);
self->priv->selected_page = NULL;
gtk_widget_set_name (GTK_WIDGET (self), "GtkBox");
}
GtkWidget *
cc_notebook_new (void)
{
return g_object_new (CC_TYPE_NOTEBOOK, NULL);
}
static void
_cc_notebook_select_page (CcNotebook *self,
GtkWidget *widget,
int index)
{
ClutterPoint pos;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
pos.y = 0;
pos.x = self->priv->last_width * index;
clutter_actor_save_easing_state (self->priv->scroll);
clutter_actor_set_easing_duration (self->priv->scroll, 500);
clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (self->priv->scroll), &pos);
clutter_actor_restore_easing_state (self->priv->scroll);
/* Remember the last selected page */
self->priv->selected_page = widget;
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURRENT_PAGE]);
}
void
cc_notebook_select_page (CcNotebook *self,
GtkWidget *widget)
{
int i, n_children;
GList *children, *l;
ClutterActor *frame;
gboolean found;
if (widget == self->priv->selected_page)
return;
found = FALSE;
frame = g_object_get_data (G_OBJECT (widget), "cc-notebook-frame");
n_children = clutter_actor_get_n_children (self->priv->bin);
children = clutter_actor_get_children (self->priv->bin);
for (i = 0, l = children; i < n_children; i++, l = l->next) {
if (frame == l->data) {
_cc_notebook_select_page (self, widget, i);
found = TRUE;
break;
}
}
g_list_free (children);
if (found == FALSE)
g_warning ("Could not find widget '%p' in CcNotebook '%p'", widget, self);
}
void
cc_notebook_add_page (CcNotebook *self,
GtkWidget *widget)
{
ClutterActor *frame;
ClutterActor *embed;
int res;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
frame = clutter_actor_new ();
clutter_actor_set_layout_manager (frame, clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
CLUTTER_BIN_ALIGNMENT_FILL));
embed = gtk_clutter_actor_new_with_contents (widget);
g_object_set_data (G_OBJECT (widget), "cc-notebook-frame", frame);
clutter_actor_add_child (frame, embed);
gtk_widget_show (widget);
res = clutter_actor_get_n_children (self->priv->bin);
clutter_actor_insert_child_at_index (self->priv->bin, frame, res);
self->priv->pages = g_list_prepend (self->priv->pages, widget);
if (self->priv->selected_page == NULL)
_cc_notebook_select_page (self, widget, res);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
void
cc_notebook_remove_page (CcNotebook *self,
GtkWidget *widget)
{
ClutterActorIter iter;
ClutterActor *child, *frame, *selected_frame;
int index;
gboolean found_current;
ClutterPoint pos;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (widget != self->priv->selected_page);
found_current = FALSE;
frame = g_object_get_data (G_OBJECT (widget), "cc-notebook-frame");
selected_frame = g_object_get_data (G_OBJECT (self->priv->selected_page), "cc-notebook-frame");
index = 0;
clutter_actor_iter_init (&iter, self->priv->bin);
while (clutter_actor_iter_next (&iter, &child)) {
if (frame == child) {
clutter_actor_iter_remove (&iter);
break;
} else if (selected_frame == child) {
found_current = TRUE;
}
index++;
}
self->priv->pages = g_list_remove (self->priv->pages, widget);
gtk_widget_queue_resize (GTK_WIDGET (self));
/* The current page is before the one we removed, so no
* need to shift the scroll view */
if (found_current)
return;
pos.y = 0;
pos.x = self->priv->last_width * index;
clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (self->priv->scroll), &pos);
}
GtkWidget *
cc_notebook_get_selected_page (CcNotebook *self)
{
g_return_val_if_fail (CC_IS_NOTEBOOK (self), NULL);
return self->priv->selected_page;
}

69
shell/cc-notebook.h Normal file
View file

@ -0,0 +1,69 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright © 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Written by:
* Bastien Nocera <hadess@hadess.net>
*/
#ifndef _CC_NOTEBOOK_H_
#define _CC_NOTEBOOK_H_
#include <gtk/gtk.h>
#include <clutter-gtk/clutter-gtk.h>
G_BEGIN_DECLS
#define CC_TYPE_NOTEBOOK (cc_notebook_get_type ())
#define CC_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_NOTEBOOK, CcNotebook))
#define CC_IS_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_NOTEBOOK))
#define CC_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_NOTEBOOK, CcNotebookClass))
#define CC_IS_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_NOTEBOOK))
#define CC_NOTEBOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_NOTEBOOK, CcNotebookClass))
typedef struct _CcNotebook CcNotebook;
typedef struct _CcNotebookPrivate CcNotebookPrivate;
typedef struct _CcNotebookClass CcNotebookClass;
struct _CcNotebook
{
GtkBox parent_class;
CcNotebookPrivate *priv;
};
struct _CcNotebookClass
{
GtkBoxClass parent_class;
};
GType cc_notebook_get_type (void) G_GNUC_CONST;
GtkWidget * cc_notebook_new (void);
void cc_notebook_add_page (CcNotebook *self,
GtkWidget *widget);
void cc_notebook_remove_page (CcNotebook *self,
GtkWidget *widget);
void cc_notebook_select_page (CcNotebook *self,
GtkWidget *widget);
GtkWidget * cc_notebook_get_selected_page (CcNotebook *self);
G_END_DECLS
#endif /* _CC_NOTEBOOK_H_ */

92
shell/test-notebook.c Normal file
View file

@ -0,0 +1,92 @@
#include <stdlib.h>
#include "cc-notebook.h"
#define NUM_PAGES 5
static GHashTable *pages;
static GtkWidget *notebook;
static void
goto_page (GtkButton *button,
gpointer user_data)
{
int target = GPOINTER_TO_INT (user_data);
GtkWidget *widget;
if (target < 1)
target = NUM_PAGES;
else if (target > NUM_PAGES)
target = 1;
widget = g_hash_table_lookup (pages, GINT_TO_POINTER (target));
cc_notebook_select_page (CC_NOTEBOOK (notebook), widget);
}
static GtkWidget *
create_page_contents (int page_num)
{
GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
char *text = g_strdup_printf ("Page number %d", page_num);
GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
GtkWidget *back_button = gtk_button_new_with_label ("");
gtk_widget_set_halign (back_button, GTK_ALIGN_START);
gtk_box_pack_start (GTK_BOX (hbox), back_button, FALSE, FALSE, 0);
g_signal_connect (G_OBJECT (back_button), "clicked",
G_CALLBACK (goto_page), GINT_TO_POINTER (page_num - 1));
GtkWidget *fwd_button = gtk_button_new_with_label ("");
gtk_widget_set_halign (fwd_button, GTK_ALIGN_END);
gtk_box_pack_end (GTK_BOX (hbox), fwd_button, FALSE, FALSE, 0);
g_signal_connect (G_OBJECT (fwd_button), "clicked",
G_CALLBACK (goto_page), GINT_TO_POINTER (page_num + 1));
GtkWidget *label = gtk_label_new (text);
gtk_box_pack_end (GTK_BOX (vbox), label, TRUE, TRUE, 0);
gtk_widget_show_all (vbox);
g_object_set_data_full (G_OBJECT (vbox), "display-name", text, g_free);
g_hash_table_insert (pages, GINT_TO_POINTER (page_num), vbox);
return vbox;
}
static void
on_page_change (CcNotebook *notebook)
{
g_print (G_STRLOC ": Currently selected page: %s\n",
(char *) g_object_get_data (G_OBJECT (cc_notebook_get_selected_page (notebook)), "display-name"));
}
int
main (int argc, char *argv[])
{
guint i;
if (gtk_clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
return EXIT_FAILURE;
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_show (window);
notebook = cc_notebook_new ();
gtk_container_add (GTK_CONTAINER (window), notebook);
g_signal_connect (notebook, "notify::current-page", G_CALLBACK (on_page_change), NULL);
pages = g_hash_table_new (g_direct_hash, g_direct_equal);
for (i = 1; i <= NUM_PAGES; i++) {
GtkWidget *page = create_page_contents (i);
cc_notebook_add_page ((CcNotebook *) notebook, page);
}
gtk_widget_show_all (window);
gtk_main ();
return EXIT_SUCCESS;
}