1/** 2 * Copyright (c) 2019-2021 Alecaddd (https://alecaddd.com) 3 * 4 * This file is part of Akira. 5 * 6 * Akira is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 11 * Akira is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 16 * You should have received a copy of the GNU General Public License 17 * along with Akira. If not, see <https://www.gnu.org/licenses/>. 18 * 19 * Authored by: Alessandro "Alecaddd" Castellani <castellani.ale@gmail.com> 20 */ 21 22/** 23 * Middleware handling the currently selected objects coordinates with the Transform Panel. 24 * This is used to guarantee correct values in the Transform Panel no matter if one or 25 * multiple items are selected, and to always return the true items' coordinates which 26 * are held by the GooCanvasItem. 27 */ 28public class Akira.StateManagers.CoordinatesMiddleware : Object { 29 public weak Akira.Window window { get; construct; } 30 private weak Akira.Lib.Canvas canvas; 31 32 // Store the initial coordinates of the item before the values are edited by 33 // the user interacting with the Transform Panel fields. 34 private double initial_x; 35 private double initial_y; 36 37 // These attributes represent only the primary X & Y coordinates of the selected shapes. 38 // These are not the origin points of each selected shape, but only the TOP-LEFT values 39 // of the bounding box selection. 40 private double? _x = null; 41 public double x { 42 get { 43 return _x != null ? _x : 0; 44 } 45 set { 46 if (value == _x) { 47 return; 48 } 49 50 _x = Utils.AffineTransform.fix_size (value); 51 move_from_panel (); 52 } 53 } 54 55 private double? _y = null; 56 public double y { 57 get { 58 return _y != null ? _y : 0; 59 } 60 set { 61 if (value == _y) { 62 return; 63 } 64 65 _y = Utils.AffineTransform.fix_size (value); 66 move_from_panel (); 67 } 68 } 69 70 // Allow or deny updating the items position. 71 private bool do_update = true; 72 73 public CoordinatesMiddleware (Akira.Window window) { 74 Object ( 75 window: window 76 ); 77 } 78 79 construct { 80 // Get the canvas on construct as we will need to use its methods. 81 canvas = window.main_window.main_canvas.canvas; 82 83 // Initialize event listeners. 84 window.event_bus.init_state_coords.connect (on_init_state_coords); 85 window.event_bus.reset_state_coords.connect (on_reset_state_coords); 86 window.event_bus.update_state_coords.connect (on_update_state_coords); 87 } 88 89 private void get_coordinates_from_items () { 90 // Reset the selected coordinates to always get correct values. 91 initial_x = 0; 92 initial_y = 0; 93 94 var nob_data = new Akira.Lib.Managers.NobManager.ItemNobData (); 95 Lib.Managers.NobManager.populate_nob_bounds_from_items ( 96 canvas.selected_bound_manager.selected_items, 97 ref nob_data 98 ); 99 100 initial_x = nob_data.selected_x; 101 initial_y = nob_data.selected_y; 102 } 103 104 /** 105 * Initialize the manager coordinates with the selected items coordinates. 106 * The coordinates change comes from a canvas action that already moved the items, 107 * therefore we set the do_update to false to prevent updating the selected 108 * items' Cairo Matrix. 109 */ 110 private void on_init_state_coords () { 111 do_update = false; 112 113 // Get the items X & Y coordinates. 114 get_coordinates_from_items (); 115 116 x = initial_x; 117 y = initial_y; 118 119 do_update = true; 120 } 121 122 /** 123 * Update the coordinates to trigger the shapes transformation. 124 * This action comes from an arrow keypress event from the Canvas. 125 */ 126 private void on_update_state_coords (double moved_x, double moved_y) { 127 x += moved_x; 128 y += moved_y; 129 130 window.event_bus.file_edited (); 131 } 132 133 /** 134 * Reset the coordinates to get the newly updated coordinates from the selected items. 135 * This method is called when items are moved from the canvas, so we only need to update 136 * the X and Y values for the Transform Panel without triggering the update_items_*(). 137 */ 138 private void on_reset_state_coords () { 139 on_init_state_coords (); 140 141 window.event_bus.item_value_changed (); 142 window.event_bus.file_edited (); 143 } 144 145 /** 146 * Update the position of all selected items. 147 */ 148 private void move_from_panel () { 149 if (_x == null || _y == null || !do_update) { 150 return; 151 } 152 153 // Get the current item X & Y coordinates before translating. 154 get_coordinates_from_items (); 155 156 // Loop through all the selected items to update their position. 157 foreach (Lib.Items.CanvasItem item in canvas.selected_bound_manager.selected_items) { 158 // Set the ignore_offset attribute to true to avoid the forced 159 // respositioning of the item (magnetic offset snapping). 160 161 var drag_state = new Akira.Lib.Modes.TransformMode.InitialDragState (); 162 var tmp = new GLib.List<Lib.Items.CanvasItem> (); 163 tmp.append (item); 164 if (Akira.Lib.Modes.TransformMode.initialize_items_drag_state (tmp, ref drag_state)) { 165 drag_state.wants_snapping = false; 166 drag_state.press_x = initial_x; 167 drag_state.press_y = initial_y; 168 drag_state.nob_x = initial_x; 169 drag_state.nob_y = initial_y; 170 var guide_data = new Akira.Lib.Managers.SnapManager.SnapGuideData (); 171 Akira.Lib.Modes.TransformMode.move_from_event (canvas, tmp, drag_state, x, y, ref guide_data); 172 } 173 } 174 175 window.event_bus.item_value_changed (); 176 } 177} 178