mirror of
https://github.com/elementary/gala.git
synced 2024-11-23 20:07:21 +03:00
pip: Add Picture In Picture plugin (#32)
This commit is contained in:
parent
3b064fa99e
commit
52678172d3
@ -301,6 +301,7 @@ vapi/Makefile
|
||||
plugins/Makefile
|
||||
plugins/maskcorners/Makefile
|
||||
plugins/notify/Makefile
|
||||
plugins/pip/Makefile
|
||||
plugins/zoom/Makefile
|
||||
po/Makefile.in
|
||||
])
|
||||
|
@ -141,6 +141,11 @@
|
||||
<summary>Cycle to previous keyboard layout</summary>
|
||||
<description>DEPRECATED: This key is deprecated and ignored.</description>
|
||||
</key>
|
||||
<key type="as" name="pip">
|
||||
<default><![CDATA[['<Super>D']]]></default>
|
||||
<summary>The shortcut to enable picture-in-picture window</summary>
|
||||
<description>The shortcut to show the selection area to choose a window</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema path="/org/pantheon/desktop/gala/appearance/" id="org.pantheon.desktop.gala.appearance" gettext-domain="@GETTEXT_PACKAGE@">
|
||||
|
@ -1,6 +1,7 @@
|
||||
SUBDIRS = \
|
||||
maskcorners \
|
||||
notify \
|
||||
pip \
|
||||
zoom \
|
||||
$(NULL)
|
||||
|
||||
|
191
plugins/pip/Main.vala
Normal file
191
plugins/pip/Main.vala
Normal file
@ -0,0 +1,191 @@
|
||||
//
|
||||
// Copyright (C) 2017 Adam Bieńkowski
|
||||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
public class Gala.Plugins.PIP.Plugin : Gala.Plugin
|
||||
{
|
||||
private const int MIN_SELECTION_SIZE = 30;
|
||||
|
||||
private Gee.ArrayList<PopupWindow> windows;
|
||||
private Gala.WindowManager? wm = null;
|
||||
private SelectionArea? selection_area;
|
||||
|
||||
construct
|
||||
{
|
||||
windows = new Gee.ArrayList<PopupWindow> ();
|
||||
}
|
||||
|
||||
public override void initialize (Gala.WindowManager wm)
|
||||
{
|
||||
this.wm = wm;
|
||||
var display = wm.get_screen ().get_display ();
|
||||
var settings = new GLib.Settings (Config.SCHEMA + ".keybindings");
|
||||
|
||||
display.add_keybinding ("pip", settings, Meta.KeyBindingFlags.NONE, (Meta.KeyHandlerFunc) on_initiate);
|
||||
}
|
||||
|
||||
public override void destroy ()
|
||||
{
|
||||
clear_selection_area ();
|
||||
|
||||
foreach (var popup_window in windows) {
|
||||
untrack_window (popup_window);
|
||||
}
|
||||
|
||||
windows.clear ();
|
||||
}
|
||||
|
||||
[CCode (instance_pos = -1)]
|
||||
void on_initiate (Meta.Display display, Meta.Screen screen,
|
||||
Meta.Window? window, Clutter.KeyEvent event, Meta.KeyBinding binding)
|
||||
{
|
||||
selection_area = new SelectionArea (wm);
|
||||
selection_area.selected.connect (on_selection_actor_selected);
|
||||
selection_area.captured.connect (on_selection_actor_captured);
|
||||
selection_area.closed.connect (clear_selection_area);
|
||||
|
||||
track_actor (selection_area);
|
||||
wm.ui_group.add_child (selection_area);
|
||||
|
||||
selection_area.start_selection ();
|
||||
}
|
||||
|
||||
private void on_selection_actor_selected (int x, int y)
|
||||
{
|
||||
clear_selection_area ();
|
||||
select_window_at (x, y);
|
||||
}
|
||||
|
||||
private void on_selection_actor_captured (int x, int y, int width, int height)
|
||||
{
|
||||
clear_selection_area ();
|
||||
|
||||
if (width < MIN_SELECTION_SIZE || height < MIN_SELECTION_SIZE) {
|
||||
select_window_at (x, y);
|
||||
} else {
|
||||
var active = get_active_window_actor ();
|
||||
if (active != null) {
|
||||
int point_x = x - (int)active.x;
|
||||
int point_y = y - (int)active.y;
|
||||
|
||||
var rect = Clutter.Rect.alloc ();
|
||||
var clip = rect.init (point_x, point_y, width, height);
|
||||
|
||||
var popup_window = new PopupWindow (wm, active, clip);
|
||||
add_window (popup_window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void select_window_at (int x, int y)
|
||||
{
|
||||
var selected = get_window_actor_at (x, y);
|
||||
if (selected != null) {
|
||||
var popup_window = new PopupWindow (wm, selected, null);
|
||||
add_window (popup_window);
|
||||
}
|
||||
}
|
||||
|
||||
private void clear_selection_area ()
|
||||
{
|
||||
if (selection_area != null) {
|
||||
untrack_actor (selection_area);
|
||||
update_region ();
|
||||
|
||||
selection_area.destroy ();
|
||||
selection_area = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Meta.WindowActor? get_window_actor_at (float x, float y)
|
||||
{
|
||||
var screen = wm.get_screen ();
|
||||
unowned List<weak Meta.WindowActor> actors = Meta.Compositor.get_window_actors (screen);
|
||||
|
||||
var copy = actors.copy ();
|
||||
copy.reverse ();
|
||||
|
||||
weak Meta.WindowActor? selected = null;
|
||||
copy.@foreach ((actor) => {
|
||||
if (selected != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var window = actor.get_meta_window ();
|
||||
var bbox = actor.get_allocation_box ();
|
||||
if (!window.is_hidden () && !window.is_skip_taskbar () && bbox.contains (x, y)) {
|
||||
selected = actor;
|
||||
}
|
||||
});
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
private Meta.WindowActor? get_active_window_actor ()
|
||||
{
|
||||
var screen = wm.get_screen ();
|
||||
unowned List<weak Meta.WindowActor> actors = Meta.Compositor.get_window_actors (screen);
|
||||
|
||||
var copy = actors.copy ();
|
||||
copy.reverse ();
|
||||
|
||||
weak Meta.WindowActor? active = null;
|
||||
actors.@foreach ((actor) => {
|
||||
if (active != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var window = actor.get_meta_window ();
|
||||
if (!window.is_hidden () && !window.is_skip_taskbar () && window.has_focus ()) {
|
||||
active = actor;
|
||||
}
|
||||
});
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
private void add_window (PopupWindow popup_window)
|
||||
{
|
||||
popup_window.closed.connect (() => remove_window (popup_window));
|
||||
windows.add (popup_window);
|
||||
track_actor (popup_window);
|
||||
wm.ui_group.add_child (popup_window);
|
||||
}
|
||||
|
||||
private void remove_window (PopupWindow popup_window)
|
||||
{
|
||||
windows.remove (popup_window);
|
||||
untrack_window (popup_window);
|
||||
}
|
||||
|
||||
private void untrack_window (PopupWindow popup_window)
|
||||
{
|
||||
untrack_actor (popup_window);
|
||||
update_region ();
|
||||
popup_window.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
public Gala.PluginInfo register_plugin ()
|
||||
{
|
||||
return Gala.PluginInfo () {
|
||||
name = "Popup Window",
|
||||
author = "Adam Bieńkowski <donadigos159@gmail.com>",
|
||||
plugin_type = typeof (Gala.Plugins.PIP.Plugin),
|
||||
provides = Gala.PluginFunction.ADDITION,
|
||||
load_priority = Gala.LoadPriority.IMMEDIATE
|
||||
};
|
||||
}
|
61
plugins/pip/Makefile.am
Normal file
61
plugins/pip/Makefile.am
Normal file
@ -0,0 +1,61 @@
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
VAPIDIR = $(top_srcdir)/vapi
|
||||
|
||||
BUILT_SOURCES = libgala_pip_la_vala.stamp
|
||||
|
||||
libgala_pip_la_LTLIBRARIES = libgala-pip.la
|
||||
|
||||
libgala_pip_ladir = $(pkglibdir)/plugins
|
||||
|
||||
libgala_pip_la_LDFLAGS = \
|
||||
$(PLUGIN_LDFLAGS) \
|
||||
$(GALA_CORE_LDFLAGS) \
|
||||
$(top_builddir)/lib/libgala.la \
|
||||
$(NULL)
|
||||
|
||||
libgala_pip_la_CFLAGS = \
|
||||
$(GALA_CORE_CFLAGS) \
|
||||
-include config.h \
|
||||
-w \
|
||||
-I$(top_builddir)/lib \
|
||||
$(NULL)
|
||||
|
||||
libgala_pip_la_VALAFLAGS = \
|
||||
$(GALA_CORE_VALAFLAGS) \
|
||||
$(top_builddir)/lib/gala.vapi \
|
||||
--vapidir $(VAPIDIR) \
|
||||
$(VAPIDIR)/config.vapi \
|
||||
$(NULL)
|
||||
|
||||
libgala_pip_la_LIBADD = \
|
||||
$(GALA_CORE_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
libgala_pip_la_VALASOURCES = \
|
||||
Main.vala \
|
||||
MoveAction.vala \
|
||||
PopupWindow.vala \
|
||||
SelectionArea.vala \
|
||||
ShadowEffect.vala \
|
||||
$(NULL)
|
||||
|
||||
nodist_libgala_pip_la_SOURCES = \
|
||||
$(BUILT_SOURCES) \
|
||||
$(libgala_pip_la_VALASOURCES:.vala=.c) \
|
||||
$(NULL)
|
||||
|
||||
libgala_pip_la_vala.stamp: $(libgala_pip_la_VALASOURCES) $(top_builddir)/lib/gala.vapi Makefile
|
||||
$(AM_V_VALA)$(VALAC) \
|
||||
$(libgala_pip_la_VALAFLAGS) \
|
||||
-C \
|
||||
$(filter %.vala %.c,$^)
|
||||
$(AM_V_at)touch $@
|
||||
|
||||
CLEANFILES = \
|
||||
$(nodist_libgala_pip_la_SOURCES) \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(libgala_pip_la_VALASOURCES) \
|
||||
$(NULL)
|
27
plugins/pip/MoveAction.vala
Normal file
27
plugins/pip/MoveAction.vala
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (C) 2017 Adam Bieńkowski
|
||||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
public class Gala.Plugins.PIP.MoveAction : Clutter.DragAction
|
||||
{
|
||||
public signal void move ();
|
||||
|
||||
public override bool drag_progress (Clutter.Actor actor, float delta_x, float delta_y)
|
||||
{
|
||||
move ();
|
||||
return false;
|
||||
}
|
||||
}
|
403
plugins/pip/PopupWindow.vala
Normal file
403
plugins/pip/PopupWindow.vala
Normal file
@ -0,0 +1,403 @@
|
||||
//
|
||||
// Copyright (C) 2017 Adam Bieńkowski
|
||||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor
|
||||
{
|
||||
private const int BUTTON_SIZE = 36;
|
||||
private const int CONTAINER_MARGIN = BUTTON_SIZE / 2;
|
||||
private const int SHADOW_SIZE = 100;
|
||||
private const uint FADE_OUT_TIMEOUT = 200;
|
||||
private const float MINIMUM_SCALE = 0.1f;
|
||||
private const float MAXIMUM_SCALE = 1.0f;
|
||||
private const int SCREEN_MARGIN = 0;
|
||||
|
||||
private static Clutter.Image? resize_image;
|
||||
|
||||
public signal void closed ();
|
||||
|
||||
public Gala.WindowManager wm { get; construct; }
|
||||
public Meta.WindowActor window_actor { get; construct; }
|
||||
public Clutter.Rect? container_clip { get; construct; }
|
||||
|
||||
private Clutter.Actor clone;
|
||||
private Clutter.Actor container;
|
||||
private Clutter.Actor close_button;
|
||||
private Clutter.Actor resize_button;
|
||||
private Clutter.Actor resize_handle;
|
||||
private Clutter.ClickAction close_action;
|
||||
private Clutter.DragAction resize_action;
|
||||
private MoveAction move_action;
|
||||
|
||||
private bool dragging = false;
|
||||
private bool clicked = false;
|
||||
|
||||
private int x_offset_press = 0;
|
||||
private int y_offset_press = 0;
|
||||
|
||||
private float begin_resize_width = 0.0f;
|
||||
private float begin_resize_height = 0.0f;
|
||||
|
||||
// From https://opensourcehacker.com/2011/12/01/calculate-aspect-ratio-conserving-resize-for-images-in-javascript/
|
||||
static void calculate_aspect_ratio_size_fit (float src_width, float src_height, float max_width, float max_height,
|
||||
out float width, out float height)
|
||||
{
|
||||
float ratio = float.min (max_width / src_width, max_height / src_height);
|
||||
width = src_width * ratio;
|
||||
height = src_height * ratio;
|
||||
}
|
||||
|
||||
static Clutter.Image? get_resize_image ()
|
||||
{
|
||||
if (resize_image == null) {
|
||||
try {
|
||||
string filename = Path.build_filename (Config.PKGDATADIR, "resize.svg");
|
||||
var pixbuf = new Gdk.Pixbuf.from_file (filename);
|
||||
|
||||
resize_image = new Clutter.Image ();
|
||||
resize_image.set_data (pixbuf.get_pixels (),
|
||||
Cogl.PixelFormat.RGBA_8888,
|
||||
pixbuf.get_width (),
|
||||
pixbuf.get_height (),
|
||||
pixbuf.get_rowstride ());
|
||||
} catch (Error e) {
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return resize_image;
|
||||
}
|
||||
|
||||
static void get_current_cursor_position (out int x, out int y)
|
||||
{
|
||||
Gdk.Display.get_default ().get_device_manager ().get_client_pointer ().get_position (null, out x, out y);
|
||||
}
|
||||
|
||||
public PopupWindow (Gala.WindowManager wm, Meta.WindowActor window_actor, Clutter.Rect? container_clip)
|
||||
{
|
||||
Object (wm: wm, window_actor: window_actor, container_clip: container_clip);
|
||||
}
|
||||
|
||||
construct
|
||||
{
|
||||
reactive = true;
|
||||
|
||||
set_pivot_point (0.5f, 0.5f);
|
||||
set_easing_mode (Clutter.AnimationMode.EASE_IN_QUAD);
|
||||
|
||||
var window = window_actor.get_meta_window ();
|
||||
window.unmanaged.connect (on_close_click_clicked);
|
||||
|
||||
clone = new Clutter.Clone (window_actor.get_texture ());
|
||||
|
||||
move_action = new MoveAction ();
|
||||
move_action.drag_begin.connect (on_move_begin);
|
||||
move_action.drag_end.connect (on_move_end);
|
||||
move_action.move.connect (on_move);
|
||||
|
||||
container = new Clutter.Actor ();
|
||||
container.reactive = true;
|
||||
container.set_scale (0.35f, 0.35f);
|
||||
container.clip_rect = container_clip;
|
||||
container.add_effect (new Gala.ShadowEffect (SHADOW_SIZE, 2));
|
||||
container.add_child (clone);
|
||||
container.add_action (move_action);
|
||||
|
||||
if (container_clip == null) {
|
||||
window_actor.notify["allocation"].connect (on_allocation_changed);
|
||||
container.set_position (CONTAINER_MARGIN, CONTAINER_MARGIN);
|
||||
}
|
||||
|
||||
update_size ();
|
||||
update_container_position ();
|
||||
|
||||
Meta.Rectangle monitor_rect;
|
||||
get_current_monitor_rect (out monitor_rect);
|
||||
|
||||
set_position (SCREEN_MARGIN + monitor_rect.x, monitor_rect.height + monitor_rect.y - SCREEN_MARGIN - height);
|
||||
|
||||
close_action = new Clutter.ClickAction ();
|
||||
close_action.clicked.connect (on_close_click_clicked);
|
||||
|
||||
close_button = Gala.Utils.create_close_button ();
|
||||
close_button.set_size (BUTTON_SIZE, BUTTON_SIZE);
|
||||
close_button.opacity = 0;
|
||||
close_button.reactive = true;
|
||||
close_button.set_easing_duration (300);
|
||||
close_button.add_action (close_action);
|
||||
|
||||
resize_action = new Clutter.DragAction ();
|
||||
resize_action.drag_begin.connect (on_resize_drag_begin);
|
||||
resize_action.drag_end.connect (on_resize_drag_end);
|
||||
resize_action.drag_motion.connect (on_resize_drag_motion);
|
||||
|
||||
resize_handle = new Clutter.Actor ();
|
||||
resize_handle.set_size (BUTTON_SIZE, BUTTON_SIZE);
|
||||
resize_handle.set_pivot_point (0.5f, 0.5f);
|
||||
resize_handle.set_position (width - BUTTON_SIZE, height - BUTTON_SIZE);
|
||||
resize_handle.reactive = true;
|
||||
resize_handle.add_action (resize_action);
|
||||
|
||||
resize_button = new Clutter.Actor ();
|
||||
resize_button.set_pivot_point (0.5f, 0.5f);
|
||||
resize_button.set_size (BUTTON_SIZE, BUTTON_SIZE);
|
||||
resize_button.set_position (width - BUTTON_SIZE, height - BUTTON_SIZE);
|
||||
resize_button.opacity = 0;
|
||||
resize_button.reactive = true;
|
||||
resize_button.content = get_resize_image ();
|
||||
|
||||
add_child (container);
|
||||
add_child (close_button);
|
||||
add_child (resize_button);
|
||||
add_child (resize_handle);
|
||||
}
|
||||
|
||||
public override void show ()
|
||||
{
|
||||
base.show ();
|
||||
|
||||
opacity = 0;
|
||||
|
||||
set_easing_duration (200);
|
||||
opacity = 255;
|
||||
|
||||
set_easing_duration (0);
|
||||
}
|
||||
|
||||
public override bool enter_event (Clutter.CrossingEvent event)
|
||||
{
|
||||
close_button.opacity = 255;
|
||||
|
||||
resize_button.set_easing_duration (300);
|
||||
resize_button.opacity = 255;
|
||||
resize_button.set_easing_duration (0);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool leave_event (Clutter.CrossingEvent event)
|
||||
{
|
||||
close_button.opacity = 0;
|
||||
|
||||
resize_button.set_easing_duration (300);
|
||||
resize_button.opacity = 0;
|
||||
resize_button.set_easing_duration (0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void on_move_begin ()
|
||||
{
|
||||
int px, py;
|
||||
get_current_cursor_position (out px, out py);
|
||||
|
||||
x_offset_press = (int)(px - x);
|
||||
y_offset_press = (int)(py - y);
|
||||
|
||||
clicked = true;
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
private void on_move_end ()
|
||||
{
|
||||
clicked = false;
|
||||
|
||||
if (dragging) {
|
||||
update_screen_position ();
|
||||
dragging = false;
|
||||
} else {
|
||||
activate ();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_move ()
|
||||
{
|
||||
if (!clicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
float motion_x, motion_y;
|
||||
move_action.get_motion_coords (out motion_x, out motion_y);
|
||||
|
||||
x = (int)motion_x - x_offset_press;
|
||||
y = (int)motion_y - y_offset_press;
|
||||
|
||||
if (!dragging) {
|
||||
dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void on_resize_drag_begin (Clutter.Actor actor, float event_x, float event_y, Clutter.ModifierType type)
|
||||
{
|
||||
begin_resize_width = width;
|
||||
begin_resize_height = height;
|
||||
}
|
||||
|
||||
private void on_resize_drag_end (Clutter.Actor actor, float event_x, float event_y, Clutter.ModifierType type)
|
||||
{
|
||||
reposition_resize_handle ();
|
||||
update_screen_position ();
|
||||
}
|
||||
|
||||
private void on_resize_drag_motion (Clutter.Actor actor, float delta_x, float delta_y)
|
||||
{
|
||||
float press_x, press_y;
|
||||
resize_action.get_press_coords (out press_x, out press_y);
|
||||
|
||||
int motion_x, motion_y;
|
||||
get_current_cursor_position (out motion_x, out motion_y);
|
||||
|
||||
float diff_x = motion_x - press_x;
|
||||
float diff_y = motion_y - press_y;
|
||||
|
||||
width = begin_resize_width + diff_x;
|
||||
height = begin_resize_height + diff_y;
|
||||
|
||||
update_container_scale ();
|
||||
update_size ();
|
||||
reposition_resize_button ();
|
||||
}
|
||||
|
||||
private void on_allocation_changed ()
|
||||
{
|
||||
update_size ();
|
||||
reposition_resize_button ();
|
||||
reposition_resize_handle ();
|
||||
}
|
||||
|
||||
private void on_close_click_clicked ()
|
||||
{
|
||||
set_easing_duration (FADE_OUT_TIMEOUT);
|
||||
|
||||
opacity = 0;
|
||||
|
||||
Clutter.Threads.Timeout.add (FADE_OUT_TIMEOUT, () => {
|
||||
closed ();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void update_size ()
|
||||
{
|
||||
if (container_clip != null) {
|
||||
width = (int)(container_clip.get_width () * container.scale_x + BUTTON_SIZE);
|
||||
height = (int)(container_clip.get_height () * container.scale_y + BUTTON_SIZE);
|
||||
} else {
|
||||
width = (int)(container.width * container.scale_x + BUTTON_SIZE);
|
||||
height = (int)(container.height * container.scale_y + BUTTON_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
private void update_container_scale ()
|
||||
{
|
||||
float src_width;
|
||||
float src_height;
|
||||
if (container_clip != null) {
|
||||
src_width = container_clip.get_width ();
|
||||
src_height = container_clip.get_height ();
|
||||
} else {
|
||||
src_width = container.width;
|
||||
src_height = container.height;
|
||||
}
|
||||
|
||||
float max_width = width - BUTTON_SIZE;
|
||||
float max_height = height - BUTTON_SIZE;
|
||||
|
||||
float new_width, new_height;
|
||||
calculate_aspect_ratio_size_fit (
|
||||
src_width, src_height,
|
||||
max_width, max_height,
|
||||
out new_width, out new_height
|
||||
);
|
||||
|
||||
float window_width, window_height;
|
||||
get_target_window_size (out window_width, out window_height);
|
||||
|
||||
float new_scale_x = new_width / window_width;
|
||||
float new_scale_y = new_height / window_height;
|
||||
|
||||
container.scale_x = new_scale_x.clamp (MINIMUM_SCALE, MAXIMUM_SCALE);
|
||||
container.scale_y = new_scale_y.clamp (MINIMUM_SCALE, MAXIMUM_SCALE);
|
||||
|
||||
update_container_position ();
|
||||
}
|
||||
|
||||
private void update_container_position ()
|
||||
{
|
||||
if (container_clip != null) {
|
||||
container.x = (float)(-container_clip.get_x () * container.scale_x + CONTAINER_MARGIN);
|
||||
container.y = (float)(-container_clip.get_y () * container.scale_y + CONTAINER_MARGIN);
|
||||
}
|
||||
}
|
||||
|
||||
private void update_screen_position ()
|
||||
{
|
||||
Meta.Rectangle monitor_rect;
|
||||
get_current_monitor_rect (out monitor_rect);
|
||||
|
||||
int monitor_x = monitor_rect.x;
|
||||
int monitor_y = monitor_rect.y;
|
||||
int monitor_width = monitor_rect.width;
|
||||
int monitor_height = monitor_rect.height;
|
||||
|
||||
set_easing_duration (300);
|
||||
set_easing_mode (Clutter.AnimationMode.EASE_OUT_BACK);
|
||||
|
||||
var screen_limit_start = SCREEN_MARGIN + monitor_x;
|
||||
var screen_limit_end = monitor_width + monitor_x - SCREEN_MARGIN - width;
|
||||
|
||||
x = x.clamp (screen_limit_start, screen_limit_end);
|
||||
|
||||
screen_limit_start = SCREEN_MARGIN + monitor_y;
|
||||
screen_limit_end = monitor_height + monitor_y - SCREEN_MARGIN - height;
|
||||
|
||||
y = y.clamp (screen_limit_start, screen_limit_end);
|
||||
|
||||
set_easing_mode (Clutter.AnimationMode.EASE_IN_QUAD);
|
||||
set_easing_duration (0);
|
||||
}
|
||||
|
||||
private void reposition_resize_button ()
|
||||
{
|
||||
resize_button.set_position (width - BUTTON_SIZE, height - BUTTON_SIZE);
|
||||
}
|
||||
|
||||
private void reposition_resize_handle ()
|
||||
{
|
||||
resize_handle.set_position (width - BUTTON_SIZE, height - BUTTON_SIZE);
|
||||
}
|
||||
|
||||
private void get_current_monitor_rect (out Meta.Rectangle rect)
|
||||
{
|
||||
var screen = wm.get_screen ();
|
||||
rect = screen.get_monitor_geometry (screen.get_current_monitor ());
|
||||
}
|
||||
|
||||
private void get_target_window_size (out float width, out float height)
|
||||
{
|
||||
if (container_clip != null) {
|
||||
width = container_clip.get_width ();
|
||||
height = container_clip.get_height ();
|
||||
} else {
|
||||
width = window_actor.width;
|
||||
height = window_actor.height;
|
||||
}
|
||||
}
|
||||
|
||||
private void activate ()
|
||||
{
|
||||
var window = window_actor.get_meta_window ();
|
||||
window.activate (Clutter.get_current_event_time ());
|
||||
}
|
||||
}
|
174
plugins/pip/SelectionArea.vala
Normal file
174
plugins/pip/SelectionArea.vala
Normal file
@ -0,0 +1,174 @@
|
||||
//
|
||||
// Copyright (C) 2017 Santiago León O., Adam Bieńkowski
|
||||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
public class Gala.Plugins.PIP.SelectionArea : Clutter.Actor
|
||||
{
|
||||
public signal void captured (int x, int y, int width, int height);
|
||||
public signal void selected (int x, int y);
|
||||
public signal void closed ();
|
||||
|
||||
public Gala.WindowManager wm { get; construct; }
|
||||
|
||||
private Gala.ModalProxy? modal_proxy;
|
||||
private Gdk.Point start_point;
|
||||
private Gdk.Point end_point;
|
||||
private bool dragging = false;
|
||||
private bool clicked = false;
|
||||
|
||||
public SelectionArea (Gala.WindowManager wm)
|
||||
{
|
||||
Object (wm: wm);
|
||||
}
|
||||
|
||||
construct
|
||||
{
|
||||
start_point = { 0, 0 };
|
||||
end_point = { 0, 0 };
|
||||
visible = true;
|
||||
reactive = true;
|
||||
|
||||
int screen_width, screen_height;
|
||||
wm.get_screen ().get_size (out screen_width, out screen_height);
|
||||
width = screen_width;
|
||||
height = screen_height;
|
||||
|
||||
var canvas = new Clutter.Canvas ();
|
||||
canvas.set_size (screen_width, screen_height);
|
||||
canvas.draw.connect (draw_area);
|
||||
set_content (canvas);
|
||||
|
||||
canvas.invalidate ();
|
||||
}
|
||||
|
||||
public override bool key_press_event (Clutter.KeyEvent e)
|
||||
{
|
||||
if (e.keyval == Clutter.Key.Escape) {
|
||||
close ();
|
||||
closed ();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool button_press_event (Clutter.ButtonEvent e)
|
||||
{
|
||||
if (dragging || e.button != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
clicked = true;
|
||||
|
||||
start_point.x = (int) e.x;
|
||||
start_point.y = (int) e.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool button_release_event (Clutter.ButtonEvent e)
|
||||
{
|
||||
if (e.button != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dragging) {
|
||||
selected ((int) e.x, (int) e.y);
|
||||
close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
dragging = false;
|
||||
clicked = false;
|
||||
|
||||
int x, y, w, h;
|
||||
get_selection_rectangle (out x, out y, out w, out h);
|
||||
close ();
|
||||
start_point = { 0, 0 };
|
||||
end_point = { 0, 0 };
|
||||
this.hide ();
|
||||
content.invalidate ();
|
||||
|
||||
captured (x, y, w, h);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool motion_event (Clutter.MotionEvent e)
|
||||
{
|
||||
if (!clicked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
end_point.x = (int) e.x;
|
||||
end_point.y = (int) e.y;
|
||||
content.invalidate ();
|
||||
|
||||
if (!dragging) {
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close ()
|
||||
{
|
||||
wm.get_screen ().set_cursor (Meta.Cursor.DEFAULT);
|
||||
|
||||
if (modal_proxy != null) {
|
||||
wm.pop_modal (modal_proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public void start_selection ()
|
||||
{
|
||||
wm.get_screen ().set_cursor (Meta.Cursor.CROSSHAIR);
|
||||
grab_key_focus ();
|
||||
|
||||
modal_proxy = wm.push_modal ();
|
||||
}
|
||||
|
||||
private void get_selection_rectangle (out int x, out int y, out int width, out int height)
|
||||
{
|
||||
x = int.min (start_point.x, end_point.x);
|
||||
y = int.min (start_point.y, end_point.y);
|
||||
width = (start_point.x - end_point.x).abs ();
|
||||
height = (start_point.y - end_point.y).abs ();
|
||||
}
|
||||
|
||||
private bool draw_area (Cairo.Context ctx)
|
||||
{
|
||||
Clutter.cairo_clear (ctx);
|
||||
|
||||
if (!dragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int x, y, w, h;
|
||||
get_selection_rectangle (out x, out y, out w, out h);
|
||||
|
||||
ctx.rectangle (x, y, w, h);
|
||||
ctx.set_source_rgba (0.1, 0.1, 0.1, 0.2);
|
||||
ctx.fill ();
|
||||
|
||||
ctx.rectangle (x, y, w, h);
|
||||
ctx.set_source_rgb (0.7, 0.7, 0.7);
|
||||
ctx.set_line_width (1.0);
|
||||
ctx.stroke ();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
153
plugins/pip/ShadowEffect.vala
Normal file
153
plugins/pip/ShadowEffect.vala
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// Copyright (C) 2014 Tom Beckmann
|
||||
//
|
||||
// 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/>.
|
||||
//
|
||||
|
||||
using Clutter;
|
||||
|
||||
namespace Gala
|
||||
{
|
||||
public class ShadowEffect : Effect
|
||||
{
|
||||
private class Shadow
|
||||
{
|
||||
public int users;
|
||||
public Cogl.Texture texture;
|
||||
|
||||
public Shadow (Cogl.Texture _texture)
|
||||
{
|
||||
texture = _texture;
|
||||
users = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// the sizes of the textures often repeat, especially for the background actor
|
||||
// so we keep a cache to avoid creating the same texture all over again.
|
||||
static Gee.HashMap<string,Shadow> shadow_cache;
|
||||
|
||||
static construct
|
||||
{
|
||||
shadow_cache = new Gee.HashMap<string,Shadow> ();
|
||||
}
|
||||
|
||||
public int shadow_size { get; construct; }
|
||||
public int shadow_spread { get; construct; }
|
||||
|
||||
public float scale_factor { get; set; default = 1; }
|
||||
public uint8 shadow_opacity { get; set; default = 255; }
|
||||
|
||||
Cogl.Material material;
|
||||
string? current_key = null;
|
||||
|
||||
public ShadowEffect (int shadow_size, int shadow_spread)
|
||||
{
|
||||
Object (shadow_size: shadow_size, shadow_spread: shadow_spread);
|
||||
}
|
||||
|
||||
construct
|
||||
{
|
||||
material = new Cogl.Material ();
|
||||
}
|
||||
|
||||
~ShadowEffect ()
|
||||
{
|
||||
if (current_key != null)
|
||||
decrement_shadow_users (current_key);
|
||||
}
|
||||
|
||||
Cogl.Texture? get_shadow (int width, int height, int shadow_size, int shadow_spread)
|
||||
{
|
||||
var old_key = current_key;
|
||||
|
||||
current_key = "%ix%i:%i:%i".printf (width, height, shadow_size, shadow_spread);
|
||||
if (old_key == current_key)
|
||||
return null;
|
||||
|
||||
if (old_key != null)
|
||||
decrement_shadow_users (old_key);
|
||||
|
||||
Shadow? shadow = null;
|
||||
if ((shadow = shadow_cache.@get (current_key)) != null) {
|
||||
shadow.users++;
|
||||
return shadow.texture;
|
||||
}
|
||||
|
||||
// fill a new texture for this size
|
||||
var buffer = new Granite.Drawing.BufferSurface (width, height);
|
||||
buffer.context.rectangle (shadow_size - shadow_spread, shadow_size - shadow_spread,
|
||||
width - shadow_size * 2 + shadow_spread * 2, height - shadow_size * 2 + shadow_spread * 2);
|
||||
buffer.context.set_source_rgba (0, 0, 0, 0.7);
|
||||
buffer.context.fill ();
|
||||
|
||||
buffer.exponential_blur (shadow_size / 2);
|
||||
|
||||
var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
|
||||
var cr = new Cairo.Context (surface);
|
||||
|
||||
cr.set_source_surface (buffer.surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
var texture = new Cogl.Texture.from_data (width, height, 0, Cogl.PixelFormat.BGRA_8888_PRE,
|
||||
Cogl.PixelFormat.ANY, surface.get_stride (), surface.get_data ());
|
||||
|
||||
shadow_cache.@set (current_key, new Shadow (texture));
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void decrement_shadow_users (string key)
|
||||
{
|
||||
var shadow = shadow_cache.@get (key);
|
||||
|
||||
if (shadow == null)
|
||||
return;
|
||||
|
||||
if (--shadow.users == 0)
|
||||
shadow_cache.unset (key);
|
||||
}
|
||||
|
||||
public override void paint (EffectPaintFlags flags)
|
||||
{
|
||||
var bounding_box = get_bounding_box ();
|
||||
var shadow = get_shadow ((int) (bounding_box.x2 - bounding_box.x1), (int) (bounding_box.y2 - bounding_box.y1),
|
||||
shadow_size, shadow_spread);
|
||||
|
||||
if (shadow != null)
|
||||
material.set_layer (0, shadow);
|
||||
|
||||
var opacity = actor.get_paint_opacity () * shadow_opacity / 255;
|
||||
var alpha = Cogl.Color.from_4ub (255, 255, 255, opacity);
|
||||
alpha.premultiply ();
|
||||
|
||||
material.set_color (alpha);
|
||||
|
||||
Cogl.set_source (material);
|
||||
Cogl.rectangle (bounding_box.x1, bounding_box.y1, bounding_box.x2, bounding_box.y2);
|
||||
|
||||
actor.continue_paint ();
|
||||
}
|
||||
|
||||
public virtual ActorBox get_bounding_box ()
|
||||
{
|
||||
var size = shadow_size * scale_factor;
|
||||
var bounding_box = ActorBox ();
|
||||
|
||||
bounding_box.set_origin (-size, -size);
|
||||
bounding_box.set_size (actor.width + size * 2, actor.height + size * 2);
|
||||
|
||||
return bounding_box;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user