diff --git a/data/confirm.svg b/data/confirm.svg
new file mode 100644
index 000000000..2d92d7322
--- /dev/null
+++ b/data/confirm.svg
@@ -0,0 +1,184 @@
+
+
diff --git a/data/gala.gresource.xml b/data/gala.gresource.xml
index 1bcc56851..92ef51d9e 100644
--- a/data/gala.gresource.xml
+++ b/data/gala.gresource.xml
@@ -19,6 +19,7 @@
close.svg
resize.svg
+ confirm.svg
shaders/box-blur.frag
diff --git a/plugins/pip/Main.vala b/plugins/pip/Main.vala
index c5976e7e2..0ec53febc 100644
--- a/plugins/pip/Main.vala
+++ b/plugins/pip/Main.vala
@@ -1,42 +1,21 @@
-//
-// 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 .
-//
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2017 Adam Bieńkowski
+ * 2024-2025 elementary, Inc. (https://elementary.io)
+ */
public class Gala.Plugins.PIP.Plugin : Gala.Plugin {
- private const int MIN_SELECTION_SIZE = 30;
+ public const int MIN_SELECTION_SIZE = 100;
- private Gee.ArrayList windows;
- private Gala.WindowManager? wm = null;
+ private Gee.ArrayList windows = new Gee.ArrayList ();
+ private WindowManager wm;
private SelectionArea? selection_area;
- private static inline bool meta_rectangle_contains (Mtk.Rectangle rect, int x, int y) {
- return x >= rect.x && x < rect.x + rect.width
- && y >= rect.y && y < rect.y + rect.height;
- }
-
- construct {
- windows = new Gee.ArrayList ();
- }
+ public override void initialize (Gala.WindowManager _wm) {
+ wm = _wm;
- public override void initialize (Gala.WindowManager wm) {
- this.wm = wm;
- var display = wm.get_display ();
var settings = new GLib.Settings ("io.elementary.desktop.wm.keybindings");
-
- display.add_keybinding ("pip", settings, IGNORE_AUTOREPEAT, on_initiate);
+ wm.get_display ().add_keybinding ("pip", settings, IGNORE_AUTOREPEAT, on_initiate);
}
public override void destroy () {
@@ -49,10 +28,23 @@ public class Gala.Plugins.PIP.Plugin : Gala.Plugin {
windows.clear ();
}
- private void on_initiate (Meta.Display display, Meta.Window? window, Clutter.KeyEvent? event,
- Meta.KeyBinding binding) {
- selection_area = new SelectionArea (wm);
- selection_area.selected.connect (on_selection_actor_selected);
+ private void on_initiate (Meta.Display display, Meta.Window? window, Clutter.KeyEvent? event, Meta.KeyBinding binding) {
+ unowned var target_window = display.focus_window;
+ if (target_window == null || !Utils.get_window_is_normal (target_window) || target_window.skip_taskbar) {
+ return;
+ }
+
+ var target_frame = target_window.get_frame_rect ();
+ if (target_frame.width < MIN_SELECTION_SIZE || target_frame.height < MIN_SELECTION_SIZE) {
+ return;
+ }
+
+ unowned var target_actor = (Meta.WindowActor) target_window.get_compositor_private ();
+ if (target_actor == null) {
+ return;
+ }
+
+ selection_area = new SelectionArea (wm, target_actor);
selection_area.captured.connect (on_selection_actor_captured);
selection_area.closed.connect (clear_selection_area);
@@ -62,141 +54,48 @@ public class Gala.Plugins.PIP.Plugin : Gala.Plugin {
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 ();
-
- var active = get_active_window_actor ();
- if (active == null) {
- return;
- }
-
- var window = active.meta_window;
- var buffer_rect = window.get_buffer_rect ();
- var frame_rect = window.get_frame_rect ();
+ private void on_selection_actor_captured (Mtk.Rectangle selection) {
+ var popup_window = new PopupWindow (wm.get_display (), selection_area.target_actor);
+ windows.add (popup_window);
+ wm.ui_group.add_child (popup_window);
- Mtk.Rectangle mtk_rect = {x, y, width, height};
- Mtk.Rectangle intersection;
- mtk_rect.intersect (frame_rect, out intersection);
+ popup_window.show.connect ((_popup_window) => {
+ track_actor (_popup_window);
+ update_region ();
+ });
+ popup_window.hide.connect ((_popup_window) => {
+ untrack_actor (_popup_window);
+ update_region ();
+ });
+ popup_window.closed.connect ((_popup_window) => {
+ windows.remove (_popup_window);
+ untrack_window (_popup_window);
+ });
- // Compensate for server-side window decorations
- var x_offset = buffer_rect.x - frame_rect.x;
- var y_offset = buffer_rect.y - frame_rect.y;
+ var frame = selection_area.target_actor.meta_window.get_frame_rect ();
- Graphene.Rect rect = {
- {intersection.x - buffer_rect.x + x_offset, intersection.y - buffer_rect.y + y_offset},
- {intersection.width, intersection.height}
- };
+ // Don't clip if the entire window was selected
+ if (!frame.equal (selection)) {
+ selection.x -= frame.x;
+ selection.y -= frame.y;
- if (rect.get_width () < MIN_SELECTION_SIZE || rect.get_height () < MIN_SELECTION_SIZE) {
- select_window_at (x, y);
- return;
+ // FIXME: Mtk.Rectangle.to_graphene_rect is broken in Vala
+ popup_window.set_container_clip ({ { selection.x, selection.y }, { selection.width, selection.height } });
}
- var popup_window = new PopupWindow (wm.get_display (), active);
- popup_window.set_container_clip (rect);
- popup_window.show.connect (on_popup_window_show);
- popup_window.hide.connect (on_popup_window_hide);
- add_window (popup_window);
- }
-
- private void on_popup_window_show (Clutter.Actor popup_window) {
- track_actor (popup_window);
- update_region ();
- }
-
- private void on_popup_window_hide (Clutter.Actor popup_window) {
- untrack_actor (popup_window);
- update_region ();
- }
-
- 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.get_display (), selected);
- popup_window.show.connect (on_popup_window_show);
- popup_window.hide.connect (on_popup_window_hide);
- add_window (popup_window);
- }
+ clear_selection_area ();
}
private void clear_selection_area () {
- if (selection_area != null) {
- untrack_actor (selection_area);
- update_region ();
-
- selection_area.destroy ();
- selection_area = null;
+ if (selection_area == null) {
+ return;
}
- }
-
- private Meta.WindowActor? get_window_actor_at (int x, int y) {
- unowned Meta.Display display = wm.get_display ();
-#if HAS_MUTTER48
- unowned List actors = display.get_compositor ().get_window_actors ();
-#else
- unowned List actors = display.get_window_actors ();
-#endif
-
- 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 rect = window.get_frame_rect ();
-
- if (!actor.is_destroyed () && !window.is_hidden () && !window.is_skip_taskbar () && meta_rectangle_contains (rect, x, y)) {
- selected = actor;
- }
- });
-
- return selected;
- }
-
- private Meta.WindowActor? get_active_window_actor () {
- unowned Meta.Display display = wm.get_display ();
-#if HAS_MUTTER48
- unowned List actors = display.get_compositor ().get_window_actors ();
-#else
- unowned List actors = display.get_window_actors ();
-#endif
-
- 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 (!actor.is_destroyed () && !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);
- wm.ui_group.add_child (popup_window);
- }
+ untrack_actor (selection_area);
+ update_region ();
- private void remove_window (PopupWindow popup_window) {
- windows.remove (popup_window);
- untrack_window (popup_window);
+ selection_area.destroy ();
+ selection_area = null;
}
private void untrack_window (PopupWindow popup_window) {
diff --git a/plugins/pip/PopupWindow.vala b/plugins/pip/PopupWindow.vala
index 5e6fbb319..90170ea25 100644
--- a/plugins/pip/PopupWindow.vala
+++ b/plugins/pip/PopupWindow.vala
@@ -1,6 +1,6 @@
/*
* Copyright 2017 Adam Bieńkowski
- * Copyright 2023 elementary, Inc.
+ * Copyright 2023-2025 elementary, Inc.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@@ -62,8 +62,8 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor {
clone = new Clutter.Clone (window_actor);
clone_container = new Clutter.Actor () {
- scale_x = 0.35f,
- scale_y = 0.35f
+ scale_x = 0.35,
+ scale_y = 0.35
};
clone_container.add_child (clone);
@@ -319,11 +319,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor {
opacity = 0;
restore_easing_state ();
-#if HAS_MUTTER48
- GLib.Timeout.add (duration, () => {
-#else
- Clutter.Threads.Timeout.add (duration, () => {
-#endif
+ Timeout.add (duration, () => {
closed ();
return Source.REMOVE;
});
diff --git a/plugins/pip/SelectionArea.vala b/plugins/pip/SelectionArea.vala
index fb6ee4ac0..979a8ee14 100644
--- a/plugins/pip/SelectionArea.vala
+++ b/plugins/pip/SelectionArea.vala
@@ -1,117 +1,306 @@
-//
-// 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 .
-//
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2017 Santiago León O.
+ * 2017 Adam Bieńkowski
+ * 2025 elementary, Inc. (https://elementary.io)
+ */
public class Gala.Plugins.PIP.SelectionArea : CanvasActor {
- public signal void captured (int x, int y, int width, int height);
- public signal void selected (int x, int y);
+ private const int HANDLER_RADIUS = 6;
+ private const int BORDER_WIDTH = 2;
+ private const int RESIZE_THRESHOLD = 10;
+ private const int CONFIRM_BUTTON_SIZE = 48;
+
+ public signal void captured (Mtk.Rectangle selection);
public signal void closed ();
public Gala.WindowManager wm { get; construct; }
-
+ public Meta.WindowActor target_actor { get; construct; }
+
+ /**
+ * A clone of the "target_actor" displayed and clipped while the selection
+ * mask is visible.
+ */
+ private Clutter.Actor clone;
+
+ private Mtk.Rectangle max_size;
+ private Mtk.Rectangle selection;
+ private float monitor_scale;
+ private Clutter.Actor confirm_button;
private Gala.ModalProxy? modal_proxy;
- private Graphene.Point start_point;
- private Graphene.Point end_point;
+
+ /**
+ * If the user is resizing the selection area and the resize handler used.
+ */
+ private bool resizing = false;
+ private bool resizing_top = false;
+ private bool resizing_bottom = false;
+ private bool resizing_left = false;
+ private bool resizing_right = false;
+
+ /**
+ * If the user is dragging the selection area and the starting point.
+ */
private bool dragging = false;
- private bool clicked = false;
+ private float drag_x = 0.0f;
+ private float drag_y = 0.0f;
- public SelectionArea (Gala.WindowManager wm) {
- Object (wm: wm);
+ public SelectionArea (WindowManager wm, Meta.WindowActor target_actor) {
+ Object (wm: wm, target_actor: target_actor);
}
construct {
- start_point = { 0, 0 };
- end_point = { 0, 0 };
+ unowned var window = target_actor.meta_window;
+
+ max_size = window.get_frame_rect ();
+ selection = max_size;
visible = true;
reactive = true;
+ clone = new Clutter.Clone (target_actor) {
+ x = target_actor.x,
+ y = target_actor.y
+ };
+ wm.ui_group.add_child (clone);
+
+ unowned var display = wm.get_display ();
+ monitor_scale = display.get_monitor_scale (window.get_monitor ());
+
int screen_width, screen_height;
- wm.get_display ().get_size (out screen_width, out screen_height);
+ display.get_size (out screen_width, out screen_height);
width = screen_width;
height = screen_height;
+
+#if HAS_MUTTER49
+ var click_action = new Clutter.ClickGesture ();
+#else
+ var click_action = new Clutter.ClickAction ();
+#endif
+
+#if HAS_MUTTER49
+ click_action.recognize.connect (capture_selected_area);
+#else
+ click_action.clicked.connect (capture_selected_area);
+#endif
+
+ confirm_button = new Gala.Icon.from_resource (
+ CONFIRM_BUTTON_SIZE,
+ monitor_scale,
+ "/org/pantheon/desktop/gala/buttons/confirm.svg"
+ ) {
+ reactive = true
+ };
+ confirm_button.add_action (click_action);
+ add_child (confirm_button);
+
+ update_confirm_button_position ();
}
- public override bool key_press_event (Clutter.Event e) {
- if (e.get_key_symbol () == Clutter.Key.Escape) {
- close ();
- closed ();
- return true;
+ public override bool key_press_event (Clutter.Event event) {
+ switch (event.get_key_symbol ()) {
+ case Clutter.Key.Escape:
+ close ();
+ closed ();
+
+ return Clutter.EVENT_STOP;
+ case Clutter.Key.Return:
+ case Clutter.Key.KP_Enter:
+ capture_selected_area ();
+
+ return Clutter.EVENT_STOP;
}
- return false;
+ return Clutter.EVENT_PROPAGATE;
}
- public override bool button_press_event (Clutter.Event e) {
- if (dragging || e.get_button () != Clutter.Button.PRIMARY) {
- return true;
+ private void capture_selected_area () {
+ close ();
+ captured (selection);
+ }
+
+ public override bool button_press_event (Clutter.Event event) {
+ if (dragging || resizing || event.get_button () != Clutter.Button.PRIMARY) {
+ return Clutter.EVENT_STOP;
}
- clicked = true;
+ float event_x, event_y;
+ event.get_coords (out event_x, out event_y);
+
+ // Check that the user clicked on a resize handler
+ resizing_top = is_close_to_coord (event_y, selection.y, RESIZE_THRESHOLD);
+ resizing_bottom = is_close_to_coord (event_y, selection.y + selection.height, RESIZE_THRESHOLD);
+ resizing_left = is_close_to_coord (event_x, selection.x, RESIZE_THRESHOLD);
+ resizing_right = is_close_to_coord (event_x, selection.x + selection.width, RESIZE_THRESHOLD);
+ resizing = (resizing_top && resizing_left) ||
+ (resizing_top && resizing_right) ||
+ (resizing_bottom && resizing_left) ||
+ (resizing_bottom && resizing_right);
+
+ if (resizing) {
+ return Clutter.EVENT_STOP;
+ }
- float press_x, press_y;
- e.get_coords (out press_x, out press_y);
- start_point = { (int) press_x, (int) press_y};
+#if HAS_MUTTER48
+ dragging = selection.contains_pointf (event_x, event_y);
+#else
+ dragging = selection.contains_rect ({ (int) event_x, (int) event_y, 0, 0 });
+#endif
+ if (dragging) {
+ drag_x = event_x - selection.x;
+ drag_y = event_y - selection.y;
+#if HAS_MUTTER50
+ set_cursor_type (MOVE);
+#elif HAS_MUTTER48
+ wm.get_display ().set_cursor (MOVE);
+#else
+ wm.get_display ().set_cursor (MOVE_OR_RESIZE_WINDOW);
+#endif
+
+ return Clutter.EVENT_STOP;
+ }
- return true;
+ return Clutter.EVENT_STOP;
}
- public override bool button_release_event (Clutter.Event e) {
- if (e.get_button () != Clutter.Button.PRIMARY) {
- return true;
+ private static bool is_close_to_coord (float c, int target, int threshold) {
+ return (c >= target - threshold) &&
+ (c <= target + threshold);
+ }
+
+ public override bool button_release_event (Clutter.Event event) {
+ if (event.get_button () != Clutter.Button.PRIMARY) {
+ return Clutter.EVENT_STOP;
}
- if (!dragging) {
- float event_x, event_y;
- e.get_coords (out event_x, out event_y);
- selected ((int) event_x, (int) event_y);
+ float event_x, event_y;
+ event.get_coords (out event_x, out event_y);
+
+#if HAS_MUTTER48
+ if (!resizing && !dragging && !selection.contains_pointf (event_x, event_y)) {
+#else
+ if (!resizing && !dragging && !selection.contains_rect ({ (int) event_x, (int) event_y, 0, 0 })) {
+#endif
close ();
+ closed ();
return true;
}
dragging = false;
- clicked = false;
+ resizing = false;
+#if HAS_MUTTER50
+ set_cursor_type (DEFAULT);
+#else
+ wm.get_display ().set_cursor (DEFAULT);
+#endif
- 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 ();
+ return Clutter.EVENT_STOP;
+ }
- captured (x, y, w, h);
+ public override bool motion_event (Clutter.Event event) {
+ set_mouse_cursor_on_motion (event);
- return true;
- }
+ if (!resizing && !dragging) {
+ return Clutter.EVENT_STOP;
+ }
- public override bool motion_event (Clutter.Event e) {
- if (!clicked) {
- return true;
+ if (resizing) {
+ resize_selection_area (event);
+ } else if (dragging) {
+ drag_selection_area (event);
}
- float press_x, press_y;
- e.get_coords (out press_x, out press_y);
- end_point = { (int) press_x, (int) press_y};
content.invalidate ();
- if (!dragging) {
- dragging = true;
+ return Clutter.EVENT_STOP;
+ }
+
+ private void resize_selection_area (Clutter.Event event) {
+ float event_x, event_y;
+ event.get_coords (out event_x, out event_y);
+
+ var start_x = selection.x;
+ var end_x = selection.x + selection.width;
+ var start_y = selection.y;
+ var end_y = selection.y + selection.height;
+
+ if (resizing_top) {
+ start_y = (int) event_y.clamp (max_size.y, end_y - Plugin.MIN_SELECTION_SIZE);
+ } else if (resizing_bottom) {
+ end_y = (int) event_y.clamp (start_y + Plugin.MIN_SELECTION_SIZE, max_size.y + max_size.height);
+ }
+
+ if (resizing_left) {
+ start_x = (int) event_x.clamp (max_size.x, end_x - Plugin.MIN_SELECTION_SIZE);
+ } else if (resizing_right) {
+ end_x = (int) event_x.clamp (start_x + Plugin.MIN_SELECTION_SIZE, max_size.x + max_size.width);
+ }
+
+ selection = { start_x, start_y, end_x - start_x, end_y - start_y };
+
+ update_confirm_button_position ();
+ }
+
+ private void drag_selection_area (Clutter.Event event) {
+ float event_x, event_y;
+ event.get_coords (out event_x, out event_y);
+
+ selection.x = (int) (event_x - drag_x).clamp (max_size.x, max_size.x + max_size.width - selection.width);
+ selection.y = (int) (event_y - drag_y).clamp (max_size.y, max_size.y + max_size.height - selection.height);
+
+ update_confirm_button_position ();
+ }
+
+ private void update_confirm_button_position () {
+ confirm_button.set_position (
+ selection.x + (selection.width - (int) confirm_button.width) / 2,
+ selection.y + (selection.height - (int) confirm_button.height) / 2
+ );
+ }
+
+ private void set_mouse_cursor_on_motion (Clutter.Event event) {
+ if (resizing || dragging) {
+ return;
}
- return true;
+ float event_x, event_y;
+ event.get_coords (out event_x, out event_y);
+
+ var top = is_close_to_coord (event_y, selection.y, RESIZE_THRESHOLD);
+ var bottom = is_close_to_coord (event_y, selection.y + selection.height, RESIZE_THRESHOLD);
+ var left = is_close_to_coord (event_x, selection.x, RESIZE_THRESHOLD);
+ var right = is_close_to_coord (event_x, selection.x + selection.width, RESIZE_THRESHOLD);
+
+ if (top && left) {
+#if HAS_MUTTER50
+ set_cursor_type (NW_RESIZE);
+#else
+ wm.get_display ().set_cursor (NW_RESIZE);
+#endif
+ } else if (top && right) {
+#if HAS_MUTTER50
+ set_cursor_type (NE_RESIZE);
+#else
+ wm.get_display ().set_cursor (NE_RESIZE);
+#endif
+ } else if (bottom && left) {
+#if HAS_MUTTER50
+ set_cursor_type (SW_RESIZE);
+#else
+ wm.get_display ().set_cursor (SW_RESIZE);
+#endif
+ } else if (bottom && right) {
+#if HAS_MUTTER50
+ set_cursor_type (SE_RESIZE);
+#else
+ wm.get_display ().set_cursor (SE_RESIZE);
+#endif
+ } else {
+#if HAS_MUTTER50
+ set_cursor_type (DEFAULT);
+#else
+ wm.get_display ().set_cursor (DEFAULT);
+#endif
+ }
}
public void close () {
@@ -120,6 +309,7 @@ public class Gala.Plugins.PIP.SelectionArea : CanvasActor {
#else
wm.get_display ().set_cursor (Meta.Cursor.DEFAULT);
#endif
+ wm.ui_group.remove_child (clone);
if (modal_proxy != null) {
wm.pop_modal (modal_proxy);
@@ -127,45 +317,62 @@ public class Gala.Plugins.PIP.SelectionArea : CanvasActor {
}
public void start_selection () {
-#if HAS_MUTTER50
- set_cursor_type (Clutter.CursorType.CROSSHAIR);
-#else
- wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR);
-#endif
grab_key_focus ();
modal_proxy = wm.push_modal (this, true);
}
- private void get_selection_rectangle (out int x, out int y, out int width, out int height) {
- x = (int) float.min (start_point.x, end_point.x);
- y = (int) float.min (start_point.y, end_point.y);
- width = (int) (start_point.x - end_point.x).abs ();
- height = (int) (start_point.y - end_point.y).abs ();
- }
-
protected override void draw (Cairo.Context ctx, int width, int height) {
+ // Draws a full-screen colored rectangle with a smaller transparent
+ // rectangle inside with border with handlers
ctx.save ();
-
ctx.set_operator (Cairo.Operator.CLEAR);
ctx.paint ();
+ ctx.restore ();
+ // Full-screen rectangle
+ ctx.save ();
+ ctx.set_operator (Cairo.Operator.OVER);
+ ctx.rectangle (0, 0, width, height);
+ ctx.set_source_rgba (0.0, 0.0, 0.0, 0.5);
+ ctx.fill ();
ctx.restore ();
- if (!dragging) {
- return;
- }
+ // Transparent rectangle
+ ctx.save ();
+ ctx.set_operator (Cairo.Operator.SOURCE);
+ ctx.rectangle (selection.x, selection.y, selection.width, selection.height);
+ ctx.set_source_rgba (0.0, 0.0, 0.0, 0.0);
+ ctx.fill ();
+ ctx.restore ();
- int x, y, w, h;
- get_selection_rectangle (out x, out y, out w, out h);
+ ctx.save ();
- ctx.rectangle (x, y, w, h);
- ctx.set_source_rgba (0.1, 0.1, 0.1, 0.2);
- ctx.fill ();
+ var accent_color = Drawing.StyleManager.get_instance ().theme_accent_color;
+ ctx.set_source_rgba (accent_color.red / 255.0, accent_color.green / 255.0, accent_color.blue / 255.0, 1.0);
- ctx.rectangle (x, y, w, h);
- ctx.set_source_rgb (0.7, 0.7, 0.7);
- ctx.set_line_width (1.0);
+ // Border
+ ctx.set_operator (Cairo.Operator.OVER);
+ ctx.rectangle (selection.x, selection.y, selection.width, selection.height);
+ ctx.set_line_width (Utils.scale_to_int (BORDER_WIDTH, monitor_scale));
ctx.stroke ();
+
+ // Handlers
+ var start_x = selection.x;
+ var end_x = selection.x + selection.width;
+ var start_y = selection.y;
+ var end_y = selection.y + selection.height;
+ var scaled_handler_radius = Utils.scale_to_int (HANDLER_RADIUS, monitor_scale);
+
+ ctx.arc (start_x, start_y, scaled_handler_radius, 0.0, 2.0 * Math.PI);
+ ctx.fill ();
+ ctx.arc (start_x, end_y, scaled_handler_radius, 0.0, 2.0 * Math.PI);
+ ctx.fill ();
+ ctx.arc (end_x, start_y, scaled_handler_radius, 0.0, 2.0 * Math.PI);
+ ctx.fill ();
+ ctx.arc (end_x, end_y, scaled_handler_radius, 0.0, 2.0 * Math.PI);
+ ctx.fill ();
+
+ ctx.restore ();
}
}