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