From 157b0841b5abb1d1dd92c7639589d65d45060554 Mon Sep 17 00:00:00 2001 From: Abdallah-Moh Date: Tue, 11 May 2021 07:20:44 +0200 Subject: [PATCH] Fix an Issue --- .../com.github.akiraux.akira.gschema.xml.in | 15 + src/Dialogs/SettingsDialog.vala | 44 +++ src/Layouts/LeftSideBar.vala | 2 + src/Layouts/Partials/TextOptionsPanel.vala | 326 ++++++++++++++++++ src/Lib/Canvas.vala | 63 ++-- src/Lib/Components/Font.vala | 161 +++++++++ src/Lib/Items/CanvasArtboard.vala | 29 +- src/Lib/Items/CanvasItem.vala | 7 + src/Lib/Items/CanvasText.vala | 10 +- src/Lib/Items/CanvasTextView.vala | 86 +++++ src/Lib/Managers/ItemsManager.vala | 20 +- src/Lib/Modes/EditMode.vala | 68 ++++ src/Lib/Modes/InteractionMode.vala | 3 +- src/Lib/Modes/ItemInsertMode.vala | 2 - src/Services/Settings.vala | 12 + src/Utils/Font.vala | 95 +++++ src/meson.build | 5 + 17 files changed, 898 insertions(+), 50 deletions(-) create mode 100644 src/Layouts/Partials/TextOptionsPanel.vala create mode 100644 src/Lib/Components/Font.vala create mode 100644 src/Lib/Items/CanvasTextView.vala create mode 100644 src/Lib/Modes/EditMode.vala create mode 100644 src/Utils/Font.vala diff --git a/data/schemas/com.github.akiraux.akira.gschema.xml.in b/data/schemas/com.github.akiraux.akira.gschema.xml.in index bfe2941d8..f720e82d8 100644 --- a/data/schemas/com.github.akiraux.akira.gschema.xml.in +++ b/data/schemas/com.github.akiraux.akira.gschema.xml.in @@ -112,6 +112,21 @@ Default Border Color. The default color of the border for a newly created shape. + + '#000' + Default CanvasText Font Color. + The default color of the CanvasText font for a newly created shape. + + + 18 + Default CanvasText Text Size + The default font size of the CanvasText for a newly created shape. + + + 'OpenSans' + Default CanvasText Font. + The default font for the CanvasText. + false diff --git a/src/Dialogs/SettingsDialog.vala b/src/Dialogs/SettingsDialog.vala index 6aeb53cbb..da064537a 100644 --- a/src/Dialogs/SettingsDialog.vala +++ b/src/Dialogs/SettingsDialog.vala @@ -31,6 +31,11 @@ public class Akira.Dialogs.SettingsDialog : Gtk.Dialog { private Gtk.ColorButton fill_color; private Gtk.ColorButton border_color; private Gtk.SpinButton border_size; + private Gtk.ColorButton text_fill_color; + private Partials.InputField text_size; + private Gtk.ComboBoxText font_name; + + public string [] fonts = Utils.Font.get_fonts (); public SettingsDialog (Akira.Window _window) { Object ( @@ -223,6 +228,9 @@ public class Akira.Dialogs.SettingsDialog : Gtk.Dialog { var border_rgba = Gdk.RGBA (); border_rgba.parse (settings.border_color); + var text_fill_rgba = Gdk.RGBA (); + text_fill_rgba.parse (settings.text_color); + grid.attach (new SettingsHeader (_("Default Colors")), 0, 0, 2, 1); var description = new Gtk.Label (_("Define the default style used when creating a new shape.")); @@ -232,6 +240,8 @@ public class Akira.Dialogs.SettingsDialog : Gtk.Dialog { grid.attach (new SettingsLabel (_("Fill Color:")), 0, 2, 1, 1); fill_color = new Gtk.ColorButton.with_rgba (fill_rgba); + text_fill_color = new Gtk.ColorButton.with_rgba (text_fill_rgba); + text_fill_color.halign = Gtk.Align.START; fill_color.halign = Gtk.Align.START; grid.attach (fill_color, 1, 2, 1, 1); @@ -251,6 +261,22 @@ public class Akira.Dialogs.SettingsDialog : Gtk.Dialog { settings.fill_color = rgba_str; }); + text_fill_color.color_set.connect (() => { + var rgba = text_fill_color.get_rgba (); + + // Gdk.RGBA uses rgb() if alpha is 1. + string rgba_str = "rgba(%d,%d,%d,%d)".printf ( + (int) (rgba.red * 255), + (int) (rgba.green * 255), + (int) (rgba.blue * 255), + (int) (rgba.alpha) + ); + + debug ("setting color: %s", rgba_str); + + settings.text_color = rgba_str; + }); + grid.attach (new SettingsLabel (_("Enable Border Style:")), 0, 3, 1, 1); border_switch = new SettingsSwitch ("set-border"); grid.attach (border_switch, 1, 3, 1, 1); @@ -284,8 +310,26 @@ public class Akira.Dialogs.SettingsDialog : Gtk.Dialog { border_size.secondary_icon_sensitive = false; border_size.secondary_icon_activatable = false; grid.attach (border_size, 1, 5, 1, 1); + grid.attach (new SettingsLabel (_("Text Tool Fill:")), 0, 6, 1, 1); + grid.attach (text_fill_color, 1, 6, 1, 1); + text_size = new Akira.Partials.InputField (Akira.Partials.InputField.Unit.PIXEL, 6, true, true); + text_size.halign = Gtk.Align.START; + text_size.entry.hexpand = false; + text_size.entry.sensitive = true; + text_size.set_range (1, 5000); + grid.attach (new SettingsLabel (_("Text Tool Font Size:")), 0, 7, 1, 1); + grid.attach (text_size, 1, 7, 1, 1); + font_name = new Gtk.ComboBoxText.with_entry (); + font_name.halign = Gtk.Align.START; + foreach (string font in fonts) { + font_name.append (font, font); + } + grid.attach (new SettingsLabel (_("Text Tool Font Name:")), 0, 8, 1, 1); + grid.attach (font_name, 1, 8, 1, 1); settings.bind ("border-size", border_size, "value", SettingsBindFlags.DEFAULT); + settings.bind ("text-size", text_size.entry, "value", SettingsBindFlags.DEFAULT); + settings.bind ("text-font", font_name, "active_id", SettingsBindFlags.DEFAULT); border_switch.bind_property ("active", border_color, "sensitive", BindingFlags.SYNC_CREATE); border_switch.bind_property ("active", border_size, "sensitive", BindingFlags.SYNC_CREATE); diff --git a/src/Layouts/LeftSideBar.vala b/src/Layouts/LeftSideBar.vala index 28f3ccf43..a62e91d80 100644 --- a/src/Layouts/LeftSideBar.vala +++ b/src/Layouts/LeftSideBar.vala @@ -51,6 +51,7 @@ public class Akira.Layouts.LeftSideBar : Gtk.Grid { var align_items_panel = new Akira.Layouts.Partials.AlignItemsPanel (window); transform_panel = new Akira.Layouts.Partials.TransformPanel (window); var border_radius_panel = new Akira.Layouts.Partials.BorderRadiusPanel (window); + var text_options_panel = new Akira.Layouts.Partials.TextOptionsPanel (window); fills_panel = new Akira.Layouts.Partials.FillsPanel (window); borders_panel = new Akira.Layouts.Partials.BordersPanel (window); @@ -61,6 +62,7 @@ public class Akira.Layouts.LeftSideBar : Gtk.Grid { scrolled_grid.expand = true; scrolled_grid.attach (transform_panel, 0, 0, 1, 1); scrolled_grid.attach (border_radius_panel, 0, 1, 1, 1); + scrolled_grid.attach (text_options_panel, 0, 4, 1, 1); scrolled_grid.attach (fills_panel, 0, 2, 1, 1); scrolled_grid.attach (borders_panel, 0, 3, 1, 1); scrolled_window.add (scrolled_grid); diff --git a/src/Layouts/Partials/TextOptionsPanel.vala b/src/Layouts/Partials/TextOptionsPanel.vala new file mode 100644 index 000000000..a1c786f68 --- /dev/null +++ b/src/Layouts/Partials/TextOptionsPanel.vala @@ -0,0 +1,326 @@ +/* +* Copyright (c) 2019 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: Abdallah "Abdallah-Moh" Mohammad +*/ + +public class Akira.Layouts.Partials.TextOptionsPanel : Gtk.Grid { + public weak Akira.Window window { get; construct; } + + public Gtk.Label label; + // private Akira.Partials.InputField font_size_input; + private Gtk.ComboBoxText font_weight; + private Gtk.ComboBoxText selected_font; + private Akira.Partials.InputField font_size_input; + private Granite.Widgets.ModeButton scale_button; + private Granite.Widgets.ModeButton align_scale_button; + private string[] text_weights = {"Light", "Light Italic", "Thin", "Thin Italic", "Regular", "Regular Italic", "SemiBold", "SemiBold Italic", "Bold", "Bold Italic", "ExtraBold", "ExtraBold Italic",}; + public string [] fonts = Utils.Font.get_fonts (); + + private Akira.Lib.Items.CanvasText _selected_item; + private Akira.Lib.Items.CanvasText selected_item { + get { + return _selected_item; + } set { + // If the same item is already selected, or the value is still null + // we don't do anything to prevent redraw and calculations. + if (_selected_item == value) { + return; + } + + _selected_item = value; + + if (_selected_item == null) { + return; + } + enable (); + } + } + + public bool toggled { + get { + return visible; + } set { + visible = value; + no_show_all = !value; + } + } + + public TextOptionsPanel (Akira.Window window) { + Object ( + window: window, + orientation: Gtk.Orientation.VERTICAL + ); + } + + construct { + var title_cont = new Gtk.Grid (); + title_cont.get_style_context ().add_class ("option-panel"); + + label = new Gtk.Label (_("Typography")); + label.halign = Gtk.Align.FILL; + label.xalign = 0; + label.hexpand = true; + label.set_ellipsize (Pango.EllipsizeMode.END); + title_cont.attach (label, 0, 0, 1, 1); + + attach (title_cont, 0, 0, 1, 1); + + var panel_grid = new Gtk.Grid (); + get_style_context ().add_class ("style-panel"); + panel_grid.row_spacing = 6; + panel_grid.border_width = 12; + panel_grid.column_spacing = 6; + panel_grid.hexpand = true; + attach (panel_grid, 0, 1, 1, 1) + ; + + add_children (panel_grid); + + show_all (); + + bind_signals (); + } + + private void add_children (Gtk.Grid children_grid) { + // Font Box + selected_font = new Gtk.ComboBoxText.with_entry (); + selected_font.hexpand = true; + foreach (string font in fonts) { + selected_font.append (font, font); + } + selected_font.set_active (0); + // Font Weight Box + font_weight = new Gtk.ComboBoxText (); + foreach (string weight in text_weights) { + font_weight.append (weight, weight); + } + // Font Size InputField + font_size_input = new Akira.Partials.InputField (Akira.Partials.InputField.Unit.PIXEL, 7, true, true); + font_size_input.entry.hexpand = false; + font_size_input.entry.sensitive = true; + font_size_input.entry.width_request = 64; + font_size_input.set_range (1, 5000); + // Scale Button + scale_button = new Granite.Widgets.ModeButton (); + scale_button.halign = Gtk.Align.FILL; + scale_button.append_text ("Auto"); + scale_button.append_text ("Fixed"); + scale_button.set_active (0); + // Align Scale Button + align_scale_button = new Granite.Widgets.ModeButton (); + align_scale_button.halign = Gtk.Align.FILL; + align_scale_button.append_icon ("format-justify-left-symbolic", Gtk.IconSize.BUTTON); + align_scale_button.append_icon ("format-justify-center-symbolic", Gtk.IconSize.BUTTON); + align_scale_button.append_icon ("format-justify-right-symbolic", Gtk.IconSize.BUTTON); + align_scale_button.append_icon ("format-justify-fill-symbolic", Gtk.IconSize.BUTTON); + align_scale_button.set_active (0); + + + // Add Components to Grid + children_grid.attach (selected_font, 0, 0, 6); + children_grid.attach (font_weight, 0, 1, 4); + children_grid.attach (font_size_input, 4, 1, 2); + children_grid.attach (scale_button, 0, 2, 4); + children_grid.attach (align_scale_button, 4, 2, 2); + } + + private void bind_signals () { + toggled = false; + window.event_bus.selected_items_list_changed.connect (on_selected_items_list_changed); + font_weight.changed.connect (()=> { + switch (text_weights[font_weight.get_active ()]) { + case "Light": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.LIGHT); + break; + case "Light Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.LIGHT_ITALIC); + break; + case "Thin": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.THIN); + break; + case "Thin Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.THIN_ITALIC); + break; + case "Regular": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.REGULAR); + break; + case "Regular Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.REGULAR_ITALIC); + break; + case "SemiBold": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.SEMIBOLD); + break; + case "SemiBold Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.SEMIBOLD_ITALIC); + break; + case "Bold": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.BOLD); + break; + case "Bold Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.BOLD_ITALIC); + break; + case "ExtraBold": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.EXTRABOLD); + break; + case "ExtraBold Italic": + selected_item.text_font.set_text_weight (Akira.Lib.Components.Font.FontWeight.EXTRABOLD_ITALIC); + break; + } + selected_item.text_font.toggle_size_auto (!selected_item.text_font.is_size_fixed); + }); + selected_font.changed.connect (()=> { + if (Utils.Font.is_string_in_array (fonts[selected_font.get_entry_text_column ()], fonts)) { + selected_item.text_font.set_font (selected_font.get_active_text ()); + font_weight.set_active (4); + selected_item.text_font.toggle_size_auto (!selected_item.text_font.is_size_fixed); + } + }); + // + var combo_box_children = selected_font.get_children (); + combo_box_children.foreach ((box_child) => { + if (box_child is Gtk.Entry) { + box_child.key_press_event.connect (()=> { + window.event_bus.disconnect_typing_accel (); + return false; + }); + box_child.focus_out_event.connect (()=> { + window.event_bus.connect_typing_accel (); + return false; + }); + } + }); + font_size_input.entry.changed.connect (()=> { + selected_item.text_font.font_size = int.parse (font_size_input.entry.text); + }); + scale_button.mode_changed.connect (()=> { + if (scale_button.selected == 0) { + selected_item.text_font.toggle_size_auto (true); + return; + } + selected_item.text_font.toggle_size_auto (false); + }); + align_scale_button.mode_changed.connect (()=> { + switch (align_scale_button.selected) { + case 0: + selected_item.alignment = Pango.Alignment.LEFT; + selected_item.wrap = Pango.WrapMode.WORD; + break; + case 1: + selected_item.alignment = Pango.Alignment.CENTER; + selected_item.wrap = Pango.WrapMode.WORD; + break; + case 2: + selected_item.alignment = Pango.Alignment.RIGHT; + selected_item.wrap = Pango.WrapMode.WORD; + break; + case 3: + selected_item.alignment = Pango.Alignment.LEFT; + selected_item.wrap = Pango.WrapMode.CHAR; + break; + } + }); + } + + private void on_selected_items_list_changed (List selected_items) { + // Interrupt if we don't have an item selected or if more than 1 is selected + // since we can't handle the border radius of multiple items at once. + if (selected_items.length () == 0 || selected_items.length () > 1) { + selected_item = null; + toggled = false; + return; + } + + if (!(selected_items.nth_data (0) is Akira.Lib.Items.CanvasText)) { + selected_item = null; + toggled = false; + return; + } + + if (selected_item == null || selected_item != selected_items.nth_data (0)) { + toggled = true; + selected_item = (Akira.Lib.Items.CanvasText) selected_items.nth_data (0); + } + } + + private void on_size_change () { + Pango.Rectangle ink_rect; + Pango.Rectangle logical_rect; + selected_item.get_natural_extents (out ink_rect, out logical_rect); + int width = logical_rect.width / 1024; + int height = logical_rect.height / 1024; + if (!(selected_item.size.width == width && selected_item.size.height == height) && !selected_item.text_font.size_changed_from_code) { + selected_item.text_font.is_size_fixed = true; + scale_button.set_active (1); + } + } + + private void enable () { + font_size_input.entry.value = (double)selected_item.text_font.font_size; + selected_item.notify["width"].connect (on_size_change); + selected_item.notify["height"].connect (on_size_change); + switch (selected_item.text_font.font_weight) { + case LIGHT: + font_weight.set_active (0); + break; + case LIGHT_ITALIC: + font_weight.set_active (1); + break; + case THIN: + font_weight.set_active (2); + break; + case THIN_ITALIC: + font_weight.set_active (3); + break; + case REGULAR: + font_weight.set_active (4); + break; + case REGULAR_ITALIC: + font_weight.set_active (5); + break; + case SEMIBOLD: + font_weight.set_active (6); + break; + case SEMIBOLD_ITALIC: + font_weight.set_active (7); + break; + case BOLD: + font_weight.set_active (8); + break; + case BOLD_ITALIC: + font_weight.set_active (9); + break; + case EXTRABOLD: + font_weight.set_active (10); + break; + case EXTRABOLD_ITALIC: + font_weight.set_active (11); + break; + } + for (int i = 0; i < fonts.length; i++) { + if (selected_item.text_font.font_name == fonts[i]) { + selected_font.set_active (i); + } + } + if (selected_item.text_font.is_size_fixed) { + scale_button.set_active (1); + return; + } + scale_button.set_active (0); + } +} diff --git a/src/Lib/Canvas.vala b/src/Lib/Canvas.vala index 756d160aa..da72d60b6 100644 --- a/src/Lib/Canvas.vala +++ b/src/Lib/Canvas.vala @@ -54,6 +54,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { private Goo.CanvasRect ghost; // Used to show a pixel grid on the whole canvas. + private Goo.CanvasItem root; private Goo.CanvasGrid pixel_grid; private bool is_grid_visible; @@ -71,6 +72,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { events |= Gdk.EventMask.TOUCHPAD_GESTURE_MASK; events |= Gdk.EventMask.TOUCH_MASK; + root = get_root_item (); export_manager = new Managers.ExportManager (this); selected_bound_manager = new Managers.SelectedBoundManager (this); nob_manager = new Managers.NobManager (this); @@ -127,7 +129,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { } Gtk.drag_finish (drag_context, true, false, time); - + mode_manager.deregister_mode (Modes.InteractionMode.ModeType.ITEM_INSERT); update_canvas (); } @@ -145,7 +147,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { pixel_grid.horz_grid_line_width = pixel_grid.vert_grid_line_width = 0.02; pixel_grid.horz_grid_line_color_gdk_rgba = pixel_grid.vert_grid_line_color_gdk_rgba = grid_rgba; pixel_grid.visibility = Goo.CanvasItemVisibility.HIDDEN; - pixel_grid.set ("parent", get_root_item ()); + pixel_grid.set ("parent", root); pixel_grid.can_focus = false; pixel_grid.pointer_events = Goo.CanvasPointerEvents.NONE; is_grid_visible = false; @@ -266,6 +268,11 @@ public class Akira.Lib.Canvas : Goo.Canvas { hover_manager.remove_hover_effect (); + if (event.type == Gdk.EventType.@2BUTTON_PRESS) { + var new_mode = new Akira.Lib.Modes.EditMode (this, mode_manager); + mode_manager.register_mode (new_mode); + } + if (mode_manager.button_press_event (event)) { return true; } @@ -325,7 +332,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { public void update_canvas () { // Update the pixel grid if it's visible in order to move it to the foreground. if (is_grid_visible) { - update_pixel_grid (); + update_pixel_grid_visibility (); } // Synchronous update to make sure item is initialized before any other event. update (); @@ -352,7 +359,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { } public void focus_canvas () { - grab_focus (get_root_item ()); + grab_focus (root); } private bool press_event_on_selection (Gdk.EventButton event) { @@ -405,8 +412,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { if (mode_manager.button_press_event (event)) { return true; } - } - else { + } else { nob_manager.set_selected_by_name (Akira.Lib.Managers.NobManager.Nob.NONE); } @@ -446,16 +452,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { return; } - // If the pixel grid is visible, hide it based on the canvas scale - // in order to avoid a visually jarring canvas. - if (current_scale < GRID_THRESHOLD) { - pixel_grid.visibility = Goo.CanvasItemVisibility.HIDDEN; - } else { - pixel_grid.visibility = Goo.CanvasItemVisibility.VISIBLE; - // Always move the grid to the top of the stack. - var root = get_root_item (); - root.move_child (root.find_child (pixel_grid), window.items_manager.get_items_count ()); - } + update_pixel_grid_visibility (); } private void set_cursor (Gdk.CursorType? cursor_type) { @@ -487,7 +484,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { "stroke-color", "#41c9fd", null ); - ghost.set ("parent", get_root_item ()); + ghost.set ("parent", root); ghost.can_focus = false; ghost.pointer_events = Goo.CanvasPointerEvents.NONE; return; @@ -516,7 +513,7 @@ public class Akira.Lib.Canvas : Goo.Canvas { */ private void on_toggle_pixel_grid () { if (!is_grid_visible) { - update_pixel_grid (); + update_pixel_grid_visibility (); is_grid_visible = true; return; } @@ -528,19 +525,29 @@ public class Akira.Lib.Canvas : Goo.Canvas { /* * Updates pixel grid if visible, useful to guarantee z-order in paint composition. */ - public void update_pixel_grid_if_visible () { - if (is_grid_visible) { - update_pixel_grid (); + private void update_pixel_grid_visibility () { + // If the pixel grid is visible, hide it based on the canvas scale + // in order to avoid a visually jarring canvas. + if (current_scale < GRID_THRESHOLD) { + pixel_grid.visibility = Goo.CanvasItemVisibility.HIDDEN; + return; } - } - private void update_pixel_grid () { - // Show the grid only if we're zoomed in enough. - if (current_scale >= GRID_THRESHOLD) { + // Show the pixel grid if is currently hidden. + if (pixel_grid.visibility == Goo.CanvasItemVisibility.HIDDEN) { pixel_grid.visibility = Goo.CanvasItemVisibility.VISIBLE; - // Always move the grid to the top of the stack. - var root = get_root_item (); - root.move_child (root.find_child (pixel_grid), window.items_manager.get_items_count ()); + } + + var current_position = root.find_child (pixel_grid); + var top_position = root.get_n_children (); + // The grid should always be below the select effect and nobs, + // so we decrease the count to account for that, otherwise we + // decrease by 1 to ignore the grid current position. + top_position -= selected_bound_manager.selected_items.length () > 0 ? 11 : 1; + + // Always move the grid to the top of the stack if necessary. + if (current_position < top_position) { + root.move_child (current_position, top_position); } } } diff --git a/src/Lib/Components/Font.vala b/src/Lib/Components/Font.vala new file mode 100644 index 000000000..fb58ea20e --- /dev/null +++ b/src/Lib/Components/Font.vala @@ -0,0 +1,161 @@ +/* + * 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: Abdallah "Abdallah-Moh" Mohammad +*/ + +/* + * Font component to keep track of the CanvasText font, which includes different attributes. +*/ + +public class Akira.Lib.Components.Font : Component { + + Items.CanvasText canvas_text_item; + + public string font_name { get; set; } + public string font_parameters { get; set; } + public bool is_italic = false; + public bool is_size_fixed = false; + public bool size_changed_from_code = false; + int _font_size; + public int font_size { + get { return _font_size; } + set { + _font_size = value; + canvas_text_item.font = to_string (); + if (!is_size_fixed) { + toggle_size_auto (true); + } + } + } + public FontWeight font_weight { get; set; } + public Akira.Window window { get; set; } + + public enum FontWeight { + LIGHT, + LIGHT_ITALIC, + THIN, + THIN_ITALIC, + REGULAR, + REGULAR_ITALIC, + SEMIBOLD, + SEMIBOLD_ITALIC, + BOLD, + BOLD_ITALIC, + EXTRABOLD, + EXTRABOLD_ITALIC, + } + + public Font (Items.CanvasItem _item, string item_font_name, int text_font_size, FontWeight? _font_weight = FontWeight.REGULAR) { + item = _item; + canvas_text_item = (Lib.Items.CanvasText)item; + font_name = item_font_name; + font_weight = _font_weight; + font_parameters = ""; + font_size = text_font_size; + // Since the size is auto we are making text auto + toggle_size_auto (true); + } + + public string to_string () { + return font_name + " " + font_parameters + " " + font_size.to_string (); + } + + public void set_font (string fnt_fam, int? fnt_size = font_size) { + font_name = fnt_fam; + fnt_size = font_size; + canvas_text_item.font = to_string (); + } + + public void make_italic () { + font_parameters += " Italic"; + is_italic = true; + item.font = to_string (); + } + + public void remove_italic () { + font_parameters += " Italic"; + is_italic = false; + set_text_weight (font_weight); + } + + public void set_text_weight (FontWeight text_weight) { + switch (text_weight) { + case LIGHT_ITALIC: + font_parameters = "Light Italic"; + break; + case LIGHT: + font_parameters = "Light"; + break; + case THIN_ITALIC: + font_parameters = "Thin Italic"; + break; + case THIN: + font_parameters = "Thin"; + break; + case REGULAR_ITALIC: + font_parameters = "Regular Italic"; + break; + case REGULAR: + // If REGULAR no need to use the font paramaters + font_parameters = ""; + break; + case SEMIBOLD_ITALIC: + font_parameters = "SemiBold Italic"; + break; + case SEMIBOLD: + font_parameters = "SemiBold"; + break; + case BOLD_ITALIC: + font_parameters = "Bold Italic"; + break; + case BOLD: + font_parameters = "Bold"; + break; + case EXTRABOLD_ITALIC: + font_parameters = "ExtraBold Italic"; + break; + case EXTRABOLD: + font_parameters = "ExtraBold"; + break; + } + if (font_parameters.contains ("Italic")) { + is_italic = true; + } + font_weight = text_weight; + item.font = to_string (); + } + + public void toggle_size_auto (bool make_auto) { + if (make_auto) { + size_changed_from_code = true; + // We are setting the width big because getting the get_natural_extents () will get affected by the wrap + canvas_text_item.size.width = 1000000; + // We divide the values by 1024 to convert them to pt + Pango.Rectangle ink_rect; + Pango.Rectangle logical_rect; + canvas_text_item.get_natural_extents (out ink_rect, out logical_rect); + int width = logical_rect.width / 1024; + int height = logical_rect.height / 1024; + canvas_text_item.size.width = width; + canvas_text_item.size.height = height; + } + is_size_fixed = !make_auto; + size_changed_from_code = false; + } +} diff --git a/src/Lib/Items/CanvasArtboard.vala b/src/Lib/Items/CanvasArtboard.vala index a0b925f5b..171c52d5c 100644 --- a/src/Lib/Items/CanvasArtboard.vala +++ b/src/Lib/Items/CanvasArtboard.vala @@ -119,14 +119,24 @@ public class Akira.Lib.Items.CanvasArtboard : Goo.CanvasGroup, Akira.Lib.Items.C this.size.bind_property ("width", label, "width", BindingFlags.SYNC_CREATE); // Listen to the theme changing event to update the label color. - akira_canvas.window.event_bus.change_theme.connect (() => { - label.set ("fill-color-rgba", settings.dark_theme ? light_color : dark_color); - }); + akira_canvas.window.event_bus.change_theme.connect (on_theme_changed); // Update the label font size when the canvas zoom changes. - akira_canvas.window.event_bus.set_scale.connect ((scale) => { - label.set ("font", "Open Sans " + (FONT_SIZE / scale).to_string ()); - }); + akira_canvas.window.event_bus.set_scale.connect (on_canvas_scaled); + } + + /* + * Update the color of the artboard label based on the light/dark theme. + */ + private void on_theme_changed () { + label.set ("fill-color-rgba", settings.dark_theme ? light_color : dark_color); + } + + /* + * Update the artboard label font size based on the current scale. + */ + private void on_canvas_scaled (double scale) { + label.set ("font", "Open Sans " + (FONT_SIZE / scale).to_string ()); } /** @@ -167,6 +177,13 @@ public class Akira.Lib.Items.CanvasArtboard : Goo.CanvasGroup, Akira.Lib.Items.C public void delete () { background.remove (); + + // Type cast the akira canvas to gain access to its attributes. + var akira_canvas = canvas as Lib.Canvas; + // Disconnect previously set events. + akira_canvas.window.event_bus.change_theme.disconnect (on_theme_changed); + akira_canvas.window.event_bus.set_scale.disconnect (on_canvas_scaled); + // Reassign the Canvas as parent to the label in order to remove it. label.parent = parent; label.remove (); diff --git a/src/Lib/Items/CanvasItem.vala b/src/Lib/Items/CanvasItem.vala index 1429bc102..a2a1b2231 100644 --- a/src/Lib/Items/CanvasItem.vala +++ b/src/Lib/Items/CanvasItem.vala @@ -134,6 +134,13 @@ public interface Akira.Lib.Items.CanvasItem : Goo.CanvasItemSimple, Goo.CanvasIt } } + public Components.Font? text_font { + get { + Component? component = this.get_component (typeof (Components.Font)); + return (Components.Font) component; + } + } + public Components.Layer? layer { get { Component? component = this.get_component (typeof (Components.Layer)); diff --git a/src/Lib/Items/CanvasText.vala b/src/Lib/Items/CanvasText.vala index 737f375b4..6fd80e12f 100644 --- a/src/Lib/Items/CanvasText.vala +++ b/src/Lib/Items/CanvasText.vala @@ -17,6 +17,7 @@ * along with Akira. If not, see . * * Authored by: Alessandro "Alecaddd" Castellani + * Authored by: Abdallah "Abdallah-Moh" Mohammad */ using Akira.Lib.Components; @@ -36,7 +37,9 @@ public class Akira.Lib.Items.CanvasText : Goo.CanvasText, Akira.Lib.Items.Canvas double _width, double _height, Goo.CanvasAnchorType _anchor = Goo.CanvasAnchorType.NW, - string _font = "Open Sans 16", + string font_name, + int font_size, + Gdk.RGBA fill_color, Goo.CanvasItem? _parent, Items.CanvasArtboard? _artboard ) { @@ -48,7 +51,8 @@ public class Akira.Lib.Items.CanvasText : Goo.CanvasText, Akira.Lib.Items.Canvas width = height = 1; text = _text; anchor = _anchor; - font = _font; + font = font_name + " " + font_size.to_string (); + init_position (this, _x, _y); // Add the newly created item to the Canvas or Artboard. @@ -67,6 +71,8 @@ public class Akira.Lib.Items.CanvasText : Goo.CanvasText, Akira.Lib.Items.Canvas components.add (new Size (this)); components.add (new Flipped (this)); components.add (new Layer ()); + components.add (new Fills (this, fill_color)); + components.add (new Font (this, font_name, font_size)); check_add_to_artboard (this); } diff --git a/src/Lib/Items/CanvasTextView.vala b/src/Lib/Items/CanvasTextView.vala new file mode 100644 index 000000000..27decfe8d --- /dev/null +++ b/src/Lib/Items/CanvasTextView.vala @@ -0,0 +1,86 @@ +/** + * 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: Abdallah "Abdallah-Moh" Mohammad +*/ + +using Akira.Lib.Components; + +/** + * Generate a simple Text item. + */ +public class Akira.Lib.Items.CanvasTextView : Goo.CanvasWidget { + + public Items.CanvasArtboard? artboard { get; set; } + + public CanvasTextView (CanvasText text_item) { + parent = text_item.parent; + + // Create the text item. + x = text_item.coordinates.x; + y = text_item.coordinates.y; + width = text_item.size.width; + height = text_item.size.height; + + var text_view = new Gtk.TextView.with_buffer(new Gtk.TextBuffer(null)); + text_view.buffer.notify["text"].connect(()=>{ + text_item.text = text_view.buffer.text; + }); + text_view.buffer.text = text_item.text; + text_view.is_focus = true; + print(text_view.get_cursor_visible().to_string()); + widget = text_view; + + // Add the newly created item to the Canvas or Artboard. + parent.add_child (this, -1); + + widget.get_style_context ().add_class ("canvas-widget"); + add_css(text_item); + // text_item.visibility = Goo.CanvasItemVisibility.INVISIBLE; + } + + private void add_css (CanvasText item) { + try { + var provider = new Gtk.CssProvider (); + var context = widget.get_style_context (); + var upper_font_weight = item.text_font.font_parameters.replace (" Italic", ""); + var font_weight = ""; + for (int i = 0; i <= upper_font_weight.length; i++) { + var letter = upper_font_weight.get_char (i); + font_weight += letter.tolower ().to_string (); + } + var font_style = item.text_font.is_italic ? "italic" : "normal"; + font_weight = font_weight == "" ? "normal" : font_weight; + + var css = """.canvas-widget { + background:none; + font-size:%dpt; + font-family:%s; + font-weight:%s; + font-style:%s; + }""".printf (item.text_font.font_size, item.text_font.font_name, font_weight,font_style); + // print (item.text_font.font_size.to_string () + item.text_font.font_name + font_weight + font_style); + + provider.load_from_data (css, css.length); + + context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } catch (Error e) { + warning ("Style error: %s", e.message); + } + } +} diff --git a/src/Lib/Managers/ItemsManager.vala b/src/Lib/Managers/ItemsManager.vala index 404973551..95b8a7a9b 100644 --- a/src/Lib/Managers/ItemsManager.vala +++ b/src/Lib/Managers/ItemsManager.vala @@ -31,6 +31,7 @@ public class Akira.Lib.Managers.ItemsManager : Object { private int border_size; private Gdk.RGBA border_color; private Gdk.RGBA fill_color; + private Gdk.RGBA text_fill_color; // Keep track of the expensive Artboard change method. private bool is_changing = false; @@ -51,6 +52,7 @@ public class Akira.Lib.Managers.ItemsManager : Object { border_color = Gdk.RGBA (); fill_color = Gdk.RGBA (); + text_fill_color = Gdk.RGBA (); window.event_bus.insert_item.connect (set_item_to_insert); window.event_bus.request_delete_item.connect (on_request_delete_item); @@ -153,6 +155,7 @@ public class Akira.Lib.Managers.ItemsManager : Object { item.parent.add_child (item, -1); free_items.add_item.begin (item); window.event_bus.file_edited (); + ((Lib.Canvas) item.canvas).update_canvas (); } /** @@ -191,8 +194,9 @@ public class Akira.Lib.Managers.ItemsManager : Object { free_items.remove_item.begin (item); } - item.delete (); + // Let the app know we're deleting an item. window.event_bus.item_deleted (item); + item.delete (); window.event_bus.file_edited (); } @@ -253,7 +257,9 @@ public class Akira.Lib.Managers.ItemsManager : Object { 200, 25f, Goo.CanvasAnchorType.NW, - "Open Sans 18", + settings.text_font, + settings.text_size, + text_fill_color, parent, artboard ); @@ -334,6 +340,7 @@ public class Akira.Lib.Managers.ItemsManager : Object { private void update_default_values () { fill_color.parse (settings.fill_color); + text_fill_color.parse (settings.text_color); // Do not set the border if the user disabled it. if (settings.set_border) { @@ -689,13 +696,4 @@ public class Akira.Lib.Managers.ItemsManager : Object { return; } } - - /** - * Helper method to get the count of all the created items in the canvas. - * This count excludes pseudo items like the select effect, hover effect, - * or grids items. - */ - public int get_items_count () { - return (int) free_items.get_n_items () + (int) artboards.get_n_items (); - } } diff --git a/src/Lib/Modes/EditMode.vala b/src/Lib/Modes/EditMode.vala new file mode 100644 index 000000000..be1096e15 --- /dev/null +++ b/src/Lib/Modes/EditMode.vala @@ -0,0 +1,68 @@ +/** + * Copyright (c) 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: Abdallah "Abdallah-Moh" Mohammad + */ + +public class Akira.Lib.Modes.EditMode : InteractionMode { + public weak Akira.Lib.Canvas canvas { get; construct; } + public weak Akira.Lib.Managers.ModeManager mode_manager { get; construct; } + + public EditMode (Akira.Lib.Canvas canvas, Akira.Lib.Managers.ModeManager mode_manager) { + Object ( + canvas: canvas, + mode_manager : mode_manager + ); + } + + public override void mode_begin () {} + public override void mode_end () {} + public override InteractionMode.ModeType mode_type () { return InteractionMode.ModeType.EDIT; } + + public override Gdk.CursorType? cursor_type () { + return Gdk.CursorType.ARROW; + } + + public override bool key_press_event (Gdk.EventKey event) { + return false; + } + + public override bool key_release_event (Gdk.EventKey event) { + return false; + } + + public override bool button_press_event (Gdk.EventButton event) { + // Get Clicked Item + var clicked_item = canvas.get_item_at (event.x, event.y, true); + // Check if it is CanvasText + if(clicked_item is Lib.Items.CanvasText) { + var canavs_text = (Lib.Items.CanvasText)clicked_item; + new Lib.Items.CanvasTextView(canavs_text); + } + + return false; + } + + public override bool button_release_event (Gdk.EventButton event) { + return false; + } + + public override bool motion_notify_event (Gdk.EventMotion event) { + return false; + } +} diff --git a/src/Lib/Modes/InteractionMode.vala b/src/Lib/Modes/InteractionMode.vala index 5370c93fd..df0b8eebb 100644 --- a/src/Lib/Modes/InteractionMode.vala +++ b/src/Lib/Modes/InteractionMode.vala @@ -46,7 +46,8 @@ public abstract class Akira.Lib.Modes.InteractionMode : Object { RESIZE, ITEM_INSERT, EXPORT, - PAN + PAN, + EDIT } /* diff --git a/src/Lib/Modes/ItemInsertMode.vala b/src/Lib/Modes/ItemInsertMode.vala index 9fd7499e1..4a770ead8 100644 --- a/src/Lib/Modes/ItemInsertMode.vala +++ b/src/Lib/Modes/ItemInsertMode.vala @@ -7,12 +7,10 @@ * 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 . * diff --git a/src/Services/Settings.vala b/src/Services/Settings.vala index f93fa0e06..85532a2cc 100644 --- a/src/Services/Settings.vala +++ b/src/Services/Settings.vala @@ -100,6 +100,18 @@ public class Akira.Services.Settings : GLib.Settings { owned get { return get_string ("border-color"); } set { set_string ("border-color", value); } } + public string text_color { + owned get { return get_string ("text-color"); } + set { set_string ("text-color", value); } + } + public int text_size { + get { return get_int ("text-size"); } + set { set_int ("text-size", value); } + } + public string text_font { + owned get { return get_string ("text-font"); } + set { set_string ("text-font", value); } + } // File settings. public bool open_quick { diff --git a/src/Utils/Font.vala b/src/Utils/Font.vala new file mode 100644 index 000000000..7d7293e45 --- /dev/null +++ b/src/Utils/Font.vala @@ -0,0 +1,95 @@ +/* + * Copyright (c) 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: Abdallah "Abdallah-Moh" Mohammad + */ + + public class Akira.Utils.Font : Object { + private const string[] ACCEPTED_TYPES = { + "font/ttf", + "font/fot", + "font/otf", + "font/woff", + }; + + public static bool is_valid_font (File file) { + try { + var file_info = file.query_info ("standard::*", 0); + + // Check for correct file type, don't try to load directories. + if (file_info.get_file_type () != GLib.FileType.REGULAR) { + return false; + } + + foreach (var type in ACCEPTED_TYPES) { + if (GLib.ContentType.equals (file_info.get_content_type (), type)) { + return true; + } + } + } catch (Error e) { + warning ("Could not get file info: %s", e.message); + } + + return false; + } + + public static string[] get_fonts () { + string[] fonts = {}; + // Directory Paths to search for fonts + string home_dir = GLib.Environment.get_home_dir (); + string[] font_paths = {home_dir + "/.local/share/fonts", "/usr/share/fonts"}; + + foreach (var font_path in font_paths) { + fonts = get_font_from_path (font_path, fonts); + } + + return fonts; + } + + public static string[] get_font_from_path (string font_path, string[] fonts) { + string[] fonts_found = fonts; + var file_name = ""; + try { + var font_dir = Dir.open (font_path); + while ((file_name = font_dir.read_name ()) != null) { + string path = Path.build_filename (font_path, file_name); + if (FileUtils.test (path, FileTest.IS_REGULAR)) { + string name_of_font = file_name.split ("-")[0].split (".")[0]; + if (!is_string_in_array (name_of_font, fonts_found) && is_valid_font (File.new_for_path (path))) { + fonts_found += name_of_font; + } + } + else if (FileUtils.test (path, FileTest.IS_DIR)) { + fonts_found = get_font_from_path (path, fonts_found); + } + } + } catch (FileError err) { + print (err.message); + } + return fonts_found; + } + + public static bool is_string_in_array (string value, string [] array) { + foreach (var font_item in array) { + if (font_item == value) { + return true; + } + } + return false; + } +} diff --git a/src/meson.build b/src/meson.build index 61c20d44e..1068b895d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -35,6 +35,7 @@ sources = files( 'Utils/Color.vala', 'Utils/Image.vala', 'Utils/ColorPicker.vala', + 'Utils/Font.vala', 'Utils/Snapping.vala', 'Layouts/HeaderBar.vala', @@ -54,6 +55,7 @@ sources = files( 'Layouts/Partials/TransformPanel.vala', 'Layouts/Partials/BorderRadiusPanel.vala', 'Layouts/Partials/AlignItemsPanel.vala', + 'Layouts/Partials/TextOptionsPanel.vala', 'Partials/HeaderBarButton.vala', 'Partials/MenuButton.vala', @@ -87,6 +89,7 @@ sources = files( 'Lib/Components/Fill.vala', 'Lib/Components/Fills.vala', 'Lib/Components/Flipped.vala', + 'Lib/Components/Font.vala', 'Lib/Components/Layer.vala', 'Lib/Components/Name.vala', 'Lib/Components/Opacity.vala', @@ -99,6 +102,7 @@ sources = files( 'Lib/Items/CanvasItem.vala', 'Lib/Items/CanvasRect.vala', 'Lib/Items/CanvasText.vala', + 'Lib/Items/CanvasTextView.vala', 'Lib/Managers/ExportManager.vala', 'Lib/Managers/HoverManager.vala', @@ -115,6 +119,7 @@ sources = files( 'Middlewares/SizeMiddleware.vala', 'Lib/Modes/InteractionMode.vala', + 'Lib/Modes/EditMode.vala', 'Lib/Modes/TransformMode.vala', 'Lib/Modes/ItemInsertMode.vala', 'Lib/Modes/ExportMode.vala',