From 51268af2e3dc7a06b5358991fa29494a1d0bf4df Mon Sep 17 00:00:00 2001 From: Mkefss Date: Mon, 28 Jun 2021 14:53:00 -0500 Subject: [PATCH] Color picker working under wayland --- .../flatpak/com.github.akiraux.akira.json | 11 +- meson.build | 1 + src/Layouts/Partials/FillItem.vala | 7 + src/Services/ActionManager.vala | 15 - src/Utils/ColorPicker.vala | 349 ++---------------- src/Widgets/ColorRow.vala | 6 - src/meson.build | 1 + vapi/libportal.vapi | 29 ++ 8 files changed, 79 insertions(+), 340 deletions(-) create mode 100644 vapi/libportal.vapi diff --git a/build-aux/flatpak/com.github.akiraux.akira.json b/build-aux/flatpak/com.github.akiraux.akira.json index 854b41887..372ec3dd3 100644 --- a/build-aux/flatpak/com.github.akiraux.akira.json +++ b/build-aux/flatpak/com.github.akiraux.akira.json @@ -23,7 +23,16 @@ "--filesystem=xdg-run/gvfs", "--filesystem=xdg-run/gvfsd" ], - "modules": [{ + "modules": [ + { + "name": "libportal", + "buildsystem": "meson", + "sources": [{ + "type": "git", + "url": "https://github.com/flatpak/libportal.git" + }] + }, + { "name": "goocanvas", "config-opts": ["--enable-python=no"], "build-options": { diff --git a/meson.build b/meson.build index 5185ccfa8..ea774fae1 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ cairo_dependency = dependency('cairo', version: '>=1.14') goocanvas_dependency = dependency('goocanvas-3.0') libarchive_dependency = dependency('libarchive') json_glib_dependency = dependency('json-glib-1.0') +dep_libportal = dependency('libportal', required: true) # Optional dependencies desktop_file_validate = find_program('desktop-file-validate', required: false) diff --git a/src/Layouts/Partials/FillItem.vala b/src/Layouts/Partials/FillItem.vala index f7bf349ce..e05baa633 100644 --- a/src/Layouts/Partials/FillItem.vala +++ b/src/Layouts/Partials/FillItem.vala @@ -86,6 +86,13 @@ public class Akira.Layouts.Partials.FillItem : Gtk.Grid { hidden_button.clicked.connect (toggle_visibility); } + private void on_color_changed () { + color_set_manually = true; + color = color_chooser_widget.rgba.to_string (); + alpha = ((int)(color_chooser_widget.rgba.alpha * 255)); + set_button_color (); + } + private void on_delete_item () { fill.remove (); fill_deleted (); diff --git a/src/Services/ActionManager.vala b/src/Services/ActionManager.vala index 61466963b..552ab2ddb 100644 --- a/src/Services/ActionManager.vala +++ b/src/Services/ActionManager.vala @@ -470,19 +470,6 @@ public class Akira.Services.ActionManager : Object { bool is_holding_shift = false; var color_picker = new Akira.Utils.ColorPicker (); - color_picker.show_all (); - - color_picker.key_pressed.connect (e => { - is_holding_shift = e.keyval == Gdk.Key.Shift_L; - }); - - color_picker.key_released.connect (e => { - is_holding_shift = e.keyval == Gdk.Key.Shift_L; - }); - - color_picker.cancelled.connect (() => { - color_picker.close (); - }); color_picker.picked.connect (color => { foreach (var item in canvas.selected_bound_manager.selected_items) { @@ -500,8 +487,6 @@ public class Akira.Services.ActionManager : Object { item.fills.update_color_from_action (color); } - color_picker.close (); - // Force a UI reload of the fills and borders panel since some items // had their properties changed. canvas.window.event_bus.selected_items_list_changed (canvas.selected_bound_manager.selected_items); diff --git a/src/Utils/ColorPicker.vala b/src/Utils/ColorPicker.vala index 249377052..467d3b170 100644 --- a/src/Utils/ColorPicker.vala +++ b/src/Utils/ColorPicker.vala @@ -20,323 +20,36 @@ * Ported from: https://github.com/ColorPicker/RonnyDo */ -public class Akira.Utils.ColorPicker : Gtk.Window { - public signal void picked (Gdk.RGBA color); - public signal void cancelled (); - public signal void moved (Gdk.RGBA color); - public signal void key_pressed (Gdk.EventKey e); - public signal void key_released (Gdk.EventKey e); +public class Akira.Utils.ColorPicker { + public signal void picked (Gdk.RGBA color); + private Xdp.Parent parent; + private Xdp.Portal portal; + + + public ColorPicker () { + Gtk.Application app = (Gtk.Application) GLib.Application.get_default (); + Gtk.Window window = app.get_active_window (); + + parent = new Xdp.Parent (window); + portal = new Xdp.Portal (); + + portal.pick_color.begin(parent, null, picked_color); + } + + public void picked_color (GLib.Object? object, GLib.AsyncResult? result) { + GLib.Variant v = portal.pick_color_finish (result); + if (v == null) + return; + + Gdk.RGBA color = Gdk.RGBA (); + color.alpha = 1; + + VariantIter iterator = v.iterator (); + iterator.next ("d", &color.red); + iterator.next ("d", &color.green); + iterator.next ("d", &color.blue); + + picked (color); + } - const string DARK_BORDER_COLOR_STRING = "#333333"; - private Gdk.RGBA dark_border_color = Gdk.RGBA (); - - const string BRIGHT_BORDER_COLOR_STRING = "#FFFFFF"; - private Gdk.RGBA bright_border_color = Gdk.RGBA (); - - - // 1. Snapsize is the amount of pixel going to be magnified by the zoomlevel. - // 2. The snapsize must be odd to have a 1px magnifier center. - // 3. Asure that snapsize*max_zoomlevel+shadow_width*2 is smaller than 2 * get_screen ().get_display ().get_maximal_cursor_size() - // Valid: snapsize = 31, max_zoomlevel = 7, shadow_width = 15 --> 247px - // get_maximal_cursor_size = 128 --> 256px - // Otherwise the cursor starts to flicker. See https://github.com/stuartlangridge/ColourPicker/issues/6#issuecomment-277972290 - // and https://github.com/RonnyDo/ColorPicker/issues/19 - int snapsize = 31; - int min_zoomlevel = 2; - int max_zoomlevel = 7; - int zoomlevel = 6; - int shadow_width = 15; - - private Gdk.Cursor magnifier = null; - - construct { - app_paintable = true; - decorated = false; - resizable = false; - set_visual (get_screen ().get_rgba_visual ()); - type = Gtk.WindowType.POPUP; - } - - - public ColorPicker () { - stick (); - set_resizable (true); - set_deletable (false); - set_skip_taskbar_hint (true); - set_skip_pager_hint (true); - set_keep_above (true); - - - dark_border_color.parse (DARK_BORDER_COLOR_STRING); - bright_border_color.parse (BRIGHT_BORDER_COLOR_STRING); - - // TODO remove the zoom level restauration if we do not need it - // restore zoomlevel - // if (settings.zoomlevel >= min_zoomlevel && settings.zoomlevel <= max_zoomlevel) { - // zoomlevel = settings.zoomlevel; - // } - - var display = Gdk.Display.get_default (); - Gdk.Monitor monitor = display.get_primary_monitor (); - Gdk.Rectangle geom = monitor.get_geometry (); - set_default_size (geom.width, geom.height); - } - - - public override bool button_release_event (Gdk.EventButton e) { - // button_1 is left mouse button - if (e.button == 1) { - Gdk.RGBA color = get_color_at ((int) e.x_root, (int) e.y_root); - picked (color); - // button_3 is right mouse button - } else if (e.button == 3) { - cancelled (); - } - - return true; - } - - - public override bool draw (Cairo.Context cr) { - return false; - } - - - public override bool motion_notify_event (Gdk.EventMotion e) { - Gdk.RGBA color = get_color_at ((int) e.x_root, (int) e.y_root); - - moved (color); - - set_magnifier_cursor (); - - return true; - } - - - public override bool scroll_event (Gdk.EventScroll e) { - switch (e.direction) { - case Gdk.ScrollDirection.UP: - if (zoomlevel < max_zoomlevel) { - zoomlevel++; - } - set_magnifier_cursor (); - break; - case Gdk.ScrollDirection.DOWN: - if (zoomlevel > min_zoomlevel) { - zoomlevel--; - } - set_magnifier_cursor (); - break; - default: - break; - } - - return true; - } - - public void set_magnifier_cursor () { - var manager = Gdk.Display.get_default ().get_default_seat (); - - // get cursor position - int px, py; - get_window ().get_device_position (manager.get_pointer (), out px, out py, null); - - var radius = snapsize * zoomlevel / 2; - - // get a small area (snap) meant to be zoomed - var snapped_pixbuf = snap (px - snapsize / 2, py - snapsize / 2, snapsize, snapsize); - - // Zoom that screenshot up, and grab a snapsize-sized piece from the middle - var scaled_pb = snapped_pixbuf.scale_simple ( - snapsize * zoomlevel + shadow_width * 2 , - snapsize * zoomlevel + shadow_width * 2 , - Gdk.InterpType.NEAREST - ); - - - // Create the base surface for our cursor - var base_surface = new Cairo.ImageSurface ( - Cairo.Format.ARGB32, - snapsize * zoomlevel + shadow_width * 2 , - snapsize * zoomlevel + shadow_width * 2 - ); - - var base_context = new Cairo.Context (base_surface); - - - // Create the circular path on our base surface - base_context.arc (radius + shadow_width, radius + shadow_width, radius, 0, 2 * Math.PI); - - // Paste in the screenshot - Gdk.cairo_set_source_pixbuf (base_context, scaled_pb, 0, 0); - - // Clip to that circular path, keeping the path around for later, and paint the pasted screenshot - base_context.save (); - base_context.clip_preserve (); - base_context.paint (); - base_context.restore (); - - - // Draw a shadow as outside magnifier border - double shadow_alpha = 0.6; - base_context.set_line_width (1); - - for (int i = 0; i <= shadow_width; i++) { - base_context.arc ( - radius + shadow_width, radius + shadow_width, - radius + shadow_width - i, 0, 2 * Math.PI - ); - Gdk.RGBA shadow_color = Gdk.RGBA (); - shadow_color.parse (DARK_BORDER_COLOR_STRING); - shadow_color.alpha = shadow_alpha / ((shadow_width - i + 1) * (shadow_width - i + 1)); - Gdk.cairo_set_source_rgba (base_context, shadow_color); - base_context.stroke (); - } - - - // Draw an outside bright magnifier border - Gdk.cairo_set_source_rgba (base_context, bright_border_color); - base_context.arc (radius + shadow_width, radius + shadow_width, radius - 1, 0, 2 * Math.PI); - base_context.stroke (); - - - // Draw inside square - base_context.set_line_width (1); - - Gdk.cairo_set_source_rgba (base_context, dark_border_color); - base_context.move_to (radius + shadow_width - zoomlevel, radius + shadow_width - zoomlevel); - base_context.line_to (radius + shadow_width + zoomlevel, radius + shadow_width - zoomlevel); - base_context.line_to (radius + shadow_width + zoomlevel, radius + shadow_width + zoomlevel); - base_context.line_to (radius + shadow_width - zoomlevel, radius + shadow_width + zoomlevel); - base_context.close_path (); - base_context.stroke (); - - Gdk.cairo_set_source_rgba (base_context, bright_border_color); - base_context.move_to (radius + shadow_width - zoomlevel + 1, radius + shadow_width - zoomlevel + 1); - base_context.line_to (radius + shadow_width + zoomlevel - 1, radius + shadow_width - zoomlevel + 1); - base_context.line_to (radius + shadow_width + zoomlevel - 1, radius + shadow_width + zoomlevel - 1); - base_context.line_to (radius + shadow_width - zoomlevel + 1, radius + shadow_width + zoomlevel - 1); - base_context.close_path (); - base_context.stroke (); - - - magnifier = new Gdk.Cursor.from_surface ( - get_screen ().get_display (), - base_surface, - base_surface.get_width () / 2, - base_surface.get_height () / 2); - - // Set the cursor - manager.grab ( - get_window (), - Gdk.SeatCapabilities.ALL, - true, - magnifier, - new Gdk.Event (Gdk.EventType.BUTTON_PRESS | Gdk.EventType.MOTION_NOTIFY | Gdk.EventType.SCROLL), - null); - - } - - - public Gdk.Pixbuf? snap (int x, int y, int w, int h) { - var root = Gdk.get_default_root_window (); - - var screenshot = Gdk.pixbuf_get_from_window (root, x, y, w, h); - return screenshot; - } - - - public override bool key_press_event (Gdk.EventKey e) { - var manager = Gdk.Display.get_default ().get_default_seat (); - int px, py; - get_window ().get_device_position (manager.get_pointer (), out px, out py, null); - - switch (e.keyval) { - case Gdk.Key.Escape: - cancelled (); - break; - case Gdk.Key.Return: - Gdk.RGBA color = get_color_at (px, py); - picked (color); - break; - case Gdk.Key.Up: - manager.get_pointer ().warp (get_screen (), px, py - 1); - break; - case Gdk.Key.Down: - manager.get_pointer ().warp (get_screen (), px, py + 1); - break; - case Gdk.Key.Left: - manager.get_pointer ().warp (get_screen (), px - 1, py); - break; - case Gdk.Key.Right: - manager.get_pointer ().warp (get_screen (), px + 1, py); - break; - } - - key_pressed (e); - - return true; - } - - public override bool key_release_event (Gdk.EventKey e) { - key_released (e); - - return true; - } - - public Gdk.RGBA get_color_at (int x, int y) { - var root = Gdk.get_default_root_window (); - Gdk.Pixbuf? pixbuf = Gdk.pixbuf_get_from_window (root, x, y, 1, 1); - - if (pixbuf != null) { - // see https://hackage.haskell.org/package/gtk3-0.14.6/docs/Graphics-UI-Gtk-Gdk-Pixbuf.html - uint8 red = pixbuf.get_pixels ()[0]; - uint8 green = pixbuf.get_pixels ()[1]; - uint8 blue = pixbuf.get_pixels ()[2]; - - Gdk.RGBA color = Gdk.RGBA (); - string spec = "rgb(" + red.to_string () + "," + green.to_string () + "," + blue.to_string () + ")"; - if (color.parse (spec)) { - return color; - } else { - stdout.printf ("ERROR: Parse pixel rgb values failed."); - } - } - - // fallback: default RGBA color - stdout.printf ("ERROR: Gdk.pixbuf_get_from_window failed"); - return Gdk.RGBA (); - } - - - public override void show_all () { - base.show_all (); - - var manager = Gdk.Display.get_default ().get_default_seat (); - var window = get_window (); - - var status = manager.grab ( - window, - Gdk.SeatCapabilities.ALL, - false, - new Gdk.Cursor.for_display (window.get_display (), Gdk.CursorType.CROSSHAIR), - new Gdk.Event (Gdk.EventType.BUTTON_PRESS | Gdk.EventType.BUTTON_RELEASE | Gdk.EventType.MOTION_NOTIFY), - null); - - if (status != Gdk.GrabStatus.SUCCESS) { - manager.ungrab (); - } - - // show magnifier - set_magnifier_cursor (); - } - - public new void close () { - // TODO remove the zoom level saving if we do not need it - // save zoomlevel - // settings.zoomlevel = zoomlevel; - - get_window ().set_cursor (null); - base.close (); - } } diff --git a/src/Widgets/ColorRow.vala b/src/Widgets/ColorRow.vala index 2d39ac5f2..f5d457702 100644 --- a/src/Widgets/ColorRow.vala +++ b/src/Widgets/ColorRow.vala @@ -328,16 +328,10 @@ public class Akira.Widgets.ColorRow : Gtk.Grid { private void on_eyedropper_click () { var eyedropper = new Akira.Utils.ColorPicker (); - eyedropper.show_all (); eyedropper.picked.connect ((picked_color) => { init_color_chooser (); color_chooser_widget.set_rgba (picked_color); - eyedropper.close (); - }); - - eyedropper.cancelled.connect (() => { - eyedropper.close (); }); } } diff --git a/src/meson.build b/src/meson.build index 402bd3b23..16c066c39 100644 --- a/src/meson.build +++ b/src/meson.build @@ -132,6 +132,7 @@ deps = [ goocanvas_dependency, libarchive_dependency, json_glib_dependency, + dep_libportal, m_dep ] diff --git a/vapi/libportal.vapi b/vapi/libportal.vapi new file mode 100644 index 000000000..40876da45 --- /dev/null +++ b/vapi/libportal.vapi @@ -0,0 +1,29 @@ +/* + * libportal vapi + * This is only the vapi for color picking + * */ + +[CCode (cheader_filename = "libportal/portal-gtk3.h")] +namespace Xdp { + public class Portal : GLib.Object { + public Portal (); + + public async void + pick_color (Parent parent, GLib.Cancellable? cancelable = null); + + public GLib.Variant + pick_color_finish (GLib.AsyncResult result) throws GLib.Error; + } + + [Compact] + [CCode (cname = "XdpParent", free_function = "xdp_parent_free", has_type_id = false)] + public class Parent { + public Gtk.Window object; + + [CCode (cname = "xdp_parent_new_gtk")] + public Parent (Gtk.Window window); + + [CCode (cname = "_xdp_parent_export_gtk")] + public async bool export_gtk () throws GLib.Error; + } +}