mirror of
https://github.com/elementary/gala.git
synced 2024-09-11 07:05:30 +03:00
add dnd interactions
This commit is contained in:
parent
84355bbdca
commit
2977609308
@ -38,11 +38,22 @@ namespace Gala
|
||||
|
||||
public interface WindowManager : Meta.Plugin
|
||||
{
|
||||
/**
|
||||
* Window stacking changed. You can read the plugin's stacking_order property
|
||||
* to restack window clones.
|
||||
*/
|
||||
public signal void windows_restacked ();
|
||||
|
||||
public abstract Clutter.Actor ui_group { get; protected set; }
|
||||
public abstract Clutter.Stage stage { get; protected set; }
|
||||
public abstract Clutter.Actor window_group { get; protected set; }
|
||||
public abstract Clutter.Actor top_window_group { get; protected set; }
|
||||
public abstract Meta.BackgroundGroup background_group { get; protected set; }
|
||||
/*
|
||||
* Provides a hashtable of windows' stable sequences and their stacking order
|
||||
* counted from 0 up.
|
||||
*/
|
||||
public abstract HashTable<int,int> window_stacking_order { get; protected set; }
|
||||
|
||||
public abstract void begin_modal ();
|
||||
public abstract void end_modal ();
|
||||
|
@ -47,7 +47,6 @@ namespace Gala
|
||||
}
|
||||
|
||||
destroy_all_children ();
|
||||
print ("DESTROYING!!!! \n");
|
||||
|
||||
var settings = BackgroundSettings.get_default ().schema;
|
||||
|
||||
|
@ -25,19 +25,15 @@ namespace Gala
|
||||
|
||||
public class DragDropAction : Clutter.Action
|
||||
{
|
||||
public DragDropActionType drag_type { get; construct; }
|
||||
public string drag_id { get; construct; }
|
||||
public Clutter.Actor handle { get; private set; }
|
||||
|
||||
Clutter.Actor? hovered = null;
|
||||
bool clicked = false;
|
||||
bool dragging = false;
|
||||
|
||||
/**
|
||||
* A drag has been started. You have to connect to this signal
|
||||
* @return A ClutterActor that serves as handle
|
||||
* A drag has been started. You have to connect to this signal and
|
||||
* return an actor that is transformed during the drag operation.
|
||||
*
|
||||
* @param x The global x coordinate where the action was activated
|
||||
* @param y The global y coordinate where the action was activated
|
||||
* @return A ClutterActor that serves as handle
|
||||
*/
|
||||
public signal Clutter.Actor drag_begin ();
|
||||
public signal Clutter.Actor drag_begin (float x, float y);
|
||||
|
||||
/**
|
||||
* A drag has been canceled. You may want to consider cleaning up
|
||||
@ -59,13 +55,55 @@ namespace Gala
|
||||
*/
|
||||
public signal void crossed (bool hovered);
|
||||
|
||||
/**
|
||||
* Emitted on the source when a destination is crossed.
|
||||
*
|
||||
* @param destination The destination actor that has been crossed
|
||||
* @param hovered Whether the actor is now hovered or has just been left
|
||||
*/
|
||||
public signal void destination_crossed (Clutter.Actor destination, bool hovered);
|
||||
|
||||
/**
|
||||
* The source has been clicked, but the movement was not larger than
|
||||
* the drag threshold. Useful if the source is also activable.
|
||||
*/
|
||||
public signal void actor_clicked ();
|
||||
|
||||
/**
|
||||
* The type of the action
|
||||
*/
|
||||
public DragDropActionType drag_type { get; construct; }
|
||||
|
||||
/**
|
||||
* The unique id given to this drag-drop-group
|
||||
*/
|
||||
public string drag_id { get; construct; }
|
||||
|
||||
public Clutter.Actor handle { get; private set; }
|
||||
/**
|
||||
* Indicates whether a drag action is currently active
|
||||
*/
|
||||
public bool dragging { get; private set; default = false; }
|
||||
|
||||
/**
|
||||
* Allow checking the parents of reactive children if they are valid destinations
|
||||
* if the child is none
|
||||
*/
|
||||
public bool allow_bubbling { get; set; default = true; }
|
||||
|
||||
Clutter.Actor? hovered = null;
|
||||
bool clicked = false;
|
||||
bool actor_was_reactive = false;
|
||||
float last_x;
|
||||
float last_y;
|
||||
|
||||
/**
|
||||
* Create a new DragDropAction
|
||||
*
|
||||
* @param type The type of this actor
|
||||
* @param id An ID that marks which sources can be dragged on
|
||||
* which destinations. It has to be the same for all actors that
|
||||
* should be compatible with each other.
|
||||
* @param id An ID that marks which sources can be dragged on
|
||||
* which destinations. It has to be the same for all actors that
|
||||
* should be compatible with each other.
|
||||
*/
|
||||
public DragDropAction (DragDropActionType type, string id)
|
||||
{
|
||||
@ -95,7 +133,6 @@ namespace Gala
|
||||
{
|
||||
if (drag_type == DragDropActionType.SOURCE) {
|
||||
actor.button_press_event.disconnect (source_clicked);
|
||||
actor.motion_event.disconnect (source_motion);
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,38 +140,72 @@ namespace Gala
|
||||
{
|
||||
if (drag_type == DragDropActionType.SOURCE) {
|
||||
actor.button_press_event.connect (source_clicked);
|
||||
actor.motion_event.connect (source_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void emit_crossed (Clutter.Actor destination, bool hovered)
|
||||
{
|
||||
get_drag_drop_action (destination).crossed (hovered);
|
||||
destination_crossed (destination, hovered);
|
||||
}
|
||||
|
||||
bool source_clicked (Clutter.ButtonEvent event)
|
||||
{
|
||||
if (event.button != 1)
|
||||
return false;
|
||||
|
||||
clicked = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool source_motion (Clutter.MotionEvent event)
|
||||
{
|
||||
if (!clicked)
|
||||
return false;
|
||||
|
||||
handle = drag_begin ();
|
||||
if (handle == null) {
|
||||
critical ("No handle has been returned by the started signal, aborting drag.");
|
||||
return false;
|
||||
}
|
||||
|
||||
dragging = true;
|
||||
clicked = false;
|
||||
actor.get_stage ().captured_event.connect (follow_move);
|
||||
clicked = true;
|
||||
last_x = event.x;
|
||||
last_y = event.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool follow_move (Clutter.Event event)
|
||||
{
|
||||
// still determining if we actually want to start a drag action
|
||||
if (!dragging) {
|
||||
switch (event.get_type ()) {
|
||||
case Clutter.EventType.MOTION:
|
||||
float x, y;
|
||||
event.get_coords (out x, out y);
|
||||
|
||||
var drag_threshold = Clutter.Settings.get_default ().dnd_drag_threshold;
|
||||
if (Math.fabsf (last_x - x) > drag_threshold || Math.fabsf (last_y - y) > drag_threshold) {
|
||||
handle = drag_begin (x, y);
|
||||
if (handle == null) {
|
||||
actor.get_stage ().captured_event.disconnect (follow_move);
|
||||
critical ("No handle has been returned by the started signal, aborting drag.");
|
||||
return false;
|
||||
}
|
||||
|
||||
actor_was_reactive = handle.reactive;
|
||||
handle.reactive = false;
|
||||
|
||||
clicked = false;
|
||||
dragging = true;
|
||||
}
|
||||
return true;
|
||||
case Clutter.EventType.BUTTON_RELEASE:
|
||||
float x, y, ex, ey;
|
||||
event.get_coords (out ex, out ey);
|
||||
actor.get_transformed_position (out x, out y);
|
||||
|
||||
// release has happened within bounds of actor
|
||||
if (x < ex && x + actor.width > ex && y < ey && y + actor.height > ey) {
|
||||
actor_clicked ();
|
||||
}
|
||||
|
||||
actor.get_stage ().captured_event.disconnect (follow_move);
|
||||
clicked = false;
|
||||
dragging = false;
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (event.get_type ()) {
|
||||
case Clutter.EventType.KEY_PRESS:
|
||||
if (event.get_key_code () == Clutter.Key.Escape) {
|
||||
@ -144,25 +215,44 @@ namespace Gala
|
||||
case Clutter.EventType.MOTION:
|
||||
float x, y;
|
||||
event.get_coords (out x, out y);
|
||||
handle.x = x;
|
||||
handle.y = y;
|
||||
handle.x -= last_x - x;
|
||||
handle.y -= last_y - y;
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
|
||||
var stage = actor.get_stage ();
|
||||
var actor = actor.get_stage ().get_actor_at_pos (Clutter.PickMode.REACTIVE, (int)x, (int)y);
|
||||
DragDropAction action = null;
|
||||
if (actor == null || (action = get_drag_drop_action (actor)) == null) {
|
||||
// if we're allowed to bubble and we this actor is not a destination, check its parents
|
||||
if (actor != null && (action = get_drag_drop_action (actor)) == null && allow_bubbling) {
|
||||
while ((actor = actor.get_parent ()) != stage) {
|
||||
if ((action = get_drag_drop_action (actor)) != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// didn't change, no need to do anything
|
||||
if (actor == hovered)
|
||||
return true;
|
||||
|
||||
if (action == null) {
|
||||
// apparently we left ours if we had one before
|
||||
if (hovered != null) {
|
||||
get_drag_drop_action (hovered).crossed (false);
|
||||
emit_crossed (hovered, false);
|
||||
hovered = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// signal the previous one that we left it
|
||||
if (hovered != null) {
|
||||
get_drag_drop_action (hovered).crossed (false);
|
||||
emit_crossed (hovered, false);
|
||||
}
|
||||
|
||||
// tell the new one that it is hovered
|
||||
hovered = actor;
|
||||
action.crossed (true);
|
||||
emit_crossed (hovered, true);
|
||||
|
||||
return true;
|
||||
case Clutter.EventType.BUTTON_RELEASE:
|
||||
@ -212,16 +302,22 @@ namespace Gala
|
||||
actor.get_stage ().captured_event.disconnect (follow_move);
|
||||
}
|
||||
|
||||
drag_canceled ();
|
||||
dragging = false;
|
||||
actor.reactive = actor_was_reactive;
|
||||
|
||||
drag_canceled ();
|
||||
}
|
||||
|
||||
void finish ()
|
||||
{
|
||||
// make sure they reset the style or whatever they changed when hovered
|
||||
get_drag_drop_action (hovered).crossed (false);
|
||||
emit_crossed (hovered, false);
|
||||
|
||||
actor.get_stage ().captured_event.disconnect (follow_move);
|
||||
|
||||
dragging = false;
|
||||
actor.reactive = actor_was_reactive;
|
||||
|
||||
drag_end (hovered);
|
||||
}
|
||||
}
|
||||
|
@ -106,5 +106,390 @@ namespace Gala
|
||||
var xregion = X.Fixes.create_region (display.get_xdisplay (), rects);
|
||||
Util.set_stage_input_region (screen, xregion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Code ported from KWin present windows effect
|
||||
* https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp
|
||||
**/
|
||||
|
||||
// constants, mainly for natural expo
|
||||
static const int GAPS = 10;
|
||||
static const int MAX_TRANSLATIONS = 100000;
|
||||
static const int ACCURACY = 20;
|
||||
|
||||
// some math utilities
|
||||
static int squared_distance (Gdk.Point a, Gdk.Point b)
|
||||
{
|
||||
var k1 = b.x - a.x;
|
||||
var k2 = b.y - a.y;
|
||||
|
||||
return k1*k1 + k2*k2;
|
||||
}
|
||||
|
||||
static bool rect_is_overlapping_any (Meta.Rectangle rect, Meta.Rectangle[] rects, Meta.Rectangle border)
|
||||
{
|
||||
if (!border.contains_rect (rect))
|
||||
return true;
|
||||
foreach (var comp in rects) {
|
||||
if (comp == rect)
|
||||
continue;
|
||||
|
||||
if (rect.overlap (comp))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Meta.Rectangle rect_adjusted (Meta.Rectangle rect, int dx1, int dy1, int dx2, int dy2)
|
||||
{
|
||||
return {rect.x + dx1, rect.y + dy1, rect.width + (-dx1 + dx2), rect.height + (-dy1 + dy2)};
|
||||
}
|
||||
|
||||
static Gdk.Point rect_center (Meta.Rectangle rect)
|
||||
{
|
||||
return {rect.x + rect.width / 2, rect.y + rect.height / 2};
|
||||
}
|
||||
|
||||
public struct TilableWindow
|
||||
{
|
||||
Meta.Rectangle rect;
|
||||
void *id;
|
||||
}
|
||||
|
||||
public static List<TilableWindow?> calculate_grid_placement (Meta.Rectangle area,
|
||||
List<TilableWindow?> windows)
|
||||
{
|
||||
uint window_count = windows.length ();
|
||||
int columns = (int)Math.ceil (Math.sqrt (window_count));
|
||||
int rows = (int)Math.ceil (window_count / (double)columns);
|
||||
|
||||
// Assign slots
|
||||
int slot_width = area.width / columns;
|
||||
int slot_height = area.height / rows;
|
||||
|
||||
TilableWindow?[] taken_slots = {};
|
||||
taken_slots.resize (rows * columns);
|
||||
|
||||
// precalculate all slot centers
|
||||
Gdk.Point[] slot_centers = {};
|
||||
slot_centers.resize (rows * columns);
|
||||
for (int x = 0; x < columns; x++) {
|
||||
for (int y = 0; y < rows; y++) {
|
||||
slot_centers[x + y * columns] = {area.x + slot_width * x + slot_width / 2,
|
||||
area.y + slot_height * y + slot_height / 2};
|
||||
}
|
||||
}
|
||||
|
||||
// Assign each window to the closest available slot
|
||||
var tmplist = windows.copy ();
|
||||
while (tmplist.length () > 0) {
|
||||
unowned List<TilableWindow?> link = tmplist.nth (0);
|
||||
var window = link.data;
|
||||
var rect = window.rect;
|
||||
|
||||
var slot_candidate = -1;
|
||||
var slot_candidate_distance = int.MAX;
|
||||
var pos = rect_center (rect);
|
||||
|
||||
// all slots
|
||||
for (int i = 0; i < columns * rows; i++) {
|
||||
if (i > window_count - 1)
|
||||
break;
|
||||
|
||||
var dist = squared_distance (pos, slot_centers[i]);
|
||||
|
||||
if (dist < slot_candidate_distance) {
|
||||
// window is interested in this slot
|
||||
var occupier = taken_slots[i];
|
||||
if (occupier == window)
|
||||
continue;
|
||||
|
||||
if (occupier == null || dist < squared_distance (rect_center (occupier.rect), slot_centers[i])) {
|
||||
// either nobody lives here, or we're better - takeover the slot if it's our best
|
||||
slot_candidate = i;
|
||||
slot_candidate_distance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (slot_candidate == -1)
|
||||
continue;
|
||||
|
||||
if (taken_slots[slot_candidate] != null)
|
||||
tmplist.prepend (taken_slots[slot_candidate]);
|
||||
|
||||
tmplist.remove_link (link);
|
||||
taken_slots[slot_candidate] = window;
|
||||
}
|
||||
|
||||
var result = new List<TilableWindow?> ();
|
||||
|
||||
// see how many windows we have on the last row
|
||||
int left_over = (int)window_count - columns * (rows - 1);
|
||||
|
||||
for (int slot = 0; slot < columns * rows; slot++) {
|
||||
var window = taken_slots[slot];
|
||||
// some slots might be empty
|
||||
if (window == null)
|
||||
continue;
|
||||
|
||||
var rect = window.rect;
|
||||
|
||||
// Work out where the slot is
|
||||
Meta.Rectangle target = {area.x + (slot % columns) * slot_width,
|
||||
area.y + (slot / columns) * slot_height,
|
||||
slot_width,
|
||||
slot_height};
|
||||
target = rect_adjusted (target, 10, 10, -10, -10);
|
||||
|
||||
float scale;
|
||||
if (target.width / (double)rect.width < target.height / (double)rect.height) {
|
||||
// Center vertically
|
||||
scale = target.width / (float)rect.width;
|
||||
target.y += (target.height - (int)(rect.height * scale)) / 2;
|
||||
target.height = (int)Math.floorf (rect.height * scale);
|
||||
} else {
|
||||
// Center horizontally
|
||||
scale = target.height / (float)rect.height;
|
||||
target.x += (target.width - (int)(rect.width * scale)) / 2;
|
||||
target.width = (int)Math.floorf (rect.width * scale);
|
||||
}
|
||||
|
||||
// Don't scale the windows too much
|
||||
if (scale > 1.0) {
|
||||
scale = 1.0f;
|
||||
target = {rect_center (target).x - (int)Math.floorf (rect.width * scale) / 2,
|
||||
rect_center (target).y - (int)Math.floorf (rect.height * scale) / 2,
|
||||
(int)Math.floorf (scale * rect.width),
|
||||
(int)Math.floorf (scale * rect.height)};
|
||||
}
|
||||
|
||||
// put the last row in the center, if necessary
|
||||
if (left_over != columns && slot >= columns * (rows - 1))
|
||||
target.x += (columns - left_over) * slot_width / 2;
|
||||
|
||||
result.prepend ({ target, window.id });
|
||||
}
|
||||
|
||||
result.reverse ();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*public List<Meta.Rectangle?> natural_placement (Meta.Rectangle area, List<Meta.Rectangle?> windows)
|
||||
{
|
||||
Meta.Rectangle bounds = {area.x, area.y, area.width, area.height};
|
||||
|
||||
var window_count = windows.length ();
|
||||
|
||||
var direction = 0;
|
||||
int[] directions = new int[window_count];
|
||||
Meta.Rectangle[] rects = new Meta.Rectangle[window_count];
|
||||
|
||||
for (int i = 0; i < window_count; i++) {
|
||||
// save rectangles into 4-dimensional arrays representing two corners of the rectangular: [left_x, top_y, right_x, bottom_y]
|
||||
var rect = clones.nth_data (i);
|
||||
rect = rect_adjusted(rect, -GAPS, -GAPS, GAPS, GAPS);
|
||||
rects[i] = rect;
|
||||
bounds = bounds.union (rect);
|
||||
|
||||
// This is used when the window is on the edge of the screen to try to use as much screen real estate as possible.
|
||||
directions[i] = direction;
|
||||
direction++;
|
||||
if (direction == 4)
|
||||
direction = 0;
|
||||
}
|
||||
|
||||
var loop_counter = 0;
|
||||
var overlap = false;
|
||||
do {
|
||||
overlap = false;
|
||||
for (var i = 0; i < rects.length; i++) {
|
||||
for (var j = 0; j < rects.length; j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
var rect = rects[i];
|
||||
var comp = rects[j];
|
||||
|
||||
if (!rect.overlap (comp))
|
||||
continue;
|
||||
|
||||
loop_counter ++;
|
||||
overlap = true;
|
||||
|
||||
// Determine pushing direction
|
||||
Gdk.Point i_center = rect_center (rect);
|
||||
Gdk.Point j_center = rect_center (comp);
|
||||
Gdk.Point diff = {j_center.x - i_center.x, j_center.y - i_center.y};
|
||||
|
||||
// Prevent dividing by zero and non-movement
|
||||
if (diff.x == 0 && diff.y == 0)
|
||||
diff.x = 1;
|
||||
|
||||
// Approximate a vector of between 10px and 20px in magnitude in the same direction
|
||||
var length = Math.sqrtf (diff.x * diff.x + diff.y * diff.y);
|
||||
diff.x = (int)Math.floorf (diff.x * ACCURACY / length);
|
||||
diff.y = (int)Math.floorf (diff.y * ACCURACY / length);
|
||||
// Move both windows apart
|
||||
rect.x += -diff.x;
|
||||
rect.y += -diff.y;
|
||||
comp.x += diff.x;
|
||||
comp.y += diff.y;
|
||||
|
||||
// Try to keep the bounding rect the same aspect as the screen so that more
|
||||
// screen real estate is utilised. We do this by splitting the screen into nine
|
||||
// equal sections, if the window center is in any of the corner sections pull the
|
||||
// window towards the outer corner. If it is in any of the other edge sections
|
||||
// alternate between each corner on that edge. We don't want to determine it
|
||||
// randomly as it will not produce consistant locations when using the filter.
|
||||
// Only move one window so we don't cause large amounts of unnecessary zooming
|
||||
// in some situations. We need to do this even when expanding later just in case
|
||||
// all windows are the same size.
|
||||
// (We are using an old bounding rect for this, hopefully it doesn't matter)
|
||||
var x_section = (int)Math.roundf ((rect.x - bounds.x) / (bounds.width / 3.0f));
|
||||
var y_section = (int)Math.roundf ((comp.y - bounds.y) / (bounds.height / 3.0f));
|
||||
|
||||
i_center = rect_center (rect);
|
||||
diff.x = 0;
|
||||
diff.y = 0;
|
||||
if (x_section != 1 || y_section != 1) { // Remove this if you want the center to pull as well
|
||||
if (x_section == 1)
|
||||
x_section = (directions[i] / 2 == 1 ? 2 : 0);
|
||||
if (y_section == 1)
|
||||
y_section = (directions[i] % 2 == 1 ? 2 : 0);
|
||||
}
|
||||
if (x_section == 0 && y_section == 0) {
|
||||
diff.x = bounds.x - i_center.x;
|
||||
diff.y = bounds.y - i_center.y;
|
||||
}
|
||||
if (x_section == 2 && y_section == 0) {
|
||||
diff.x = bounds.x + bounds.width - i_center.x;
|
||||
diff.y = bounds.y - i_center.y;
|
||||
}
|
||||
if (x_section == 2 && y_section == 2) {
|
||||
diff.x = bounds.x + bounds.width - i_center.x;
|
||||
diff.y = bounds.y + bounds.height - i_center.y;
|
||||
}
|
||||
if (x_section == 0 && y_section == 2) {
|
||||
diff.x = bounds.x - i_center.x;
|
||||
diff.y = bounds.y + bounds.height - i_center.y;
|
||||
}
|
||||
if (diff.x != 0 || diff.y != 0) {
|
||||
length = Math.sqrtf (diff.x * diff.x + diff.y * diff.y);
|
||||
diff.x *= (int)Math.floorf (ACCURACY / length / 2.0f);
|
||||
diff.y *= (int)Math.floorf (ACCURACY / length / 2.0f);
|
||||
rect.x += diff.x;
|
||||
rect.y += diff.y;
|
||||
}
|
||||
|
||||
// Update bounding rect
|
||||
bounds = bounds.union(rect);
|
||||
bounds = bounds.union(comp);
|
||||
|
||||
//we took copies from the rects from our list so we need to reassign them
|
||||
rects[i] = rect;
|
||||
rects[j] = comp;
|
||||
}
|
||||
}
|
||||
} while (overlap && loop_counter < MAX_TRANSLATIONS);
|
||||
|
||||
// Work out scaling by getting the most top-left and most bottom-right window coords.
|
||||
float scale = Math.fminf (Math.fminf (area.width / (float)bounds.width, area.height / (float)bounds.height), 1.0f);
|
||||
|
||||
// Make bounding rect fill the screen size for later steps
|
||||
bounds.x = (int)Math.floorf (bounds.x - (area.width - bounds.width * scale) / 2);
|
||||
bounds.y = (int)Math.floorf (bounds.y - (area.height - bounds.height * scale) / 2);
|
||||
bounds.width = (int)Math.floorf (area.width / scale);
|
||||
bounds.height = (int)Math.floorf (area.height / scale);
|
||||
|
||||
// Move all windows back onto the screen and set their scale
|
||||
var index = 0;
|
||||
foreach (var rect in rects) {
|
||||
rect = {(int)Math.floorf ((rect.x - bounds.x) * scale + area.x),
|
||||
(int)Math.floorf ((rect.y - bounds.y) * scale + area.y),
|
||||
(int)Math.floorf (rect.width * scale),
|
||||
(int)Math.floorf (rect.height * scale)};
|
||||
|
||||
rects[index] = rect;
|
||||
index++;
|
||||
}
|
||||
|
||||
// fill gaps by enlarging windows
|
||||
bool moved = false;
|
||||
Meta.Rectangle border = area;
|
||||
do {
|
||||
moved = false;
|
||||
|
||||
index = 0;
|
||||
foreach (var rect in rects) {
|
||||
|
||||
int width_diff = ACCURACY;
|
||||
int height_diff = (int)Math.floorf ((((rect.width + width_diff) - rect.height) /
|
||||
(float)rect.width) * rect.height);
|
||||
int x_diff = width_diff / 2;
|
||||
int y_diff = height_diff / 2;
|
||||
|
||||
//top right
|
||||
Meta.Rectangle old = rect;
|
||||
rect = {rect.x + x_diff, rect.y - y_diff - height_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//bottom right
|
||||
old = rect;
|
||||
rect = {rect.x + x_diff, rect.y + y_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//bottom left
|
||||
old = rect;
|
||||
rect = {rect.x - x_diff, rect.y + y_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//top left
|
||||
old = rect;
|
||||
rect = {rect.x - x_diff, rect.y - y_diff - height_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
rects[index] = rect;
|
||||
index++;
|
||||
}
|
||||
} while (moved);
|
||||
|
||||
var result = new List<Meta.Rectangle?> ();
|
||||
|
||||
index = 0;
|
||||
foreach (var rect in rects) {
|
||||
var window_rect = clones.nth_data (index);
|
||||
|
||||
rect = rect_adjusted (rect, GAPS, GAPS, -GAPS, -GAPS);
|
||||
scale = rect.width / (float)window_rect.width;
|
||||
|
||||
if (scale > 2.0 || (scale > 1.0 && (window_rect.width > 300 || window_rect.height > 300))) {
|
||||
scale = (window_rect.width > 300 || window_rect.height > 300) ? 1.0f : 2.0f;
|
||||
rect = {rect_center (rect).x - (int)Math.floorf (window_rect.width * scale) / 2,
|
||||
rect_center (rect).y - (int)Math.floorf (window_rect.height * scale) / 2,
|
||||
(int)Math.floorf (window_rect.width * scale),
|
||||
(int)Math.floorf (window_rect.height * scale)};
|
||||
}
|
||||
|
||||
result.prepend (rect);
|
||||
index++;
|
||||
}
|
||||
|
||||
result.reverse ();
|
||||
return result;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ gala_VALASOURCES = \
|
||||
Widgets/WorkspaceView.vala \
|
||||
Widgets/MultitaskingView/IconGroup.vala \
|
||||
Widgets/MultitaskingView/MultitaskingView.vala \
|
||||
Widgets/MultitaskingView/TiledWindowContainer.vala \
|
||||
Widgets/MultitaskingView/Workspace.vala \
|
||||
$(NULL)
|
||||
|
||||
|
@ -4,8 +4,6 @@ namespace Gala
|
||||
{
|
||||
public class IconGroup : Actor
|
||||
{
|
||||
List<Meta.Window> windows;
|
||||
|
||||
const int SIZE = 64;
|
||||
|
||||
static const int PLUS_SIZE = 8;
|
||||
@ -13,8 +11,14 @@ namespace Gala
|
||||
|
||||
public signal void selected ();
|
||||
|
||||
public IconGroup ()
|
||||
public Meta.Workspace workspace { get; construct; }
|
||||
|
||||
List<Meta.Window> windows;
|
||||
|
||||
public IconGroup (Meta.Workspace workspace)
|
||||
{
|
||||
Object (workspace: workspace);
|
||||
|
||||
clear ();
|
||||
|
||||
width = SIZE;
|
||||
@ -47,6 +51,12 @@ namespace Gala
|
||||
redraw ();
|
||||
}
|
||||
|
||||
public void remove_window (Meta.Window window)
|
||||
{
|
||||
windows.remove (window);
|
||||
redraw ();
|
||||
}
|
||||
|
||||
public void redraw ()
|
||||
{
|
||||
content.invalidate ();
|
||||
|
@ -57,7 +57,7 @@ namespace Gala
|
||||
{
|
||||
float x = 0;
|
||||
WorkspaceClone? active = null;
|
||||
var active_index = screen.get_active_workspace ().index ();;
|
||||
var active_index = screen.get_active_workspace ().index ();
|
||||
|
||||
foreach (var child in workspaces.get_children ()) {
|
||||
var workspace_clone = child as WorkspaceClone;
|
||||
@ -80,7 +80,7 @@ namespace Gala
|
||||
|
||||
void add_workspace (int num)
|
||||
{
|
||||
var workspace = new WorkspaceClone (screen.get_workspace_by_index (num));
|
||||
var workspace = new WorkspaceClone (screen.get_workspace_by_index (num), wm);
|
||||
workspace.window_selected.connect (window_selected);
|
||||
workspace.selected.connect (activate_workspace);
|
||||
|
||||
|
296
src/Widgets/MultitaskingView/TiledWindowContainer.vala
Normal file
296
src/Widgets/MultitaskingView/TiledWindowContainer.vala
Normal file
@ -0,0 +1,296 @@
|
||||
using Clutter;
|
||||
using Meta;
|
||||
|
||||
namespace Gala
|
||||
{
|
||||
public class TiledWindow : Actor
|
||||
{
|
||||
public signal void selected ();
|
||||
public signal void request_reposition ();
|
||||
|
||||
public Meta.Window window { get; construct; }
|
||||
|
||||
DragDropAction drag_action;
|
||||
Clone? clone = null;
|
||||
|
||||
Actor prev_parent = null;
|
||||
int prev_index = -1;
|
||||
|
||||
public TiledWindow (Meta.Window window)
|
||||
{
|
||||
Object (window: window);
|
||||
|
||||
reactive = true;
|
||||
|
||||
load_clone ();
|
||||
|
||||
window.unmanaged.connect (unmanaged);
|
||||
|
||||
drag_action = new DragDropAction (DragDropActionType.SOURCE, "multitaskingview-window");
|
||||
drag_action.drag_begin.connect (drag_begin);
|
||||
drag_action.destination_crossed.connect (drag_destination_crossed);
|
||||
drag_action.drag_end.connect (drag_end);
|
||||
drag_action.drag_canceled.connect (drag_canceled);
|
||||
drag_action.actor_clicked.connect (() => { selected (); });
|
||||
|
||||
add_action (drag_action);
|
||||
}
|
||||
|
||||
~TiledWindow ()
|
||||
{
|
||||
window.unmanaged.disconnect (unmanaged);
|
||||
}
|
||||
|
||||
public void load_clone ()
|
||||
{
|
||||
var actor = window.get_compositor_private () as WindowActor;
|
||||
if (actor == null) {
|
||||
Idle.add (() => {
|
||||
if (window.get_compositor_private () != null)
|
||||
load_clone ();
|
||||
return false;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
clone = new Clone (actor);
|
||||
clone.add_constraint (new BindConstraint (this, BindCoordinate.SIZE, 0));
|
||||
add_child (clone);
|
||||
|
||||
set_easing_duration (250);
|
||||
set_easing_mode (AnimationMode.EASE_OUT_QUAD);
|
||||
set_position (actor.x, actor.y);
|
||||
|
||||
request_reposition ();
|
||||
}
|
||||
|
||||
public void transition_to_original_state ()
|
||||
{
|
||||
var actor = window.get_compositor_private () as Actor;
|
||||
|
||||
set_easing_mode (AnimationMode.EASE_IN_OUT_CUBIC);
|
||||
set_easing_duration (300);
|
||||
set_size (actor.width, actor.height);
|
||||
set_position (actor.x, actor.y);
|
||||
}
|
||||
|
||||
void unmanaged ()
|
||||
{
|
||||
if (drag_action.dragging)
|
||||
drag_action.cancel ();
|
||||
|
||||
if (clone != null)
|
||||
clone.destroy ();
|
||||
destroy ();
|
||||
}
|
||||
|
||||
Actor drag_begin (float click_x, float click_y)
|
||||
{
|
||||
float abs_x, abs_y;
|
||||
get_transformed_position (out abs_x, out abs_y);
|
||||
|
||||
prev_parent = get_parent ();
|
||||
prev_index = prev_parent.get_children ().index (this);
|
||||
reparent (get_stage ());
|
||||
|
||||
save_easing_state ();
|
||||
set_easing_duration (200);
|
||||
set_easing_mode (AnimationMode.EASE_IN_OUT_CUBIC);
|
||||
set_scale (0.4, 0.4);
|
||||
restore_easing_state ();
|
||||
|
||||
request_reposition ();
|
||||
|
||||
save_easing_state ();
|
||||
set_easing_duration (0);
|
||||
set_position (abs_x, abs_y);
|
||||
set_pivot_point ((click_x - abs_x) / width, (click_y - abs_y) / height);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void drag_destination_crossed (Actor destination, bool hovered)
|
||||
{
|
||||
if (!(destination is IconGroup))
|
||||
return;
|
||||
|
||||
var scale = hovered ? 0.2 : 0.4;
|
||||
var opacity = hovered ? 100 : 255;
|
||||
var mode = hovered ? AnimationMode.EASE_IN_OUT_BACK : AnimationMode.EASE_OUT_ELASTIC;
|
||||
|
||||
save_easing_state ();
|
||||
|
||||
set_easing_mode (mode);
|
||||
set_easing_duration (300);
|
||||
set_scale (scale, scale);
|
||||
|
||||
set_easing_mode (AnimationMode.LINEAR);
|
||||
set_opacity (opacity);
|
||||
|
||||
restore_easing_state ();
|
||||
}
|
||||
|
||||
void drag_end (Actor destination)
|
||||
{
|
||||
Meta.Workspace workspace = null;
|
||||
|
||||
if (destination is IconGroup) {
|
||||
workspace = (destination as IconGroup).workspace;
|
||||
} else if (destination is FramedBackground) {
|
||||
workspace = (destination.get_parent () as WorkspaceClone).workspace;
|
||||
}
|
||||
|
||||
if (workspace != null && workspace != window.get_workspace ()) {
|
||||
window.change_workspace (workspace);
|
||||
unmanaged ();
|
||||
} else {
|
||||
// if we're dropped at the place where we came from interpret as cancel
|
||||
drag_canceled ();
|
||||
}
|
||||
}
|
||||
|
||||
void drag_canceled ()
|
||||
{
|
||||
get_parent ().remove_child (this);
|
||||
prev_parent.insert_child_at_index (this, prev_index);
|
||||
|
||||
save_easing_state ();
|
||||
set_easing_duration (400);
|
||||
set_easing_mode (AnimationMode.EASE_OUT_BOUNCE);
|
||||
set_scale (1, 1);
|
||||
|
||||
request_reposition ();
|
||||
|
||||
restore_easing_state ();
|
||||
restore_easing_state ();
|
||||
|
||||
opacity = 255;
|
||||
}
|
||||
}
|
||||
|
||||
public class TiledWorkspaceContainer : Actor
|
||||
{
|
||||
public signal void window_selected (Meta.Window window);
|
||||
|
||||
public int padding_top { get; set; }
|
||||
public int padding_left { get; set; }
|
||||
public int padding_right { get; set; }
|
||||
public int padding_bottom { get; set; }
|
||||
|
||||
HashTable<int,int> _stacking_order;
|
||||
public HashTable<int,int> stacking_order {
|
||||
get {
|
||||
return _stacking_order;
|
||||
}
|
||||
set {
|
||||
_stacking_order = value;
|
||||
restack ();
|
||||
}
|
||||
}
|
||||
|
||||
public TiledWorkspaceContainer (HashTable<int,int> stacking_order)
|
||||
{
|
||||
_stacking_order = stacking_order;
|
||||
}
|
||||
|
||||
public void add_window (Meta.Window window, bool reflow_windows = true)
|
||||
{
|
||||
var new_window = new TiledWindow (window);
|
||||
var new_seq = stacking_order.get ((int)window.get_stable_sequence ());
|
||||
|
||||
new_window.selected.connect (window_selected_cb);
|
||||
new_window.destroy.connect (window_destroyed);
|
||||
new_window.request_reposition.connect (reflow);
|
||||
|
||||
var children = get_children ();
|
||||
var added = false;
|
||||
foreach (var child in children) {
|
||||
if (stacking_order.get ((int)(child as TiledWindow).window.get_stable_sequence ()) < new_seq) {
|
||||
insert_child_below (new_window, child);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// top most or no other children
|
||||
if (!added)
|
||||
add_child (new_window);
|
||||
|
||||
if (reflow_windows)
|
||||
reflow ();
|
||||
}
|
||||
|
||||
void window_selected_cb (TiledWindow tiled)
|
||||
{
|
||||
window_selected (tiled.window);
|
||||
}
|
||||
|
||||
void window_destroyed (Actor actor)
|
||||
{
|
||||
var window = actor as TiledWindow;
|
||||
|
||||
window.destroy.disconnect (window_destroyed);
|
||||
window.selected.disconnect (window_selected_cb);
|
||||
|
||||
Idle.add (() => {
|
||||
reflow ();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void restack ()
|
||||
{
|
||||
// FIXME there is certainly a way to do this in less than n^2 steps
|
||||
foreach (var child1 in get_children ()) {
|
||||
var i = 0;
|
||||
foreach (var child2 in get_children ()) {
|
||||
int index1 = stacking_order.get ((int)(child1 as TiledWindow).window.get_stable_sequence ());
|
||||
int index2 = stacking_order.get ((int)(child2 as TiledWindow).window.get_stable_sequence ());
|
||||
if (index1 < index2) {
|
||||
set_child_at_index (child1, i);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reflow ()
|
||||
{
|
||||
var windows = new List<InternalUtils.TilableWindow?> ();
|
||||
foreach (var child in get_children ()) {
|
||||
var window = child as TiledWindow;
|
||||
windows.prepend ({ window.window.get_outer_rect (), window });
|
||||
}
|
||||
windows.reverse ();
|
||||
|
||||
if (windows.length () < 1)
|
||||
return;
|
||||
|
||||
Meta.Rectangle area = {
|
||||
padding_left,
|
||||
padding_top,
|
||||
(int)width - padding_left - padding_right,
|
||||
(int)height - padding_top - padding_bottom
|
||||
};
|
||||
|
||||
var result = InternalUtils.calculate_grid_placement (area, windows);
|
||||
|
||||
foreach (var tilable in result) {
|
||||
var window = (TiledWindow)tilable.id;
|
||||
window.set_position (tilable.rect.x, tilable.rect.y);
|
||||
window.set_size (tilable.rect.width, tilable.rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
public void transition_to_original_state ()
|
||||
{
|
||||
foreach (var child in get_children ()) {
|
||||
var clone = child as TiledWindow;
|
||||
clone.transition_to_original_state ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,19 +77,27 @@ namespace Gala
|
||||
|
||||
public class WorkspaceClone : Clutter.Actor
|
||||
{
|
||||
public static const int BOTTOM_OFFSET = 100;
|
||||
const int TOP_OFFSET = 20;
|
||||
const int HOVER_ACTIVATE_DELAY = 400;
|
||||
|
||||
public WindowManager wm { get; construct; }
|
||||
public Meta.Workspace workspace { get; construct set; }
|
||||
public BackgroundManager background { get; private set; }
|
||||
public IconGroup icon_group { get; private set; }
|
||||
public TiledWorkspaceContainer window_container { get; private set; }
|
||||
|
||||
public signal void window_selected (Meta.Window window);
|
||||
public signal void selected (bool close_view);
|
||||
|
||||
const int TOP_OFFSET = 20;
|
||||
public static const int BOTTOM_OFFSET = 100;
|
||||
uint hover_activate_timeout = 0;
|
||||
|
||||
public WorkspaceClone (Meta.Workspace workspace)
|
||||
public WorkspaceClone (Meta.Workspace workspace, WindowManager wm)
|
||||
{
|
||||
Object (workspace: workspace);
|
||||
Object (workspace: workspace, wm: wm);
|
||||
|
||||
var screen = workspace.get_screen ();
|
||||
var monitor_geometry = screen.get_monitor_geometry (screen.get_primary_monitor ());
|
||||
|
||||
background = new FramedBackground (workspace.get_screen ());
|
||||
background.reactive = true;
|
||||
@ -98,12 +106,40 @@ namespace Gala
|
||||
return false;
|
||||
});
|
||||
|
||||
icon_group = new IconGroup ();
|
||||
window_container = new TiledWorkspaceContainer (wm.window_stacking_order);
|
||||
window_container.window_selected.connect ((w) => { window_selected (w); });
|
||||
window_container.width = monitor_geometry.width;
|
||||
window_container.height = monitor_geometry.height;
|
||||
wm.windows_restacked.connect (() => {
|
||||
window_container.stacking_order = wm.window_stacking_order;
|
||||
});
|
||||
|
||||
icon_group = new IconGroup (workspace);
|
||||
icon_group.selected.connect (() => {
|
||||
selected (false);
|
||||
});
|
||||
|
||||
var screen = workspace.get_screen ();
|
||||
var icons_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window");
|
||||
icon_group.add_action (icons_drop_action);
|
||||
|
||||
var background_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window");
|
||||
background.add_action (background_drop_action);
|
||||
background_drop_action.crossed.connect ((hovered) => {
|
||||
if (!hovered && hover_activate_timeout != 0) {
|
||||
Source.remove (hover_activate_timeout);
|
||||
hover_activate_timeout = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hovered && hover_activate_timeout == 0) {
|
||||
hover_activate_timeout = Timeout.add (HOVER_ACTIVATE_DELAY, () => {
|
||||
selected (false);
|
||||
hover_activate_timeout = 0;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
screen.window_left_monitor.connect ((monitor, window) => {
|
||||
if (monitor == screen.get_primary_monitor ())
|
||||
remove_window (window);
|
||||
@ -117,22 +153,26 @@ namespace Gala
|
||||
workspace.window_added.connect (add_window);
|
||||
|
||||
add_child (background);
|
||||
add_child (window_container);
|
||||
}
|
||||
|
||||
private void add_window (Meta.Window window)
|
||||
{
|
||||
if (window.window_type != Meta.WindowType.NORMAL)
|
||||
return;
|
||||
|
||||
foreach (var child in window_container.get_children ())
|
||||
if ((child as TiledWindow).window == window)
|
||||
return;
|
||||
|
||||
window_container.add_window (window);
|
||||
icon_group.add_window (window);
|
||||
}
|
||||
|
||||
private void remove_window (Meta.Window window)
|
||||
{
|
||||
var window_actor = window.get_compositor_private ();
|
||||
|
||||
foreach (var child in get_children ()) {
|
||||
if (child is Clone && (child as Clone).source == window_actor) {
|
||||
child.destroy ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
icon_group.remove_window (window);
|
||||
// TODO ?
|
||||
}
|
||||
|
||||
private void shrink_rectangle (ref Meta.Rectangle rect, int amount)
|
||||
@ -164,51 +204,31 @@ namespace Gala
|
||||
|
||||
icon_group.clear ();
|
||||
|
||||
var unsorted_windows = workspace.list_windows ();
|
||||
var used_windows = new SList<Meta.Window> ();
|
||||
foreach (var window in unsorted_windows) {
|
||||
// TODO this can be optimized
|
||||
window_container.destroy_all_children ();
|
||||
window_container.padding_top = TOP_OFFSET;
|
||||
window_container.padding_left =
|
||||
window_container.padding_right = (int)(monitor.width - monitor.width * scale) / 2;
|
||||
window_container.padding_bottom = BOTTOM_OFFSET;
|
||||
|
||||
var windows = workspace.list_windows ();
|
||||
foreach (var window in windows) {
|
||||
if (window.window_type == Meta.WindowType.NORMAL) {
|
||||
used_windows.append (window);
|
||||
window_container.add_window (window);
|
||||
icon_group.add_window (window, true);
|
||||
}
|
||||
}
|
||||
|
||||
icon_group.redraw ();
|
||||
|
||||
var windows = display.sort_windows_by_stacking (used_windows);
|
||||
var clones = new List<WindowThumb> ();
|
||||
|
||||
foreach (var window in windows) {
|
||||
var window_actor = window.get_compositor_private () as Meta.WindowActor;
|
||||
|
||||
var clone = new WindowThumb (window, false);
|
||||
clone.selected.connect (() => {
|
||||
window_selected (clone.window);
|
||||
});
|
||||
clone.set_position (window_actor.x, window_actor.y);
|
||||
|
||||
add_child (clone);
|
||||
clones.append (clone);
|
||||
}
|
||||
|
||||
if (clones.length () > 0)
|
||||
WindowOverview.grid_placement (area, clones, place_window);
|
||||
}
|
||||
|
||||
void place_window (Actor window, Meta.Rectangle rect)
|
||||
{
|
||||
window.animate (AnimationMode.EASE_OUT_CUBIC, 250,
|
||||
x: rect.x + 0.0f, y: rect.y + 0.0f, width: rect.width + 0.0f, height: rect.height + 0.0f);
|
||||
(window as WindowThumb).place_children (rect.width, rect.height);
|
||||
window_container.restack ();
|
||||
window_container.reflow ();
|
||||
}
|
||||
|
||||
public void close ()
|
||||
{
|
||||
background.animate (AnimationMode.EASE_IN_OUT_CUBIC, 300, scale_x: 1.0f, scale_y: 1.0f);
|
||||
|
||||
foreach (var child in get_children ()) {
|
||||
if (child is WindowThumb)
|
||||
(child as WindowThumb).close (true, false);
|
||||
}
|
||||
window_container.transition_to_original_state ();
|
||||
}
|
||||
|
||||
~Workspace ()
|
||||
|
@ -31,6 +31,10 @@ namespace Gala
|
||||
|
||||
public class WindowOverview : Actor
|
||||
{
|
||||
const int BORDER = 10;
|
||||
const int TOP_GAP = 20;
|
||||
const int BOTTOM_GAP = 100;
|
||||
|
||||
WindowManager wm;
|
||||
Screen screen;
|
||||
|
||||
@ -81,54 +85,6 @@ namespace Gala
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Code ported from KWin present windows effect
|
||||
* https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp
|
||||
**/
|
||||
|
||||
//constants, mainly for natural expo
|
||||
const int GAPS = 10;
|
||||
const int MAX_TRANSLATIONS = 100000;
|
||||
const int ACCURACY = 20;
|
||||
const int BORDER = 10;
|
||||
const int TOP_GAP = 20;
|
||||
const int BOTTOM_GAP = 100;
|
||||
|
||||
//some math utilities
|
||||
static int squared_distance (Gdk.Point a, Gdk.Point b)
|
||||
{
|
||||
var k1 = b.x - a.x;
|
||||
var k2 = b.y - a.y;
|
||||
|
||||
return k1*k1 + k2*k2;
|
||||
}
|
||||
|
||||
static bool rect_is_overlapping_any (Meta.Rectangle rect, Meta.Rectangle[] rects, Meta.Rectangle border)
|
||||
{
|
||||
if (!border.contains_rect (rect))
|
||||
return true;
|
||||
foreach (var comp in rects) {
|
||||
if (comp == rect)
|
||||
continue;
|
||||
|
||||
if (rect.overlap (comp))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Meta.Rectangle rect_adjusted (Meta.Rectangle rect, int dx1, int dy1, int dx2, int dy2)
|
||||
{
|
||||
return {rect.x + dx1, rect.y + dy1, rect.width + (-dx1 + dx2), rect.height + (-dy1 + dy2)};
|
||||
}
|
||||
|
||||
static Gdk.Point rect_center (Meta.Rectangle rect)
|
||||
{
|
||||
return {rect.x + rect.width / 2, rect.y + rect.height / 2};
|
||||
}
|
||||
|
||||
|
||||
void calculate_places (List<Actor> windows)
|
||||
{
|
||||
var clones = windows.copy ();
|
||||
@ -137,20 +93,21 @@ namespace Gala
|
||||
(int)(b as WindowThumb).window.get_stable_sequence ();
|
||||
});
|
||||
|
||||
//sort windows by monitor
|
||||
List<Actor>[] monitors = {};
|
||||
// sort windows by monitor
|
||||
List<InternalUtils.TilableWindow?>[] monitors = {};
|
||||
monitors.resize (screen.get_n_monitors ());
|
||||
|
||||
foreach (var clone in clones) {
|
||||
// we had some crashes here so there's a reasonable suspicion
|
||||
// that get_monitor() could be larger than get_n_monitors()
|
||||
var index = (clone as WindowThumb).window.get_monitor ();
|
||||
var thumb = clone as WindowThumb;
|
||||
var index = thumb.window.get_monitor ();
|
||||
if (index >= screen.get_n_monitors ()) {
|
||||
critical ("Window '%s' has a monitor assigned that does not actually exists",
|
||||
(clone as WindowThumb).window.get_title ());
|
||||
index = screen.get_n_monitors () - 1;
|
||||
}
|
||||
monitors[index].append (clone);
|
||||
monitors[index].append ({ thumb.window.get_outer_rect (), thumb });
|
||||
}
|
||||
|
||||
for (var i = 0; i < screen.get_n_monitors (); i++) {
|
||||
@ -164,340 +121,21 @@ namespace Gala
|
||||
(int)Math.floorf (geom.width - BORDER * 2),
|
||||
(int)Math.floorf (geom.height - BOTTOM_GAP)};
|
||||
|
||||
if (BehaviorSettings.get_default ().schema.get_enum ("window-overview-type") == WindowOverviewType.GRID)
|
||||
/*TODO if (BehaviorSettings.get_default ().schema.get_enum ("window-overview-type") == WindowOverviewType.GRID)
|
||||
grid_placement (area, monitors[i], place_window);
|
||||
else
|
||||
natural_placement (area, monitors[i]);
|
||||
}
|
||||
}
|
||||
natural_placement (area, monitors[i]);*/
|
||||
|
||||
public static void grid_placement (Meta.Rectangle area, List<Actor> clones, WindowPlacer place)
|
||||
{
|
||||
int columns = (int)Math.ceil (Math.sqrt (clones.length ()));
|
||||
int rows = (int)Math.ceil (clones.length () / (double)columns);
|
||||
|
||||
// Assign slots
|
||||
int slot_width = area.width / columns;
|
||||
int slot_height = area.height / rows;
|
||||
|
||||
WindowThumb[] taken_slots = {};
|
||||
taken_slots.resize (rows * columns);
|
||||
|
||||
// precalculate all slot centers
|
||||
Gdk.Point[] slot_centers = {};
|
||||
slot_centers.resize (rows * columns);
|
||||
for (int x = 0; x < columns; x++) {
|
||||
for (int y = 0; y < rows; y++) {
|
||||
slot_centers[x + y * columns] = {area.x + slot_width * x + slot_width / 2,
|
||||
area.y + slot_height * y + slot_height / 2};
|
||||
var result = InternalUtils.calculate_grid_placement (area, monitors[i]);
|
||||
foreach (var window in result) {
|
||||
place_window ((WindowThumb)window.id, window.rect);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign each window to the closest available slot
|
||||
var tmplist = clones.copy ();
|
||||
var window_count = tmplist.length ();
|
||||
while (tmplist.length () > 0) {
|
||||
var window = tmplist.nth_data (0) as WindowThumb;
|
||||
var rect = window.window.get_outer_rect ();
|
||||
|
||||
var slot_candidate = -1;
|
||||
var slot_candidate_distance = int.MAX;
|
||||
var pos = rect_center (rect);
|
||||
|
||||
// all slots
|
||||
for (int i = 0; i < columns * rows; i++) {
|
||||
if (i > window_count - 1)
|
||||
break;
|
||||
|
||||
var dist = squared_distance (pos, slot_centers[i]);
|
||||
|
||||
if (dist < slot_candidate_distance) {
|
||||
// window is interested in this slot
|
||||
WindowThumb occupier = taken_slots[i];
|
||||
if (occupier == window)
|
||||
continue;
|
||||
|
||||
if (occupier == null || dist < squared_distance (rect_center (occupier.window.get_outer_rect ()), slot_centers[i])) {
|
||||
// either nobody lives here, or we're better - takeover the slot if it's our best
|
||||
slot_candidate = i;
|
||||
slot_candidate_distance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (slot_candidate == -1)
|
||||
continue;
|
||||
|
||||
if (taken_slots[slot_candidate] != null)
|
||||
tmplist.prepend (taken_slots[slot_candidate]);
|
||||
|
||||
tmplist.remove_all (window);
|
||||
taken_slots[slot_candidate] = window;
|
||||
}
|
||||
|
||||
//see how many windows we have on the last row
|
||||
int left_over = (int)clones.length () - columns * (rows - 1);
|
||||
|
||||
for (int slot = 0; slot < columns * rows; slot++) {
|
||||
var window = taken_slots[slot];
|
||||
// some slots might be empty
|
||||
if (window == null)
|
||||
continue;
|
||||
|
||||
var rect = window.window.get_outer_rect ();
|
||||
|
||||
// Work out where the slot is
|
||||
Meta.Rectangle target = {area.x + (slot % columns) * slot_width,
|
||||
area.y + (slot / columns) * slot_height,
|
||||
slot_width,
|
||||
slot_height};
|
||||
target = rect_adjusted (target, 10, 10, -10, -10);
|
||||
|
||||
float scale;
|
||||
if (target.width / (double)rect.width < target.height / (double)rect.height) {
|
||||
// Center vertically
|
||||
scale = target.width / (float)rect.width;
|
||||
target.y += (target.height - (int)(rect.height * scale)) / 2;
|
||||
target.height = (int)Math.floorf (rect.height * scale);
|
||||
} else {
|
||||
// Center horizontally
|
||||
scale = target.height / (float)window.height;
|
||||
target.x += (target.width - (int)(rect.width * scale)) / 2;
|
||||
target.width = (int)Math.floorf (rect.width * scale);
|
||||
}
|
||||
|
||||
// Don't scale the windows too much
|
||||
if (scale > 1.0) {
|
||||
scale = 1.0f;
|
||||
target = {rect_center (target).x - (int)Math.floorf (rect.width * scale) / 2,
|
||||
rect_center (target).y - (int)Math.floorf (rect.height * scale) / 2,
|
||||
(int)Math.floorf (scale * rect.width),
|
||||
(int)Math.floorf (scale * rect.height)};
|
||||
}
|
||||
|
||||
//put the last row in the center, if necessary
|
||||
if (left_over != columns && slot >= columns * (rows - 1))
|
||||
target.x += (columns - left_over) * slot_width / 2;
|
||||
|
||||
place (window, target);
|
||||
}
|
||||
}
|
||||
|
||||
void natural_placement (Meta.Rectangle area, List<Actor> clones)
|
||||
{
|
||||
Meta.Rectangle bounds = {area.x, area.y, area.width, area.height};
|
||||
|
||||
var direction = 0;
|
||||
int[] directions = new int[clones.length ()];
|
||||
Meta.Rectangle[] rects = new Meta.Rectangle[clones.length ()];
|
||||
|
||||
for (int i = 0; i < clones.length (); i++) {
|
||||
// save rectangles into 4-dimensional arrays representing two corners of the rectangular: [left_x, top_y, right_x, bottom_y]
|
||||
var rect = (clones.nth_data (i) as WindowThumb).window.get_outer_rect ();
|
||||
rect = rect_adjusted(rect, -GAPS, -GAPS, GAPS, GAPS);
|
||||
rects[i] = rect;
|
||||
bounds = bounds.union (rect);
|
||||
|
||||
// This is used when the window is on the edge of the screen to try to use as much screen real estate as possible.
|
||||
directions[i] = direction;
|
||||
direction++;
|
||||
if (direction == 4)
|
||||
direction = 0;
|
||||
}
|
||||
|
||||
var loop_counter = 0;
|
||||
var overlap = false;
|
||||
do {
|
||||
overlap = false;
|
||||
for (var i = 0; i < rects.length; i++) {
|
||||
for (var j = 0; j < rects.length; j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
var rect = rects[i];
|
||||
var comp = rects[j];
|
||||
|
||||
if (!rect.overlap (comp))
|
||||
continue;
|
||||
|
||||
loop_counter ++;
|
||||
overlap = true;
|
||||
|
||||
// Determine pushing direction
|
||||
Gdk.Point i_center = rect_center (rect);
|
||||
Gdk.Point j_center = rect_center (comp);
|
||||
Gdk.Point diff = {j_center.x - i_center.x, j_center.y - i_center.y};
|
||||
|
||||
// Prevent dividing by zero and non-movement
|
||||
if (diff.x == 0 && diff.y == 0)
|
||||
diff.x = 1;
|
||||
|
||||
// Approximate a vector of between 10px and 20px in magnitude in the same direction
|
||||
var length = Math.sqrtf (diff.x * diff.x + diff.y * diff.y);
|
||||
diff.x = (int)Math.floorf (diff.x * ACCURACY / length);
|
||||
diff.y = (int)Math.floorf (diff.y * ACCURACY / length);
|
||||
// Move both windows apart
|
||||
rect.x += -diff.x;
|
||||
rect.y += -diff.y;
|
||||
comp.x += diff.x;
|
||||
comp.y += diff.y;
|
||||
|
||||
// Try to keep the bounding rect the same aspect as the screen so that more
|
||||
// screen real estate is utilised. We do this by splitting the screen into nine
|
||||
// equal sections, if the window center is in any of the corner sections pull the
|
||||
// window towards the outer corner. If it is in any of the other edge sections
|
||||
// alternate between each corner on that edge. We don't want to determine it
|
||||
// randomly as it will not produce consistant locations when using the filter.
|
||||
// Only move one window so we don't cause large amounts of unnecessary zooming
|
||||
// in some situations. We need to do this even when expanding later just in case
|
||||
// all windows are the same size.
|
||||
// (We are using an old bounding rect for this, hopefully it doesn't matter)
|
||||
var x_section = (int)Math.roundf ((rect.x - bounds.x) / (bounds.width / 3.0f));
|
||||
var y_section = (int)Math.roundf ((comp.y - bounds.y) / (bounds.height / 3.0f));
|
||||
|
||||
i_center = rect_center (rect);
|
||||
diff.x = 0;
|
||||
diff.y = 0;
|
||||
if (x_section != 1 || y_section != 1) { // Remove this if you want the center to pull as well
|
||||
if (x_section == 1)
|
||||
x_section = (directions[i] / 2 == 1 ? 2 : 0);
|
||||
if (y_section == 1)
|
||||
y_section = (directions[i] % 2 == 1 ? 2 : 0);
|
||||
}
|
||||
if (x_section == 0 && y_section == 0) {
|
||||
diff.x = bounds.x - i_center.x;
|
||||
diff.y = bounds.y - i_center.y;
|
||||
}
|
||||
if (x_section == 2 && y_section == 0) {
|
||||
diff.x = bounds.x + bounds.width - i_center.x;
|
||||
diff.y = bounds.y - i_center.y;
|
||||
}
|
||||
if (x_section == 2 && y_section == 2) {
|
||||
diff.x = bounds.x + bounds.width - i_center.x;
|
||||
diff.y = bounds.y + bounds.height - i_center.y;
|
||||
}
|
||||
if (x_section == 0 && y_section == 2) {
|
||||
diff.x = bounds.x - i_center.x;
|
||||
diff.y = bounds.y + bounds.height - i_center.y;
|
||||
}
|
||||
if (diff.x != 0 || diff.y != 0) {
|
||||
length = Math.sqrtf (diff.x * diff.x + diff.y * diff.y);
|
||||
diff.x *= (int)Math.floorf (ACCURACY / length / 2.0f);
|
||||
diff.y *= (int)Math.floorf (ACCURACY / length / 2.0f);
|
||||
rect.x += diff.x;
|
||||
rect.y += diff.y;
|
||||
}
|
||||
|
||||
// Update bounding rect
|
||||
bounds = bounds.union(rect);
|
||||
bounds = bounds.union(comp);
|
||||
|
||||
//we took copies from the rects from our list so we need to reassign them
|
||||
rects[i] = rect;
|
||||
rects[j] = comp;
|
||||
}
|
||||
}
|
||||
} while (overlap && loop_counter < MAX_TRANSLATIONS);
|
||||
|
||||
// Work out scaling by getting the most top-left and most bottom-right window coords.
|
||||
float scale = Math.fminf (Math.fminf (area.width / (float)bounds.width, area.height / (float)bounds.height), 1.0f);
|
||||
|
||||
// Make bounding rect fill the screen size for later steps
|
||||
bounds.x = (int)Math.floorf (bounds.x - (area.width - bounds.width * scale) / 2);
|
||||
bounds.y = (int)Math.floorf (bounds.y - (area.height - bounds.height * scale) / 2);
|
||||
bounds.width = (int)Math.floorf (area.width / scale);
|
||||
bounds.height = (int)Math.floorf (area.height / scale);
|
||||
|
||||
// Move all windows back onto the screen and set their scale
|
||||
var index = 0;
|
||||
foreach (var rect in rects) {
|
||||
rect = {(int)Math.floorf ((rect.x - bounds.x) * scale + area.x),
|
||||
(int)Math.floorf ((rect.y - bounds.y) * scale + area.y),
|
||||
(int)Math.floorf (rect.width * scale),
|
||||
(int)Math.floorf (rect.height * scale)};
|
||||
|
||||
rects[index] = rect;
|
||||
index++;
|
||||
}
|
||||
|
||||
// fill gaps by enlarging windows
|
||||
bool moved = false;
|
||||
Meta.Rectangle border = area;
|
||||
do {
|
||||
moved = false;
|
||||
|
||||
index = 0;
|
||||
foreach (var rect in rects) {
|
||||
|
||||
int width_diff = ACCURACY;
|
||||
int height_diff = (int)Math.floorf ((((rect.width + width_diff) - rect.height) /
|
||||
(float)rect.width) * rect.height);
|
||||
int x_diff = width_diff / 2;
|
||||
int y_diff = height_diff / 2;
|
||||
|
||||
//top right
|
||||
Meta.Rectangle old = rect;
|
||||
rect = {rect.x + x_diff, rect.y - y_diff - height_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//bottom right
|
||||
old = rect;
|
||||
rect = {rect.x + x_diff, rect.y + y_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//bottom left
|
||||
old = rect;
|
||||
rect = {rect.x - x_diff, rect.y + y_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
//top left
|
||||
old = rect;
|
||||
rect = {rect.x - x_diff, rect.y - y_diff - height_diff, rect.width + width_diff, rect.height + width_diff};
|
||||
if (rect_is_overlapping_any (rect, rects, border))
|
||||
rect = old;
|
||||
else
|
||||
moved = true;
|
||||
|
||||
rects[index] = rect;
|
||||
index++;
|
||||
}
|
||||
} while (moved);
|
||||
|
||||
index = 0;
|
||||
foreach (var rect in rects) {
|
||||
var window = clones.nth_data (index) as WindowThumb;
|
||||
var window_rect = window.window.get_outer_rect ();
|
||||
|
||||
rect = rect_adjusted(rect, GAPS, GAPS, -GAPS, -GAPS);
|
||||
scale = rect.width / (float)window_rect.width;
|
||||
|
||||
if (scale > 2.0 || (scale > 1.0 && (window_rect.width > 300 || window_rect.height > 300))) {
|
||||
scale = (window_rect.width > 300 || window_rect.height > 300) ? 1.0f : 2.0f;
|
||||
rect = {rect_center (rect).x - (int)Math.floorf (window_rect.width * scale) / 2,
|
||||
rect_center (rect).y - (int)Math.floorf (window_rect.height * scale) / 2,
|
||||
(int)Math.floorf (window_rect.width * scale),
|
||||
(int)Math.floorf (window_rect.height * scale)};
|
||||
}
|
||||
|
||||
place_window (window, rect);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// animate a window to the given position
|
||||
void place_window (Actor actor, Meta.Rectangle rect)
|
||||
void place_window (WindowThumb clone, Meta.Rectangle rect)
|
||||
{
|
||||
var clone = actor as WindowThumb;
|
||||
|
||||
var fscale = rect.width / clone.width;
|
||||
|
||||
//animate the windows and icons to the calculated positions
|
||||
|
@ -21,11 +21,13 @@ namespace Gala
|
||||
{
|
||||
public class WindowManagerGala : Meta.Plugin, WindowManager
|
||||
{
|
||||
|
||||
public Clutter.Actor ui_group { get; protected set; }
|
||||
public Clutter.Stage stage { get; protected set; }
|
||||
public Clutter.Actor window_group { get; protected set; }
|
||||
public Clutter.Actor top_window_group { get; protected set; }
|
||||
public Meta.BackgroundGroup background_group { get; protected set; }
|
||||
public HashTable<int,int> window_stacking_order { get; protected set; }
|
||||
|
||||
Meta.PluginInfo info;
|
||||
|
||||
@ -124,6 +126,10 @@ namespace Gala
|
||||
stage.remove_child (top_window_group);
|
||||
ui_group.add_child (top_window_group);
|
||||
|
||||
window_stacking_order = new HashTable<int,int> (null, null);
|
||||
update_stacking_order ();
|
||||
screen.restacked.connect (update_stacking_order);
|
||||
|
||||
/*keybindings*/
|
||||
|
||||
screen.get_display ().add_keybinding ("switch-to-workspace-first", KeybindingSettings.get_default ().schema, 0, () => {
|
||||
@ -246,6 +252,19 @@ namespace Gala
|
||||
return false;
|
||||
}
|
||||
|
||||
void update_stacking_order ()
|
||||
{
|
||||
window_stacking_order.remove_all ();
|
||||
|
||||
var i = 0;
|
||||
foreach (var window in Compositor.get_window_actors (get_screen ())) {
|
||||
var seq = window.get_meta_window ().get_stable_sequence ();
|
||||
window_stacking_order.set ((int)window.get_meta_window ().get_stable_sequence (), i++);
|
||||
}
|
||||
|
||||
windows_restacked ();
|
||||
}
|
||||
|
||||
void configure_hotcorners ()
|
||||
{
|
||||
var geometry = get_screen ().get_monitor_geometry (get_screen ().get_primary_monitor ());
|
||||
|
Loading…
Reference in New Issue
Block a user