From f30450976fe2dd88e6c6b81f9f69ca133681fc8c Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Fri, 12 Nov 2021 21:36:22 +0530 Subject: [PATCH 01/24] Create basic view layer and manager for guides --- src/Lib/Managers/GuideManager.vala | 109 +++++++++++++++++++++++++++++ src/Lib/ViewCanvas.vala | 9 +++ src/ViewLayers/ViewLayerGuide.vala | 35 +++++++++ src/meson.build | 2 + 4 files changed, 155 insertions(+) create mode 100644 src/Lib/Managers/GuideManager.vala create mode 100644 src/ViewLayers/ViewLayerGuide.vala diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala new file mode 100644 index 000000000..c64b1a88b --- /dev/null +++ b/src/Lib/Managers/GuideManager.vala @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) + * + * This file is part of Akira. + * + * Akira 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. + + * Akira 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 Akira. If not, see . + * + * Authored by: Ashish Shevale + */ + + public class Akira.Lib.Managers.GuideManager : Object { + public enum Direction { + NONE, + HORIZONTAL, + VERTICAL + } + + public unowned Lib.ViewCanvas view_canvas { get; construct; } + private GuideData guide_data; + + public GuideManager (Lib.ViewCanvas view_canvas) { + Object ( + view_canvas: view_canvas + ); + + guide_data = new GuideData (); + } + + public bool key_press_event (Gdk.EventKey event) { + uint uppercase_keyval = Gdk.keyval_to_upper (event.keyval); + + if (uppercase_keyval == Gdk.Key.Q) { + print("press q\n"); + int x_pos = 0, y_pos = 0; + get_current_pointer_position (out x_pos, out y_pos); + + guide_data.add_h_guide (y_pos); + view_canvas.guide_layer.update_guide_data (guide_data); + + return true; + } else if (uppercase_keyval == Gdk.Key.W) { + print("press w\n"); + int x_pos = 0, y_pos = 0; + get_current_pointer_position (out x_pos, out y_pos); + + guide_data.add_v_guide (x_pos); + view_canvas.guide_layer.update_guide_data (guide_data); + + return true; + } + + return false; + } + + public bool key_release_event (Gdk.EventKey event) { + return false; + } + + public bool button_press_event (Gdk.EventButton event) { + return false; + } + + public bool button_release_event (Gdk.EventButton event) { + return false; + } + + public bool motion_notify_event (Gdk.EventMotion event) { + return false; + } + + private void get_current_pointer_position (out int x_pos, out int y_pos) { + var display = Gdk.Display.get_default (); + var seat = display.get_default_seat (); + var mouse = seat.get_pointer (); + + var window = display.get_default_group (); + window.get_device_position (mouse, out x_pos, out y_pos, null); + } + } + + public class Akira.Lib.Managers.GuideData { + // Stores the coordinates of horizontal guides. + // Since a guideline is a straight line (either horizontal or vertical), + // we only need one coordinate to store a line. + public int[] h_guides; + // Stores the coordinates of vertical guides. + public int[] v_guides; + + public void add_h_guide (int pos) { + h_guides.resize (h_guides.length + 1); + h_guides[h_guides.length - 1] = pos; + } + + public void add_v_guide (int pos) { + v_guides.resize (v_guides.length + 1); + v_guides[v_guides.length - 1] = pos; + } + } \ No newline at end of file diff --git a/src/Lib/ViewCanvas.vala b/src/Lib/ViewCanvas.vala index a0b3448bf..3857d92c2 100644 --- a/src/Lib/ViewCanvas.vala +++ b/src/Lib/ViewCanvas.vala @@ -40,6 +40,7 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { public Lib.Managers.NobManager nob_manager; public Lib.Managers.SnapManager snap_manager; public Lib.Managers.CopyManager copy_manager; + public Lib.Managers.GuideManager guide_manager; public bool ctrl_is_pressed = false; public bool shift_is_pressed = false; @@ -52,6 +53,7 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { private Gdk.CursorType current_cursor = Gdk.CursorType.ARROW; private ViewLayers.ViewLayerGrid grid_layout; + public ViewLayers.ViewLayerGuide guide_layer; // Keep track of the initial coords of the press event. private double initial_event_x; @@ -84,6 +86,7 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { nob_manager = new Lib.Managers.NobManager (this); snap_manager = new Lib.Managers.SnapManager (this); copy_manager = new Lib.Managers.CopyManager (this); + guide_manager = new Lib.Managers.GuideManager (this); grid_layout = new ViewLayers.ViewLayerGrid ( 0, @@ -95,6 +98,8 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { grid_layout.add_to_canvas (ViewLayers.ViewLayer.GRID_LAYER_ID, this); grid_layout.set_visible (true); + guide_layer = new ViewLayers.ViewLayerGuide (); + set_model_to_render (items_manager.item_model); window.event_bus.adjust_zoom.connect (trigger_adjust_zoom); @@ -217,6 +222,10 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { return true; } + if (guide_manager.key_press_event (event)) { + return true; + } + switch (uppercase_keyval) { case Gdk.Key.space: mode_manager.start_panning_mode (); diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala new file mode 100644 index 000000000..5f967521e --- /dev/null +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) + * + * This file is part of Akira. + * + * Akira 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. + + * Akira 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 Akira. If not, see . + * + * Authored by: Ashish Shevale + */ + + public class Akira.ViewLayers.ViewLayerGuide : ViewLayer { + public void update_guide_data (Lib.Managers.GuideData data) { + + update (); + } + + public override void draw_layer (Cairo.Context context, Geometry.Rectangle target_bounds, double scale) { + + } + + public override void update () { + + } + } \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 04921127b..177b9f893 100644 --- a/src/meson.build +++ b/src/meson.build @@ -72,6 +72,7 @@ sources = files( 'ViewLayers/ViewLayerHover.vala', 'ViewLayers/ViewLayerGrid.vala', 'ViewLayers/ViewLayerPath.vala', + 'ViewLayers/ViewLayerGuide.vala', #'Models/ColorModel.vala', #'Models/ExportModel.vala', @@ -128,6 +129,7 @@ sources = files( 'Lib/Managers/HoverManager.vala', 'Lib/Managers/SelectionManager.vala', 'Lib/Managers/SnapManager.vala', + 'Lib/Managers/GuideManager.vala', 'Lib/Modes/AbstractInteractionMode.vala', 'Lib/Modes/TransformMode.vala', From c1cac580f02f332dc4182fcdc670ce631ab23067 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 13 Nov 2021 19:57:59 +0530 Subject: [PATCH 02/24] Draw guidelines when Q or W clicked --- src/Lib/Managers/GuideManager.vala | 60 ++++++++++++++++++++---------- src/Lib/ViewCanvas.vala | 6 +++ src/ViewLayers/ViewLayer.vala | 1 + src/ViewLayers/ViewLayerGuide.vala | 59 ++++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 21 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index c64b1a88b..89c92e172 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -27,34 +27,31 @@ } public unowned Lib.ViewCanvas view_canvas { get; construct; } + private GuideData guide_data; + private Geometry.Point current_cursor; + public GuideManager (Lib.ViewCanvas view_canvas) { Object ( view_canvas: view_canvas ); guide_data = new GuideData (); + + view_canvas.scroll_event.connect (on_scroll); } public bool key_press_event (Gdk.EventKey event) { uint uppercase_keyval = Gdk.keyval_to_upper (event.keyval); if (uppercase_keyval == Gdk.Key.Q) { - print("press q\n"); - int x_pos = 0, y_pos = 0; - get_current_pointer_position (out x_pos, out y_pos); - - guide_data.add_h_guide (y_pos); + guide_data.add_h_guide (current_cursor.y); view_canvas.guide_layer.update_guide_data (guide_data); return true; } else if (uppercase_keyval == Gdk.Key.W) { - print("press w\n"); - int x_pos = 0, y_pos = 0; - get_current_pointer_position (out x_pos, out y_pos); - - guide_data.add_v_guide (x_pos); + guide_data.add_v_guide (current_cursor.x); view_canvas.guide_layer.update_guide_data (guide_data); return true; @@ -76,16 +73,20 @@ } public bool motion_notify_event (Gdk.EventMotion event) { + // Here, we just want to get the cursor position, + // so we allow the event to propogate further by returning true. + current_cursor = Geometry.Point (event.x, event.y); return false; } - private void get_current_pointer_position (out int x_pos, out int y_pos) { - var display = Gdk.Display.get_default (); - var seat = display.get_default_seat (); - var mouse = seat.get_pointer (); + private bool on_scroll (Gdk.EventScroll event) { + double delta_x, delta_y; + event.get_scroll_deltas (out delta_x, out delta_y); + + current_cursor.x += delta_x * 10; + current_cursor.y += delta_y * 10; - var window = display.get_default_group (); - window.get_device_position (mouse, out x_pos, out y_pos, null); + return false; } } @@ -93,17 +94,36 @@ // Stores the coordinates of horizontal guides. // Since a guideline is a straight line (either horizontal or vertical), // we only need one coordinate to store a line. - public int[] h_guides; + public double[] h_guides; // Stores the coordinates of vertical guides. - public int[] v_guides; + public double[] v_guides; - public void add_h_guide (int pos) { + // Stores the extents of guidelines.that were updated. + // Can't save the extents of all guidelines as they may be spread over a large area. + public Geometry.Rectangle extents; + + public GuideData () { + h_guides = new double[0]; + v_guides = new double[0]; + + extents = Geometry.Rectangle.empty (); + } + + public void add_h_guide (double pos) { h_guides.resize (h_guides.length + 1); h_guides[h_guides.length - 1] = pos; + update_extents (); } - public void add_v_guide (int pos) { + public void add_v_guide (double pos) { v_guides.resize (v_guides.length + 1); v_guides[v_guides.length - 1] = pos; + update_extents (); + } + + private void update_extents () { + // TODO: Optimize extents here. + extents.left = extents.top = 0; + extents.right = extents.bottom = 10000; } } \ No newline at end of file diff --git a/src/Lib/ViewCanvas.vala b/src/Lib/ViewCanvas.vala index 3857d92c2..0bfce4da3 100644 --- a/src/Lib/ViewCanvas.vala +++ b/src/Lib/ViewCanvas.vala @@ -99,6 +99,8 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { grid_layout.set_visible (true); guide_layer = new ViewLayers.ViewLayerGuide (); + guide_layer.add_to_canvas (ViewLayers.ViewLayer.GUIDE_LAYER_ID, this); + guide_layer.set_visible (true); set_model_to_render (items_manager.item_model); @@ -435,6 +437,10 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { return true; } + if (guide_manager.motion_notify_event (event)) { + return true; + } + var target_nob = nob_manager.hit_test (event.x, event.y); if (target_nob != Utils.Nobs.Nob.NONE) { diff --git a/src/ViewLayers/ViewLayer.vala b/src/ViewLayers/ViewLayer.vala index a4e47901b..688852026 100644 --- a/src/ViewLayers/ViewLayer.vala +++ b/src/ViewLayers/ViewLayer.vala @@ -29,6 +29,7 @@ public class Akira.ViewLayers.ViewLayer : Object { // so they have equal priority public const string NOBS_LAYER_ID = "66_nobs_layer"; public const string PATH_LAYER_ID = "66_path_layer"; + public const string GUIDE_LAYER_ID = "55_guide_layer"; private bool _is_visible { get; set; default = false; } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 5f967521e..3cb0a4bfe 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -20,16 +20,73 @@ */ public class Akira.ViewLayers.ViewLayerGuide : ViewLayer { - public void update_guide_data (Lib.Managers.GuideData data) { + private Lib.Managers.GuideData guide_data; + public void update_guide_data (Lib.Managers.GuideData data) { + guide_data = data; update (); } public override void draw_layer (Cairo.Context context, Geometry.Rectangle target_bounds, double scale) { + if (!is_visible || canvas == null) { + return; + } + + if (guide_data == null) { + return; + } else { + // If neither kind of guides are present, only then exit. + if (guide_data.h_guides == null && guide_data.v_guides == null) { + return; + } + } + if (guide_data.extents.left > target_bounds.right || guide_data.extents.right < target_bounds.left + || guide_data.extents.top > target_bounds.bottom || guide_data.extents.bottom < target_bounds.top) { + return; + } + + draw_lines (context); } public override void update () { + if (canvas == null || guide_data == null) { + return; + } else if (guide_data != null) { + if (guide_data.h_guides == null && guide_data.v_guides == null) { + return; + } + } + + // Optimize this part. Update extents for each line individually. + // canvas.request_redraw (old_live_extents); + canvas.request_redraw (guide_data.extents); + } + + private void draw_lines (Cairo.Context context) { + + context.save (); + + context.new_path (); + context.set_source_rgba (0.5, 0.5, 0.5, 1); + context.set_line_width (1.0 / canvas.scale); + + if (guide_data.h_guides != null) { + foreach (var line in guide_data.h_guides) { + context.move_to (0, line); + context.line_to (10000, line); + } + } + + if (guide_data.v_guides != null) { + foreach (var line in guide_data.v_guides) { + context.move_to (line, 0); + context.line_to (line, 10000); + } + } + context.stroke (); + context.new_path (); + context.restore (); } } \ No newline at end of file From c43d1f192ba60bbbfe1d2ff27436775e778ad6c9 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sun, 14 Nov 2021 14:19:43 +0530 Subject: [PATCH 03/24] Move guidelines by click and drag --- src/Lib/Managers/GuideManager.vala | 313 +++++++++++++++++------------ src/Lib/ViewCanvas.vala | 11 + src/ViewLayers/ViewLayer.vala | 2 +- src/ViewLayers/ViewLayerGuide.vala | 4 +- 4 files changed, 198 insertions(+), 132 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 89c92e172..53d4598df 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -1,129 +1,184 @@ -/** - * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) - * - * This file is part of Akira. - * - * Akira 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. - - * Akira 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 Akira. If not, see . - * - * Authored by: Ashish Shevale - */ - - public class Akira.Lib.Managers.GuideManager : Object { - public enum Direction { - NONE, - HORIZONTAL, - VERTICAL - } - - public unowned Lib.ViewCanvas view_canvas { get; construct; } - - private GuideData guide_data; - - private Geometry.Point current_cursor; - - public GuideManager (Lib.ViewCanvas view_canvas) { - Object ( - view_canvas: view_canvas - ); - - guide_data = new GuideData (); - - view_canvas.scroll_event.connect (on_scroll); - } - - public bool key_press_event (Gdk.EventKey event) { - uint uppercase_keyval = Gdk.keyval_to_upper (event.keyval); - - if (uppercase_keyval == Gdk.Key.Q) { - guide_data.add_h_guide (current_cursor.y); - view_canvas.guide_layer.update_guide_data (guide_data); - - return true; - } else if (uppercase_keyval == Gdk.Key.W) { - guide_data.add_v_guide (current_cursor.x); - view_canvas.guide_layer.update_guide_data (guide_data); - - return true; - } - - return false; - } - - public bool key_release_event (Gdk.EventKey event) { - return false; - } - - public bool button_press_event (Gdk.EventButton event) { - return false; - } - - public bool button_release_event (Gdk.EventButton event) { - return false; - } - - public bool motion_notify_event (Gdk.EventMotion event) { - // Here, we just want to get the cursor position, - // so we allow the event to propogate further by returning true. - current_cursor = Geometry.Point (event.x, event.y); - return false; - } - - private bool on_scroll (Gdk.EventScroll event) { - double delta_x, delta_y; - event.get_scroll_deltas (out delta_x, out delta_y); - - current_cursor.x += delta_x * 10; - current_cursor.y += delta_y * 10; - - return false; - } - } - - public class Akira.Lib.Managers.GuideData { - // Stores the coordinates of horizontal guides. - // Since a guideline is a straight line (either horizontal or vertical), - // we only need one coordinate to store a line. - public double[] h_guides; - // Stores the coordinates of vertical guides. - public double[] v_guides; - - // Stores the extents of guidelines.that were updated. - // Can't save the extents of all guidelines as they may be spread over a large area. - public Geometry.Rectangle extents; - - public GuideData () { - h_guides = new double[0]; - v_guides = new double[0]; - - extents = Geometry.Rectangle.empty (); - } - - public void add_h_guide (double pos) { - h_guides.resize (h_guides.length + 1); - h_guides[h_guides.length - 1] = pos; - update_extents (); - } - - public void add_v_guide (double pos) { - v_guides.resize (v_guides.length + 1); - v_guides[v_guides.length - 1] = pos; - update_extents (); - } - - private void update_extents () { - // TODO: Optimize extents here. - extents.left = extents.top = 0; - extents.right = extents.bottom = 10000; - } - } \ No newline at end of file +/** + * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) + * + * This file is part of Akira. + * + * Akira 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. + + * Akira 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 Akira. If not, see . + * + * Authored by: Ashish Shevale + */ + + public class Akira.Lib.Managers.GuideManager : Object { + public enum Direction { + NONE, + HORIZONTAL, + VERTICAL + } + + public unowned Lib.ViewCanvas view_canvas { get; construct; } + + private GuideData guide_data; + + private Geometry.Point current_cursor; + private int sel_line; + private Direction sel_direction; + + public GuideManager (Lib.ViewCanvas view_canvas) { + Object ( + view_canvas: view_canvas + ); + + guide_data = new GuideData (); + + view_canvas.scroll_event.connect (on_scroll); + } + + public bool key_press_event (Gdk.EventKey event) { + uint uppercase_keyval = Gdk.keyval_to_upper (event.keyval); + + if (uppercase_keyval == Gdk.Key.Q) { + guide_data.add_h_guide (current_cursor.y); + view_canvas.guide_layer.update_guide_data (guide_data); + + return true; + } else if (uppercase_keyval == Gdk.Key.W) { + guide_data.add_v_guide (current_cursor.x); + view_canvas.guide_layer.update_guide_data (guide_data); + + return true; + } + + return false; + } + + public bool key_release_event (Gdk.EventKey event) { + return false; + } + + public bool button_press_event (Gdk.EventButton event) { + Geometry.Point point = Geometry.Point (event.x, event.y); + + if (guide_data.does_guide_exist_at (point, out sel_line, out sel_direction)) { + return true; + } + + return false; + } + + public bool button_release_event (Gdk.EventButton event) { + if (sel_direction != Direction.NONE) { + sel_direction = Direction.NONE; + sel_line = -1; + + return true; + } + + return false; + } + + public bool motion_notify_event (Gdk.EventMotion event) { + // Here, we just want to get the cursor position, + // so we allow the event to propogate further by returning false. + current_cursor = Geometry.Point (event.x, event.y); + + if (sel_direction != Direction.NONE) { + guide_data.update_guide_position (sel_line, sel_direction, current_cursor); + view_canvas.guide_layer.update_guide_data (guide_data); + + return true; + } + + return false; + } + + private bool on_scroll (Gdk.EventScroll event) { + double delta_x, delta_y; + event.get_scroll_deltas (out delta_x, out delta_y); + + current_cursor.x += delta_x * 10; + current_cursor.y += delta_y * 10; + + return false; + } + } + + public class Akira.Lib.Managers.GuideData { + // Stores the coordinates of horizontal guides. + // Since a guideline is a straight line (either horizontal or vertical), + // we only need one coordinate to store a line. + public Gee.ArrayList h_guides; + // Stores the coordinates of vertical guides. + public Gee.ArrayList v_guides; + + // Stores the extents of guidelines.that were updated. + // Can't save the extents of all guidelines as they may be spread over a large area. + public Geometry.Rectangle extents; + + public GuideData () { + h_guides = new Gee.ArrayList (); + v_guides = new Gee.ArrayList (); + + extents = Geometry.Rectangle.empty (); + } + + public void add_h_guide (double pos) { + h_guides.add (pos); + update_extents (); + } + + public void add_v_guide (double pos) { + v_guides.add (pos); + update_extents (); + } + + public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out GuideManager.Direction sel_direction) { + double thresh = 1; + + for (int i = 0; i < h_guides.size; ++i) { + if ((h_guides[i] - point.y).abs () < thresh) { + sel_line = i; + sel_direction = GuideManager.Direction.HORIZONTAL; + return true; + } + } + + for (int i = 0; i < v_guides.size; ++i) { + if ((v_guides[i] - point.x).abs () < thresh) { + sel_line = i; + sel_direction = GuideManager.Direction.VERTICAL; + return true; + } + } + + sel_line = -1; + sel_direction = GuideManager.Direction.NONE; + + return false; + } + + public void update_guide_position (int position, GuideManager.Direction direction, Geometry.Point new_pos) { + if (direction == GuideManager.Direction.HORIZONTAL) { + h_guides[position] = new_pos.y; + update_extents (); + } else if (direction == GuideManager.Direction.VERTICAL) { + v_guides[position] = new_pos.x; + } + } + + private void update_extents () { + // TODO: Optimize extents here. + extents.left = extents.top = 0; + extents.right = extents.bottom = 10000; + } + } diff --git a/src/Lib/ViewCanvas.vala b/src/Lib/ViewCanvas.vala index 0bfce4da3..7fa4a703a 100644 --- a/src/Lib/ViewCanvas.vala +++ b/src/Lib/ViewCanvas.vala @@ -305,6 +305,10 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { return true; } + if (guide_manager.button_press_event (event)) { + return true; + } + if (event.button == Gdk.BUTTON_MIDDLE) { mode_manager.start_panning_mode (); if (mode_manager.button_press_event (event)) { @@ -392,6 +396,13 @@ public class Akira.Lib.ViewCanvas : ViewLayers.BaseCanvas { } public override bool button_release_event (Gdk.EventButton event) { + + // Since guidelines are stacked on top of all elements, + // check events for it first. + if (guide_manager.button_release_event (event)) { + return true; + } + // Check if the there's a delta between the pressed and released event. if (initial_event_x == event.x || initial_event_y == event.y) { var count = selection_manager.count (); diff --git a/src/ViewLayers/ViewLayer.vala b/src/ViewLayers/ViewLayer.vala index 688852026..5d6b4d4db 100644 --- a/src/ViewLayers/ViewLayer.vala +++ b/src/ViewLayers/ViewLayer.vala @@ -24,12 +24,12 @@ public class Akira.ViewLayers.ViewLayer : Object { public const string VSNAPS_LAYER_ID = "99_vsnaps_layer"; public const string HSNAPS_LAYER_ID = "99_hsnaps_layer"; public const string GRID_LAYER_ID = "88_grid_layer"; + public const string GUIDE_LAYER_ID = "88_guide_layer"; public const string HOVER_LAYER_ID = "77_hover_layer"; // at a time, only one of nobs and path layer will be shown. // so they have equal priority public const string NOBS_LAYER_ID = "66_nobs_layer"; public const string PATH_LAYER_ID = "66_path_layer"; - public const string GUIDE_LAYER_ID = "55_guide_layer"; private bool _is_visible { get; set; default = false; } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 3cb0a4bfe..39e598b41 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -70,7 +70,7 @@ context.new_path (); context.set_source_rgba (0.5, 0.5, 0.5, 1); context.set_line_width (1.0 / canvas.scale); - + if (guide_data.h_guides != null) { foreach (var line in guide_data.h_guides) { context.move_to (0, line); @@ -89,4 +89,4 @@ context.new_path (); context.restore (); } - } \ No newline at end of file + } From ac10e5a248e8eeb65964fb35527039f310a974b3 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sun, 14 Nov 2021 15:18:34 +0530 Subject: [PATCH 04/24] Highlight guidelines on hover. --- src/Lib/Managers/GuideManager.vala | 22 ++++++++++++++++++++-- src/ViewLayers/ViewLayerGuide.vala | 25 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 53d4598df..7fa2c67c7 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -95,11 +95,21 @@ if (sel_direction != Direction.NONE) { guide_data.update_guide_position (sel_line, sel_direction, current_cursor); view_canvas.guide_layer.update_guide_data (guide_data); - return true; } - return false; + int highlight_guide; + Direction highlight_direction; + + if (guide_data.does_guide_exist_at (current_cursor, out highlight_guide, out highlight_direction)) { + guide_data.set_highlighted_guide (highlight_guide, highlight_direction); + view_canvas.guide_layer.update_guide_data (guide_data); + return true; + } else { + guide_data.set_highlighted_guide (-1, Direction.NONE); + view_canvas.guide_layer.update_guide_data (guide_data); + return false; + } } private bool on_scroll (Gdk.EventScroll event) { @@ -125,6 +135,9 @@ // Can't save the extents of all guidelines as they may be spread over a large area. public Geometry.Rectangle extents; + public int highlight_guide; + public GuideManager.Direction highlight_direction; + public GuideData () { h_guides = new Gee.ArrayList (); v_guides = new Gee.ArrayList (); @@ -142,6 +155,11 @@ update_extents (); } + public void set_highlighted_guide (int guide, GuideManager.Direction direction) { + highlight_guide = guide; + highlight_direction = direction; + } + public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out GuideManager.Direction sel_direction) { double thresh = 1; diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 39e598b41..c690853e0 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -47,6 +47,7 @@ } draw_lines (context); + draw_highlighted_guides (context); } public override void update () { @@ -68,7 +69,7 @@ context.save (); context.new_path (); - context.set_source_rgba (0.5, 0.5, 0.5, 1); + context.set_source_rgba (0.6235, 0.1686, 0.4078, 1); context.set_line_width (1.0 / canvas.scale); if (guide_data.h_guides != null) { @@ -89,4 +90,26 @@ context.new_path (); context.restore (); } + + private void draw_highlighted_guides (Cairo.Context context) { + context.save (); + + context.new_path (); + context.set_source_rgba (0.8705, 0.1921, 0.3882, 1); + context.set_line_width (2.0 / canvas.scale); + + if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + var guide = guide_data.h_guides[guide_data.highlight_guide]; + context.move_to (0, guide); + context.line_to (10000, guide); + } else if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + var guide = guide_data.v_guides[guide_data.highlight_guide]; + context.move_to (guide, 0); + context.line_to (guide, 10000); + } + + context.stroke (); + context.new_path (); + context.restore (); + } } From 2348fa1c9a8dd434385293ffa0326ea3bbb15769 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Mon, 15 Nov 2021 13:32:03 +0530 Subject: [PATCH 05/24] Performance optimizations for drawing guidelines --- src/Lib/Managers/GuideManager.vala | 27 ++++---- src/ViewLayers/ViewLayerGuide.vala | 98 ++++++++++++++++++++++-------- 2 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 7fa2c67c7..36f3d196e 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -131,28 +131,34 @@ // Stores the coordinates of vertical guides. public Gee.ArrayList v_guides; - // Stores the extents of guidelines.that were updated. - // Can't save the extents of all guidelines as they may be spread over a large area. - public Geometry.Rectangle extents; - public int highlight_guide; public GuideManager.Direction highlight_direction; public GuideData () { h_guides = new Gee.ArrayList (); v_guides = new Gee.ArrayList (); + } + + public GuideData copy () { + var clone = new GuideData (); + + clone.h_guides = new Gee.ArrayList (); + clone.h_guides.add_all (h_guides); + clone.v_guides = new Gee.ArrayList (); + clone.v_guides.add_all (v_guides); - extents = Geometry.Rectangle.empty (); + clone.highlight_guide = highlight_guide; + clone.highlight_direction = highlight_direction; + + return clone; } public void add_h_guide (double pos) { h_guides.add (pos); - update_extents (); } public void add_v_guide (double pos) { v_guides.add (pos); - update_extents (); } public void set_highlighted_guide (int guide, GuideManager.Direction direction) { @@ -188,15 +194,8 @@ public void update_guide_position (int position, GuideManager.Direction direction, Geometry.Point new_pos) { if (direction == GuideManager.Direction.HORIZONTAL) { h_guides[position] = new_pos.y; - update_extents (); } else if (direction == GuideManager.Direction.VERTICAL) { v_guides[position] = new_pos.x; } } - - private void update_extents () { - // TODO: Optimize extents here. - extents.left = extents.top = 0; - extents.right = extents.bottom = 10000; - } } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index c690853e0..546f5d6be 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -20,10 +20,15 @@ */ public class Akira.ViewLayers.ViewLayerGuide : ViewLayer { - private Lib.Managers.GuideData guide_data; + private Lib.Managers.GuideData? guide_data; + private Lib.Managers.GuideData? old_data; + public ViewLayerGuide () { + } public void update_guide_data (Lib.Managers.GuideData data) { - guide_data = data; + old_data = (guide_data == null) ? null : guide_data.copy (); + guide_data = data.copy (); + update (); } @@ -41,13 +46,37 @@ } } - if (guide_data.extents.left > target_bounds.right || guide_data.extents.right < target_bounds.left - || guide_data.extents.top > target_bounds.bottom || guide_data.extents.bottom < target_bounds.top) { - return; + foreach (var line in guide_data.h_guides) { + var extents = Geometry.Rectangle.empty (); + extents.left = 0; + extents.right = 10000; + extents.top = line - 1; + extents.bottom = line + 1; + + if (extents.left > target_bounds.right || extents.right < target_bounds.left + || extents.top > target_bounds.bottom || extents.bottom < target_bounds.top) { + continue; + } + + draw_line (context, line, Lib.Managers.GuideManager.Direction.HORIZONTAL); } - draw_lines (context); - draw_highlighted_guides (context); + foreach (var line in guide_data.v_guides) { + var extents = Geometry.Rectangle.empty (); + extents.left = line - 1; + extents.right = line + 1; + extents.top = 0; + extents.bottom = 10000; + + if (extents.left > target_bounds.right || extents.right < target_bounds.left + || extents.top > target_bounds.bottom || extents.bottom < target_bounds.top) { + continue; + } + + draw_line (context, line, Lib.Managers.GuideManager.Direction.VERTICAL); + } + + draw_highlighted_guide (context); } public override void update () { @@ -59,12 +88,39 @@ } } - // Optimize this part. Update extents for each line individually. - // canvas.request_redraw (old_live_extents); - canvas.request_redraw (guide_data.extents); + if (old_data != null) { + perform_redraw (old_data); + old_data = null; + } + + if (guide_data != null) { + perform_redraw (guide_data); + } } - private void draw_lines (Cairo.Context context) { + private void perform_redraw (Lib.Managers.GuideData data) { + foreach (var line in data.v_guides) { + var extents = Geometry.Rectangle.empty (); + extents.left = line - 1; + extents.right = line + 1; + extents.top = 0; + extents.bottom = 10000; + + canvas.request_redraw (extents); + } + + foreach (var line in data.h_guides) { + var extents = Geometry.Rectangle.empty (); + extents.top = line - 1; + extents.bottom = line + 1; + extents.left = 0; + extents.right = 10000; + + canvas.request_redraw (extents); + } + } + + private void draw_line (Cairo.Context context, double pos, Lib.Managers.GuideManager.Direction dir) { context.save (); @@ -72,18 +128,12 @@ context.set_source_rgba (0.6235, 0.1686, 0.4078, 1); context.set_line_width (1.0 / canvas.scale); - if (guide_data.h_guides != null) { - foreach (var line in guide_data.h_guides) { - context.move_to (0, line); - context.line_to (10000, line); - } - } - - if (guide_data.v_guides != null) { - foreach (var line in guide_data.v_guides) { - context.move_to (line, 0); - context.line_to (line, 10000); - } + if (dir == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + context.move_to (0, pos); + context.line_to (10000, pos); + } else { + context.move_to (pos, 0); + context.line_to (pos, 10000); } context.stroke (); @@ -91,7 +141,7 @@ context.restore (); } - private void draw_highlighted_guides (Cairo.Context context) { + private void draw_highlighted_guide (Cairo.Context context) { context.save (); context.new_path (); From 5fce80a603f71555673fe6ad7386aaf6fd05bb33 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Tue, 16 Nov 2021 12:38:26 +0530 Subject: [PATCH 06/24] Store guidelines in sorted arrays. Stacking lines deletes them --- src/Lib/Managers/GuideManager.vala | 71 +++++++++---- src/Utils/SortedArray.vala | 158 +++++++++++++++++++++++++++++ src/ViewLayers/ViewLayerGuide.vala | 34 +++++-- src/meson.build | 1 + 4 files changed, 236 insertions(+), 28 deletions(-) create mode 100644 src/Utils/SortedArray.vala diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 36f3d196e..28e11757d 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -70,6 +70,7 @@ Geometry.Point point = Geometry.Point (event.x, event.y); if (guide_data.does_guide_exist_at (point, out sel_line, out sel_direction)) { + guide_data.remove_guide (sel_direction, sel_line); return true; } @@ -78,9 +79,25 @@ public bool button_release_event (Gdk.EventButton event) { if (sel_direction != Direction.NONE) { + if (sel_direction == Direction.HORIZONTAL) { + guide_data.add_h_guide (guide_data.highlight_position); + } else if (sel_direction == VERTICAL) { + guide_data.add_v_guide (guide_data.highlight_position); + } + sel_direction = Direction.NONE; sel_line = -1; + print("horizontal are\n"); + foreach (var it in guide_data.h_guides.elements) { + print("%f ", it); + } + print("\nvertical are\n"); + foreach (var it in guide_data.v_guides.elements) { + print("%f ", it); + } + print("\n\n"); + return true; } @@ -93,7 +110,7 @@ current_cursor = Geometry.Point (event.x, event.y); if (sel_direction != Direction.NONE) { - guide_data.update_guide_position (sel_line, sel_direction, current_cursor); + guide_data.move_guide_to_position (sel_line, sel_direction, current_cursor); view_canvas.guide_layer.update_guide_data (guide_data); return true; } @@ -127,58 +144,64 @@ // Stores the coordinates of horizontal guides. // Since a guideline is a straight line (either horizontal or vertical), // we only need one coordinate to store a line. - public Gee.ArrayList h_guides; + public Utils.SortedArray h_guides; // Stores the coordinates of vertical guides. - public Gee.ArrayList v_guides; + public Utils.SortedArray v_guides; public int highlight_guide; public GuideManager.Direction highlight_direction; + public double highlight_position; public GuideData () { - h_guides = new Gee.ArrayList (); - v_guides = new Gee.ArrayList (); + h_guides = new Utils.SortedArray (); + v_guides = new Utils.SortedArray (); } public GuideData copy () { var clone = new GuideData (); - clone.h_guides = new Gee.ArrayList (); - clone.h_guides.add_all (h_guides); - clone.v_guides = new Gee.ArrayList (); - clone.v_guides.add_all (v_guides); + clone.h_guides = h_guides.clone (); + clone.v_guides = v_guides.clone (); clone.highlight_guide = highlight_guide; clone.highlight_direction = highlight_direction; + clone.highlight_position = highlight_position; return clone; } public void add_h_guide (double pos) { - h_guides.add (pos); + h_guides.insert (pos); } public void add_v_guide (double pos) { - v_guides.add (pos); + v_guides.insert (pos); } public void set_highlighted_guide (int guide, GuideManager.Direction direction) { highlight_guide = guide; highlight_direction = direction; + + if (direction == GuideManager.Direction.HORIZONTAL) { + highlight_position = h_guides.elements[guide]; + } else if (direction == GuideManager.Direction.VERTICAL) { + highlight_position = v_guides.elements[guide]; + } } public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out GuideManager.Direction sel_direction) { double thresh = 1; - for (int i = 0; i < h_guides.size; ++i) { - if ((h_guides[i] - point.y).abs () < thresh) { + for (int i = 0; i < h_guides.length; ++i) { + if ((h_guides.elements[i] - point.y).abs () < thresh) { sel_line = i; sel_direction = GuideManager.Direction.HORIZONTAL; return true; } } - for (int i = 0; i < v_guides.size; ++i) { - if ((v_guides[i] - point.x).abs () < thresh) { + for (int i = 0; i < v_guides.length; ++i) { + if ((v_guides.elements[i] - point.x).abs () < thresh) { sel_line = i; sel_direction = GuideManager.Direction.VERTICAL; return true; @@ -191,11 +214,23 @@ return false; } - public void update_guide_position (int position, GuideManager.Direction direction, Geometry.Point new_pos) { + public void move_guide_to_position (int position, GuideManager.Direction direction, Geometry.Point new_pos) { if (direction == GuideManager.Direction.HORIZONTAL) { - h_guides[position] = new_pos.y; + highlight_position = new_pos.y; + highlight_direction = direction; + highlight_guide = position; } else if (direction == GuideManager.Direction.VERTICAL) { - v_guides[position] = new_pos.x; + highlight_position = new_pos.x; + highlight_direction = direction; + highlight_guide = position; + } + } + + public void remove_guide (GuideManager.Direction dir, int pos) { + if (dir == GuideManager.Direction.HORIZONTAL) { + h_guides.remove_at (pos); + } else if (dir == GuideManager.Direction.VERTICAL) { + v_guides.remove_at (pos); } } } diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala new file mode 100644 index 000000000..093bf0e98 --- /dev/null +++ b/src/Utils/SortedArray.vala @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) + * + * This file is part of Akira. + * + * Akira 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. + + * Akira 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 Akira. If not, see . + * + * Authored by: Ashish Shevale + */ + + /* + * This data structure will be used for storing the positions of guidelines. + * Storing all elements in a sorted array will make it easier to check if a guideline + * exists at the given position, to get the nearest neighbourest neightbours. + * Since this data structure does not allow storing duplicate values, + * placing a guideline over another will automatically delete it. + * + */ + public class Akira.Utils.SortedArray : Object { + // Array to store all elements. + public double[] elements; + public int length { + get { + return elements.length; + } + } + + public SortedArray () { + elements = new double[0]; + } + + /* + * Inserts the given elements in the such that the resultant array remains sorted. + */ + public void insert (double item) { + var new_elements = new double[elements.length + 1]; + int idx = 0; + + for (idx = 0; idx < elements.length; ++idx) { + // If this element already exists, no need to insert it. + if (are_equal (elements[idx], item)) { + return; + } else if (elements[idx] > item) { + // If elements after current position are greater, then the new element must be inserted first. + break; + } + + new_elements[idx] = elements[idx]; + } + + new_elements[idx] = item; + ++idx; + + // Copy the remaining elements. + for (; idx < elements.length + 1; ++idx) { + new_elements[idx] = elements[idx - 1]; + } + + elements = new_elements; + } + + public void remove_at (int index) { + if (index > elements.length - 1) { + return; + } + + var new_elements = new double[elements.length - 1]; + + // Copy all elementss upto the given index. + for (int idx = 0; idx < index; ++idx) { + new_elements[idx] = elements[idx]; + } + + // Copy all elements after the given index. + for (int idx = index + 1; idx < elements.length; ++idx) { + new_elements[idx - 1] = elements[idx]; + } + + elements = new_elements; + } + + public void remove_item (double item) { + int index = 0; + + if (contains (item, out index)) { + remove_at (index); + } + } + + /* + * Checks if element exists in the array. + * Returns true if it exists and stores the position in index. + * Returns false if element does not exist. + */ + public bool contains (double item, out int index) { + index = inner_binary_search (0, elements.length - 1, item); + + if (index == -1) { + return false; + } + + return true; + } + + public SortedArray clone () { + var cln = new SortedArray (); + cln.elements = new double[elements.length]; + + for (int i = 0; i < elements.length; ++i) { + cln.elements[i] = elements[i]; + } + + return cln; + } + + /* + * Utility function for binary search. + */ + private int inner_binary_search (int start, int end, double key) { + if (end >= start) { + int mid = (start + end) / 2; + + if (are_equal (elements[mid], key)) { + return mid; + } else if (elements[mid] < key) { + return inner_binary_search (mid + 1, end, key); + } else { + return inner_binary_search (start, mid - 1, key); + } + } + + return -1; + } + + /* + * Checks if two floating point numbers are equal. + */ + private bool are_equal (double a, double b) { + int thresh = 1; + + if ((a - b).abs () < thresh) { + return true; + } + + return false; + } + } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 546f5d6be..b481d151c 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -46,7 +46,7 @@ } } - foreach (var line in guide_data.h_guides) { + foreach (var line in guide_data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); extents.left = 0; extents.right = 10000; @@ -61,7 +61,7 @@ draw_line (context, line, Lib.Managers.GuideManager.Direction.HORIZONTAL); } - foreach (var line in guide_data.v_guides) { + foreach (var line in guide_data.v_guides.elements) { var extents = Geometry.Rectangle.empty (); extents.left = line - 1; extents.right = line + 1; @@ -99,7 +99,7 @@ } private void perform_redraw (Lib.Managers.GuideData data) { - foreach (var line in data.v_guides) { + foreach (var line in data.v_guides.elements) { var extents = Geometry.Rectangle.empty (); extents.left = line - 1; extents.right = line + 1; @@ -109,7 +109,7 @@ canvas.request_redraw (extents); } - foreach (var line in data.h_guides) { + foreach (var line in data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); extents.top = line - 1; extents.bottom = line + 1; @@ -118,6 +118,22 @@ canvas.request_redraw (extents); } + + var highlight_extents = Geometry.Rectangle.empty (); + + if (data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + highlight_extents.top = data.highlight_position - 1; + highlight_extents.bottom = data.highlight_position + 1; + highlight_extents.left = 0; + highlight_extents.right = 10000; + } else if (data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + highlight_extents.left = data.highlight_position - 1; + highlight_extents.right = data.highlight_position + 1; + highlight_extents.top = 0; + highlight_extents.bottom = 10000; + } + + canvas.request_redraw (highlight_extents); } private void draw_line (Cairo.Context context, double pos, Lib.Managers.GuideManager.Direction dir) { @@ -149,13 +165,11 @@ context.set_line_width (2.0 / canvas.scale); if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - var guide = guide_data.h_guides[guide_data.highlight_guide]; - context.move_to (0, guide); - context.line_to (10000, guide); + context.move_to (0, guide_data.highlight_position); + context.line_to (10000, guide_data.highlight_position); } else if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - var guide = guide_data.v_guides[guide_data.highlight_guide]; - context.move_to (guide, 0); - context.line_to (guide, 10000); + context.move_to (guide_data.highlight_position, 0); + context.line_to (guide_data.highlight_position, 10000); } context.stroke (); diff --git a/src/meson.build b/src/meson.build index cc58c9f56..c1eb5df39 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,6 +42,7 @@ sources = files( 'Utils/Snapping2.vala', 'Utils/SVGUtil.vala', 'Utils/GeometryMath.vala', + 'Utils/SortedArray.vala', 'Layouts/HeaderBar.vala', 'Layouts/MainViewCanvas.vala', From eb685c7b47ab8ec93d977bda44b51da7c4aa589b Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Tue, 16 Nov 2021 19:07:36 +0530 Subject: [PATCH 07/24] Draw guidelines only on artboards --- src/Lib/Items/ModelInstance.vala | 6 ++++ src/Lib/Managers/GuideManager.vala | 44 +++++++++++++++++++------ src/ViewLayers/ViewLayerGuide.vala | 52 ++++++++++++++++++------------ 3 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/Lib/Items/ModelInstance.vala b/src/Lib/Items/ModelInstance.vala index 52c37201e..049512d85 100644 --- a/src/Lib/Items/ModelInstance.vala +++ b/src/Lib/Items/ModelInstance.vala @@ -36,6 +36,8 @@ public class Akira.Lib.Items.ModelInstance { public Components.CompiledFill compiled_fill { get { return compiled_components.compiled_fill; } } public Components.CompiledBorder compiled_border { get { return compiled_components.compiled_border; } } + public Lib.Managers.GuideData? guide_data = null; + public bool is_group { get { return type.is_group (); } } public bool is_stackable { get { return drawable != null; } } @@ -50,6 +52,10 @@ public class Akira.Lib.Items.ModelInstance { this.children = new int[0]; } + if (type.name_id == "artboard") { + guide_data = new Lib.Managers.GuideData (); + } + this.id = uid; } diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 28e11757d..336d75553 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -45,6 +45,10 @@ } public bool key_press_event (Gdk.EventKey event) { + if (!is_within_artboard ()) { + return false; + } + uint uppercase_keyval = Gdk.keyval_to_upper (event.keyval); if (uppercase_keyval == Gdk.Key.Q) { @@ -67,6 +71,12 @@ } public bool button_press_event (Gdk.EventButton event) { + if (!is_within_artboard ()) { + return false;; + } else { + view_canvas.guide_layer.update_guide_data (guide_data); + } + Geometry.Point point = Geometry.Point (event.x, event.y); if (guide_data.does_guide_exist_at (point, out sel_line, out sel_direction)) { @@ -88,16 +98,6 @@ sel_direction = Direction.NONE; sel_line = -1; - print("horizontal are\n"); - foreach (var it in guide_data.h_guides.elements) { - print("%f ", it); - } - print("\nvertical are\n"); - foreach (var it in guide_data.v_guides.elements) { - print("%f ", it); - } - print("\n\n"); - return true; } @@ -138,6 +138,24 @@ return false; } + + private bool is_within_artboard () { + var groups = view_canvas.items_manager.item_model.group_nodes; + + foreach (var item in groups) { + if (item.key >= Lib.Items.Model.GROUP_START_ID) { + var extents = item.value.instance.bounding_box; + + if (extents.contains (current_cursor.x, current_cursor.y)) { + guide_data = item.value.instance.guide_data; + guide_data.drawable_extents = item.value.instance.bounding_box; + return true; + } + } + } + + return false; + } } public class Akira.Lib.Managers.GuideData { @@ -152,6 +170,10 @@ public GuideManager.Direction highlight_direction; public double highlight_position; + // Stores the extents of the artboard. + // The guidelines will only be drawn inside this region. + public Geometry.Rectangle drawable_extents; + public GuideData () { h_guides = new Utils.SortedArray (); v_guides = new Utils.SortedArray (); @@ -167,6 +189,8 @@ clone.highlight_direction = highlight_direction; clone.highlight_position = highlight_position; + clone.drawable_extents = drawable_extents; + return clone; } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index b481d151c..39dbd2b92 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -46,10 +46,13 @@ } } + var artboard_extents = guide_data.drawable_extents; + foreach (var line in guide_data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); - extents.left = 0; - extents.right = 10000; + + extents.left = artboard_extents.left; + extents.right = artboard_extents.right; extents.top = line - 1; extents.bottom = line + 1; @@ -63,10 +66,11 @@ foreach (var line in guide_data.v_guides.elements) { var extents = Geometry.Rectangle.empty (); + extents.left = line - 1; extents.right = line + 1; - extents.top = 0; - extents.bottom = 10000; + extents.top = artboard_extents.top; + extents.bottom = artboard_extents.bottom; if (extents.left > target_bounds.right || extents.right < target_bounds.left || extents.top > target_bounds.bottom || extents.bottom < target_bounds.top) { @@ -99,22 +103,26 @@ } private void perform_redraw (Lib.Managers.GuideData data) { + var artboard_extents = data.drawable_extents; + foreach (var line in data.v_guides.elements) { var extents = Geometry.Rectangle.empty (); + extents.left = line - 1; extents.right = line + 1; - extents.top = 0; - extents.bottom = 10000; + extents.top = artboard_extents.top; + extents.bottom = artboard_extents.bottom; canvas.request_redraw (extents); } foreach (var line in data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); + extents.top = line - 1; extents.bottom = line + 1; - extents.left = 0; - extents.right = 10000; + extents.left = artboard_extents.left; + extents.right = artboard_extents.right; canvas.request_redraw (extents); } @@ -124,13 +132,13 @@ if (data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { highlight_extents.top = data.highlight_position - 1; highlight_extents.bottom = data.highlight_position + 1; - highlight_extents.left = 0; - highlight_extents.right = 10000; + highlight_extents.left = artboard_extents.left; + highlight_extents.right = artboard_extents.right; } else if (data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { highlight_extents.left = data.highlight_position - 1; highlight_extents.right = data.highlight_position + 1; - highlight_extents.top = 0; - highlight_extents.bottom = 10000; + highlight_extents.top = artboard_extents.top; + highlight_extents.bottom = artboard_extents.bottom; } canvas.request_redraw (highlight_extents); @@ -144,12 +152,14 @@ context.set_source_rgba (0.6235, 0.1686, 0.4078, 1); context.set_line_width (1.0 / canvas.scale); + var artboard_extents = guide_data.drawable_extents; + if (dir == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - context.move_to (0, pos); - context.line_to (10000, pos); + context.move_to (artboard_extents.left, pos); + context.line_to (artboard_extents.right, pos); } else { - context.move_to (pos, 0); - context.line_to (pos, 10000); + context.move_to (pos, artboard_extents.top); + context.line_to (pos, artboard_extents.bottom); } context.stroke (); @@ -164,12 +174,14 @@ context.set_source_rgba (0.8705, 0.1921, 0.3882, 1); context.set_line_width (2.0 / canvas.scale); + var artboard_extents = guide_data.drawable_extents; + if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - context.move_to (0, guide_data.highlight_position); - context.line_to (10000, guide_data.highlight_position); + context.move_to (artboard_extents.left, guide_data.highlight_position); + context.line_to (artboard_extents.right, guide_data.highlight_position); } else if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - context.move_to (guide_data.highlight_position, 0); - context.line_to (guide_data.highlight_position, 10000); + context.move_to (guide_data.highlight_position, artboard_extents.top); + context.line_to (guide_data.highlight_position, artboard_extents.bottom); } context.stroke (); From b685e5ed8af164529f9a4ae0ce003ab38bcd4891 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Tue, 16 Nov 2021 19:46:18 +0530 Subject: [PATCH 08/24] Create GuidelineModel to store all data --- src/Lib/Items/ModelInstance.vala | 4 +- src/Lib/Managers/GuideManager.vala | 105 +------------------------ src/Models/GuidelineModel.vala | 121 +++++++++++++++++++++++++++++ src/ViewLayers/ViewLayerGuide.vala | 28 +++++-- src/meson.build | 1 + 5 files changed, 149 insertions(+), 110 deletions(-) create mode 100644 src/Models/GuidelineModel.vala diff --git a/src/Lib/Items/ModelInstance.vala b/src/Lib/Items/ModelInstance.vala index 049512d85..8d0127d64 100644 --- a/src/Lib/Items/ModelInstance.vala +++ b/src/Lib/Items/ModelInstance.vala @@ -36,7 +36,7 @@ public class Akira.Lib.Items.ModelInstance { public Components.CompiledFill compiled_fill { get { return compiled_components.compiled_fill; } } public Components.CompiledBorder compiled_border { get { return compiled_components.compiled_border; } } - public Lib.Managers.GuideData? guide_data = null; + public Models.GuidelineModel? guide_data = null; public bool is_group { get { return type.is_group (); } } public bool is_stackable { get { return drawable != null; } } @@ -53,7 +53,7 @@ public class Akira.Lib.Items.ModelInstance { } if (type.name_id == "artboard") { - guide_data = new Lib.Managers.GuideData (); + guide_data = new Models.GuidelineModel (); } this.id = uid; diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 336d75553..ff1f37482 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -28,7 +28,7 @@ public unowned Lib.ViewCanvas view_canvas { get; construct; } - private GuideData guide_data; + private Models.GuidelineModel guide_data; private Geometry.Point current_cursor; private int sel_line; @@ -39,7 +39,7 @@ view_canvas: view_canvas ); - guide_data = new GuideData (); + guide_data = new Models.GuidelineModel (); view_canvas.scroll_event.connect (on_scroll); } @@ -157,104 +157,3 @@ return false; } } - - public class Akira.Lib.Managers.GuideData { - // Stores the coordinates of horizontal guides. - // Since a guideline is a straight line (either horizontal or vertical), - // we only need one coordinate to store a line. - public Utils.SortedArray h_guides; - // Stores the coordinates of vertical guides. - public Utils.SortedArray v_guides; - - public int highlight_guide; - public GuideManager.Direction highlight_direction; - public double highlight_position; - - // Stores the extents of the artboard. - // The guidelines will only be drawn inside this region. - public Geometry.Rectangle drawable_extents; - - public GuideData () { - h_guides = new Utils.SortedArray (); - v_guides = new Utils.SortedArray (); - } - - public GuideData copy () { - var clone = new GuideData (); - - clone.h_guides = h_guides.clone (); - clone.v_guides = v_guides.clone (); - - clone.highlight_guide = highlight_guide; - clone.highlight_direction = highlight_direction; - clone.highlight_position = highlight_position; - - clone.drawable_extents = drawable_extents; - - return clone; - } - - public void add_h_guide (double pos) { - h_guides.insert (pos); - } - - public void add_v_guide (double pos) { - v_guides.insert (pos); - } - - public void set_highlighted_guide (int guide, GuideManager.Direction direction) { - highlight_guide = guide; - highlight_direction = direction; - - if (direction == GuideManager.Direction.HORIZONTAL) { - highlight_position = h_guides.elements[guide]; - } else if (direction == GuideManager.Direction.VERTICAL) { - highlight_position = v_guides.elements[guide]; - } - } - - public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out GuideManager.Direction sel_direction) { - double thresh = 1; - - for (int i = 0; i < h_guides.length; ++i) { - if ((h_guides.elements[i] - point.y).abs () < thresh) { - sel_line = i; - sel_direction = GuideManager.Direction.HORIZONTAL; - return true; - } - } - - for (int i = 0; i < v_guides.length; ++i) { - if ((v_guides.elements[i] - point.x).abs () < thresh) { - sel_line = i; - sel_direction = GuideManager.Direction.VERTICAL; - return true; - } - } - - sel_line = -1; - sel_direction = GuideManager.Direction.NONE; - - return false; - } - - public void move_guide_to_position (int position, GuideManager.Direction direction, Geometry.Point new_pos) { - if (direction == GuideManager.Direction.HORIZONTAL) { - highlight_position = new_pos.y; - highlight_direction = direction; - highlight_guide = position; - } else if (direction == GuideManager.Direction.VERTICAL) { - highlight_position = new_pos.x; - highlight_direction = direction; - highlight_guide = position; - } - } - - public void remove_guide (GuideManager.Direction dir, int pos) { - if (dir == GuideManager.Direction.HORIZONTAL) { - h_guides.remove_at (pos); - } else if (dir == GuideManager.Direction.VERTICAL) { - v_guides.remove_at (pos); - } - } - } diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala new file mode 100644 index 000000000..d8d92dd74 --- /dev/null +++ b/src/Models/GuidelineModel.vala @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) + * + * This file is part of Akira. + * + * Akira 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. + + * Akira 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 Akira. If not, see . + * + * Authored by: Ashish Shevale + */ + +public class Akira.Models.GuidelineModel { + // Stores the coordinates of horizontal guides. + // Since a guideline is a straight line (either horizontal or vertical), + // we only need one coordinate to store a line. + public Utils.SortedArray h_guides; + // Stores the coordinates of vertical guides. + public Utils.SortedArray v_guides; + + public int highlight_guide; + public Lib.Managers.GuideManager.Direction highlight_direction; + public double highlight_position; + + // Stores the extents of the artboard. + // The guidelines will only be drawn inside this region. + public Geometry.Rectangle drawable_extents; + + public GuidelineModel () { + h_guides = new Utils.SortedArray (); + v_guides = new Utils.SortedArray (); + } + + public GuidelineModel copy () { + var clone = new GuidelineModel (); + + clone.h_guides = h_guides.clone (); + clone.v_guides = v_guides.clone (); + + clone.highlight_guide = highlight_guide; + clone.highlight_direction = highlight_direction; + clone.highlight_position = highlight_position; + + clone.drawable_extents = drawable_extents; + + return clone; + } + + public void add_h_guide (double pos) { + h_guides.insert (pos); + } + + public void add_v_guide (double pos) { + v_guides.insert (pos); + } + + public void set_highlighted_guide (int guide, Lib.Managers.GuideManager.Direction direction) { + highlight_guide = guide; + highlight_direction = direction; + + if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + highlight_position = h_guides.elements[guide]; + } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + highlight_position = v_guides.elements[guide]; + } + } + + public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out Lib.Managers.GuideManager.Direction sel_direction) { + double thresh = 1; + + for (int i = 0; i < h_guides.length; ++i) { + if ((h_guides.elements[i] - point.y).abs () < thresh) { + sel_line = i; + sel_direction = Lib.Managers.GuideManager.Direction.HORIZONTAL; + return true; + } + } + + for (int i = 0; i < v_guides.length; ++i) { + if ((v_guides.elements[i] - point.x).abs () < thresh) { + sel_line = i; + sel_direction = Lib.Managers.GuideManager.Direction.VERTICAL; + return true; + } + } + + sel_line = -1; + sel_direction = Lib.Managers.GuideManager.Direction.NONE; + + return false; + } + + public void move_guide_to_position (int position, Lib.Managers.GuideManager.Direction direction, Geometry.Point new_pos) { + if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + highlight_position = new_pos.y; + highlight_direction = direction; + highlight_guide = position; + } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + highlight_position = new_pos.x; + highlight_direction = direction; + highlight_guide = position; + } + } + + public void remove_guide (Lib.Managers.GuideManager.Direction dir, int pos) { + if (dir == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + h_guides.remove_at (pos); + } else if (dir == Lib.Managers.GuideManager.Direction.VERTICAL) { + v_guides.remove_at (pos); + } + } + } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 39dbd2b92..3bfd41dae 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -20,12 +20,12 @@ */ public class Akira.ViewLayers.ViewLayerGuide : ViewLayer { - private Lib.Managers.GuideData? guide_data; - private Lib.Managers.GuideData? old_data; + private Models.GuidelineModel? guide_data; + private Models.GuidelineModel? old_data; public ViewLayerGuide () { } - public void update_guide_data (Lib.Managers.GuideData data) { + public void update_guide_data (Models.GuidelineModel data) { old_data = (guide_data == null) ? null : guide_data.copy (); guide_data = data.copy (); @@ -102,7 +102,7 @@ } } - private void perform_redraw (Lib.Managers.GuideData data) { + private void perform_redraw (Models.GuidelineModel data) { var artboard_extents = data.drawable_extents; foreach (var line in data.v_guides.elements) { @@ -118,7 +118,7 @@ foreach (var line in data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); - + extents.top = line - 1; extents.bottom = line + 1; extents.left = artboard_extents.left; @@ -188,4 +188,22 @@ context.new_path (); context.restore (); } + + private void draw_distances (Cairo.Context context) { + var distance_1 = 1000; + var distance_2 = 1000; + var artboard_extents = guide_data.drawable_extents; + + context.set_source_rgba (0.8705, 0.1921, 0.3882, 1); + context.select_font_face ("monospace", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); + context.set_font_size (14); + + if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + context.move_to (artboard_extents.left, guide_data.highlight_position); + context.show_text (distance_1.to_string ()); + } else if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + context.move_to (guide_data.highlight_position, artboard_extents.top); + context.show_text (distance_2.to_string ()); + } + } } diff --git a/src/meson.build b/src/meson.build index c1eb5df39..97c66048a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -86,6 +86,7 @@ sources = files( #'Models/ExportModel.vala', #'Models/ListModel.vala', 'Models/PathEditModel.vala', + 'Models/GuidelineModel.vala', 'Dialogs/ShortcutsDialog.vala', 'Dialogs/SettingsDialog.vala', From 78b2b1333b11b2f405ca5f980b9c3d746c2bd494 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Wed, 17 Nov 2021 17:41:47 +0530 Subject: [PATCH 09/24] Distances from guidelines --- src/Lib/Managers/GuideManager.vala | 6 ++- src/Models/GuidelineModel.vala | 68 ++++++++++++++++++++++++++++-- src/ViewLayers/ViewLayerGuide.vala | 31 +++++++++----- 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index ff1f37482..66ae311d1 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -72,7 +72,7 @@ public bool button_press_event (Gdk.EventButton event) { if (!is_within_artboard ()) { - return false;; + return false; } else { view_canvas.guide_layer.update_guide_data (guide_data); } @@ -81,6 +81,7 @@ if (guide_data.does_guide_exist_at (point, out sel_line, out sel_direction)) { guide_data.remove_guide (sel_direction, sel_line); + guide_data.calculate_distance_positions (current_cursor); return true; } @@ -111,6 +112,7 @@ if (sel_direction != Direction.NONE) { guide_data.move_guide_to_position (sel_line, sel_direction, current_cursor); + guide_data.calculate_distance_positions (current_cursor); view_canvas.guide_layer.update_guide_data (guide_data); return true; } @@ -148,7 +150,7 @@ if (extents.contains (current_cursor.x, current_cursor.y)) { guide_data = item.value.instance.guide_data; - guide_data.drawable_extents = item.value.instance.bounding_box; + guide_data.set_drawable_extents (item.value.instance.bounding_box); return true; } } diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index d8d92dd74..678c72d40 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -27,10 +27,18 @@ public class Akira.Models.GuidelineModel { // Stores the coordinates of vertical guides. public Utils.SortedArray v_guides; + // Stores index of line in the sorted array. public int highlight_guide; public Lib.Managers.GuideManager.Direction highlight_direction; + // Stores the coordinate of currently highlighted guideline. public double highlight_position; + // The distances between guidelines will be displayed near the current cursor position. + public Geometry.Point cursor_position; + // This string will contain the distances either from the nearest guidelines or another item. + // This string will be draw as is on the canvas. + public string distances; + // Stores the extents of the artboard. // The guidelines will only be drawn inside this region. public Geometry.Rectangle drawable_extents; @@ -51,6 +59,8 @@ public class Akira.Models.GuidelineModel { clone.highlight_position = highlight_position; clone.drawable_extents = drawable_extents; + clone.cursor_position = cursor_position; + clone.distances = distances; return clone; } @@ -74,10 +84,34 @@ public class Akira.Models.GuidelineModel { } } - public bool does_guide_exist_at (Geometry.Point point, out int sel_line, out Lib.Managers.GuideManager.Direction sel_direction) { + public void set_drawable_extents (Geometry.Rectangle extents) { + drawable_extents = extents; + + // We also need to add the edges of the artboard. + // These lines make it easier to measure distances. + int index; + if (h_guides.contains (extents.left, out index) || h_guides.contains (extents.right, out index)) { + return; + } else if (v_guides.contains (extents.top, out index) || v_guides.contains (extents.bottom, out index)) { + return; + } + + v_guides.insert (extents.left); + v_guides.insert (extents.right); + h_guides.insert (extents.top); + h_guides.insert (extents.bottom); + } + + public bool does_guide_exist_at ( + Geometry.Point point, + out int sel_line, + out Lib.Managers.GuideManager.Direction sel_direction + ) { double thresh = 1; - for (int i = 0; i < h_guides.length; ++i) { + // We are skipping the first and last lines as those are fixed + // and only used for calculating distances. + for (int i = 1; i < h_guides.length - 1; ++i) { if ((h_guides.elements[i] - point.y).abs () < thresh) { sel_line = i; sel_direction = Lib.Managers.GuideManager.Direction.HORIZONTAL; @@ -85,7 +119,7 @@ public class Akira.Models.GuidelineModel { } } - for (int i = 0; i < v_guides.length; ++i) { + for (int i = 1; i < v_guides.length - 1; ++i) { if ((v_guides.elements[i] - point.x).abs () < thresh) { sel_line = i; sel_direction = Lib.Managers.GuideManager.Direction.VERTICAL; @@ -99,7 +133,11 @@ public class Akira.Models.GuidelineModel { return false; } - public void move_guide_to_position (int position, Lib.Managers.GuideManager.Direction direction, Geometry.Point new_pos) { + public void move_guide_to_position ( + int position, + Lib.Managers.GuideManager.Direction direction, + Geometry.Point new_pos + ) { if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { highlight_position = new_pos.y; highlight_direction = direction; @@ -118,4 +156,26 @@ public class Akira.Models.GuidelineModel { v_guides.remove_at (pos); } } + + /* + * This method will calculate the distances of selected guideline and its nearest neighbours. + * It also calculates the position where this text is to be displayed on canvas. + * The distances will always be drawn next to the cursor. + */ + public void calculate_distance_positions (Geometry.Point cursor) { + double distance_1 = 0; + double distance_2 = 0; + + // Calculate the distance between the highlighted guide and the neareast neighbour on either sides. + if (highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + distance_1 = highlight_position - h_guides.elements[highlight_guide - 1]; + distance_2 = h_guides.elements[highlight_guide] - highlight_position; + } else if (highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + distance_1 = highlight_position - v_guides.elements[highlight_guide - 1]; + distance_2 = v_guides.elements[highlight_guide] - highlight_position; + } + + cursor_position = cursor; + distances = """%.3f, %.3f""".printf (distance_1, distance_2); + } } diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 3bfd41dae..5d395dcf7 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -81,6 +81,8 @@ } draw_highlighted_guide (context); + + draw_distances (context); } public override void update () { @@ -105,6 +107,7 @@ private void perform_redraw (Models.GuidelineModel data) { var artboard_extents = data.drawable_extents; + // Draw all vertical guidelines. foreach (var line in data.v_guides.elements) { var extents = Geometry.Rectangle.empty (); @@ -116,6 +119,7 @@ canvas.request_redraw (extents); } + // Draw all horizontal guidelines. foreach (var line in data.h_guides.elements) { var extents = Geometry.Rectangle.empty (); @@ -127,6 +131,7 @@ canvas.request_redraw (extents); } + // Draw the highlighted guideline. var highlight_extents = Geometry.Rectangle.empty (); if (data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { @@ -142,6 +147,16 @@ } canvas.request_redraw (highlight_extents); + + // Draw the distance text. + var distance_extents = Geometry.Rectangle.empty (); + + distance_extents.left = data.cursor_position.x - 90; + distance_extents.right = data.cursor_position.x + 90; + distance_extents.top = data.cursor_position.y - 14; + distance_extents.bottom = data.cursor_position.y + 14; + + canvas.request_redraw (distance_extents); } private void draw_line (Cairo.Context context, double pos, Lib.Managers.GuideManager.Direction dir) { @@ -190,20 +205,14 @@ } private void draw_distances (Cairo.Context context) { - var distance_1 = 1000; - var distance_2 = 1000; - var artboard_extents = guide_data.drawable_extents; - context.set_source_rgba (0.8705, 0.1921, 0.3882, 1); context.select_font_face ("monospace", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); context.set_font_size (14); - if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - context.move_to (artboard_extents.left, guide_data.highlight_position); - context.show_text (distance_1.to_string ()); - } else if (guide_data.highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - context.move_to (guide_data.highlight_position, artboard_extents.top); - context.show_text (distance_2.to_string ()); - } + double x = guide_data.cursor_position.x - 50; + double y = guide_data.cursor_position.y - 5; + + context.move_to (x, y); + context.show_text (guide_data.distances); } } From f1cc5d27003388132e8b40c29d254077a0911215 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Wed, 17 Nov 2021 17:43:20 +0530 Subject: [PATCH 10/24] Lint fixes --- src/Models/GuidelineModel.vala | 6 +++--- src/Utils/SortedArray.vala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 678c72d40..84aa99a13 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -50,7 +50,7 @@ public class Akira.Models.GuidelineModel { public GuidelineModel copy () { var clone = new GuidelineModel (); - + clone.h_guides = h_guides.clone (); clone.v_guides = v_guides.clone (); @@ -76,7 +76,7 @@ public class Akira.Models.GuidelineModel { public void set_highlighted_guide (int guide, Lib.Managers.GuideManager.Direction direction) { highlight_guide = guide; highlight_direction = direction; - + if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { highlight_position = h_guides.elements[guide]; } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { @@ -129,7 +129,7 @@ public class Akira.Models.GuidelineModel { sel_line = -1; sel_direction = Lib.Managers.GuideManager.Direction.NONE; - + return false; } diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 093bf0e98..6a3df675e 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -130,7 +130,7 @@ private int inner_binary_search (int start, int end, double key) { if (end >= start) { int mid = (start + end) / 2; - + if (are_equal (elements[mid], key)) { return mid; } else if (elements[mid] < key) { From 3ab3ea0e4dbcb1761bebab0553678c156949fa1a Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Thu, 18 Nov 2021 20:42:05 +0530 Subject: [PATCH 11/24] Bug fix in calculating distances --- src/Utils/SortedArray.vala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 6a3df675e..19c3de85a 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -124,6 +124,17 @@ return cln; } + public void get_distance_to_neighbours (double item, out double neigh_1, out double neigh_2) { + var array_copy = this.clone (); + array_copy.insert (item); + + int position = -1; + array_copy.contains (item, out position); + neigh_1 = item - array_copy.elements[position - 1]; + neigh_2 = array_copy.elements[position + 1] - item; + print("nearest neigh %f %f\n", neigh_1, neigh_2); + } + /* * Utility function for binary search. */ From 8a991e63564fa394ed7a5d8e2ac3f0f5e162b767 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Thu, 18 Nov 2021 20:42:26 +0530 Subject: [PATCH 12/24] Possible patch for double click to delete bug --- src/Lib/Managers/GuideManager.vala | 26 ++++++++++++++++++++++ src/Models/GuidelineModel.vala | 35 ++++++++++++++++++------------ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 66ae311d1..4c4e69e03 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -71,6 +71,31 @@ } public bool button_press_event (Gdk.EventButton event) { + /* + if (event.type == Gdk.EventType.@2BUTTON_PRESS) { + // The Double Click to Delete Bug/Feature and its patch. + + // Why double clicking deletes a guideline + // When you click on a guideline, it gets deleted from the SortedArray. + // Then you can drag you anywhere. As soon as you release the button, it gets added to + // the sorted array at the correct position. + + // When you double click, the events occur as follows + // 1. click -> delete the guideline. + // 2. release -> add the guideline we deleted. + // 3. click -> again delete the same guideline. + // 4. double click -> should delete the guideline, but none exists coz we just deleted it + // At this point, we also enter the guide_data.does_exist_at method. Since there is not guide, + // we also make the selected guide as none. + // 5. release -> no action here. Since there was no selected guide. + + // In case you feel like disabling the double click to delete guide feature, just uncomment + // this 'if' block. + + return false; + } + */ + if (!is_within_artboard ()) { return false; } else { @@ -144,6 +169,7 @@ private bool is_within_artboard () { var groups = view_canvas.items_manager.item_model.group_nodes; + // Need to optimize this part. foreach (var item in groups) { if (item.key >= Lib.Items.Model.GROUP_START_ID) { var extents = item.value.instance.bounding_box; diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 84aa99a13..87d436d0b 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -28,6 +28,7 @@ public class Akira.Models.GuidelineModel { public Utils.SortedArray v_guides; // Stores index of line in the sorted array. + // In case we are moving the guide, stores index of next guideline. public int highlight_guide; public Lib.Managers.GuideManager.Direction highlight_direction; // Stores the coordinate of currently highlighted guideline. @@ -107,21 +108,25 @@ public class Akira.Models.GuidelineModel { out int sel_line, out Lib.Managers.GuideManager.Direction sel_direction ) { - double thresh = 1; - - // We are skipping the first and last lines as those are fixed - // and only used for calculating distances. - for (int i = 1; i < h_guides.length - 1; ++i) { - if ((h_guides.elements[i] - point.y).abs () < thresh) { - sel_line = i; + if (h_guides.contains (point.y, out sel_line)) { + // The first and last guidelines are only for calculating distances. + // They should not be moved. + if (sel_line == 0 || sel_line == h_guides.length) { + sel_line = -1; + sel_direction = Lib.Managers.GuideManager.Direction.NONE; + return false; + } else { sel_direction = Lib.Managers.GuideManager.Direction.HORIZONTAL; return true; } } - for (int i = 1; i < v_guides.length - 1; ++i) { - if ((v_guides.elements[i] - point.x).abs () < thresh) { - sel_line = i; + if (v_guides.contains (point.x, out sel_line)) { + if (sel_line == 0 || sel_line == v_guides.length) { + sel_line = -1; + sel_direction = Lib.Managers.GuideManager.Direction.NONE; + return false; + } else { sel_direction = Lib.Managers.GuideManager.Direction.VERTICAL; return true; } @@ -168,11 +173,13 @@ public class Akira.Models.GuidelineModel { // Calculate the distance between the highlighted guide and the neareast neighbour on either sides. if (highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - distance_1 = highlight_position - h_guides.elements[highlight_guide - 1]; - distance_2 = h_guides.elements[highlight_guide] - highlight_position; + // distance_1 = highlight_position - h_guides.elements[highlight_guide - 1]; + // distance_2 = h_guides.elements[highlight_guide] - highlight_position; + h_guides.get_distance_to_neighbours (highlight_position, out distance_1, out distance_2); } else if (highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - distance_1 = highlight_position - v_guides.elements[highlight_guide - 1]; - distance_2 = v_guides.elements[highlight_guide] - highlight_position; + // distance_1 = highlight_position - v_guides.elements[highlight_guide - 1]; + // distance_2 = v_guides.elements[highlight_guide] - highlight_position; + v_guides.get_distance_to_neighbours (highlight_position, out distance_1, out distance_2); } cursor_position = cursor; From c3ec43feb075da10b7718565b906a3c1190f5e44 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Thu, 18 Nov 2021 22:02:54 +0530 Subject: [PATCH 13/24] Delete guideline by dragging to edge --- src/Lib/Items/ModelTypeArtboard.vala | 4 ++++ src/Lib/Managers/GuideManager.vala | 22 ++++++++++++++++--- src/Models/GuidelineModel.vala | 32 +++++++++++++++++++++++++--- src/Utils/SortedArray.vala | 23 +++++++++++++++++--- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/Lib/Items/ModelTypeArtboard.vala b/src/Lib/Items/ModelTypeArtboard.vala index 8dfda60ea..0c34227c0 100644 --- a/src/Lib/Items/ModelTypeArtboard.vala +++ b/src/Lib/Items/ModelTypeArtboard.vala @@ -87,6 +87,10 @@ public class Akira.Lib.Items.ModelTypeArtboard : ModelType { instance.drawable.width = instance.components.size.width; instance.drawable.height = instance.components.size.height; instance.drawable.transform = instance.compiled_geometry.transformation_matrix; + + // If artboard is resized, we need to update the extents in guide data. + instance.guide_data.set_drawable_extents (instance.bounding_box); + instance.guide_data.changed (); break; } } diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 4c4e69e03..76d5a73bd 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -42,6 +42,7 @@ guide_data = new Models.GuidelineModel (); view_canvas.scroll_event.connect (on_scroll); + guide_data.changed.connect (on_guide_data_changed); } public bool key_press_event (Gdk.EventKey event) { @@ -99,6 +100,7 @@ if (!is_within_artboard ()) { return false; } else { + // In case we clicked on a different artboard, we need to update that data. view_canvas.guide_layer.update_guide_data (guide_data); } @@ -136,9 +138,17 @@ current_cursor = Geometry.Point (event.x, event.y); if (sel_direction != Direction.NONE) { + // If while moving a guideline, user takes it out of artboard, + // delete it immediately. + if (!guide_data.drawable_extents.contains (current_cursor.x, current_cursor.y)) { + sel_direction = Lib.Managers.GuideManager.Direction.NONE; + sel_line = -1; + + return true; + } + guide_data.move_guide_to_position (sel_line, sel_direction, current_cursor); guide_data.calculate_distance_positions (current_cursor); - view_canvas.guide_layer.update_guide_data (guide_data); return true; } @@ -147,11 +157,9 @@ if (guide_data.does_guide_exist_at (current_cursor, out highlight_guide, out highlight_direction)) { guide_data.set_highlighted_guide (highlight_guide, highlight_direction); - view_canvas.guide_layer.update_guide_data (guide_data); return true; } else { guide_data.set_highlighted_guide (-1, Direction.NONE); - view_canvas.guide_layer.update_guide_data (guide_data); return false; } } @@ -166,6 +174,12 @@ return false; } + private void on_guide_data_changed () { + if (view_canvas.guide_layer != null) { + view_canvas.guide_layer.update_guide_data (guide_data); + } + } + private bool is_within_artboard () { var groups = view_canvas.items_manager.item_model.group_nodes; @@ -175,8 +189,10 @@ var extents = item.value.instance.bounding_box; if (extents.contains (current_cursor.x, current_cursor.y)) { + guide_data.changed.disconnect (on_guide_data_changed); guide_data = item.value.instance.guide_data; guide_data.set_drawable_extents (item.value.instance.bounding_box); + guide_data.changed.connect (on_guide_data_changed); return true; } } diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 87d436d0b..8bc3c0a0d 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -44,9 +44,13 @@ public class Akira.Models.GuidelineModel { // The guidelines will only be drawn inside this region. public Geometry.Rectangle drawable_extents; + public signal void changed (); + public GuidelineModel () { - h_guides = new Utils.SortedArray (); - v_guides = new Utils.SortedArray (); + h_guides = new Utils.SortedArray (0, 0); + v_guides = new Utils.SortedArray (0, 0); + + drawable_extents = Geometry.Rectangle.empty (); } public GuidelineModel copy () { @@ -68,10 +72,12 @@ public class Akira.Models.GuidelineModel { public void add_h_guide (double pos) { h_guides.insert (pos); + changed (); } public void add_v_guide (double pos) { v_guides.insert (pos); + changed (); } public void set_highlighted_guide (int guide, Lib.Managers.GuideManager.Direction direction) { @@ -83,12 +89,21 @@ public class Akira.Models.GuidelineModel { } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { highlight_position = v_guides.elements[guide]; } + + changed (); } public void set_drawable_extents (Geometry.Rectangle extents) { + // Remove the guidelines at edges of previous extents if they exist. + // Solves bug when artboard gets resized. + h_guides.remove_item (drawable_extents.top); + h_guides.remove_item (drawable_extents.bottom); + v_guides.remove_item (drawable_extents.left); + v_guides.remove_item (drawable_extents.right); + drawable_extents = extents; - // We also need to add the edges of the artboard. + // Then add the new edges of artboard. // These lines make it easier to measure distances. int index; if (h_guides.contains (extents.left, out index) || h_guides.contains (extents.right, out index)) { @@ -101,6 +116,11 @@ public class Akira.Models.GuidelineModel { v_guides.insert (extents.right); h_guides.insert (extents.top); h_guides.insert (extents.bottom); + + h_guides.set_bounds (extents.top, extents.bottom); + v_guides.set_bounds (extents.left, extents.right); + + changed (); } public bool does_guide_exist_at ( @@ -134,6 +154,7 @@ public class Akira.Models.GuidelineModel { sel_line = -1; sel_direction = Lib.Managers.GuideManager.Direction.NONE; + changed (); return false; } @@ -147,18 +168,22 @@ public class Akira.Models.GuidelineModel { highlight_position = new_pos.y; highlight_direction = direction; highlight_guide = position; + changed (); } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { highlight_position = new_pos.x; highlight_direction = direction; highlight_guide = position; + changed (); } } public void remove_guide (Lib.Managers.GuideManager.Direction dir, int pos) { if (dir == Lib.Managers.GuideManager.Direction.HORIZONTAL) { h_guides.remove_at (pos); + changed (); } else if (dir == Lib.Managers.GuideManager.Direction.VERTICAL) { v_guides.remove_at (pos); + changed (); } } @@ -184,5 +209,6 @@ public class Akira.Models.GuidelineModel { cursor_position = cursor; distances = """%.3f, %.3f""".printf (distance_1, distance_2); + changed (); } } diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 19c3de85a..037ecb14f 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -36,7 +36,16 @@ } } - public SortedArray () { + // Represent the upper and lower bound that all guidelines must stay in + // if they don't want to be deleted. Represent the extents of artboard. + // Any guideline outside this limit will be deleted. + private double lower_bound; + private double upper_bound; + + public SortedArray (double lower_bound, double upper_bound) { + this.lower_bound = lower_bound; + this.upper_bound = upper_bound; + elements = new double[0]; } @@ -44,6 +53,10 @@ * Inserts the given elements in the such that the resultant array remains sorted. */ public void insert (double item) { + if ((item > upper_bound) || (item < lower_bound)) { + return; + } + var new_elements = new double[elements.length + 1]; int idx = 0; @@ -114,7 +127,7 @@ } public SortedArray clone () { - var cln = new SortedArray (); + var cln = new SortedArray (lower_bound, upper_bound); cln.elements = new double[elements.length]; for (int i = 0; i < elements.length; ++i) { @@ -132,7 +145,11 @@ array_copy.contains (item, out position); neigh_1 = item - array_copy.elements[position - 1]; neigh_2 = array_copy.elements[position + 1] - item; - print("nearest neigh %f %f\n", neigh_1, neigh_2); + } + + public void set_bounds (double lower_bound, double upper_bound) { + this.lower_bound = lower_bound; + this.upper_bound = upper_bound; } /* From ce1010a8b9751b15506c87db3101833ec901c215 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Fri, 19 Nov 2021 16:05:13 +0530 Subject: [PATCH 14/24] Better looking widget for distances --- src/Lib/Managers/GuideManager.vala | 4 ++ src/Models/GuidelineModel.vala | 28 ++++++----- src/Utils/SortedArray.vala | 8 +++ src/ViewLayers/ViewLayerGuide.vala | 78 +++++++++++++++++++++++++----- 4 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 76d5a73bd..fd541a222 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -126,9 +126,13 @@ sel_direction = Direction.NONE; sel_line = -1; + guide_data.reset_distances (); + return true; } + guide_data.reset_distances (); + return false; } diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 8bc3c0a0d..e61137017 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -36,9 +36,10 @@ public class Akira.Models.GuidelineModel { // The distances between guidelines will be displayed near the current cursor position. public Geometry.Point cursor_position; - // This string will contain the distances either from the nearest guidelines or another item. - // This string will be draw as is on the canvas. - public string distances; + // This array stores the distances from all four sides. + // Can be distances from neighbouring guidelines or other canvas items. + // Stored as { LEFT, RIGHT, TOP, BOTTOM } + public double[] distances; // Stores the extents of the artboard. // The guidelines will only be drawn inside this region. @@ -51,6 +52,8 @@ public class Akira.Models.GuidelineModel { v_guides = new Utils.SortedArray (0, 0); drawable_extents = Geometry.Rectangle.empty (); + distances = new double[4]; + distances[0] = distances[1] = distances[2] = distances[3] = -1; } public GuidelineModel copy () { @@ -193,22 +196,21 @@ public class Akira.Models.GuidelineModel { * The distances will always be drawn next to the cursor. */ public void calculate_distance_positions (Geometry.Point cursor) { - double distance_1 = 0; - double distance_2 = 0; - // Calculate the distance between the highlighted guide and the neareast neighbour on either sides. if (highlight_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - // distance_1 = highlight_position - h_guides.elements[highlight_guide - 1]; - // distance_2 = h_guides.elements[highlight_guide] - highlight_position; - h_guides.get_distance_to_neighbours (highlight_position, out distance_1, out distance_2); + h_guides.get_distance_to_neighbours (highlight_position, out distances[2], out distances[3]); + distances[0] = distances[1] = -1; } else if (highlight_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - // distance_1 = highlight_position - v_guides.elements[highlight_guide - 1]; - // distance_2 = v_guides.elements[highlight_guide] - highlight_position; - v_guides.get_distance_to_neighbours (highlight_position, out distance_1, out distance_2); + v_guides.get_distance_to_neighbours (highlight_position, out distances[0], out distances[1]); + distances[2] = distances[3] = -1; } cursor_position = cursor; - distances = """%.3f, %.3f""".printf (distance_1, distance_2); + changed (); + } + + public void reset_distances () { + distances[0] = distances[1] = distances[2] = distances[3] = -1; changed (); } } diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 037ecb14f..a10f33954 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -145,6 +145,14 @@ array_copy.contains (item, out position); neigh_1 = item - array_copy.elements[position - 1]; neigh_2 = array_copy.elements[position + 1] - item; + + if (neigh_1 < 0) { + neigh_1 = 0; + } + if (neigh_2 < 0) { + neigh_2 = 0; + } + } public void set_bounds (double lower_bound, double upper_bound) { diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 5d395dcf7..98952f7d3 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -22,12 +22,14 @@ public class Akira.ViewLayers.ViewLayerGuide : ViewLayer { private Models.GuidelineModel? guide_data; private Models.GuidelineModel? old_data; + private Viewlayers.DistanceContainer distance_container; public ViewLayerGuide () { } public void update_guide_data (Models.GuidelineModel data) { old_data = (guide_data == null) ? null : guide_data.copy (); guide_data = data.copy (); + distance_container = new Viewlayers.DistanceContainer (); update (); } @@ -82,7 +84,7 @@ draw_highlighted_guide (context); - draw_distances (context); + distance_container.render (context, guide_data.cursor_position, guide_data.distances); } public override void update () { @@ -150,11 +152,14 @@ // Draw the distance text. var distance_extents = Geometry.Rectangle.empty (); + var offset = Viewlayers.DistanceContainer.OFFSET; + var width = Viewlayers.DistanceContainer.WIDTH; + var height = Viewlayers.DistanceContainer.HEIGHT; - distance_extents.left = data.cursor_position.x - 90; - distance_extents.right = data.cursor_position.x + 90; - distance_extents.top = data.cursor_position.y - 14; - distance_extents.bottom = data.cursor_position.y + 14; + distance_extents.left = data.cursor_position.x + offset; + distance_extents.right = data.cursor_position.x + width + offset; + distance_extents.top = data.cursor_position.y + offset; + distance_extents.bottom = data.cursor_position.y + height + offset; canvas.request_redraw (distance_extents); } @@ -203,16 +208,65 @@ context.new_path (); context.restore (); } +} + + public class Akira.Viewlayers.DistanceContainer { + public static double WIDTH = 100; + public static double HEIGHT = 80; + public static double OFFSET = 10; + private double PADDING = 5 + OFFSET; + private double FONT_SIZE = 12; + private double LINE_HEIGHT = 16; + + public void render (Cairo.Context context, Geometry.Point pos, double[] distances) { + if (distances[0] == -1 && distances[1] == -1 && distances[2] == -1 && distances[3] == -1) { + return; + } + + string left_dist = pretty_string ("L: ", distances[0]); + string right_dist = pretty_string ("R: ", distances[1]); + string top_dist = pretty_string ("T: ", distances[2]); + string bottom_dist = pretty_string ("B: ", distances[3]); + + context.save (); + context.new_path (); + context.set_source_rgba (0.8, 0.8, 0.8, 1); + context.set_line_width (1.0); + + // Draw a rectangle. This rectangle will contain all the distance text. + context.move_to (pos.x + OFFSET, pos.y + OFFSET); + context.rectangle (pos.x + OFFSET, pos.y + OFFSET, WIDTH, HEIGHT); + context.fill (); - private void draw_distances (Cairo.Context context) { context.set_source_rgba (0.8705, 0.1921, 0.3882, 1); context.select_font_face ("monospace", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); - context.set_font_size (14); + context.set_font_size (FONT_SIZE); + + context.move_to (pos.x + PADDING, pos.y + PADDING + LINE_HEIGHT); + context.show_text (left_dist); + + context.move_to (pos.x + PADDING, pos.y + PADDING + 2 * LINE_HEIGHT); + context.show_text (right_dist); - double x = guide_data.cursor_position.x - 50; - double y = guide_data.cursor_position.y - 5; + context.move_to (pos.x + PADDING, pos.y + PADDING + 3 * LINE_HEIGHT); + context.show_text (top_dist); + + context.move_to (pos.x + PADDING, pos.y + PADDING + 4 * LINE_HEIGHT); + context.show_text (bottom_dist); + + context.new_path (); + context.restore (); + } + + private string pretty_string (string prefix, double distance) { + string pretty = prefix; + + if (distance == -1) { + pretty += "--"; + } else { + pretty += """%.2f""".printf (distance); + } - context.move_to (x, y); - context.show_text (guide_data.distances); + return pretty; } - } +} From b0bf60abda940d27422f2a8e3ec0e794461c7c83 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Fri, 19 Nov 2021 16:09:32 +0530 Subject: [PATCH 15/24] Lint --- src/Utils/SortedArray.vala | 2 +- src/ViewLayers/ViewLayerGuide.vala | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index a10f33954..5790e468d 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -152,7 +152,7 @@ if (neigh_2 < 0) { neigh_2 = 0; } - + } public void set_bounds (double lower_bound, double upper_bound) { diff --git a/src/ViewLayers/ViewLayerGuide.vala b/src/ViewLayers/ViewLayerGuide.vala index 98952f7d3..71adf6430 100644 --- a/src/ViewLayers/ViewLayerGuide.vala +++ b/src/ViewLayers/ViewLayerGuide.vala @@ -211,12 +211,12 @@ } public class Akira.Viewlayers.DistanceContainer { - public static double WIDTH = 100; - public static double HEIGHT = 80; - public static double OFFSET = 10; - private double PADDING = 5 + OFFSET; - private double FONT_SIZE = 12; - private double LINE_HEIGHT = 16; + public static double WIDTH = 100; //vala-lint=naming-convention + public static double HEIGHT = 80; //vala-lint=naming-convention + public static double OFFSET = 10; //vala-lint=naming-convention + private double PADDING = 5 + OFFSET; //vala-lint=naming-convention + private double FONT_SIZE = 12; //vala-lint=naming-convention + private double LINE_HEIGHT = 16; //vala-lint=naming-convention public void render (Cairo.Context context, Geometry.Point pos, double[] distances) { if (distances[0] == -1 && distances[1] == -1 && distances[2] == -1 && distances[3] == -1) { From 607e7a10cf0d3116308063b5229fb2bbb2b09d3e Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 20 Nov 2021 19:18:33 +0530 Subject: [PATCH 16/24] Move guidelines when artboard is moved --- src/Models/GuidelineModel.vala | 16 ++++++++++++++++ src/Utils/SortedArray.vala | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index e61137017..0ab25a8c0 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -97,6 +97,22 @@ public class Akira.Models.GuidelineModel { } public void set_drawable_extents (Geometry.Rectangle extents) { + // In case the artboard was moved without scaling. + if (extents.width == drawable_extents.width && extents.height == drawable_extents.height) { + double delta_x = 0; + double delta_y = 0; + + delta_x = drawable_extents.left - extents.left; + delta_y = drawable_extents.top - extents.top; + + drawable_extents = extents; + + h_guides.translate_all (delta_y); + v_guides.translate_all (delta_x); + changed (); + + return; + } // Remove the guidelines at edges of previous extents if they exist. // Solves bug when artboard gets resized. h_guides.remove_item (drawable_extents.top); diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 5790e468d..eba3bf126 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -160,6 +160,12 @@ this.upper_bound = upper_bound; } + public void translate_all (double delta) { + for (int i = 0; i < length; ++i) { + elements[i] -= delta; + } + } + /* * Utility function for binary search. */ From c9f6a1ea6f9345dbf59c59fbe2163cef8bb906ba Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 20 Nov 2021 19:57:18 +0530 Subject: [PATCH 17/24] Delete guidelines when artboard is deleted --- src/Lib/Managers/GuideManager.vala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index fd541a222..02b0cd131 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -29,6 +29,8 @@ public unowned Lib.ViewCanvas view_canvas { get; construct; } private Models.GuidelineModel guide_data; + // Stores the id of currently selected artboard. + private int artboard_id; private Geometry.Point current_cursor; private int sel_line; @@ -40,9 +42,11 @@ ); guide_data = new Models.GuidelineModel (); + artboard_id = -1; view_canvas.scroll_event.connect (on_scroll); guide_data.changed.connect (on_guide_data_changed); + view_canvas.items_manager.items_removed.connect (on_item_delete); } public bool key_press_event (Gdk.EventKey event) { @@ -195,6 +199,7 @@ if (extents.contains (current_cursor.x, current_cursor.y)) { guide_data.changed.disconnect (on_guide_data_changed); guide_data = item.value.instance.guide_data; + artboard_id = item.value.instance.id; guide_data.set_drawable_extents (item.value.instance.bounding_box); guide_data.changed.connect (on_guide_data_changed); return true; @@ -204,4 +209,16 @@ return false; } + + private void on_item_delete (GLib.Array del_ids) { + foreach (var del_id in del_ids.data) { + if (del_id == artboard_id) { + guide_data.changed.disconnect (on_guide_data_changed); + guide_data = new Models.GuidelineModel (); + guide_data.changed.connect (on_guide_data_changed); + artboard_id = -1; + guide_data.changed (); + } + } + } } From 662fd0ca8f3b5f2ff0d9f6ac3c65ac8f7208c09c Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 20 Nov 2021 20:18:04 +0530 Subject: [PATCH 18/24] Only delete guideline after user releases mouse on dege of artboard --- src/Lib/Managers/GuideManager.vala | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index 02b0cd131..d2a136333 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -147,12 +147,21 @@ if (sel_direction != Direction.NONE) { // If while moving a guideline, user takes it out of artboard, - // delete it immediately. + // dont let guideline outside the bounds. if (!guide_data.drawable_extents.contains (current_cursor.x, current_cursor.y)) { - sel_direction = Lib.Managers.GuideManager.Direction.NONE; - sel_line = -1; - - return true; + if (sel_direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { + current_cursor.y = Utils.GeometryMath.clamp ( + current_cursor.y, + guide_data.drawable_extents.top, + guide_data.drawable_extents.bottom + ); + } else if (sel_direction == Lib.Managers.GuideManager.Direction.VERTICAL) { + current_cursor.x = Utils.GeometryMath.clamp ( + current_cursor.x, + guide_data.drawable_extents.left, + guide_data.drawable_extents.right + ); + } } guide_data.move_guide_to_position (sel_line, sel_direction, current_cursor); From 175b5d990871068a0a9f1398c5352f67b93deaf1 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Thu, 2 Dec 2021 22:19:19 +0530 Subject: [PATCH 19/24] Use TreeSet to store guideline positions --- src/Models/GuidelineModel.vala | 4 +- src/Utils/SortedArray.vala | 122 ++++++--------------------------- 2 files changed, 24 insertions(+), 102 deletions(-) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 0ab25a8c0..042b38a0a 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -88,9 +88,9 @@ public class Akira.Models.GuidelineModel { highlight_direction = direction; if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - highlight_position = h_guides.elements[guide]; + highlight_position = h_guides.at(guide); } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - highlight_position = v_guides.elements[guide]; + highlight_position = v_guides.at(guide); } changed (); diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index eba3bf126..37917747f 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -29,10 +29,11 @@ */ public class Akira.Utils.SortedArray : Object { // Array to store all elements. - public double[] elements; + public Gee.TreeSet elements; + public int length { get { - return elements.length; + return elements.size; } } @@ -46,69 +47,28 @@ this.lower_bound = lower_bound; this.upper_bound = upper_bound; - elements = new double[0]; + elements = new Gee.TreeSet (); } /* - * Inserts the given elements in the such that the resultant array remains sorted. + * Inserts the given elements in the set such that the resultant array remains sorted. */ public void insert (double item) { if ((item > upper_bound) || (item < lower_bound)) { return; } - var new_elements = new double[elements.length + 1]; - int idx = 0; - - for (idx = 0; idx < elements.length; ++idx) { - // If this element already exists, no need to insert it. - if (are_equal (elements[idx], item)) { - return; - } else if (elements[idx] > item) { - // If elements after current position are greater, then the new element must be inserted first. - break; - } - - new_elements[idx] = elements[idx]; - } - - new_elements[idx] = item; - ++idx; - - // Copy the remaining elements. - for (; idx < elements.length + 1; ++idx) { - new_elements[idx] = elements[idx - 1]; - } - - elements = new_elements; + elements.add (item); } public void remove_at (int index) { - if (index > elements.length - 1) { - return; - } - - var new_elements = new double[elements.length - 1]; - - // Copy all elementss upto the given index. - for (int idx = 0; idx < index; ++idx) { - new_elements[idx] = elements[idx]; - } + double item = elements.to_array ()[index]; - // Copy all elements after the given index. - for (int idx = index + 1; idx < elements.length; ++idx) { - new_elements[idx - 1] = elements[idx]; - } - - elements = new_elements; + remove_item (item); } public void remove_item (double item) { - int index = 0; - - if (contains (item, out index)) { - remove_at (index); - } + elements.remove (item); } /* @@ -117,42 +77,28 @@ * Returns false if element does not exist. */ public bool contains (double item, out int index) { - index = inner_binary_search (0, elements.length - 1, item); - - if (index == -1) { + if (!elements.contains (item)) { + index = -1; return false; } + index = elements.head_set (item).size; return true; } public SortedArray clone () { var cln = new SortedArray (lower_bound, upper_bound); - cln.elements = new double[elements.length]; - for (int i = 0; i < elements.length; ++i) { - cln.elements[i] = elements[i]; + foreach (var item in elements) { + cln.insert (item); } return cln; } public void get_distance_to_neighbours (double item, out double neigh_1, out double neigh_2) { - var array_copy = this.clone (); - array_copy.insert (item); - - int position = -1; - array_copy.contains (item, out position); - neigh_1 = item - array_copy.elements[position - 1]; - neigh_2 = array_copy.elements[position + 1] - item; - - if (neigh_1 < 0) { - neigh_1 = 0; - } - if (neigh_2 < 0) { - neigh_2 = 0; - } - + neigh_1 = elements.lower (item); + neigh_2 = elements.higher (item); } public void set_bounds (double lower_bound, double upper_bound) { @@ -161,40 +107,16 @@ } public void translate_all (double delta) { - for (int i = 0; i < length; ++i) { - elements[i] -= delta; - } - } + var new_elements = new Gee.TreeSet (); - /* - * Utility function for binary search. - */ - private int inner_binary_search (int start, int end, double key) { - if (end >= start) { - int mid = (start + end) / 2; - - if (are_equal (elements[mid], key)) { - return mid; - } else if (elements[mid] < key) { - return inner_binary_search (mid + 1, end, key); - } else { - return inner_binary_search (start, mid - 1, key); - } + foreach (var item in elements) { + new_elements.add (item - delta); } - return -1; + elements = new_elements; } - /* - * Checks if two floating point numbers are equal. - */ - private bool are_equal (double a, double b) { - int thresh = 1; - - if ((a - b).abs () < thresh) { - return true; - } - - return false; + public double at (int index) { + return elements.to_array ()[index]; } } From 7f4b68cdc67041fc26e6fb7569abe440306c084e Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 4 Dec 2021 15:15:33 +0530 Subject: [PATCH 20/24] Fix crash when moving guides --- src/Utils/SortedArray.vala | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index 37917747f..d11b3a08a 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -47,7 +47,7 @@ this.lower_bound = lower_bound; this.upper_bound = upper_bound; - elements = new Gee.TreeSet (); + elements = new Gee.TreeSet (are_equal); } /* @@ -97,8 +97,17 @@ } public void get_distance_to_neighbours (double item, out double neigh_1, out double neigh_2) { - neigh_1 = elements.lower (item); - neigh_2 = elements.higher (item); + if (are_equal (elements.first (), item) <= 0) { + neigh_1 = 0; + } else { + neigh_1 = elements.lower (item); + } + + if (are_equal (elements.last (), item) <= 0) { + neigh_2 = 0; + } else { + neigh_2 = elements.higher (item); + } } public void set_bounds (double lower_bound, double upper_bound) { @@ -107,7 +116,7 @@ } public void translate_all (double delta) { - var new_elements = new Gee.TreeSet (); + var new_elements = new Gee.TreeSet (are_equal); foreach (var item in elements) { new_elements.add (item - delta); @@ -119,4 +128,16 @@ public double at (int index) { return elements.to_array ()[index]; } + + private int are_equal (double? a, double? b) { + int thresh = 1; + + if (b - a > thresh) { + return -1; + } else if (a - b > thresh) { + return 1; + } + + return 0; + } } From fbcd86f9a4ddeac2df582430540d0fc923ce5bce Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 4 Dec 2021 22:15:53 +0530 Subject: [PATCH 21/24] Fix bug in distances of guidelines --- src/Models/GuidelineModel.vala | 19 ++++++++----------- src/Utils/SortedArray.vala | 32 +++++++------------------------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 042b38a0a..497238192 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -48,8 +48,8 @@ public class Akira.Models.GuidelineModel { public signal void changed (); public GuidelineModel () { - h_guides = new Utils.SortedArray (0, 0); - v_guides = new Utils.SortedArray (0, 0); + h_guides = new Utils.SortedArray (); + v_guides = new Utils.SortedArray (); drawable_extents = Geometry.Rectangle.empty (); distances = new double[4]; @@ -124,21 +124,18 @@ public class Akira.Models.GuidelineModel { // Then add the new edges of artboard. // These lines make it easier to measure distances. - int index; - if (h_guides.contains (extents.left, out index) || h_guides.contains (extents.right, out index)) { - return; - } else if (v_guides.contains (extents.top, out index) || v_guides.contains (extents.bottom, out index)) { - return; - } + // int index; + // if (h_guides.contains (extents.left, out index) || h_guides.contains (extents.right, out index)) { + // return; + // } else if (v_guides.contains (extents.top, out index) || v_guides.contains (extents.bottom, out index)) { + // return; + // } v_guides.insert (extents.left); v_guides.insert (extents.right); h_guides.insert (extents.top); h_guides.insert (extents.bottom); - h_guides.set_bounds (extents.top, extents.bottom); - v_guides.set_bounds (extents.left, extents.right); - changed (); } diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedArray.vala index d11b3a08a..a9bf7754d 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedArray.vala @@ -37,16 +37,7 @@ } } - // Represent the upper and lower bound that all guidelines must stay in - // if they don't want to be deleted. Represent the extents of artboard. - // Any guideline outside this limit will be deleted. - private double lower_bound; - private double upper_bound; - - public SortedArray (double lower_bound, double upper_bound) { - this.lower_bound = lower_bound; - this.upper_bound = upper_bound; - + public SortedArray () { elements = new Gee.TreeSet (are_equal); } @@ -54,10 +45,6 @@ * Inserts the given elements in the set such that the resultant array remains sorted. */ public void insert (double item) { - if ((item > upper_bound) || (item < lower_bound)) { - return; - } - elements.add (item); } @@ -87,7 +74,7 @@ } public SortedArray clone () { - var cln = new SortedArray (lower_bound, upper_bound); + var cln = new SortedArray (); foreach (var item in elements) { cln.insert (item); @@ -97,24 +84,19 @@ } public void get_distance_to_neighbours (double item, out double neigh_1, out double neigh_2) { - if (are_equal (elements.first (), item) <= 0) { - neigh_1 = 0; + if (are_equal (elements.first (), item) >= 0) { + neigh_1 = (elements.first () - item).abs (); } else { - neigh_1 = elements.lower (item); + neigh_1 = (elements.floor (item) - item).abs (); } if (are_equal (elements.last (), item) <= 0) { - neigh_2 = 0; + neigh_2 = (elements.last () - item).abs (); } else { - neigh_2 = elements.higher (item); + neigh_2 = (elements.ceil (item) - item).abs (); } } - public void set_bounds (double lower_bound, double upper_bound) { - this.lower_bound = lower_bound; - this.upper_bound = upper_bound; - } - public void translate_all (double delta) { var new_elements = new Gee.TreeSet (are_equal); From ea8927d9f7924d6660770b182778f8c109de32ec Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sat, 4 Dec 2021 22:18:59 +0530 Subject: [PATCH 22/24] Change ambiguous name of sorted array --- src/Models/GuidelineModel.vala | 17 ++++------------- .../{SortedArray.vala => SortedGuides.vala} | 8 ++++---- src/meson.build | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) rename src/Utils/{SortedArray.vala => SortedGuides.vala} (95%) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index 497238192..bb4cea1fc 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -23,9 +23,9 @@ public class Akira.Models.GuidelineModel { // Stores the coordinates of horizontal guides. // Since a guideline is a straight line (either horizontal or vertical), // we only need one coordinate to store a line. - public Utils.SortedArray h_guides; + public Utils.SortedGuides h_guides; // Stores the coordinates of vertical guides. - public Utils.SortedArray v_guides; + public Utils.SortedGuides v_guides; // Stores index of line in the sorted array. // In case we are moving the guide, stores index of next guideline. @@ -48,8 +48,8 @@ public class Akira.Models.GuidelineModel { public signal void changed (); public GuidelineModel () { - h_guides = new Utils.SortedArray (); - v_guides = new Utils.SortedArray (); + h_guides = new Utils.SortedGuides (); + v_guides = new Utils.SortedGuides (); drawable_extents = Geometry.Rectangle.empty (); distances = new double[4]; @@ -122,15 +122,6 @@ public class Akira.Models.GuidelineModel { drawable_extents = extents; - // Then add the new edges of artboard. - // These lines make it easier to measure distances. - // int index; - // if (h_guides.contains (extents.left, out index) || h_guides.contains (extents.right, out index)) { - // return; - // } else if (v_guides.contains (extents.top, out index) || v_guides.contains (extents.bottom, out index)) { - // return; - // } - v_guides.insert (extents.left); v_guides.insert (extents.right); h_guides.insert (extents.top); diff --git a/src/Utils/SortedArray.vala b/src/Utils/SortedGuides.vala similarity index 95% rename from src/Utils/SortedArray.vala rename to src/Utils/SortedGuides.vala index a9bf7754d..f6b94ccef 100644 --- a/src/Utils/SortedArray.vala +++ b/src/Utils/SortedGuides.vala @@ -27,7 +27,7 @@ * placing a guideline over another will automatically delete it. * */ - public class Akira.Utils.SortedArray : Object { + public class Akira.Utils.SortedGuides : Object { // Array to store all elements. public Gee.TreeSet elements; @@ -37,7 +37,7 @@ } } - public SortedArray () { + public SortedGuides () { elements = new Gee.TreeSet (are_equal); } @@ -73,8 +73,8 @@ return true; } - public SortedArray clone () { - var cln = new SortedArray (); + public SortedGuides clone () { + var cln = new SortedGuides (); foreach (var item in elements) { cln.insert (item); diff --git a/src/meson.build b/src/meson.build index 97c66048a..4f5e753a5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,7 +42,7 @@ sources = files( 'Utils/Snapping2.vala', 'Utils/SVGUtil.vala', 'Utils/GeometryMath.vala', - 'Utils/SortedArray.vala', + 'Utils/SortedGuides.vala', 'Layouts/HeaderBar.vala', 'Layouts/MainViewCanvas.vala', From 547ba34a88b5dd6a00883a31857e1249d398e4e2 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Sun, 5 Dec 2021 16:29:32 +0530 Subject: [PATCH 23/24] Lint fixes --- src/Models/GuidelineModel.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/GuidelineModel.vala b/src/Models/GuidelineModel.vala index bb4cea1fc..cbd21e3f5 100644 --- a/src/Models/GuidelineModel.vala +++ b/src/Models/GuidelineModel.vala @@ -88,9 +88,9 @@ public class Akira.Models.GuidelineModel { highlight_direction = direction; if (direction == Lib.Managers.GuideManager.Direction.HORIZONTAL) { - highlight_position = h_guides.at(guide); + highlight_position = h_guides.at (guide); } else if (direction == Lib.Managers.GuideManager.Direction.VERTICAL) { - highlight_position = v_guides.at(guide); + highlight_position = v_guides.at (guide); } changed (); From ded27f1323eaef7630a91b8d359a8584b1898a92 Mon Sep 17 00:00:00 2001 From: Ashish Shevale Date: Mon, 13 Dec 2021 19:49:32 +0530 Subject: [PATCH 24/24] Guidemanager stores all guide data --- src/Lib/Items/ModelInstance.vala | 6 ---- src/Lib/Items/ModelTypeArtboard.vala | 4 --- src/Lib/Managers/GuideManager.vala | 46 +++++++++++++++++++++++----- src/Lib/Modes/TransformMode.vala | 2 ++ src/ViewLayers/ViewLayer.vala | 2 +- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Lib/Items/ModelInstance.vala b/src/Lib/Items/ModelInstance.vala index 102f8d780..3803c7558 100644 --- a/src/Lib/Items/ModelInstance.vala +++ b/src/Lib/Items/ModelInstance.vala @@ -69,8 +69,6 @@ public class Akira.Lib.Items.ModelInstance { public Components.CompiledBorder compiled_border { get { return compiled_components.compiled_border; } } public Components.CompiledName compiled_name { get { return compiled_components.compiled_name; } } - public Models.GuidelineModel? guide_data = null; - public bool is_group { get { return type.is_group (); } } public bool is_stackable { get { return drawable != null; } } @@ -85,10 +83,6 @@ public class Akira.Lib.Items.ModelInstance { this.children = new int[0]; } - if (type.name_id == "artboard") { - guide_data = new Models.GuidelineModel (); - } - this.id = uid; } diff --git a/src/Lib/Items/ModelTypeArtboard.vala b/src/Lib/Items/ModelTypeArtboard.vala index d161874c7..7a12054f1 100644 --- a/src/Lib/Items/ModelTypeArtboard.vala +++ b/src/Lib/Items/ModelTypeArtboard.vala @@ -87,10 +87,6 @@ public class Akira.Lib.Items.ModelTypeArtboard : ModelType { instance.drawable.width = instance.components.size.width; instance.drawable.height = instance.components.size.height; instance.drawable.transform = instance.compiled_geometry.transformation_matrix; - - // If artboard is resized, we need to update the extents in guide data. - instance.guide_data.set_drawable_extents (instance.bounding_box); - instance.guide_data.changed (); break; case Lib.Components.Component.Type.COMPILED_NAME: instance.drawable.label = instance.compiled_name.name; diff --git a/src/Lib/Managers/GuideManager.vala b/src/Lib/Managers/GuideManager.vala index d2a136333..24bd6dc67 100644 --- a/src/Lib/Managers/GuideManager.vala +++ b/src/Lib/Managers/GuideManager.vala @@ -28,25 +28,33 @@ public unowned Lib.ViewCanvas view_canvas { get; construct; } - private Models.GuidelineModel guide_data; - // Stores the id of currently selected artboard. - private int artboard_id; + // Stores a map of all artboards and their guide data. + private Gee.HashMap guide_store; + // Stores the guideline data of currently selected artboard. + private Models.GuidelineModel? guide_data; + // Stores id of selected id. + public int sel_artboard; private Geometry.Point current_cursor; private int sel_line; private Direction sel_direction; + // Trigerred when we modify the artboard. + // Based on whether we are are done modifying (is_done), we toggle visibility of guides. + public signal void artboard_geometry_in_transit (bool is_done); + public GuideManager (Lib.ViewCanvas view_canvas) { Object ( view_canvas: view_canvas ); + guide_store = new Gee.HashMap (); guide_data = new Models.GuidelineModel (); - artboard_id = -1; view_canvas.scroll_event.connect (on_scroll); guide_data.changed.connect (on_guide_data_changed); view_canvas.items_manager.items_removed.connect (on_item_delete); + artboard_geometry_in_transit.connect (on_artboard_geometry_in_transit); } public bool key_press_event (Gdk.EventKey event) { @@ -207,8 +215,13 @@ if (extents.contains (current_cursor.x, current_cursor.y)) { guide_data.changed.disconnect (on_guide_data_changed); - guide_data = item.value.instance.guide_data; - artboard_id = item.value.instance.id; + + sel_artboard = item.value.instance.id; + if (!guide_store.has_key (sel_artboard)) { + guide_store[sel_artboard] = new Models.GuidelineModel (); + } + + guide_data = guide_store.get (sel_artboard); guide_data.set_drawable_extents (item.value.instance.bounding_box); guide_data.changed.connect (on_guide_data_changed); return true; @@ -221,13 +234,30 @@ private void on_item_delete (GLib.Array del_ids) { foreach (var del_id in del_ids.data) { - if (del_id == artboard_id) { + if (guide_data == guide_store.get (del_id)) { guide_data.changed.disconnect (on_guide_data_changed); guide_data = new Models.GuidelineModel (); guide_data.changed.connect (on_guide_data_changed); - artboard_id = -1; guide_data.changed (); } } } + + private void on_artboard_geometry_in_transit (bool is_done) { + var sel_nodes = view_canvas.selection_manager.selection.nodes; + + if (!sel_nodes.has_key (sel_artboard)) { + return; + } + + if (is_done) { + var ext = view_canvas.items_manager.node_from_id (sel_artboard).instance.bounding_box; + guide_data.set_drawable_extents (ext); + } + + if (view_canvas.guide_layer != null && view_canvas.guide_layer.is_visible != is_done) { + view_canvas.guide_layer.set_visible (is_done); + } + + } } diff --git a/src/Lib/Modes/TransformMode.vala b/src/Lib/Modes/TransformMode.vala index 853c3421e..13cf63eec 100644 --- a/src/Lib/Modes/TransformMode.vala +++ b/src/Lib/Modes/TransformMode.vala @@ -143,6 +143,7 @@ public class Akira.Lib.Modes.TransformMode : AbstractInteractionMode { } public override bool button_release_event (Gdk.EventButton event) { + view_canvas.guide_manager.artboard_geometry_in_transit (true); request_deregistration (mode_type ()); return true; } @@ -180,6 +181,7 @@ public class Akira.Lib.Modes.TransformMode : AbstractInteractionMode { break; } + view_canvas.guide_manager.artboard_geometry_in_transit (false); return true; } diff --git a/src/ViewLayers/ViewLayer.vala b/src/ViewLayers/ViewLayer.vala index 13bf450d2..f82228f56 100644 --- a/src/ViewLayers/ViewLayer.vala +++ b/src/ViewLayers/ViewLayer.vala @@ -24,12 +24,12 @@ public class Akira.ViewLayers.ViewLayer : Object { public const string VSNAPS_LAYER_ID = "99_vsnaps_layer"; public const string HSNAPS_LAYER_ID = "99_hsnaps_layer"; public const string GRID_LAYER_ID = "88_grid_layer"; - public const string GUIDE_LAYER_ID = "88_guide_layer"; public const string HOVER_LAYER_ID = "77_hover_layer"; // at a time, only one of nobs and path layer will be shown. // so they have equal priority public const string NOBS_LAYER_ID = "66_nobs_layer"; public const string PATH_LAYER_ID = "66_path_layer"; + public const string GUIDE_LAYER_ID = "55_guide_layer"; private bool p_is_visible { get; set; default = false; }