[libadwaita/wip/exalm/tab-style: 10/11] tab-box: Refresh design
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/tab-style: 10/11] tab-box: Refresh design
- Date: Wed, 6 Apr 2022 21:23:16 +0000 (UTC)
commit 0a994e39c7b6e500397bace1480e3c4d60c79e1c
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Wed Apr 6 15:18:27 2022 +0400
tab-box: Refresh design
Ensure it's visible in dark variant.
Replace borders with separators. Make sure separators never touch a tab
with background.
Replace the needs-attention gradient with a bar; animate it.
Replace the undershoot gradient with a fade.
Fixes https://gitlab.gnome.org/GNOME/libadwaita/-/issues/318
src/adw-tab-bar-private.h | 5 +
src/adw-tab-bar.c | 16 ++
src/adw-tab-box.c | 283 +++++++++++++++++++++++++++++-----
src/adw-tab.c | 72 ++++++++-
src/adw-tab.ui | 6 +
src/stylesheet/widgets/_tab-view.scss | 180 ++++++---------------
6 files changed, 389 insertions(+), 173 deletions(-)
---
diff --git a/src/adw-tab-bar-private.h b/src/adw-tab-bar-private.h
index e191f092..f6670c63 100644
--- a/src/adw-tab-bar-private.h
+++ b/src/adw-tab-bar-private.h
@@ -14,8 +14,13 @@
#include "adw-tab-bar.h"
+#include "adw-tab-box-private.h"
+
G_BEGIN_DECLS
gboolean adw_tab_bar_tabs_have_visible_focus (AdwTabBar *self);
+AdwTabBox *adw_tab_bar_get_tab_box (AdwTabBar *self);
+AdwTabBox *adw_tab_bar_get_pinned_tab_box (AdwTabBar *self);
+
G_END_DECLS
diff --git a/src/adw-tab-bar.c b/src/adw-tab-bar.c
index 3c05f38d..6c7a5c32 100644
--- a/src/adw-tab-bar.c
+++ b/src/adw-tab-bar.c
@@ -1094,3 +1094,19 @@ adw_tab_bar_get_is_overflowing (AdwTabBar *self)
return self->is_overflowing;
}
+
+AdwTabBox *
+adw_tab_bar_get_tab_box (AdwTabBar *self)
+{
+ g_return_val_if_fail (ADW_IS_TAB_BAR (self), NULL);
+
+ return self->box;
+}
+
+AdwTabBox *
+adw_tab_bar_get_pinned_tab_box (AdwTabBar *self)
+{
+ g_return_val_if_fail (ADW_IS_TAB_BAR (self), NULL);
+
+ return self->pinned_box;
+}
diff --git a/src/adw-tab-box.c b/src/adw-tab-box.c
index 3f3be10b..4e62bb20 100644
--- a/src/adw-tab-box.c
+++ b/src/adw-tab-box.c
@@ -6,6 +6,10 @@
* Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
*/
+// FIXME expand broken end padding - unrelated, need to fix regardless
+// FIXME now we can have backdrop styles
+// FIXME restore the undershoot attention style
+
#include "config.h"
#include "adw-tab-box-private.h"
@@ -21,8 +25,7 @@
#include "adw-widget-utils-private.h"
#include <math.h>
-/* Border collapsing without glitches */
-#define SPACING -1
+#define SPACING 3
#define DND_THRESHOLD_MULTIPLIER 4
#define DROP_SWITCH_TIMEOUT 500
@@ -38,6 +41,9 @@
#define MAX_TAB_WIDTH_NON_EXPAND 220
+#define FADE_OFFSET 6.0f
+#define FADE_WIDTH 36.0f
+
typedef enum {
TAB_RESIZE_NORMAL,
TAB_RESIZE_FIXED_TAB_WIDTH,
@@ -63,6 +69,7 @@ typedef struct {
AdwTabPage *page;
AdwTab *tab;
GtkWidget *container;
+ GtkWidget *separator;
int pos;
int width;
@@ -101,7 +108,6 @@ struct _AdwTabBox
int n_tabs;
GtkPopover *context_menu;
- GtkWidget *background;
int allocated_width;
int last_width;
@@ -164,6 +170,9 @@ struct _AdwTabBox
GdkDragAction extra_drag_actions;
GType *extra_drag_types;
gsize extra_drag_n_types;
+
+ GskGLShader *shader;
+ gboolean shader_compiled;
};
G_DEFINE_FINAL_TYPE_WITH_CODE (AdwTabBox, adw_tab_box, GTK_TYPE_WIDGET,
@@ -200,6 +209,7 @@ static void
remove_and_free_tab_info (TabInfo *info)
{
gtk_widget_unparent (GTK_WIDGET (info->container));
+ gtk_widget_unparent (GTK_WIDGET (info->separator));
g_free (info);
}
@@ -403,6 +413,93 @@ is_touchscreen (GtkGesture *gesture)
return input_source == GDK_SOURCE_TOUCHSCREEN;
}
+static void
+update_separators (AdwTabBox *self)
+{
+ GList *l;
+ GtkStateFlags mask = GTK_STATE_FLAG_CHECKED |
+ GTK_STATE_FLAG_PRELIGHT |
+ GTK_STATE_FLAG_ACTIVE;
+ TabInfo *last_pinned_tab = NULL;
+
+ /* We have a separator between pinned and non-pinned tabs, and we need to
+ * sync it same as the ones within each tab box */
+ if (!self->pinned) {
+ AdwTabBox *box = adw_tab_bar_get_pinned_tab_box (self->tab_bar);
+
+ l = g_list_last (box->tabs);
+
+ if (l) {
+ last_pinned_tab = l->data;
+
+ if (last_pinned_tab->end_reorder_offset < 0) {
+ last_pinned_tab = box->reordered_tab;
+ } else if (l->prev && last_pinned_tab == box->reordered_tab) {
+ TabInfo *prev = l->prev->data;
+
+ if (prev->end_reorder_offset > 0)
+ last_pinned_tab = prev;
+ }
+ }
+ }
+
+ for (l = self->tabs; l; l = l->next) {
+ TabInfo *info = l->data;
+ TabInfo *prev = NULL;
+ TabInfo *prev_prev = NULL;
+ TabInfo *visually_prev = NULL;
+ GtkStateFlags flags;
+
+ if (l->prev)
+ prev = l->prev->data;
+ else if (!self->pinned)
+ prev = last_pinned_tab;
+
+ if (l->prev && l->prev->prev)
+ prev_prev = l->prev->prev->data;
+ else if (!self->pinned)
+ prev_prev = last_pinned_tab;
+
+ if (prev && prev_prev) {
+ /* Since the reordered tab has been moved away, the 2 tabs around it are
+ * now adjacent. Treat them as such for the separator purposes. */
+ if (prev == self->reordered_tab && prev_prev->end_reorder_offset > 0)
+ visually_prev = prev_prev;
+
+ if (prev == self->reordered_tab && info->end_reorder_offset < 0)
+ visually_prev = prev_prev;
+ }
+
+ if (prev && self->reordered_tab) {
+ /* There's a gap between the current and the previous tab. This means the
+ * reordered tab is between them, so treat is as the previous tab. */
+ if (info->end_reorder_offset - prev->end_reorder_offset > 0)
+ visually_prev = self->reordered_tab;
+ }
+
+ if (!visually_prev)
+ visually_prev = prev;
+
+ flags = gtk_widget_get_state_flags (GTK_WIDGET (info->tab));
+
+ if (visually_prev)
+ flags |= gtk_widget_get_state_flags (GTK_WIDGET (visually_prev->tab));
+
+ if ((flags & mask) || !visually_prev)
+ gtk_widget_add_css_class (info->separator, "hidden");
+ else
+ gtk_widget_remove_css_class (info->separator, "hidden");
+ }
+
+ /* Since the first non-pinned separator depends on pinned tabs, we need to
+ * notify the non-pinned box. We don't need to do the opposite though. */
+ if (self->pinned) {
+ AdwTabBox *box = adw_tab_bar_get_tab_box (self->tab_bar);
+
+ update_separators (box);
+ }
+}
+
/* Tab resize delay */
static void
@@ -945,6 +1042,8 @@ check_end_reordering (AdwTabBox *self)
gtk_widget_queue_allocate (GTK_WIDGET (self));
self->reordered_tab = NULL;
+
+ update_separators (self);
}
static void
@@ -956,6 +1055,8 @@ start_reordering (AdwTabBox *self,
/* The reordered tab should be displayed above everything else */
gtk_widget_insert_before (GTK_WIDGET (self->reordered_tab->container),
GTK_WIDGET (self), NULL);
+ gtk_widget_insert_before (GTK_WIDGET (self->reordered_tab->separator),
+ GTK_WIDGET (self), NULL);
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
@@ -1096,6 +1197,8 @@ reset_reorder_animations (AdwTabBox *self)
l = l->prev;
animate_reorder_offset (self, l->data, 0);
}
+
+ update_separators (self);
}
static void
@@ -1167,6 +1270,8 @@ page_reordered_cb (AdwTabBox *self,
}
self->continue_reorder = FALSE;
+
+ update_separators (self);
}
static void
@@ -1218,6 +1323,8 @@ update_drag_reodering (AdwTabBox *self)
animate_reorder_offset (self, info, offset);
}
+
+ update_separators (self);
}
static gboolean
@@ -1622,6 +1729,20 @@ allocate_tab (AdwGizmo *widget,
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-width_diff / 2, 0)));
}
+static void
+state_flags_changed_cb (GtkWidget *tab,
+ GtkStateFlags previous,
+ AdwTabBox *self)
+{
+ GtkStateFlags flags = gtk_widget_get_state_flags (tab);
+ GtkStateFlags mask = GTK_STATE_FLAG_PRELIGHT |
+ GTK_STATE_FLAG_ACTIVE |
+ GTK_STATE_FLAG_CHECKED;
+
+ if ((flags ^ previous) & mask)
+ update_separators (self);
+}
+
static TabInfo *
create_tab_info (AdwTabBox *self,
AdwTabPage *page)
@@ -1650,10 +1771,15 @@ create_tab_info (AdwTabBox *self,
self->extra_drag_types,
self->extra_drag_n_types);
+ info->separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+ gtk_widget_set_can_target (info->separator, FALSE);
+
gtk_widget_set_parent (GTK_WIDGET (info->tab), info->container);
+ gtk_widget_set_parent (info->separator, GTK_WIDGET (self));
gtk_widget_set_parent (info->container, GTK_WIDGET (self));
g_signal_connect_object (info->tab, "extra-drag-drop", G_CALLBACK (extra_drag_drop_cb), self, 0);
+ g_signal_connect_object (info->tab, "state-flags-changed", G_CALLBACK (state_flags_changed_cb), self, 0);
return info;
}
@@ -1706,6 +1832,8 @@ page_attached_cb (AdwTabBox *self,
adw_tab_box_select_page (self, page);
else
scroll_to_tab_full (self, info, -1, FOCUS_ANIMATION_DURATION, TRUE);
+
+ update_separators (self);
}
/* Closing */
@@ -1734,6 +1862,8 @@ close_animation_done_cb (TabInfo *info)
remove_and_free_tab_info (info);
self->n_tabs--;
+
+ update_separators (self);
}
static void
@@ -2001,6 +2131,8 @@ insert_placeholder (AdwTabBox *self,
G_CALLBACK (open_animation_done_cb), info);
adw_animation_play (info->appear_animation);
+
+ update_separators (self);
}
static void
@@ -2086,6 +2218,8 @@ remove_animation_done_cb (TabInfo *info)
self->n_tabs--;
self->reorder_placeholder = NULL;
+
+ update_separators (self);
}
static gboolean
@@ -2843,15 +2977,12 @@ adw_tab_box_measure (GtkWidget *widget,
AdwTabBox *self = ADW_TAB_BOX (widget);
int min, nat;
- gtk_widget_measure (self->background, orientation, -1,
- &min, &nat, NULL, NULL);
-
if (self->n_tabs == 0) {
if (minimum)
- *minimum = min;
+ *minimum = 0;
if (natural)
- *natural = nat;
+ *natural = 0;
if (minimum_baseline)
*minimum_baseline = -1;
@@ -2877,12 +3008,11 @@ adw_tab_box_measure (GtkWidget *widget,
}
if (!self->pinned)
- width += SPACING;
+ width += SPACING;
width = MAX (self->last_width, width);
- min = MAX (min, width);
- nat = MAX (nat, width);
+ min = nat = width;
} else {
GList *l;
@@ -2900,6 +3030,12 @@ adw_tab_box_measure (GtkWidget *widget,
if (child_nat > nat)
nat = child_nat;
+
+ gtk_widget_measure (info->separator, orientation, -1,
+ &child_min, NULL, NULL, NULL);
+
+ if (child_min > min)
+ min = child_min;
}
}
@@ -2929,8 +3065,6 @@ adw_tab_box_size_allocate (GtkWidget *widget,
int pos;
double value;
- gtk_widget_allocate (self->background, width, height, baseline, NULL);
-
adw_tab_box_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
&self->allocated_width, NULL, NULL, NULL);
self->allocated_width = MAX (self->allocated_width, width);
@@ -3002,6 +3136,8 @@ adw_tab_box_size_allocate (GtkWidget *widget,
for (l = self->tabs; l; l = l->next) {
TabInfo *info = l->data;
+ GtkAllocation separator_allocation;
+ int separator_width;
if (!info->appear_animation)
info->display_width = info->width;
@@ -3015,10 +3151,25 @@ adw_tab_box_size_allocate (GtkWidget *widget,
child_allocation.x = ((info == self->reordered_tab) ? self->reorder_window_x : info->pos) - (int) floor
(value);
child_allocation.y = 0;
- child_allocation.width = info->width;
+ child_allocation.width = MAX (0, info->width);
child_allocation.height = height;
+ gtk_widget_measure (info->separator, GTK_ORIENTATION_HORIZONTAL, -1,
+ &separator_width, NULL, NULL, NULL);
+ separator_allocation.x = child_allocation.x + child_allocation.width;
+ if (is_rtl) {
+ separator_allocation.x = child_allocation.x + child_allocation.width;
+ separator_allocation.x += (SPACING - separator_width) / 2;
+ } else {
+ separator_allocation.x = child_allocation.x;
+ separator_allocation.x -= (SPACING + separator_width) / 2;
+ }
+ separator_allocation.y = 0;
+ separator_allocation.width = separator_width;
+ separator_allocation.height = height;
+
gtk_widget_size_allocate (info->container, &child_allocation, baseline);
+ gtk_widget_size_allocate (info->separator, &separator_allocation, baseline);
pos += (is_rtl ? -1 : 1) * (info->width + SPACING);
}
@@ -3048,6 +3199,33 @@ adw_tab_box_size_allocate (GtkWidget *widget,
update_visible (self);
}
+static void
+ensure_shader (AdwTabBox *self)
+{
+ GtkNative *native;
+ GskRenderer *renderer;
+ GError *error = NULL;
+
+ if (self->shader)
+ return;
+
+ self->shader = gsk_gl_shader_new_from_resource ("/org/gnome/Adwaita/glsl/fade.glsl");
+
+ native = gtk_widget_get_native (GTK_WIDGET (self));
+ renderer = gtk_native_get_renderer (native);
+
+ self->shader_compiled = gsk_gl_shader_compile (self->shader, renderer, &error);
+
+ if (error) {
+ /* If shaders aren't supported, the error doesn't matter and we just
+ * silently fall back */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ g_critical ("Couldn't compile shader: %s\n", error->message);
+ }
+
+ g_clear_error (&error);
+}
+
static void
snapshot_tab (AdwTabBox *self,
GtkSnapshot *snapshot,
@@ -3066,7 +3244,7 @@ snapshot_tab (AdwTabBox *self,
scroll_pos = (int) floor (gtk_adjustment_get_value (self->adjustment));
pos = get_tab_position (self, info);
- width = info->width;
+ width = gtk_widget_get_allocated_width (info->container);
n = cairo_region_num_rectangles (clip_region);
for (i = 0; i < n; i++) {
@@ -3087,12 +3265,15 @@ snapshot_tab (AdwTabBox *self,
gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (clip_rect.x, clip_rect.y, clip_rect.width,
clip_rect.height));
gtk_widget_snapshot_child (GTK_WIDGET (self), info->container, snapshot);
+ gtk_widget_snapshot_child (GTK_WIDGET (self), info->separator, snapshot);
gtk_snapshot_pop (snapshot);
clip = TRUE;
}
- if (!clip)
+ if (!clip) {
gtk_widget_snapshot_child (GTK_WIDGET (self), info->container, snapshot);
+ gtk_widget_snapshot_child (GTK_WIDGET (self), info->separator, snapshot);
+ }
rect.x = pos - scroll_pos;
rect.width = width;
@@ -3100,15 +3281,13 @@ snapshot_tab (AdwTabBox *self,
}
static void
-adw_tab_box_snapshot (GtkWidget *widget,
- GtkSnapshot *snapshot)
+snapshot_tabs (AdwTabBox *self,
+ GtkSnapshot *snapshot)
{
- AdwTabBox *self = ADW_TAB_BOX (widget);
- int w = gtk_widget_get_width (widget);
- int h = gtk_widget_get_height (widget);
+ int w = gtk_widget_get_width (GTK_WIDGET (self));
+ int h = gtk_widget_get_height (GTK_WIDGET (self));
cairo_rectangle_int_t rect = { 0, 0, 0, 0 };
cairo_region_t *region;
- int i, n;
GList *l;
rect.width = w;
@@ -3129,17 +3308,54 @@ adw_tab_box_snapshot (GtkWidget *widget,
snapshot_tab (self, snapshot, info, region);
}
+}
- n = cairo_region_num_rectangles (region);
- for (i = 0; i < n; i++) {
- cairo_region_get_rectangle (region, i, &rect);
+static void
+adw_tab_box_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ AdwTabBox *self = ADW_TAB_BOX (widget);
+ double value = gtk_adjustment_get_value (self->adjustment);
+ double page_size = gtk_adjustment_get_page_size (self->adjustment);
+ double upper = gtk_adjustment_get_upper (self->adjustment);
+ graphene_rect_t bounds;
+ int width, height;
- gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (rect.x, rect.y, rect.width, rect.height));
- gtk_widget_snapshot_child (widget, self->background, snapshot);
- gtk_snapshot_pop (snapshot);
+ if (value <= 0 && value + page_size >= upper) {
+ snapshot_tabs (self, snapshot);
+ return;
}
- cairo_region_destroy (region);
+ ensure_shader (self);
+
+ width = gtk_widget_get_width (widget);
+ height = gtk_widget_get_height (widget);
+
+ graphene_rect_init (&bounds, 0, 0, width, height);
+
+ if (self->shader_compiled) {
+ gboolean fadeLeft = value > 0;
+ gboolean fadeRight = value + page_size < upper;
+
+ gtk_snapshot_push_gl_shader (snapshot, self->shader, &bounds,
+ gsk_gl_shader_format_args (self->shader,
+ "offsetLeft", FADE_OFFSET,
+ "offsetRight", FADE_OFFSET,
+ "strengthLeft", fadeLeft ? 1.0f : 0.0f,
+ "strengthRight", fadeRight ? 1.0f : 0.0f,
+ "widthLeft", FADE_WIDTH,
+ "widthRight", FADE_WIDTH,
+ NULL));
+ } else {
+ gtk_snapshot_push_clip (snapshot, &bounds);
+ }
+
+ snapshot_tabs (self, snapshot);
+
+ if (self->shader_compiled)
+ gtk_snapshot_gl_shader_pop_texture (snapshot);
+
+ gtk_snapshot_pop (snapshot);
}
static gboolean
@@ -3162,6 +3378,8 @@ adw_tab_box_unrealize (GtkWidget *widget)
g_clear_pointer ((GtkWidget **) &self->context_menu, gtk_widget_unparent);
GTK_WIDGET_CLASS (adw_tab_box_parent_class)->unrealize (widget);
+
+ g_clear_object (&self->shader);
}
static void
@@ -3216,8 +3434,6 @@ adw_tab_box_dispose (GObject *object)
g_clear_handle_id (&self->drop_switch_timeout_id, g_source_remove);
- g_clear_pointer (&self->background, gtk_widget_unparent);
-
self->drag_gesture = NULL;
self->tab_bar = NULL;
adw_tab_box_set_view (self, NULL);
@@ -3440,11 +3656,6 @@ adw_tab_box_init (AdwTabBox *self)
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
- self->background = adw_gizmo_new ("background", NULL, NULL, NULL, NULL, NULL, NULL);
- gtk_widget_set_can_target (self->background, FALSE);
- gtk_widget_set_can_focus (self->background, FALSE);
- gtk_widget_set_parent (self->background, GTK_WIDGET (self));
-
controller = gtk_event_controller_motion_new ();
g_signal_connect_swapped (controller, "motion", G_CALLBACK (motion_cb), self);
g_signal_connect_swapped (controller, "leave", G_CALLBACK (leave_cb), self);
diff --git a/src/adw-tab.c b/src/adw-tab.c
index 36978829..a9e0eb9c 100644
--- a/src/adw-tab.c
+++ b/src/adw-tab.c
@@ -20,6 +20,12 @@
#define BASE_WIDTH 118
#define BASE_WIDTH_PINNED 28
+#define ATTENTION_INDICATOR_PINNED_WIDTH 14
+#define ATTENTION_INDICATOR_WIDTH_MULTIPLIER 0.6
+#define ATTENTION_INDICATOR_MIN_WIDTH 20
+#define ATTENTION_INDICATOR_MAX_WIDTH 180
+#define ATTENTION_INDICATOR_ANIMATION_DURATION 250
+
struct _AdwTab
{
GtkWidget parent_instance;
@@ -31,6 +37,7 @@ struct _AdwTab
GtkImage *indicator_icon;
GtkWidget *indicator_btn;
GtkWidget *close_btn;
+ GtkWidget *needs_attention_indicator;
GtkDropTarget *drop_target;
AdwTabView *view;
@@ -47,6 +54,7 @@ struct _AdwTab
gboolean fully_visible;
AdwAnimation *close_btn_animation;
+ AdwAnimation *needs_attention_animation;
GskGLShader *shader;
gboolean shader_compiled;
@@ -93,6 +101,13 @@ close_btn_animation_value_cb (double value,
gtk_widget_queue_draw (GTK_WIDGET (self));
}
+static void
+attention_indicator_animation_value_cb (double value,
+ AdwTab *self)
+{
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
static void
update_state (AdwTab *self)
{
@@ -197,8 +212,15 @@ update_indicator (AdwTab *self)
static void
update_needs_attention (AdwTab *self)
{
- set_style_class (GTK_WIDGET (self), "needs-attention",
- adw_tab_page_get_needs_attention (self->page));
+ gboolean needs_attention = adw_tab_page_get_needs_attention (self->page);
+
+ adw_timed_animation_set_value_from (ADW_TIMED_ANIMATION (self->needs_attention_animation),
+ adw_animation_get_value (self->needs_attention_animation));
+ adw_timed_animation_set_value_to (ADW_TIMED_ANIMATION (self->needs_attention_animation),
+ needs_attention ? 1 : 0);
+ adw_animation_play (self->needs_attention_animation);
+
+ set_style_class (GTK_WIDGET (self), "needs-attention", needs_attention);
}
static void
@@ -381,6 +403,11 @@ adw_tab_measure (GtkWidget *widget,
&child_min, &child_nat, NULL, NULL);
min = MAX (min, child_min);
nat = MAX (nat, child_nat);
+
+ gtk_widget_measure (self->needs_attention_indicator, orientation, for_size,
+ &child_min, &child_nat, NULL, NULL);
+ min = MAX (min, child_min);
+ nat = MAX (nat, child_nat);
}
if (minimum)
@@ -426,6 +453,22 @@ allocate_child (GtkWidget *child,
gtk_widget_size_allocate (child, &child_alloc, baseline);
}
+static int
+get_attention_indicator_width (AdwTab *self,
+ int center_width)
+{
+ double base_width;
+
+ if (self->pinned) {
+ base_width = ATTENTION_INDICATOR_PINNED_WIDTH;
+ } else {
+ base_width = center_width * ATTENTION_INDICATOR_WIDTH_MULTIPLIER;
+ base_width = CLAMP (base_width, ATTENTION_INDICATOR_MIN_WIDTH, ATTENTION_INDICATOR_MAX_WIDTH);
+ }
+
+ return base_width * adw_animation_get_value (self->needs_attention_animation);
+}
+
static void
adw_tab_size_allocate (GtkWidget *widget,
int width,
@@ -433,14 +476,16 @@ adw_tab_size_allocate (GtkWidget *widget,
int baseline)
{
AdwTab *self = ADW_TAB (widget);
- int indicator_width, close_width, icon_width, title_width;
+ int indicator_width, close_width, icon_width, title_width, needs_attention_width;
int center_x, center_width = 0;
int start_width = 0, end_width = 0;
+ int needs_attention_x;
measure_child (self->icon_stack, height, &icon_width);
measure_child (self->title, height, &title_width);
measure_child (self->indicator_btn, height, &indicator_width);
measure_child (self->close_btn, height, &close_width);
+ measure_child (self->needs_attention_indicator, height, &needs_attention_width);
if (gtk_widget_get_visible (self->indicator_btn)) {
if (self->pinned) {
@@ -489,6 +534,13 @@ adw_tab_size_allocate (GtkWidget *widget,
gtk_widget_get_visible (self->close_btn) &&
center_x + center_width > width - close_width;
+ needs_attention_width = MAX (needs_attention_width,
+ get_attention_indicator_width (self, center_width));
+ needs_attention_x = (width - needs_attention_width) / 2;
+
+ allocate_child (self->needs_attention_indicator, width, height,
+ needs_attention_x, needs_attention_width, baseline);
+
if (gtk_widget_get_visible (self->icon_stack)) {
allocate_child (self->icon_stack, width, height,
center_x, icon_width, baseline);
@@ -530,6 +582,7 @@ adw_tab_snapshot (GtkWidget *widget,
float opacity = gtk_widget_get_opacity (self->close_btn);
gboolean draw_fade = self->close_overlap && opacity > 0;
+ gtk_widget_snapshot_child (widget, self->needs_attention_indicator, snapshot);
gtk_widget_snapshot_child (widget, self->indicator_btn, snapshot);
gtk_widget_snapshot_child (widget, self->icon_stack, snapshot);
@@ -697,10 +750,12 @@ adw_tab_dispose (GObject *object)
g_clear_object (&self->shader);
g_clear_object (&self->close_btn_animation);
+ g_clear_object (&self->needs_attention_animation);
gtk_widget_unparent (self->indicator_btn);
gtk_widget_unparent (self->icon_stack);
gtk_widget_unparent (self->title);
gtk_widget_unparent (self->close_btn);
+ gtk_widget_unparent (self->needs_attention_indicator);
G_OBJECT_CLASS (adw_tab_parent_class)->dispose (object);
}
@@ -780,6 +835,7 @@ adw_tab_class_init (AdwTabClass *klass)
gtk_widget_class_bind_template_child (widget_class, AdwTab, indicator_icon);
gtk_widget_class_bind_template_child (widget_class, AdwTab, indicator_btn);
gtk_widget_class_bind_template_child (widget_class, AdwTab, close_btn);
+ gtk_widget_class_bind_template_child (widget_class, AdwTab, needs_attention_indicator);
gtk_widget_class_bind_template_child (widget_class, AdwTab, drop_target);
gtk_widget_class_bind_template_callback (widget_class, close_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, indicator_clicked_cb);
@@ -815,6 +871,16 @@ adw_tab_init (AdwTab *self)
adw_timed_animation_set_easing (ADW_TIMED_ANIMATION (self->close_btn_animation),
ADW_EASE_IN_OUT_CUBIC);
+
+ target = adw_callback_animation_target_new ((AdwAnimationTargetFunc)
+ attention_indicator_animation_value_cb,
+ self, NULL);
+ self->needs_attention_animation =
+ adw_timed_animation_new (GTK_WIDGET (self), 0, 0,
+ ATTENTION_INDICATOR_ANIMATION_DURATION, target);
+
+ adw_timed_animation_set_easing (ADW_TIMED_ANIMATION (self->needs_attention_animation),
+ ADW_EASE_IN_OUT_CUBIC);
}
AdwTab *
diff --git a/src/adw-tab.ui b/src/adw-tab.ui
index ff16772f..cbf5d992 100644
--- a/src/adw-tab.ui
+++ b/src/adw-tab.ui
@@ -88,5 +88,11 @@
</style>
</object>
</child>
+ <child>
+ <object class="AdwGizmo" id="needs_attention_indicator">
+ <property name="css-name">attention-indicator</property>
+ <property name="valign">end</property>
+ </object>
+ </child>
</template>
</interface>
diff --git a/src/stylesheet/widgets/_tab-view.scss b/src/stylesheet/widgets/_tab-view.scss
index 551e1d14..eea14252 100644
--- a/src/stylesheet/widgets/_tab-view.scss
+++ b/src/stylesheet/widgets/_tab-view.scss
@@ -1,102 +1,55 @@
-$tab_needs_attention_gradient: radial-gradient(ellipse at bottom,
- transparentize(white, .2),
- gtkalpha($accent_color, .4) 10%,
- gtkalpha($accent_color, 0) 30%);
-
-
-@mixin undershoot-gradient($dir, $color) {
- background: linear-gradient(to #{$dir},
- $color,
- transparentize(black, 1) 20px);
-}
-
-@mixin need-attention-gradient($dir) {
- background: linear-gradient(to #{$dir},
- gtkalpha($accent_color, .5),
- gtkalpha($accent_color, .3) 1px,
- gtkalpha($accent_color, 0) 20px);
-}
-
tabbar {
.box {
min-height: 38px;
}
- scrolledwindow.pinned {
- undershoot {
- border: 0 solid $border_color;
- }
-
- &:dir(rtl) undershoot.left {
- border-left-width: 1px;
- }
-
- &:dir(ltr) undershoot.right {
- border-right-width: 1px;
+ tabbox {
+ > widget {
+ @include focus-ring();
+ border-radius: $button_radius;
+ margin-bottom: 3px;
+ margin-top: 3px;
}
- tabbox > background {
- &:dir(ltr) {
- box-shadow: inset -1px 0 $border_color;
- }
+ > separator {
+ margin-top: 9px;
+ margin-bottom: 9px;
+ transition: opacity 150ms ease-in-out;
- &:dir(rtl) {
- box-shadow: inset 1px 0 $border_color;
+ &.hidden {
+ opacity: 0;
}
}
}
- undershoot {
+ tab {
transition: background 150ms ease-in-out;
- &.left {
- @include undershoot-gradient("right", $shade_color);
- }
-
- &.right {
- @include undershoot-gradient("left", $shade_color);
- }
- }
-
- .needs-attention-left undershoot.left {
- @include need-attention-gradient("right");
- }
-
- .needs-attention-right undershoot.right {
- @include need-attention-gradient("left");
- }
-
- tabbox {
- > background {
- background-color: $shade_color;
+ @if $contrast == 'high' {
+ &:hover,
+ &:active,
+ &:checked {
+ box-shadow: inset 0 0 0 1px $border_color;
+ }
}
- > widget {
- @include focus-ring();
- }
- }
+ &:checked {
+ background-color: $view_selected_color;
- tab {
- border-style: solid;
- border-color: $border_color;
- border-width: 0 1px 0 1px;
- transition: background 150ms ease-in-out;
- background-color: gtkalpha($shade_color, .6);
- background-clip: padding-box;
+ &:hover {
+ background-color: $view_selected_hover_color;
- &:checked {
- background-color: transparent;
+ &:active {
+ background-color: $view_selected_active_color;
+ }
+ }
}
&:hover {
- background-image: image(gtkalpha(currentColor, .03));
- }
+ background-color: $view_hover_color;
- &.needs-attention {
- background-image: $tab_needs_attention_gradient;
-
- &:hover {
- background-image: image(gtkalpha(currentColor, .03)), $tab_needs_attention_gradient;
+ &:active {
+ background-color: $view_active_color;
}
}
}
@@ -132,53 +85,6 @@ tabbar {
border-bottom: 1px solid $headerbar_border_color;
}
- scrolledwindow.pinned {
- undershoot {
- border-color: $headerbar_border_color;
- }
-
- tabbox > background {
- &:dir(ltr) {
- box-shadow: inset -1px 0 $headerbar_border_color;
- }
-
- &:dir(rtl) {
- box-shadow: inset 1px 0 $headerbar_border_color;
- }
- }
- }
-
- undershoot {
- &.left {
- @include undershoot-gradient("right", $headerbar_shade_color);
- }
-
- &.right {
- @include undershoot-gradient("left", $headerbar_shade_color);
- }
- }
-
- .needs-attention-left undershoot.left {
- @include need-attention-gradient("right");
- }
-
- .needs-attention-right undershoot.right {
- @include need-attention-gradient("left");
- }
-
- tabbox > background {
- background-color: $headerbar_shade_color;
- }
-
- tab {
- border-color: $headerbar_border_color;
- background-color: gtkalpha($headerbar_shade_color, .6);
-
- &:checked {
- background-color: transparent;
- }
- }
-
.start-action,
.end-action {
background-color: gtkalpha($headerbar_shade_color, .6);
@@ -193,20 +99,18 @@ tabbar {
}
dnd tab {
- min-height: 26px;
background-color: $headerbar_bg_color;
+ background-image: image($view_selected_active_color);
color: $headerbar_fg_color;
- &.needs-attention {
- background-image: $tab_needs_attention_gradient;
- }
+ box-shadow: 0 0 0 1px transparentize(black, 0.97),
+ 0 1px 3px 1px transparentize(black, .93),
+ 0 2px 6px 2px transparentize(black, .97);
- box-shadow: 0 1px 5px 1px transparentize(black, .91),
- 0 2px 14px 3px transparentize(black, .95),
- 0 0 0 1px transparentize(black, if($contrast == 'high', .2, .95));
-
- outline: 1px solid $window_outline_color;
- outline-offset: -1px;
+ @if $contrast == 'high' {
+ outline: 1px solid $border_color;
+ outline-offset: -1px;
+ }
margin: 25px;
}
@@ -214,7 +118,9 @@ dnd tab {
tabbar,
dnd {
tab {
+ min-height: 24px;
padding: 6px;
+ border-radius: $button_radius;
button.image-button {
padding: 0;
@@ -223,6 +129,12 @@ dnd {
min-height: 24px;
border-radius: 99px;
}
+
+ attention-indicator {
+ min-height: 2px;
+ background: if($contrast == 'high', $accent_color, gtkalpha($accent_color, 0.5));
+ transform: translateY(6px);
+ }
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]