From 816ffed24b033da36923e9e387d540e5e876a578 Mon Sep 17 00:00:00 2001 From: Jonny Lamb Date: Tue, 4 Mar 2014 20:31:45 +0100 Subject: launcher: add new app launcher window and replace old grid with it --- shell/Makefile.am | 2 + shell/gtk-shell.c | 44 +++--- shell/launcher.c | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ shell/launcher.h | 43 ++++++ shell/style.css | 18 +++ 5 files changed, 502 insertions(+), 25 deletions(-) create mode 100644 shell/launcher.c create mode 100644 shell/launcher.h diff --git a/shell/Makefile.am b/shell/Makefile.am index 2d63195..6231337 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -29,6 +29,8 @@ weston_gtk_shell_SOURCES = \ panel.h \ vertical-clock.c \ vertical-clock.h \ + launcher.c \ + launcher.h \ weston-gtk-shell-resources.c \ weston-gtk-shell-resources.h \ desktop-shell-client-protocol.h \ diff --git a/shell/gtk-shell.c b/shell/gtk-shell.c index 36f059a..83c4b4a 100644 --- a/shell/gtk-shell.c +++ b/shell/gtk-shell.c @@ -12,7 +12,7 @@ #include "app-icon.h" #include "clock.h" #include "favorites.h" -#include "launcher-grid.h" +#include "launcher.h" #include "panel.h" #include "vertical-clock.h" @@ -79,18 +79,22 @@ desktop_shell_configure(void *data, { struct desktop *desktop = data; int window_height; + int grid_width, grid_height; gtk_widget_set_size_request (desktop->background->window, width, height); - gtk_widget_set_size_request (desktop->launcher_grid->window, - width - 56, height); - /* TODO: make this height a little nicer */ window_height = height * WESTON_GTK_PANEL_HEIGHT_RATIO; gtk_window_resize (GTK_WINDOW (desktop->panel->window), WESTON_GTK_PANEL_WIDTH, window_height); + weston_gtk_launcher_calculate (WESTON_GTK_LAUNCHER(desktop->launcher_grid->window), + &grid_width, &grid_height, NULL); + gtk_widget_set_size_request (desktop->launcher_grid->window, + grid_width, + grid_height); + shell_helper_move_surface(desktop->helper, desktop->panel->surface, 0, (height - window_height) / 2); @@ -104,6 +108,11 @@ desktop_shell_configure(void *data, WESTON_GTK_PANEL_WIDTH, (height - window_height) / 2); + shell_helper_move_surface(desktop->helper, + desktop->launcher_grid->surface, + - grid_width, + ((height - window_height) / 2) + WESTON_GTK_CLOCK_HEIGHT); + desktop_shell_desktop_ready(desktop->shell); /* TODO: why does the panel signal leave on drawing for @@ -135,35 +144,20 @@ static void launcher_grid_create (struct desktop *desktop) { struct element *launcher_grid; - GtkWidget *grid; + GdkWindow *gdk_window; launcher_grid = malloc (sizeof *launcher_grid); memset (launcher_grid, 0, sizeof *launcher_grid); - launcher_grid->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - - gtk_window_set_title(GTK_WINDOW(launcher_grid->window), "gtk shell"); - gtk_window_set_decorated(GTK_WINDOW(launcher_grid->window), FALSE); - gtk_widget_realize(launcher_grid->window); - - grid = launcher_grid_new (); + launcher_grid->window = weston_gtk_launcher_new (desktop->background->window); + gdk_window = gtk_widget_get_window(launcher_grid->window); + launcher_grid->surface = gdk_wayland_window_get_wl_surface(gdk_window); - gtk_container_add (GTK_CONTAINER (launcher_grid->window), grid); - - gtk_widget_show_all (grid); + gtk_widget_show_all(launcher_grid->window); desktop->launcher_grid = launcher_grid; } -static void -launcher_grid_toggle (GtkWidget *widget, struct desktop *desktop) -{ - gboolean grid_visible; - - grid_visible = gtk_widget_is_visible (desktop->launcher_grid->window); - gtk_widget_set_visible (desktop->launcher_grid->window, !grid_visible); -} - static GtkWidget * clock_create (struct desktop *desktop) { @@ -445,8 +439,8 @@ main(int argc, char *argv[]) css_setup(desktop); panel_create(desktop); clock_create(desktop); - launcher_grid_create (desktop); background_create(desktop); + launcher_grid_create (desktop); gtk_main(); diff --git a/shell/launcher.c b/shell/launcher.c new file mode 100644 index 0000000..679b40a --- /dev/null +++ b/shell/launcher.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2014 Collabora Ltd. + * + * Author: Jonny Lamb + */ + +#include "config.h" + +#include "launcher.h" + +#include "clock.h" +#include "panel.h" +#include "shell-app-system.h" + +enum { + PROP_0, + PROP_BACKGROUND, +}; + +enum { + APP_LAUNCHED, + N_SIGNALS +}; +static guint signals[N_SIGNALS] = { 0 }; + +struct WestonGtkLauncherPrivate { + /* background widget so we know the output size */ + GtkWidget *background; + ShellAppSystem *app_system; + GtkWidget *scrolled_window; + GtkWidget *grid; +}; + +G_DEFINE_TYPE(WestonGtkLauncher, weston_gtk_launcher, GTK_TYPE_WINDOW) + +/* each grid item is 114x114 */ +#define GRID_ITEM_WIDTH 114 + +static void +weston_gtk_launcher_init (WestonGtkLauncher *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + WESTON_GTK_LAUNCHER_TYPE, + WestonGtkLauncherPrivate); +} + +static gint +sort_apps (gconstpointer a, + gconstpointer b) +{ + GAppInfo *info1 = G_APP_INFO (a); + GAppInfo *info2 = G_APP_INFO (b); + gchar *s1, *s2; + gint ret; + + s1 = g_utf8_casefold (g_app_info_get_display_name (info1), -1); + s2 = g_utf8_casefold (g_app_info_get_display_name (info2), -1); + + ret = g_strcmp0 (s1, s2); + + g_free (s1); + g_free (s2); + + return ret; +} + +static gboolean +get_child_position_cb (GtkOverlay *overlay, + GtkWidget *widget, + GdkRectangle *allocation, + gpointer user_data) +{ + GtkOverlayClass *klass = GTK_OVERLAY_GET_CLASS (overlay); + + klass->get_child_position (overlay, widget, allocation); + + /* use the same valign and halign properties, but given we have a + * border of 1px, respect it and don't draw the overlay widget over + * the border. */ + allocation->x += 1; + allocation->y -= 1; + allocation->width -= 2; + + return TRUE; +} + +static gboolean +app_launched_idle_cb (gpointer data) +{ + WestonGtkLauncher *self = data; + GtkAdjustment *adjustment; + + /* make the scrolled window go back to the top */ + + adjustment = gtk_scrolled_window_get_vadjustment ( + GTK_SCROLLED_WINDOW (self->priv->scrolled_window)); + + gtk_adjustment_set_value (adjustment, 0.0); + + return FALSE; +} + +static void +clicked_cb (GtkWidget *widget, + GDesktopAppInfo *info) +{ + WestonGtkLauncher *self; + + g_app_info_launch (G_APP_INFO (info), NULL, NULL, NULL); + + self = g_object_get_data (G_OBJECT (widget), "launcher"); + g_assert (self); + g_signal_emit (self, signals[APP_LAUNCHED], 0); + + /* do this in an idle so it's not done so obviously onscreen */ + g_idle_add (app_launched_idle_cb, self); +} + +static gboolean +app_enter_cb (GtkWidget *widget, + GdkEvent *event, + GtkWidget *revealer) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE); + return FALSE; +} + +static gboolean +app_leave_cb (GtkWidget *widget, + GdkEvent *event, + GtkWidget *revealer) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), FALSE); + return FALSE; +} + +static GtkWidget * +app_launcher_new_from_desktop_info (WestonGtkLauncher *self, + GDesktopAppInfo *info) +{ + GIcon *icon; + GtkWidget *alignment; + GtkWidget *image; + GtkWidget *button; + GtkWidget *overlay; + GtkWidget *revealer; + GtkWidget *label; + GtkWidget *ebox; + + /* we need an ebox to catch enter and leave events */ + ebox = gtk_event_box_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (ebox), + "wgs-grid-item"); + + /* we use an overlay so we can have the app icon showing but use a + * GtkRevealer to show a label of the app's name. */ + overlay = gtk_overlay_new (); + gtk_container_add (GTK_CONTAINER (ebox), overlay); + + /* ...but each item has a border of 1px and we don't want the + * revealer to paint into the border, so overload this function to + * know where to put it. */ + g_signal_connect (overlay, "get-child-position", + G_CALLBACK (get_child_position_cb), NULL); + + revealer = gtk_revealer_new (); + g_object_set (revealer, + "halign", GTK_ALIGN_FILL, /* all the width */ + "valign", GTK_ALIGN_END, /* only at the bottom */ + NULL); + gtk_revealer_set_transition_type (GTK_REVEALER (revealer), + GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP); + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), FALSE); + gtk_overlay_add_overlay (GTK_OVERLAY (overlay), revealer); + + /* app name */ + label = gtk_label_new (g_app_info_get_display_name (G_APP_INFO (info))); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "wgs-grid-label"); + gtk_container_add (GTK_CONTAINER (revealer), label); + + /* icon button to load the app */ + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_container_add (GTK_CONTAINER (overlay), alignment); + + icon = g_app_info_get_icon (G_APP_INFO (info)); + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG); + button = gtk_button_new (); + gtk_style_context_remove_class ( + gtk_widget_get_style_context (button), + "button"); + gtk_style_context_remove_class ( + gtk_widget_get_style_context (button), + "image-button"); + gtk_button_set_image (GTK_BUTTON (button), image); + g_object_set (image, + "margin", 30, + NULL); + gtk_container_add (GTK_CONTAINER (alignment), button); + + /* TODO: a bit ugly */ + g_object_set_data (G_OBJECT (button), "launcher", self); + g_signal_connect (button, "clicked", G_CALLBACK (clicked_cb), info); + + /* now we have set everything up, we can refernce the ebox and the + * revealer. enter will show the label and leave will hide the label. */ + g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (app_enter_cb), revealer); + g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (app_leave_cb), revealer); + + return ebox; +} + +static void +installed_changed_cb (ShellAppSystem *app_system, + WestonGtkLauncher *self) +{ + GHashTable *entries = shell_app_system_get_entries (app_system); + GList *l, *values; + + gint output_width, output_height; + guint cols; + guint left, top; + + /* remove all children first */ + gtk_container_foreach (GTK_CONTAINER (self->priv->grid), + (GtkCallback) gtk_widget_destroy, NULL); + + values = g_hash_table_get_values (entries); + values = g_list_sort (values, sort_apps); + + weston_gtk_launcher_calculate (self, NULL, NULL, &cols); + cols--; /* because we start from zero here */ + + left = top = 0; + for (l = values; l; l = l->next) + { + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (l->data); + GtkWidget *app = app_launcher_new_from_desktop_info (self, info); + + gtk_grid_attach (GTK_GRID (self->priv->grid), app, left++, top, 1, 1); + + if (left > cols) + { + left = 0; + top++; + } + } + + g_list_free (values); + + gtk_widget_show_all (self->priv->grid); +} + +static void +background_size_allocate_cb (GtkWidget *widget, + GdkRectangle *allocation, + WestonGtkLauncher *self) +{ + installed_changed_cb (self->priv->app_system, self); +} + +static void +weston_gtk_launcher_constructed (GObject *object) +{ + WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object); + + G_OBJECT_CLASS (weston_gtk_launcher_parent_class)->constructed (object); + + /* window properties */ + gtk_window_set_title (GTK_WINDOW (self), "gtk shell"); + gtk_window_set_decorated (GTK_WINDOW (self), FALSE); + gtk_widget_realize (GTK_WIDGET (self)); + + /* make it black and slightly alpha */ + gtk_style_context_add_class ( + gtk_widget_get_style_context (GTK_WIDGET (self)), + "wgs-grid"); + + /* scroll it */ + self->priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (self), self->priv->scrolled_window); + + /* main grid for apps */ + self->priv->grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (self->priv->scrolled_window), + self->priv->grid); + + /* fill the grid with apps */ + self->priv->app_system = shell_app_system_get_default (); + g_signal_connect (self->priv->app_system, "installed-changed", + G_CALLBACK (installed_changed_cb), self); + + /* refill the grid if the background is changed */ + g_assert (self->priv->background != NULL); + g_signal_connect (self->priv->background, "size-allocate", + G_CALLBACK (background_size_allocate_cb), self); + + /* now actually fill the grid */ + installed_changed_cb (self->priv->app_system, self); +} + +static void +weston_gtk_launcher_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object); + + switch (param_id) + { + case PROP_BACKGROUND: + g_value_set_object (value, self->priv->background); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +weston_gtk_launcher_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object); + + switch (param_id) + { + case PROP_BACKGROUND: + self->priv->background = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +weston_gtk_launcher_class_init (WestonGtkLauncherClass *klass) +{ + GObjectClass *object_class = (GObjectClass *)klass; + + object_class->constructed = weston_gtk_launcher_constructed; + object_class->get_property = weston_gtk_launcher_get_property; + object_class->set_property = weston_gtk_launcher_set_property; + + g_object_class_install_property (object_class, PROP_BACKGROUND, + g_param_spec_object ("background", + "background", + "The #GtkWidget of the background of the output", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + signals[APP_LAUNCHED] = g_signal_new ("app-launched", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 0); + + g_type_class_add_private (object_class, sizeof (WestonGtkLauncherPrivate)); +} + +GtkWidget * +weston_gtk_launcher_new (GtkWidget *background_widget) +{ + return g_object_new (WESTON_GTK_LAUNCHER_TYPE, + "background", background_widget, + NULL); +} + +void +weston_gtk_launcher_calculate (WestonGtkLauncher *self, + gint *grid_window_width, + gint *grid_window_height, + gint *grid_cols) +{ + gint output_width, output_height; + gint panel_height; + gint usable_width, usable_height; + guint cols, rows; + + gtk_widget_get_size_request (self->priv->background, + &output_width, &output_height); + + panel_height = output_height * WESTON_GTK_PANEL_HEIGHT_RATIO; + + /* the scrollbar is 13 pixels wide */ + usable_width = output_width - WESTON_GTK_PANEL_WIDTH - 13; + + /* we want the height from under the clock which means we need to + * work out where the clock is, then add the clock height */ + usable_height = output_height - (((output_height - panel_height) / 2) + WESTON_GTK_CLOCK_HEIGHT); + + cols = (guint) (usable_width / GRID_ITEM_WIDTH); + rows = (guint) (usable_height / GRID_ITEM_WIDTH); + + /* we don't want any blank space at the bottom of the grid so make + * sure it's full and resize if necessary */ + /* TODO: this might not actually be a great idea. */ + if (cols > 0) + { + guint num_apps; + + num_apps = g_hash_table_size ( + shell_app_system_get_entries (self->priv->app_system)); + + /* worst case cols = 1 */ + while ((num_apps % cols) > 0) + cols--; + } + + if (grid_window_width) + *grid_window_width = (cols * GRID_ITEM_WIDTH) + 13; /* add back the 13 for the scrollbar */ + + if (grid_window_height) + *grid_window_height = (rows * GRID_ITEM_WIDTH); + + if (grid_cols) + *grid_cols = cols; +} diff --git a/shell/launcher.h b/shell/launcher.h new file mode 100644 index 0000000..3bd10b0 --- /dev/null +++ b/shell/launcher.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 Collabora Ltd. + * + * Author: Jonny Lamb + */ + +#ifndef __WESTON_GTK_LAUNCHER_H__ +#define __WESTON_GTK_LAUNCHER_H__ + +#include + +#define WESTON_GTK_LAUNCHER_TYPE (weston_gtk_launcher_get_type ()) +#define WESTON_GTK_LAUNCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WESTON_GTK_LAUNCHER_TYPE, WestonGtkLauncher)) +#define WESTON_GTK_LAUNCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WESTON_GTK_LAUNCHER_TYPE, WestonGtkLauncherClass)) +#define WESTON_GTK_IS_LAUNCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WESTON_GTK_LAUNCHER_TYPE)) +#define WESTON_GTK_IS_LAUNCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WESTON_GTK_LAUNCHER_TYPE)) +#define WESTON_GTK_LAUNCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WESTON_GTK_LAUNCHER_TYPE, WestonGtkLauncherClass)) + +typedef struct WestonGtkLauncher WestonGtkLauncher; +typedef struct WestonGtkLauncherClass WestonGtkLauncherClass; +typedef struct WestonGtkLauncherPrivate WestonGtkLauncherPrivate; + +struct WestonGtkLauncher +{ + GtkWindow parent; + + WestonGtkLauncherPrivate *priv; +}; + +struct WestonGtkLauncherClass +{ + GtkWindowClass parent_class; +}; + +GType weston_gtk_launcher_get_type (void) G_GNUC_CONST; + +GtkWidget * weston_gtk_launcher_new (GtkWidget *background_widget); + +void weston_gtk_launcher_calculate (WestonGtkLauncher *self, + gint *grid_window_width, gint *grid_window_height, + gint *grid_cols); + +#endif /* __WESTON_GTK_LAUNCHER_H__ */ diff --git a/shell/style.css b/shell/style.css index 4cb3fb5..b623430 100644 --- a/shell/style.css +++ b/shell/style.css @@ -29,3 +29,21 @@ background-color: #929292; color: white; } + +.wgs-grid { + background-color: alpha(white, 0.5); +} + +.wgs-grid-item { + background-color: alpha(black, 0.7); + color: white; + font: Droid Sans 12; + border-style: solid; + border-color: #6d6d6d; + border-width: 1px; + border-radius: 1px; +} + +.wgs-grid-label { + background-color: #929292; +} \ No newline at end of file -- cgit v1.3.1