326 lines
10 KiB
JavaScript
326 lines
10 KiB
JavaScript
|
|
const { Clutter, GObject, Meta, Shell, St } = imports.gi;
|
|
|
|
const Background = imports.ui.background;
|
|
const SwitcherPopup = imports.ui.switcherPopup;
|
|
const Layout = imports.ui.layout;
|
|
const Main = imports.ui.main;
|
|
|
|
const WINDOW_ANIMATION_TIME = 2000;
|
|
var APP_ICON_SIZE = 96;
|
|
|
|
var RealmItem = GObject.registerClass(
|
|
class RealmItem extends St.BoxLayout {
|
|
_init(realm, workspace_group) {
|
|
super._init({ vertical: true });
|
|
this.realm = realm;
|
|
|
|
this.add_child(workspace_group);
|
|
|
|
this.label = new St.Label({
|
|
text: `realm-${this.realm.realm_name}`,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
});
|
|
|
|
this.add_child(this.label);
|
|
}
|
|
|
|
activate() {
|
|
this.realm.set_current();
|
|
}
|
|
|
|
});
|
|
|
|
function getRealmItems() {
|
|
const monitor = Main.layoutManager.primaryMonitor;
|
|
const realms = Shell.Realms.get_default();
|
|
let realm_list = realms.get_running_realms();
|
|
let items = [];
|
|
realm_list.forEach(realm => {
|
|
let ws = realm.get_active_workspace();
|
|
if (ws) {
|
|
let size = 256; // default thumbnail size
|
|
let scale = Math.min(1.0, size / monitor.width, size / monitor.height);
|
|
let wsgroup = new WorkspaceGroup(ws, monitor, scale);
|
|
items.push(new RealmItem(realm, wsgroup));
|
|
}
|
|
});
|
|
return items;
|
|
}
|
|
|
|
var SwitchRealmList = GObject.registerClass(
|
|
class SwitchRealmList extends SwitcherPopup.SwitcherList {
|
|
_init(items) {
|
|
super._init(false);
|
|
|
|
items.forEach(item => {
|
|
this.addItem(item, item.label);
|
|
});
|
|
}
|
|
});
|
|
|
|
var SwitchRealmPopup = GObject.registerClass(
|
|
class SwitchRealmPopup extends SwitcherPopup.SwitcherPopup {
|
|
_init(action, actionBackward) {
|
|
super._init();
|
|
this._action = action;
|
|
this._actionBackward = actionBackward;
|
|
this._items = getRealmItems();
|
|
this._switcherList = new SwitchRealmList(this._items);
|
|
}
|
|
|
|
_keyPressHandler(keysym, action) {
|
|
if (action == this._action)
|
|
this._select(this._next());
|
|
else if (action == this._actionBackward)
|
|
this._select(this._previous());
|
|
else if (keysym == Clutter.KEY_Left)
|
|
this._select(this._previous());
|
|
else if (keysym == Clutter.KEY_Right)
|
|
this._select(this._next());
|
|
else
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
|
|
_finish() {
|
|
super._finish();
|
|
this._items[this._selectedIndex].activate();
|
|
}
|
|
|
|
});
|
|
|
|
const WorkspaceGroup = GObject.registerClass(
|
|
class WorkspaceGroup extends Clutter.Actor {
|
|
_init(workspace, monitor, scale = 1.0) {
|
|
super._init();
|
|
this._workspace = workspace;
|
|
this._monitor = monitor;
|
|
this._scale = scale;
|
|
this._windowRecords = [];
|
|
this.width = monitor.width * scale;
|
|
this.height = monitor.height * scale;
|
|
this._background = new Meta.BackgroundGroup({
|
|
width: this.width * this._scale,
|
|
height: this.height * this._scale,
|
|
});
|
|
this.add_actor(this._background);
|
|
this._bgManager = new Background.BackgroundManager({
|
|
container: this._background,
|
|
monitorIndex: this._monitor.index,
|
|
controlPosition: false,
|
|
});
|
|
this.clip_to_allocation = true;
|
|
|
|
this._createWindows();
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
this._restackedId = global.display.connect('restacked', this._syncStacking.bind(this));
|
|
}
|
|
|
|
get workspace() {
|
|
return this._workspace;
|
|
}
|
|
|
|
_shouldShowWindow(window) {
|
|
if (!window.showing_on_its_workspace())
|
|
return false;
|
|
|
|
const geometry = global.display.get_monitor_geometry(this._monitor.index);
|
|
const [intersects] = window.get_frame_rect().intersect(geometry);
|
|
if (!intersects)
|
|
return false;
|
|
|
|
const isSticky = window.is_on_all_workspaces();
|
|
|
|
return !isSticky && window.located_on_workspace(this._workspace);
|
|
}
|
|
|
|
_syncStacking() {
|
|
const windowActors = global.get_window_actors().filter(w =>
|
|
this._shouldShowWindow(w.meta_window));
|
|
|
|
let lastRecord;
|
|
|
|
for (const windowActor of windowActors) {
|
|
const record = this._windowRecords.find(r => r.windowActor === windowActor);
|
|
this.set_child_above_sibling(record.clone, lastRecord ? lastRecord.clone : this._background);
|
|
lastRecord = record;
|
|
}
|
|
}
|
|
|
|
_createWindows() {
|
|
const windowActors = global.get_window_actors().filter(w =>
|
|
this._shouldShowWindow(w.meta_window));
|
|
for (const windowActor of windowActors) {
|
|
let [width,height] = windowActor.get_size();
|
|
const clone = new Clutter.Clone({
|
|
source: windowActor,
|
|
width: width * this._scale,
|
|
height: height * this._scale,
|
|
x: (windowActor.x - this._monitor.x) * this._scale,
|
|
y: (windowActor.y - this._monitor.y) * this._scale,
|
|
});
|
|
this.add_child(clone);
|
|
const record = {windowActor, clone };
|
|
record.windowDestroyId = windowActor.connect('destroy', () => {
|
|
clone.destroy();
|
|
this._windowRecords.splice(this._windowRecords.indexOf(record), 1);
|
|
});
|
|
this._windowRecords.push(record);
|
|
}
|
|
}
|
|
|
|
_removeWindows() {
|
|
for (const record of this._windowRecords) {
|
|
record.windowActor.disconnect(record.windowDestroyId);
|
|
record.clone.destroy();
|
|
}
|
|
this._windowRecords = [];
|
|
}
|
|
|
|
_onDestroy() {
|
|
global.display.disconnect(this._restackedId);
|
|
this._removeWindows();
|
|
this._bgManager.destroy();
|
|
}
|
|
|
|
});
|
|
|
|
const MonitorGroup = GObject.registerClass({
|
|
Properties: {
|
|
'progress': GObject.ParamSpec.double(
|
|
'progress', 'progress', 'progress',
|
|
GObject.ParamFlags.READWRITE,
|
|
-Infinity, Infinity, 0),
|
|
},
|
|
}, class MonitorGroup extends St.Widget {
|
|
_init(monitor, fromIndex, toIndex) {
|
|
super._init({
|
|
clip_to_allocation: true,
|
|
style_class: 'workspace-animation',
|
|
});
|
|
this._monitor = monitor;
|
|
const constraint = new Layout.MonitorConstraint({ index: monitor.index });
|
|
this.add_constraint(constraint);
|
|
|
|
this._container = new Clutter.Actor();
|
|
this.add_child(this._container);
|
|
|
|
this._progress = 0;
|
|
this._fadeOut = true;
|
|
|
|
this._workspaceGroups = [];
|
|
this._blackBackground = new Clutter.Actor();
|
|
this._blackBackground.width = monitor.width;
|
|
this._blackBackground.height = monitor.height;
|
|
let [_res, color] = Clutter.Color.from_string("#000000ff");
|
|
this._blackBackground.background_color = color;
|
|
|
|
|
|
|
|
this.addWorkspaceByIndex(toIndex, monitor);
|
|
// add opaque black actor
|
|
this._container.add_child(this._blackBackground);
|
|
this.addWorkspaceByIndex(fromIndex, monitor);
|
|
|
|
// tween 'from' WorkspaceGroup opacity from 255 to 0 fading workspace to black background
|
|
// tween 'block' actor opacity from 255 to 0 revealing 'to' WorkspaceGroup
|
|
}
|
|
|
|
addWorkspaceByIndex(idx, monitor) {
|
|
const workspaceManager = global.workspace_manager;
|
|
const ws = workspaceManager.get_workspace_by_index(idx);
|
|
if (ws) {
|
|
const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && w.is_fullscreen());
|
|
const group = new WorkspaceGroup(ws, monitor);
|
|
this._workspaceGroups.push(group);
|
|
this._container.add_child(group);
|
|
}
|
|
}
|
|
|
|
get progress() {
|
|
return this._progress;
|
|
}
|
|
|
|
// Interpolate opacity from 0 (full opaque) to 50 (full transparent)
|
|
calculateOpacity(progress) {
|
|
return 255 - (255 * (progress / 50.0));
|
|
}
|
|
|
|
set progress(p) {
|
|
const fromGroup = this._workspaceGroups[this._workspaceGroups.length - 1];
|
|
this._progress = p;
|
|
// 0 - 50
|
|
if (p < 50) {
|
|
this._blackBackground.opacity = 255;
|
|
fromGroup.opacity = this.calculateOpacity(p);
|
|
} else if (p < 100) {
|
|
if (this._fadeOut) {
|
|
this._fadeOut = false;
|
|
}
|
|
fromGroup.opacity = 0;
|
|
this._blackBackground.opacity = this.calculateOpacity(p - 50);
|
|
} else {
|
|
fromGroup.opacity = 0;
|
|
this._blackBackground.opacity = 0;
|
|
}
|
|
}
|
|
});
|
|
|
|
var ContextSwitchAnimationController = class {
|
|
constructor(indicator) {
|
|
this._switchData = null;
|
|
this._indicator = indicator;
|
|
}
|
|
|
|
_prepareContextSwitch(fromIdx, toIdx) {
|
|
if (this._switchData) {
|
|
this._switchData.monitors[0].remove_all_transitions();
|
|
this._finishContextSwitch(this._switchData);
|
|
}
|
|
|
|
const switchData = {};
|
|
this._switchData = switchData;
|
|
switchData.monitors = [];
|
|
switchData.inProgress = false;
|
|
|
|
const monitor = Main.layoutManager.primaryMonitor;
|
|
|
|
const group = new MonitorGroup(monitor, fromIdx, toIdx);
|
|
|
|
Main.uiGroup.insert_child_above(group, global.window_group);
|
|
|
|
switchData.monitors.push(group);
|
|
|
|
Meta.disable_unredirect_for_display(global.display);
|
|
}
|
|
|
|
_finishContextSwitch(switchData) {
|
|
Meta.enable_unredirect_for_display(global.display);
|
|
this._indicator.update();
|
|
this._switchData = null;
|
|
switchData.monitors.forEach(m => m.destroy());
|
|
if (switchData.onComplete) {
|
|
switchData.onComplete();
|
|
}
|
|
}
|
|
|
|
animateSwitch(fromIdx, toIdx, onComplete) {
|
|
|
|
this._prepareContextSwitch(fromIdx, toIdx);
|
|
this._switchData.inProgress = true;
|
|
this._switchData.onComplete = onComplete;
|
|
|
|
const params = {
|
|
duration: WINDOW_ANIMATION_TIME,
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
|
};
|
|
params.onComplete = () => {
|
|
this._finishContextSwitch(this._switchData);
|
|
};
|
|
this._indicator.clear();
|
|
this._switchData.monitors[0].ease_property('progress', 100, params);
|
|
}
|
|
}
|