Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guidelines Feature in Akira #669

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f304509
Create basic view layer and manager for guides
AshishS-1123 Nov 12, 2021
c1cac58
Draw guidelines when Q or W clicked
AshishS-1123 Nov 13, 2021
c43d1f1
Move guidelines by click and drag
AshishS-1123 Nov 14, 2021
721bda4
Merge branch 'main' of github.com:AshishS-1123/Akira into guides
AshishS-1123 Nov 14, 2021
ac10e5a
Highlight guidelines on hover.
AshishS-1123 Nov 14, 2021
2348fa1
Performance optimizations for drawing guidelines
AshishS-1123 Nov 15, 2021
5fce80a
Store guidelines in sorted arrays. Stacking lines deletes them
AshishS-1123 Nov 16, 2021
eb685c7
Draw guidelines only on artboards
AshishS-1123 Nov 16, 2021
b685e5e
Create GuidelineModel to store all data
AshishS-1123 Nov 16, 2021
78b2b13
Distances from guidelines
AshishS-1123 Nov 17, 2021
f1cc5d2
Lint fixes
AshishS-1123 Nov 17, 2021
3ab3ea0
Bug fix in calculating distances
AshishS-1123 Nov 18, 2021
8a991e6
Possible patch for double click to delete bug
AshishS-1123 Nov 18, 2021
c3ec43f
Delete guideline by dragging to edge
AshishS-1123 Nov 18, 2021
ce1010a
Better looking widget for distances
AshishS-1123 Nov 19, 2021
b0bf60a
Lint
AshishS-1123 Nov 19, 2021
08f3e99
Merge branch 'main' of github.com:AshishS-1123/Akira into guides
AshishS-1123 Nov 19, 2021
607e7a1
Move guidelines when artboard is moved
AshishS-1123 Nov 20, 2021
c9f6a1e
Delete guidelines when artboard is deleted
AshishS-1123 Nov 20, 2021
662fd0c
Only delete guideline after user releases mouse on dege of artboard
AshishS-1123 Nov 20, 2021
175b5d9
Use TreeSet to store guideline positions
AshishS-1123 Dec 2, 2021
7f4b68c
Fix crash when moving guides
AshishS-1123 Dec 4, 2021
fbcd86f
Fix bug in distances of guidelines
AshishS-1123 Dec 4, 2021
ea8927d
Change ambiguous name of sorted array
AshishS-1123 Dec 4, 2021
547ba34
Lint fixes
AshishS-1123 Dec 5, 2021
e2a2f9b
Merge branch 'main' of github.com:AshishS-1123/Akira into guides
AshishS-1123 Dec 5, 2021
bc7ca1c
Merge branch 'main' of github.com:AshishS-1123/Akira into guides
AshishS-1123 Dec 5, 2021
ad6f298
Merge branch 'main' of github.com:AshishS-1123/Akira into guides
AshishS-1123 Dec 12, 2021
ded27f1
Guidemanager stores all guide data
AshishS-1123 Dec 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Lib/Items/ModelInstance.vala
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ 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; } }

Expand All @@ -83,6 +85,10 @@ public class Akira.Lib.Items.ModelInstance {
this.children = new int[0];
}

if (type.name_id == "artboard") {
guide_data = new Models.GuidelineModel ();
}

this.id = uid;
}

Expand Down
4 changes: 4 additions & 0 deletions src/Lib/Items/ModelTypeArtboard.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
case Lib.Components.Component.Type.COMPILED_NAME:
instance.drawable.label = instance.compiled_name.name;
Expand Down
233 changes: 233 additions & 0 deletions src/Lib/Managers/GuideManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/**
* 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 <https://www.gnu.org/licenses/>.
*
* Authored by: Ashish Shevale <[email protected]>
*/

public class Akira.Lib.Managers.GuideManager : Object {
public enum Direction {
NONE,
HORIZONTAL,
VERTICAL
}

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;
private Direction sel_direction;

public GuideManager (Lib.ViewCanvas view_canvas) {
Object (
view_canvas: view_canvas
);

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) {
if (!is_within_artboard ()) {
return false;
}

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) {
/*
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 {
// In case we clicked on a different artboard, we need to update that data.
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)) {
guide_data.remove_guide (sel_direction, sel_line);
guide_data.calculate_distance_positions (current_cursor);
return true;
}

return false;
}

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;

guide_data.reset_distances ();

return true;
}

guide_data.reset_distances ();

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) {
// If while moving a guideline, user takes it out of artboard,
// dont let guideline outside the bounds.
if (!guide_data.drawable_extents.contains (current_cursor.x, current_cursor.y)) {
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);
guide_data.calculate_distance_positions (current_cursor);
return true;
}

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);
return true;
} else {
guide_data.set_highlighted_guide (-1, Direction.NONE);
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;
}

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;

// 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;

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;
}
}
}

return false;
}

private void on_item_delete (GLib.Array<int> 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 ();
}
}
}
}
26 changes: 26 additions & 0 deletions src/Lib/ViewCanvas.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -95,6 +98,10 @@ 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 ();
guide_layer.add_to_canvas (ViewLayers.ViewLayer.GUIDE_LAYER_ID, this);
guide_layer.set_visible (true);

set_model_to_render (items_manager.item_model);

window.event_bus.adjust_zoom.connect (trigger_adjust_zoom);
Expand Down Expand Up @@ -217,6 +224,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 ();
Expand Down Expand Up @@ -298,6 +309,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)) {
Expand Down Expand Up @@ -390,6 +405,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 no delta between the pressed and released event.
if (initial_event_x == event.x || initial_event_y == event.y) {
var count = selection_manager.count ();
Expand Down Expand Up @@ -437,6 +459,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) {
Expand Down
Loading