Show window titles in Multitasking View (#1076)

This commit is contained in:
José Expósito 2021-04-06 03:19:52 +02:00 committed by GitHub
parent 3916d058c9
commit ac8a21e990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 233 additions and 29 deletions

View File

@ -195,8 +195,7 @@ public class Gala.GestureTracker : Object {
float value = ((target_value - initial_value) * (float) percentage) + initial_value;
if (rounded) {
var scale_factor = InternalUtils.get_ui_scaling_factor ();
value = (float) Math.round (value * scale_factor) / scale_factor;
value = (float) InternalUtils.pixel_align (value);
}
return value;

View File

@ -337,6 +337,14 @@ namespace Gala {
return Meta.Backend.get_backend ().get_settings ().get_ui_scaling_factor ();
}
/**
* Round the value to match physical pixels.
*/
public static int pixel_align (float value) {
var scale_factor = InternalUtils.get_ui_scaling_factor ();
return (int) Math.round (value * scale_factor) / scale_factor;
}
private static Gtk.StyleContext selection_style_context = null;
public static Gdk.RGBA get_theme_accent_color () {
if (selection_style_context == null) {

147
src/Widgets/Tooltip.vala Normal file
View File

@ -0,0 +1,147 @@
/*
* Copyright 2021 elementary, Inc (https://elementary.io)
* 2021 José Expósito <jose.exposito89@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Clutter actor to display text in a tooltip-like component.
*/
public class Gala.Tooltip : Clutter.Actor {
private static Clutter.Color text_color;
private static Gdk.RGBA bg_color;
private static Gtk.Border padding;
private static int border_radius;
/**
* Canvas to draw the Tooltip background.
*/
private Clutter.Canvas background_canvas;
/**
* Actor to display the Tooltip text.
*/
private Clutter.Text? text_actor = null;
/**
* Text displayed in the Tooltip.
* @see set_text
*/
private string text;
/**
* Maximum width of the Tooltip.
* @see set_max_width
*/
public float max_width;
static construct {
var label_widget_path = new Gtk.WidgetPath ();
label_widget_path.append_type (GLib.Type.from_name ("label"));
label_widget_path.iter_set_object_name (-1, "tooltip");
var tooltip_style_context = new Gtk.StyleContext ();
tooltip_style_context.add_class (Gtk.STYLE_CLASS_BACKGROUND);
tooltip_style_context.set_path (label_widget_path);
bg_color = (Gdk.RGBA) tooltip_style_context.get_property (
Gtk.STYLE_PROPERTY_BACKGROUND_COLOR,
Gtk.StateFlags.NORMAL
);
border_radius = (int) tooltip_style_context.get_property (
Gtk.STYLE_PROPERTY_BORDER_RADIUS,
Gtk.StateFlags.NORMAL
);
padding = tooltip_style_context.get_padding (Gtk.StateFlags.NORMAL);
text_color = Clutter.Color.from_string ("#ffffff");
}
construct {
text = "";
max_width = 200;
background_canvas = new Clutter.Canvas ();
background_canvas.draw.connect (draw_background);
content = background_canvas;
draw ();
}
public void set_text (string new_text, bool redraw = true) {
text = new_text;
if (redraw) {
draw ();
}
}
public void set_max_width (float new_max_width, bool redraw = true) {
max_width = new_max_width;
if (redraw) {
draw ();
}
}
private void draw () {
visible = (text.length != 0);
if (!visible) {
return;
}
// First set the text
remove_child (text_actor);
text_actor = new Clutter.Text () {
color = text_color,
x = padding.left,
y = padding.top,
ellipsize = Pango.EllipsizeMode.MIDDLE,
use_markup = true
};
text_actor.set_markup (Markup.printf_escaped ("<span size='large'>%s</span>", text));
if ((text_actor.width + padding.left + padding.right) > max_width) {
text_actor.width = max_width - padding.left - padding.right;
}
add_child (text_actor);
// Adjust the size of the tooltip to the text
width = text_actor.width + padding.left + padding.right;
height = text_actor.height + padding.top + padding.bottom;
background_canvas.set_size ((int) width, (int) height);
// And paint the background
background_canvas.invalidate ();
}
private static bool draw_background (Cairo.Context cr, int width, int height) {
cr.save ();
cr.set_operator (Cairo.Operator.CLEAR);
cr.paint ();
cr.restore ();
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, 0, 0, width, height, border_radius);
cr.set_source_rgba (bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha);
cr.fill ();
return false;
}
}

View File

@ -42,12 +42,14 @@ namespace Gala {
}
/**
* A container for a clone of the texture of a MetaWindow, a WindowIcon,
* A container for a clone of the texture of a MetaWindow, a WindowIcon, a Tooltip with the title,
* a close button and a shadow. Used together with the WindowCloneContainer.
*/
public class WindowClone : Actor {
const int WINDOW_ICON_SIZE = 64;
const int ACTIVE_SHAPE_SIZE = 12;
const int FADE_ANIMATION_DURATION = 200;
const int TITLE_MAX_WIDTH_MARGIN = 60;
/**
* The window was selected. The MultitaskingView should consider activating
@ -83,7 +85,7 @@ namespace Gala {
_active = value;
active_shape.save_easing_state ();
active_shape.set_easing_duration (200);
active_shape.set_easing_duration (FADE_ANIMATION_DURATION);
active_shape.opacity = _active ? 255 : 0;
@ -116,10 +118,12 @@ namespace Gala {
ulong check_confirm_dialog_cb = 0;
uint shadow_update_timeout = 0;
int scale_factor = 0;
bool in_slot_animation = false;
Actor close_button;
Actor active_shape;
Actor window_icon;
Tooltip window_title;
public WindowClone (Meta.Window window, GestureTracker? gesture_tracker, bool overview_mode = false) {
Object (window: window, gesture_tracker: gesture_tracker, overview_mode: overview_mode);
@ -154,7 +158,7 @@ namespace Gala {
close_button = Utils.create_close_button ();
close_button.opacity = 0;
close_button.set_easing_duration (200);
close_button.set_easing_duration (FADE_ANIMATION_DURATION);
close_button.button_press_event.connect (() => {
close_window ();
return true;
@ -168,9 +172,11 @@ namespace Gala {
window_icon.set_pivot_point (0.5f, 0.5f);
window_icon.set_easing_duration (MultitaskingView.ANIMATION_DURATION);
window_icon.set_easing_mode (MultitaskingView.ANIMATION_MODE);
window_icon.set_position (
(window_frame_rect.width - WINDOW_ICON_SIZE) / 2,
window_frame_rect.height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f);
set_window_icon_position (window_frame_rect.width, window_frame_rect.height);
window_title = new Tooltip ();
window_title.opacity = 0;
window_title.set_easing_duration (FADE_ANIMATION_DURATION);
active_shape = new Clutter.Actor ();
active_shape.background_color = { 255, 255, 255, 200 };
@ -178,6 +184,7 @@ namespace Gala {
add_child (active_shape);
add_child (window_icon);
add_child (window_title);
add_child (close_button);
load_clone ();
@ -225,6 +232,7 @@ namespace Gala {
set_child_below_sibling (active_shape, clone);
set_child_above_sibling (close_button, clone);
set_child_above_sibling (window_icon, clone);
set_child_above_sibling (window_title, clone);
transition_to_original_state (false);
@ -295,6 +303,9 @@ namespace Gala {
var target_x = outer_rect.x - offset_x;
var target_y = outer_rect.y - offset_y;
in_slot_animation = true;
place_widgets (outer_rect.width, outer_rect.height);
GestureTracker.OnBegin on_animation_begin = () => {
window_icon.set_easing_duration (0);
};
@ -308,9 +319,9 @@ namespace Gala {
set_size (width, height);
set_position (x, y);
window_icon.opacity = (uint) opacity;
window_icon.set_position ((width - WINDOW_ICON_SIZE) / 2,
height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f);
set_window_icon_position (width, height, false);
};
GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => {
@ -337,9 +348,13 @@ namespace Gala {
toggle_shadow (false);
}
window_icon.set_position ((outer_rect.width - WINDOW_ICON_SIZE) / 2, outer_rect.height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f);
window_icon.opacity = 0;
close_button.opacity = 0;
set_window_icon_position (outer_rect.width, outer_rect.height);
window_icon.get_transition ("opacity").completed.connect (() => {
in_slot_animation = false;
place_widgets (outer_rect.width, outer_rect.height);
});
};
if (!animate || gesture_tracker == null || !with_gesture) {
@ -360,10 +375,11 @@ namespace Gala {
var initial_width = width;
var initial_height = height;
GestureTracker.OnBegin on_animation_begin = () => {
window_icon.opacity = 0;
window_icon.set_easing_duration (0);
};
window_icon.opacity = 0;
window_icon.set_easing_duration (0);
in_slot_animation = true;
place_widgets (rect.width, rect.height);
GestureTracker.OnUpdate on_animation_update = (percentage) => {
var x = GestureTracker.animation_value (initial_x, rect.x, percentage);
@ -374,9 +390,9 @@ namespace Gala {
set_size (width, height);
set_position (x, y);
window_icon.opacity = (uint) opacity;
window_icon.set_position ((width - WINDOW_ICON_SIZE) / 2,
height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f);
set_window_icon_position (width, height, false);
};
GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => {
@ -394,9 +410,7 @@ namespace Gala {
set_position (rect.x, rect.y);
window_icon.opacity = 255;
window_icon.set_position ((rect.width - WINDOW_ICON_SIZE) / 2,
rect.height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f);
set_window_icon_position (rect.width, rect.height);
restore_easing_state ();
toggle_shadow (true);
@ -409,13 +423,17 @@ namespace Gala {
opacity = 255;
restore_easing_state ();
}
window_icon.get_transition ("opacity").completed.connect (() => {
in_slot_animation = false;
place_widgets (rect.width, rect.height);
});
};
if (gesture_tracker == null || !with_gesture) {
on_animation_begin (0);
on_animation_end (1, false, 0);
} else {
gesture_tracker.connect_handlers ((owned) on_animation_begin, (owned) on_animation_update, (owned) on_animation_end);
gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end);
}
}
@ -478,14 +496,14 @@ namespace Gala {
}
public override bool enter_event (Clutter.CrossingEvent event) {
close_button.opacity = 255;
close_button.opacity = in_slot_animation ? 0 : 255;
window_title.opacity = in_slot_animation ? 0 : 255;
return false;
}
public override bool leave_event (Clutter.CrossingEvent event) {
close_button.opacity = 0;
window_title.opacity = 0;
return false;
}
@ -498,7 +516,9 @@ namespace Gala {
Granite.Widgets.Utils.get_default_close_button_position (out pos);
close_button.save_easing_state ();
window_title.save_easing_state ();
close_button.set_easing_duration (0);
window_title.set_easing_duration (0);
close_button.y = -close_button.height * 0.33f;
@ -510,7 +530,17 @@ namespace Gala {
close_button.x = -close_button.width * 0.5f;
break;
}
bool show = has_pointer && !in_slot_animation;
close_button.opacity = show ? 255 : 0;
window_title.opacity = close_button.opacity;
window_title.set_text (window.get_title (), false);
window_title.set_max_width (dest_width - (TITLE_MAX_WIDTH_MARGIN * scale_factor));
set_window_title_position (dest_width, dest_height);
close_button.restore_easing_state ();
window_title.restore_easing_state ();
}
void toggle_shadow (bool show) {
@ -615,7 +645,7 @@ namespace Gala {
clone.get_transformed_position (out abs_x, out abs_y);
clone.save_easing_state ();
clone.set_easing_duration (200);
clone.set_easing_duration (FADE_ANIMATION_DURATION);
clone.set_easing_mode (AnimationMode.EASE_IN_CUBIC);
clone.set_scale (scale, scale);
clone.opacity = 0;
@ -631,13 +661,14 @@ namespace Gala {
set_position (abs_x + prev_parent_x, abs_y + prev_parent_y);
window_icon.save_easing_state ();
window_icon.set_easing_duration (200);
window_icon.set_easing_duration (FADE_ANIMATION_DURATION);
window_icon.set_easing_mode (AnimationMode.EASE_IN_OUT_CUBIC);
window_icon.set_position (click_x - (abs_x + prev_parent_x) - window_icon.width / 2,
click_y - (abs_y + prev_parent_y) - window_icon.height / 2);
window_icon.restore_easing_state ();
close_button.opacity = 0;
window_title.opacity = 0;
dragging = true;
@ -782,10 +813,28 @@ namespace Gala {
window_icon.save_easing_state ();
window_icon.set_easing_duration (250);
window_icon.set_easing_mode (AnimationMode.EASE_OUT_QUAD);
window_icon.set_position ((slot.width - WINDOW_ICON_SIZE) / 2, slot.height - WINDOW_ICON_SIZE * 0.75f);
set_window_icon_position (slot.width, slot.height);
window_icon.restore_easing_state ();
dragging = false;
}
private void set_window_icon_position (float window_width, float window_height, bool aligned = true) {
var x = (window_width - WINDOW_ICON_SIZE) / 2;
var y = window_height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f;
if (aligned) {
x = InternalUtils.pixel_align (x);
y = InternalUtils.pixel_align (y);
}
window_icon.set_position (x, y);
}
private void set_window_title_position (float window_width, float window_height) {
var x = InternalUtils.pixel_align ((window_width - window_title.width) / 2);
var y = InternalUtils.pixel_align (window_height - (WINDOW_ICON_SIZE * scale_factor) * 0.75f - (window_title.height / 2) - (18 * scale_factor));
window_title.set_position (x, y);
}
}
}

View File

@ -36,6 +36,7 @@ gala_bin_sources = files(
'Widgets/SafeWindowClone.vala',
'Widgets/ScreenShield.vala',
'Widgets/SelectionArea.vala',
'Widgets/Tooltip.vala',
'Widgets/WindowClone.vala',
'Widgets/WindowCloneContainer.vala',
'Widgets/WindowIconActor.vala',