Skip to content

Commit

Permalink
WindowSwitcher: Merge icon and indicator (#1637)
Browse files Browse the repository at this point in the history
  • Loading branch information
lenemter committed Apr 11, 2023
1 parent faaeb65 commit b83a023
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
namespace Gala {
public class WindowSwitcher : Clutter.Actor {
public const int ICON_SIZE = 64;
public const int WRAPPER_BORDER_RADIUS = 3;
public const int WRAPPER_PADDING = 12;
public const string CAPTION_FONT_NAME = "Inter";

private const int MIN_OFFSET = 64;
private const int FIX_TIMEOUT_INTERVAL = 100;

public bool opened { get; private set; default = false; }

Expand All @@ -24,19 +22,28 @@ namespace Gala {
private Granite.Settings granite_settings;
private Clutter.Canvas canvas;
private Clutter.Actor container;
private Clutter.Actor indicator;
private Clutter.Text caption;

private int modifier_mask;

private WindowIcon? cur_icon = null;
private WindowSwitcherIcon? _current_icon = null;
private WindowSwitcherIcon? current_icon {
get {
return _current_icon;
}
set {
if (_current_icon != null) {
_current_icon.selected = false;
}

private float scaling_factor = 1.0f;
_current_icon = value;
_current_icon.selected = true;

// For some reason, on Odin, the height of the caption loses
// its padding after the first time the switcher displays. As a
// workaround, I store the initial value here once we have it.
private float caption_height = -1.0f;
update_caption_text ();
}
}

private float scaling_factor = 1.0f;

public WindowSwitcher (Gala.WindowManager wm) {
Object (wm: wm);
Expand All @@ -53,6 +60,8 @@ namespace Gala {
canvas.scale_factor = scaling_factor;
set_content (canvas);

opacity = 0;

// Carry out the initial draw
create_components ();

Expand Down Expand Up @@ -123,51 +132,23 @@ namespace Gala {
private void create_components () {
// We've already been constructed once, start again
if (container != null) {
caption_height = -1.0f;
destroy_all_children ();
}

var margin = InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor);
var layout = new Clutter.FlowLayout (Clutter.FlowOrientation.HORIZONTAL);
container = new Clutter.Actor ();
container.layout_manager = layout;
container.reactive = true;
container = new Clutter.Actor () {
reactive = true,
layout_manager = layout,
margin_left = margin,
margin_top = margin,
margin_right = margin,
margin_bottom = margin
};

container.button_release_event.connect (container_mouse_release);
container.motion_event.connect (container_motion_event);

var rgba = InternalUtils.get_theme_accent_color ();
var accent_color = Clutter.Color ();
accent_color.init (
(uint8) (rgba.red * 255),
(uint8) (rgba.green * 255),
(uint8) (rgba.blue * 255),
(uint8) (rgba.alpha * 255)
);

var rect_radius = InternalUtils.scale_to_int (WRAPPER_BORDER_RADIUS, scaling_factor);
indicator = new Clutter.Actor ();
indicator.margin_left = indicator.margin_top =
indicator.margin_right = indicator.margin_bottom = 0;
indicator.set_pivot_point (0.5f, 0.5f);
var indicator_canvas = new Clutter.Canvas ();
indicator.set_content (indicator_canvas);
indicator_canvas.scale_factor = scaling_factor;
indicator_canvas.draw.connect ((ctx, width, height) => {
ctx.save ();
ctx.set_operator (Cairo.Operator.CLEAR);
ctx.paint ();
ctx.clip ();
ctx.reset_clip ();

// draw rect
Clutter.cairo_set_source_color (ctx, accent_color);
Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius);
ctx.set_operator (Cairo.Operator.SOURCE);
ctx.fill ();

ctx.restore ();
return true;
});

var caption_color = "#2e2e31";

if (granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK) {
Expand All @@ -179,7 +160,6 @@ namespace Gala {
caption.set_ellipsize (Pango.EllipsizeMode.END);
caption.set_line_alignment (Pango.Alignment.CENTER);

add_child (indicator);
add_child (container);
add_child (caption);
}
Expand Down Expand Up @@ -216,7 +196,6 @@ namespace Gala {
}

open_switcher ();
update_indicator_position (true);
}

var binding_name = binding.get_name ();
Expand All @@ -237,9 +216,9 @@ namespace Gala {
container.destroy_all_children ();

foreach (unowned var window in windows) {
var icon = new WindowIcon (window, InternalUtils.scale_to_int (ICON_SIZE, scaling_factor));
var icon = new WindowSwitcherIcon (window, InternalUtils.scale_to_int (ICON_SIZE, scaling_factor));
if (window == current_window) {
cur_icon = icon;
current_icon = icon;
}

container.add_child (icon);
Expand All @@ -266,9 +245,9 @@ namespace Gala {
var app = window_tracker.get_app_for_window (current_window);
foreach (unowned var window in windows) {
if (window_tracker.get_app_for_window (window) == app) {
var icon = new WindowIcon (window, InternalUtils.scale_to_int (ICON_SIZE, scaling_factor));
var icon = new WindowSwitcherIcon (window, InternalUtils.scale_to_int (ICON_SIZE, scaling_factor));
if (window == current_window) {
cur_icon = icon;
current_icon = icon;
}

container.add_child (icon);
Expand All @@ -288,20 +267,9 @@ namespace Gala {
return;
}

container.margin_left = container.margin_top =
container.margin_right = container.margin_bottom = InternalUtils.scale_to_int (WRAPPER_PADDING * 2, scaling_factor);

var l = container.layout_manager as Clutter.FlowLayout;
l.column_spacing = l.row_spacing = InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor);

indicator.visible = false;
var indicator_size = InternalUtils.scale_to_int ((ICON_SIZE + WRAPPER_PADDING * 2), scaling_factor);
indicator.set_size (indicator_size, indicator_size);
((Clutter.Canvas) indicator.content).set_size (indicator_size, indicator_size);
caption.visible = false;
caption.margin_bottom = caption.margin_top = InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor);
opacity = 0;

var display = wm.get_display ();
unowned var display = wm.get_display ();
var monitor = display.get_current_monitor ();
var geom = display.get_monitor_geometry (monitor);

Expand All @@ -316,23 +284,9 @@ namespace Gala {
}

float nat_width, nat_height;
container.get_preferred_size (null, null, out nat_width, null);

nat_width -= InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor) / container.get_n_children ();
container.get_preferred_size (null, null, out nat_width, out nat_height);

container.get_preferred_size (null, null, null, out nat_height);

// For some reason, on Odin, the height of the caption loses
// its padding after the first time the switcher displays. As a
// workaround, I store the initial value here once we have it
// and use that correct value on subsequent attempts.
if (caption_height == -1.0f) {
caption_height = caption.height;
}

opacity = 0;

var switcher_height = (int) (nat_height + caption_height / 2 - container.margin_bottom + WRAPPER_PADDING * 3 * scaling_factor);
var switcher_height = (int) (nat_height + caption.height / 2 - container.margin_bottom + WRAPPER_PADDING * 3 * scaling_factor);
set_size ((int) nat_width, switcher_height);
canvas.set_size ((int) nat_width, switcher_height);
canvas.invalidate ();
Expand Down Expand Up @@ -399,7 +353,7 @@ namespace Gala {
return;
}

var window = cur_icon.window;
var window = current_icon.window;
if (window == null) {
return;
}
Expand All @@ -418,7 +372,7 @@ namespace Gala {

private void next_window (Meta.Display display, Meta.Workspace? workspace, bool backward) {
Clutter.Actor actor;
var current = cur_icon;
var current = current_icon;

if (container.get_n_children () == 1) {
Clutter.get_default_backend ().get_default_seat ().bell_notify ();
Expand All @@ -437,54 +391,22 @@ namespace Gala {
}
}

cur_icon = (WindowIcon) actor;
update_indicator_position ();
current_icon = (WindowSwitcherIcon) actor;
}

private void update_caption_text () {
var current_window = cur_icon.window;
var current_caption = "n/a";
if (current_window != null) {
current_caption = current_window.get_title ();
}
var current_window = current_icon.window;
var current_caption = current_window != null ? current_window.title : "n/a";
caption.set_text (current_caption);
caption.visible = true;

// Make caption smaller than the wrapper, so it doesn't overflow.
caption.width = width - WRAPPER_PADDING * 2 * scaling_factor;
caption.set_position (
InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor),
(int) (height - caption_height / 2 - InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor) * 2)
(int) (height - caption.height / 2 - InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor) * 2)
);
}

private void update_indicator_position (bool initial = false) {
// FIXME there are some troubles with layouting, in some cases we
// are here too early, in which case all the children are at
// (0|0), so we can easily check for that and come back later
if (container.get_n_children () > 1
&& container.get_child_at_index (1).x < 1) {

GLib.Timeout.add (FIX_TIMEOUT_INTERVAL, () => {
update_indicator_position (initial);
return false;
}, GLib.Priority.DEFAULT);
return;
}

float x = cur_icon.x;
float y = cur_icon.y;

if (initial) {
indicator.visible = true;
}

// Move the indicator without animating it.
indicator.x = container.margin_left + (container.get_n_children () > 1 ? x : 0) - InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor);
indicator.y = container.margin_top + y - InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor);
update_caption_text ();
}

public override void key_focus_out () {
close_switcher (wm.get_display ().get_current_time ());
}
Expand All @@ -495,14 +417,13 @@ namespace Gala {
return true;
}

var selected = actor as WindowIcon;
var selected = actor as WindowSwitcherIcon;
if (selected == null) {
return true;
}

if (cur_icon != selected) {
cur_icon = selected;
update_indicator_position ();
if (current_icon != selected) {
current_icon = selected;
}

return true;
Expand Down
91 changes: 91 additions & 0 deletions src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2023 elementary, Inc. <https://elementary.io>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

public class Gala.WindowSwitcherIcon : Clutter.Actor {
private const int WRAPPER_BORDER_RADIUS = 3;

public Meta.Window window { get; construct; }

private WindowIcon icon;
private Clutter.Canvas canvas;

private bool _selected = false;
public bool selected {
get {
return _selected;
}
set {
_selected = value;
canvas.invalidate ();
}
}

private float _scale_factor = 1.0f;
public float scale_factor {
get {
return _scale_factor;
}
set {
_scale_factor = value;
canvas.scale_factor = _scale_factor;

update_size ();
canvas.invalidate ();
}
}

public WindowSwitcherIcon (Meta.Window window, int icon_size) {
Object (window: window);

icon = new WindowIcon (window, icon_size);
icon.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.BOTH, 0.5f));
add_child (icon);

canvas = new Clutter.Canvas ();
canvas.draw.connect (draw_background);
set_content (canvas);

update_size ();
}

private void update_size () {
var indicator_size = InternalUtils.scale_to_int (
(WindowSwitcher.ICON_SIZE + WindowSwitcher.WRAPPER_PADDING * 2),
scale_factor
);
set_size (indicator_size, indicator_size);
canvas.set_size (indicator_size, indicator_size);
}

private bool draw_background (Cairo.Context ctx, int width, int height) {
ctx.save ();
ctx.set_operator (Cairo.Operator.CLEAR);
ctx.paint ();
ctx.clip ();
ctx.reset_clip ();

if (selected) {
var rgba = InternalUtils.get_theme_accent_color ();
Clutter.Color accent_color = {
(uint8) (rgba.red * 255),
(uint8) (rgba.green * 255),
(uint8) (rgba.blue * 255),
(uint8) (rgba.alpha * 255)
};

var rect_radius = InternalUtils.scale_to_int (WRAPPER_BORDER_RADIUS, scale_factor);

// draw rect
Clutter.cairo_set_source_color (ctx, accent_color);
Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius);
ctx.set_operator (Cairo.Operator.SOURCE);
ctx.fill ();

ctx.restore ();
}

return true;
}
}

0 comments on commit b83a023

Please sign in to comment.