Compare commits

...

2 Commits

Author SHA1 Message Date
Matthias Clasen
5215ae5228 theme: Adapt some of the notebook style
Make the tab bar appear similar to the way
GtkNotebook looks.
2023-07-04 18:27:37 -04:00
Matthias Clasen
389da86351 Add a GtkTabBar widget
This is a widget that functions as a stackswitcher,
providing GtkNotebook-like functionality with
GtkStack.
2023-07-04 18:27:30 -04:00
7 changed files with 974 additions and 0 deletions

View File

@@ -263,6 +263,7 @@
#include <gtk/gtkstyleprovider.h> #include <gtk/gtkstyleprovider.h>
#include <gtk/gtkswitch.h> #include <gtk/gtkswitch.h>
#include <gtk/gtksymbolicpaintable.h> #include <gtk/gtksymbolicpaintable.h>
#include <gtk/gtktabbar.h>
#include <gtk/gtktext.h> #include <gtk/gtktext.h>
#include <gtk/gtktextbuffer.h> #include <gtk/gtktextbuffer.h>
#include <gtk/gtktextchild.h> #include <gtk/gtktextchild.h>

571
gtk/gtktabbar.c Normal file
View File

@@ -0,0 +1,571 @@
#include "config.h"
#include "gtktabbar.h"
#include "gtkboxlayout.h"
#include "gtkenums.h"
#include "gtklabel.h"
#include "gtkorientable.h"
#include "gtkprivate.h"
#include "gtkselectionmodel.h"
#include "gtkstack.h"
#include "gtktabwidgetprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
/**
* GtkTabBar:
*
* `GtkTabBar` is a stack switcher that can be used with `GtkStack`
* to prove an user experience similar to `GtkNotebook`.
*
* Since: 4.12
*/
struct _GtkTabBar {
GtkWidget parent_instance;
GtkStack *stack;
GtkSelectionModel *pages;
GPtrArray *tabs;
GtkPositionType position;
};
struct _GtkTabBarClass {
GtkWidget parent_class;
};
enum {
PROP_POSITION = 1,
PROP_STACK,
NUM_PROPERTIES,
PROP_ORIENTATION = NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES] = { 0, };
G_DEFINE_TYPE_WITH_CODE (GtkTabBar, gtk_tab_bar, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
static void
create_tabs (GtkTabBar *self)
{
for (unsigned int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->pages)); i++)
{
GtkStackPage *page;
GtkWidget *tab;
page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
tab = gtk_tab_widget_new (page, i);
gtk_widget_set_parent (tab, GTK_WIDGET (self));
/* Note: self->tabs matches pages for order, not tabs */
g_ptr_array_add (self->tabs, tab);
g_object_unref (page);
}
}
static void
clear_tabs (GtkTabBar *self)
{
for (unsigned int i = 0; i < self->tabs->len; i++)
{
GtkWidget *tab = g_ptr_array_index (self->tabs, i);
gtk_widget_unparent (tab);
}
g_ptr_array_set_size (self->tabs, 0);
}
static void
items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkTabBar *self)
{
clear_tabs (self);
create_tabs (self);
}
static void
selection_changed_cb (GtkSelectionModel *model,
guint position,
guint n_items,
GtkTabBar *self)
{
for (unsigned int i = position; i < position + n_items; i++)
{
GtkStackPage *page;
GtkWidget *tab;
gboolean selected;
page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
tab = g_ptr_array_index (self->tabs, i);
selected = gtk_selection_model_is_selected (self->pages, i);
if (selected)
gtk_widget_set_state_flags (tab, GTK_STATE_FLAG_SELECTED, FALSE);
else
gtk_widget_unset_state_flags (tab, GTK_STATE_FLAG_SELECTED);
gtk_accessible_update_state (GTK_ACCESSIBLE (tab),
GTK_ACCESSIBLE_STATE_SELECTED, selected,
-1);
g_object_unref (page);
}
}
static void
set_stack (GtkTabBar *self,
GtkStack *stack)
{
g_assert (self->stack == NULL);
if (stack)
{
self->stack = g_object_ref (stack);
self->pages = gtk_stack_get_pages (stack);
create_tabs (self);
selection_changed_cb (self->pages,
0, g_list_model_get_n_items (G_LIST_MODEL (self->pages)),
self);
g_signal_connect (self->pages,
"items-changed", G_CALLBACK (items_changed_cb), self);
g_signal_connect (self->pages,
"selection-changed", G_CALLBACK (selection_changed_cb), self);
}
}
static void
unset_stack (GtkTabBar *self)
{
if (self->stack)
{
g_signal_handlers_disconnect_by_func (self->pages, items_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->pages, selection_changed_cb, self);
clear_tabs (self);
g_clear_object (&self->pages);
g_clear_object (&self->stack);
}
}
static void
gtk_tab_bar_init (GtkTabBar *self)
{
self->position = GTK_POS_TOP;
self->tabs = g_ptr_array_new ();
gtk_widget_add_css_class (GTK_WIDGET (self), "top");
}
static void
gtk_tab_bar_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTabBar *self = GTK_TAB_BAR (object);
GtkLayoutManager *box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, gtk_orientable_get_orientation (GTK_ORIENTABLE (box_layout)));
break;
case PROP_POSITION:
g_value_set_enum (value, self->position);
break;
case PROP_STACK:
g_value_set_object (value, self->stack);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tab_bar_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTabBar *self = GTK_TAB_BAR (object);
GtkLayoutManager *box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
switch (prop_id)
{
case PROP_ORIENTATION:
{
GtkOrientation orientation = g_value_get_enum (value);
if (gtk_orientable_get_orientation (GTK_ORIENTABLE (box_layout)) != orientation)
{
gtk_orientable_set_orientation (GTK_ORIENTABLE (box_layout), orientation);
gtk_widget_update_orientation (GTK_WIDGET (self), orientation);
g_object_notify_by_pspec (object, pspec);
}
}
break;
case PROP_POSITION:
gtk_tab_bar_set_position (self, g_value_get_enum (value));
break;
case PROP_STACK:
gtk_tab_bar_set_stack (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tab_bar_dispose (GObject *object)
{
GtkTabBar *self = GTK_TAB_BAR (object);
unset_stack (self);
G_OBJECT_CLASS (gtk_tab_bar_parent_class)->dispose (object);
}
static void
gtk_tab_bar_finalize (GObject *object)
{
GtkTabBar *self = GTK_TAB_BAR (object);
g_assert (self->tabs->len == 0);
g_ptr_array_unref (self->tabs);
G_OBJECT_CLASS (gtk_tab_bar_parent_class)->finalize (object);
}
static void
gtk_tab_bar_switch_tab (GtkWidget *widget,
const char *action_name,
GVariant *parameters)
{
GtkTabBar *self = GTK_TAB_BAR (widget);
unsigned int position;
if (!self->stack)
return;
g_variant_get (parameters, "u", &position);
gtk_selection_model_select_item (self->pages, position, TRUE);
}
static GtkDirectionType
get_effective_direction (GtkTabBar *self,
GtkDirectionType direction)
{
/* Remap the directions into the effective direction it would be for a
* ltr-and-top tabbar.
*/
#define D(rest) GTK_DIR_##rest
static const GtkDirectionType translate_direction[2][4][6] = {
/* LEFT */ {{ D(TAB_FORWARD), D(TAB_BACKWARD), D(LEFT), D(RIGHT), D(UP), D(DOWN) },
/* RIGHT */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(LEFT), D(RIGHT), D(DOWN), D(UP) },
/* TOP */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(UP), D(DOWN), D(LEFT), D(RIGHT) },
/* BOTTOM */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(DOWN), D(UP), D(LEFT), D(RIGHT) }},
/* LEFT */ {{ D(TAB_BACKWARD), D(TAB_FORWARD), D(LEFT), D(RIGHT), D(DOWN), D(UP) },
/* RIGHT */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(LEFT), D(RIGHT), D(UP), D(DOWN) },
/* TOP */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(UP), D(DOWN), D(RIGHT), D(LEFT) },
/* BOTTOM */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(DOWN), D(UP), D(RIGHT), D(LEFT) }},
};
#undef D
int text_dir = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL ? 1 : 0;
return translate_direction[text_dir][self->position][direction];
}
static GtkPositionType
get_effective_position (GtkTabBar *self)
{
if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
{
switch ((int)self->position)
{
case GTK_POS_LEFT:
return GTK_POS_RIGHT;
case GTK_POS_RIGHT:
return GTK_POS_LEFT;
default: ;
}
}
return self->position;
}
static gboolean
gtk_tab_bar_focus (GtkWidget *widget,
GtkDirectionType dir)
{
GtkTabBar *self = GTK_TAB_BAR (widget);
GtkWidget *old_focus_child;
unsigned int n_items;
GtkDirectionType direction;
direction = get_effective_direction (self, dir);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->pages));
old_focus_child = gtk_widget_get_focus_child (widget);
if (old_focus_child)
{
unsigned int position;
g_object_get (old_focus_child, "position", &position, NULL);
if (direction == GTK_DIR_TAB_FORWARD ||
direction == GTK_DIR_TAB_BACKWARD)
return FALSE;
if (direction == GTK_DIR_LEFT ||
direction == GTK_DIR_RIGHT)
{
GtkWidget *tab;
if (direction == GTK_DIR_LEFT)
position = (position + n_items - 1) % n_items;
else
position = (position + 1) % n_items;
tab = g_ptr_array_index (self->tabs, position);
gtk_widget_grab_focus (tab);
gtk_selection_model_select_item (self->pages, position, TRUE);
return TRUE;
}
if (gtk_widget_child_focus (old_focus_child, direction))
{
return TRUE;
}
}
else
{
for (unsigned int i = 0; i < n_items; i++)
{
if (gtk_selection_model_is_selected (self->pages, i))
{
GtkWidget *tab;
tab = g_ptr_array_index (self->tabs, i);
gtk_widget_grab_focus (tab);
return TRUE;
}
}
}
return gtk_widget_focus_child (widget, direction);
}
static void
gtk_tab_bar_class_init (GtkTabBarClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->get_property = gtk_tab_bar_get_property;
object_class->set_property = gtk_tab_bar_set_property;
object_class->dispose = gtk_tab_bar_dispose;
object_class->finalize = gtk_tab_bar_finalize;
widget_class->focus = gtk_tab_bar_focus;
/**
* GtkTabBar:position: (attributes org.gtk.Property.get=gtk_tab_bar_get_position org.gtk.Property.set=gtk_tab_bar_set_position)
*
* The position of the tab bar relative to the stack it controls.
*
* This information is used in keynav and tab rendering.
*/
properties[PROP_POSITION] =
g_param_spec_enum ("position", NULL, NULL,
GTK_TYPE_POSITION_TYPE,
GTK_POS_TOP,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTabBar:stack: (attributes org.gtk.Property.get=gtk_tab_bar_get_stack org.gtk.Property.set=gtk_tab_bar_set_stack)
*
* The stack that is controlled by this tab bar.
*/
properties[PROP_STACK] =
g_param_spec_object ("stack", NULL, NULL,
GTK_TYPE_STACK,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
gtk_widget_class_install_action (widget_class, "tab.switch", "u",
gtk_tab_bar_switch_tab);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("tabbar"));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB_LIST);
}
/**
* gtk_tab_bar_new:
*
* Creates a new `GtkTabBar`.
*
* Returns: the newly created widget
*
* Since: 4.12
*/
GtkTabBar *
gtk_tab_bar_new (void)
{
return g_object_new (GTK_TYPE_TAB_BAR, NULL);
}
/**
* gtk_stack_bar_set_stack:
* @self: a `GtkTabBar`
* @stack: (nullable): a `GtkStack`
*
* Sets the stack that is controlled by this tab bar.
*
* Since: 4.12
*/
void
gtk_tab_bar_set_stack (GtkTabBar *self,
GtkStack *stack)
{
g_return_if_fail (GTK_IS_TAB_BAR (self));
g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
if (self->stack == stack)
return;
unset_stack (self);
set_stack (self, stack);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STACK]);
}
/**
* gtk_tab_bar_get_stack:
* @self: a `GtkStack`
*
* Returns the stack that is controlled by this tab bar.
*
* Returns: (nullable): the stack
*
* Since: 4.12
*/
GtkStack *
gtk_tab_bar_get_stack (GtkTabBar *self)
{
g_return_val_if_fail (GTK_IS_TAB_BAR (self), NULL);
return self->stack;
}
static void
update_css_class_for_position (GtkTabBar *self)
{
GtkWidget *widget = GTK_WIDGET (self);
gtk_widget_remove_css_class (widget, "top");
gtk_widget_remove_css_class (widget, "bottom");
gtk_widget_remove_css_class (widget, "left");
gtk_widget_remove_css_class (widget, "right");
switch (get_effective_position (self))
{
case GTK_POS_TOP:
gtk_widget_add_css_class (widget, "top");
break;
case GTK_POS_BOTTOM:
gtk_widget_add_css_class (widget, "bottom");
break;
case GTK_POS_LEFT:
gtk_widget_add_css_class (widget, "left");
break;
case GTK_POS_RIGHT:
gtk_widget_add_css_class (widget, "right");
break;
default:
g_assert_not_reached ();
}
}
/**
* gtk_tab_bar_set_position:
* @self: a `GtkTabBar`
* @position: the position of the tabs
*
* Sets the position of the tab bar relative to
* the stack that it controls.
*
* This information is used in keynav and for
* drawing the tabs. Setting the position also
* updates the orientation accordingly.
*
* Since: 4.12
*/
void
gtk_tab_bar_set_position (GtkTabBar *self,
GtkPositionType position)
{
g_return_if_fail (GTK_IS_TAB_BAR (self));
if (self->position == position)
return;
self->position = position;
if (position == GTK_POS_LEFT || position == GTK_POS_RIGHT)
gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
else
gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
update_css_class_for_position (self);
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]);
}
/**
* gtk_tab_bar_get_position:
* @self: a `GtkTabBar`
*
* Gets the position of the tab bar relative to
* the stack that it controls.
*
* Returns: the position of the tabss
*
* Since: 4.12
*/
GtkPositionType
gtk_tab_bar_get_position (GtkTabBar *self)
{
g_return_val_if_fail (GTK_IS_TAB_BAR (self), GTK_POS_TOP);
return self->position;
}

24
gtk/gtktabbar.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <gtk/gtkstack.h>
#include <gtk/gtkwidget.h>
#define GTK_TYPE_TAB_BAR (gtk_tab_bar_get_type ())
G_DECLARE_FINAL_TYPE (GtkTabBar, gtk_tab_bar, GTK, TAB_BAR, GtkWidget)
GDK_AVAILABLE_IN_4_12
GtkTabBar * gtk_tab_bar_new (void);
GDK_AVAILABLE_IN_4_12
void gtk_tab_bar_set_stack (GtkTabBar *self,
GtkStack *stack);
GDK_AVAILABLE_IN_4_12
GtkStack * gtk_tab_bar_get_stack (GtkTabBar *self);
GDK_AVAILABLE_IN_4_12
void gtk_tab_bar_set_position (GtkTabBar *self,
GtkPositionType position);
GDK_AVAILABLE_IN_4_12
GtkPositionType gtk_tab_bar_get_position (GtkTabBar *self);

277
gtk/gtktabwidget.c Normal file
View File

@@ -0,0 +1,277 @@
#include "config.h"
#include "gtktabwidgetprivate.h"
#include "gtkaccessible.h"
#include "gtkbinlayout.h"
#include "gtkdropcontrollermotion.h"
#include "gtkgestureclick.h"
#include "gtklabel.h"
#include "gtkprivate.h"
#include "gtkstack.h"
#include "gtkwidget.h"
#define TIMEOUT_EXPAND 500
struct _GtkTabWidget {
GtkWidget parent_instance;
GtkStackPage *page;
GtkWidget *label;
unsigned int position;
unsigned int switch_timeout;
};
struct _GtkTabWidgetClass {
GtkWidget parent_class;
};
enum {
PROP_PAGE = 1,
PROP_POSITION,
NUM_PROPERTIES,
};
static GParamSpec *properties[NUM_PROPERTIES] = { 0, };
G_DEFINE_TYPE (GtkTabWidget, gtk_tab_widget, GTK_TYPE_WIDGET)
static void
pressed_cb (GtkGestureClick *gesture,
unsigned int n_press,
double x,
double y,
GtkTabWidget *self)
{
gtk_widget_activate_action (GTK_WIDGET (self), "tab.switch", "u", self->position);
}
static void
update_tab (GtkStackPage *page,
GParamSpec *pspec,
GtkTabWidget *self)
{
char *title;
char *icon_name;
gboolean needs_attention;
gboolean visible;
gboolean use_underline;
g_object_get (page,
"title", &title,
"icon-name", &icon_name,
"needs-attention", &needs_attention,
"visible", &visible,
"use-underline", &use_underline,
NULL);
gtk_label_set_label (GTK_LABEL (self->label), title);
gtk_accessible_update_property (GTK_ACCESSIBLE (self),
GTK_ACCESSIBLE_PROPERTY_LABEL, title,
-1);
gtk_widget_set_visible (GTK_WIDGET (self), visible && (title != NULL || icon_name != NULL));
if (needs_attention)
gtk_widget_add_css_class (GTK_WIDGET (self), "needs-attention");
else
gtk_widget_remove_css_class (GTK_WIDGET (self), "needs-attention");
g_free (title);
g_free (icon_name);
}
static void
unset_page (GtkTabWidget *self)
{
if (!self->page)
return;
g_signal_handlers_disconnect_by_func (self->page, update_tab, self);
g_clear_object (&self->page);
}
static void
set_page (GtkTabWidget *self,
GtkStackPage *page)
{
if (self->page == page)
return;
unset_page (self);
g_set_object (&self->page, page);
g_signal_connect (self->page, "notify", G_CALLBACK (update_tab), self);
update_tab (page, NULL, self);
}
static gboolean
gtk_tab_widget_switch_timeout (gpointer data)
{
GtkTabWidget *self = data;
gtk_widget_activate_action (GTK_WIDGET (self), "tab.switch", "u", self->position);
self->switch_timeout = 0;
return G_SOURCE_REMOVE;
}
static void
gtk_tab_widget_drag_enter (GtkDropControllerMotion *motion,
double x,
double y,
GtkTabWidget *self)
{
if ((gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_SELECTED) == 0)
{
self->switch_timeout = g_timeout_add (TIMEOUT_EXPAND,
gtk_tab_widget_switch_timeout,
self);
gdk_source_set_static_name_by_id (self->switch_timeout,
"[gtk] gtk_tab_widget_switch_timeout");
}
}
static void
gtk_tab_widget_drag_leave (GtkDropControllerMotion *motion,
GtkTabWidget *self)
{
if (self->switch_timeout)
{
g_source_remove (self->switch_timeout);
self->switch_timeout = 0;
}
}
static void
gtk_tab_widget_init (GtkTabWidget *self)
{
GtkEventController *controller;
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
self->label = gtk_label_new ("");
gtk_widget_set_parent (self->label, GTK_WIDGET (self));
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
g_signal_connect (controller, "pressed", G_CALLBACK (pressed_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = gtk_drop_controller_motion_new ();
g_signal_connect (controller, "enter", G_CALLBACK (gtk_tab_widget_drag_enter), self);
g_signal_connect (controller, "leave", G_CALLBACK (gtk_tab_widget_drag_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
static void
gtk_tab_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTabWidget *self = GTK_TAB_WIDGET (object);
switch (prop_id)
{
case PROP_PAGE:
g_value_set_object (value, self->page);
break;
case PROP_POSITION:
g_value_set_uint (value, self->position);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tab_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTabWidget *self = GTK_TAB_WIDGET (object);
switch (prop_id)
{
case PROP_PAGE:
set_page (self, g_value_get_object (value));
break;
case PROP_POSITION:
self->position = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tab_widget_dispose (GObject *object)
{
GtkTabWidget *self = GTK_TAB_WIDGET (object);
unset_page (self);
g_clear_pointer (&self->label, gtk_widget_unparent);
if (self->switch_timeout)
{
g_source_remove (self->switch_timeout);
self->switch_timeout = 0;
}
G_OBJECT_CLASS (gtk_tab_widget_parent_class)->dispose (object);
}
static void
gtk_tab_widget_finalize (GObject *object)
{
G_OBJECT_CLASS (gtk_tab_widget_parent_class)->finalize (object);
}
static void
gtk_tab_widget_class_init (GtkTabWidgetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->get_property = gtk_tab_widget_get_property;
object_class->set_property = gtk_tab_widget_set_property;
object_class->dispose = gtk_tab_widget_dispose;
object_class->finalize = gtk_tab_widget_finalize;
properties[PROP_PAGE] =
g_param_spec_object ("page", NULL, NULL,
GTK_TYPE_STACK_PAGE,
GTK_PARAM_READWRITE);
properties[PROP_POSITION] =
g_param_spec_uint ("position", NULL, NULL,
0, G_MAXUINT, 0,
GTK_PARAM_READWRITE);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("tab"));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB);
}
GtkWidget *
gtk_tab_widget_new (GtkStackPage *page,
unsigned int position)
{
return g_object_new (GTK_TYPE_TAB_WIDGET,
"page", page,
"position", position,
NULL);
}

10
gtk/gtktabwidgetprivate.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <gtk/gtkwidget.h>
#include <gtk/gtkstack.h>
#define GTK_TYPE_TAB_WIDGET (gtk_tab_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkTabWidget, gtk_tab_widget, GTK, TAB_WIDGET, GtkWidget)
GtkWidget * gtk_tab_widget_new (GtkStackPage *page,
unsigned int position);

View File

@@ -95,6 +95,7 @@ gtk_private_sources = files([
'gtkcssstylechange.c', 'gtkcssstylechange.c',
'gtkcssstyleproperty.c', 'gtkcssstyleproperty.c',
'gtkcssstylepropertyimpl.c', 'gtkcssstylepropertyimpl.c',
'gtktabwidget.c',
'gtkcsstransformvalue.c', 'gtkcsstransformvalue.c',
'gtkcsstransientnode.c', 'gtkcsstransientnode.c',
'gtkcsstransition.c', 'gtkcsstransition.c',
@@ -365,6 +366,7 @@ gtk_public_sources = files([
'gtkstyleprovider.c', 'gtkstyleprovider.c',
'gtkswitch.c', 'gtkswitch.c',
'gtksymbolicpaintable.c', 'gtksymbolicpaintable.c',
'gtktabbar.c',
'gtktestatcontext.c', 'gtktestatcontext.c',
'gtktestutils.c', 'gtktestutils.c',
'gtktext.c', 'gtktext.c',
@@ -594,6 +596,7 @@ gtk_public_headers = files([
'gtkstyleprovider.h', 'gtkstyleprovider.h',
'gtkswitch.h', 'gtkswitch.h',
'gtksymbolicpaintable.h', 'gtksymbolicpaintable.h',
'gtktabbar.h',
'gtktestatcontext.h', 'gtktestatcontext.h',
'gtktestutils.h', 'gtktestutils.h',
'gtktext.h', 'gtktext.h',

View File

@@ -2103,6 +2103,94 @@ menubar {
} }
/************
* Tabbar *
************/
tabbar {
padding: 1px;
border-color: $borders_color;
border-width: 1px;
background-color: $dark_fill;
margin: -1px;
&.top {
border-bottom-style: solid;
margin-bottom: -2px;
> tab {
&:hover { box-shadow: inset 0 -4px $borders_color; }
&:selected { box-shadow: inset 0 -4px $selected_bg_color; }
}
}
&.bottom {
border-top-style: solid;
margin-top: -2px;
> tab {
&:hover { box-shadow: inset 0 4px $borders_color; }
&:selected { box-shadow: inset 0 4px $selected_bg_color; }
}
}
&.left {
border-right-style: solid;
margin-right: -2px;
> tab {
&:hover { box-shadow: inset -4px 0 $borders_color; }
&:selected { box-shadow: inset -4px 0 $selected_bg_color; }
}
}
&.right {
border-left-style: solid;
margin-left: -2px;
> tab {
&:hover { box-shadow: inset 4px 0 $borders_color; }
&:selected { box-shadow: inset 4px 0 $selected_bg_color; }
}
}
> tab {
transition: $focus_transition;
min-height: 30px;
min-width: 30px;
padding: 3px 12px;
color: $fg_color;
font-weight: normal;
border-width: 1px; // for reorderable tabs
border-color: transparent; //
&:hover {
color: $fg_color;
background-color: darken($dark_fill,4%);
&.reorderable-page {
border-color: transparentize($borders_color, 0.7);
background-color: transparentize($bg_color, 0.8);
}
}
&:not(:selected) {
outline-color: transparent;
}
&:selected {
color: $fg_color;
&.reorderable-page {
border-color: transparentize($borders_color, 0.5);
background-color: transparentize($bg_color, 0.5);
&:hover { background-color: transparentize($bg_color, 0.3); }
}
}
}
&.top > tab { padding-bottom: 4px; }
&.bottom > tab { padding-top: 4px; }
}
/************* /*************
* Notebooks * * Notebooks *
*************/ *************/