From 6f73f1e7cd0dad2cc898d3c4d0aa868fa2874e1e Mon Sep 17 00:00:00 2001 From: MewPurPur Date: Sun, 9 Jun 2024 23:58:34 +0300 Subject: [PATCH] Implement group and gradient tags --- AppInfo.gd | 41 -- app_info.json | 28 ++ export_presets.cfg | 184 +++++++- godot_only/scripts/tests.gd | 2 +- src/DB.gd | 186 ++++---- src/FileUtils.gd | 31 +- src/GlobalSettings.gd | 42 +- src/HandlerGUI.gd | 1 + src/Indications.gd | 417 +++++++++--------- src/SVG.gd | 60 +-- src/ThemeGenerator.gd | 9 +- src/Utils.gd | 84 ++-- src/data_classes/Attribute.gd | 50 +-- src/data_classes/AttributeColor.gd | 15 +- src/data_classes/AttributeEnum.gd | 8 +- src/data_classes/AttributeID.gd | 6 + src/data_classes/AttributeList.gd | 12 +- src/data_classes/AttributeNumeric.gd | 8 +- ...{AttributePath.gd => AttributePathdata.gd} | 62 +-- src/data_classes/AttributeTransform.gd | 141 ------ src/data_classes/AttributeTransformList.gd | 60 +++ src/data_classes/Tag.gd | 177 ++++++-- src/data_classes/TagCircle.gd | 85 ++-- src/data_classes/TagEllipse.gd | 89 ++-- src/data_classes/TagG.gd | 16 + src/data_classes/TagLine.gd | 58 +-- src/data_classes/TagLinearGradient.gd | 28 ++ src/data_classes/TagPath.gd | 199 ++++++++- src/data_classes/TagRadialGradient.gd | 27 ++ src/data_classes/TagRect.gd | 128 +++--- src/data_classes/TagRoot.gd | 255 +++++++++++ src/data_classes/TagSVG.gd | 327 +------------- src/data_classes/TagShape.gd | 12 + src/data_classes/TagStop.gd | 19 +- src/data_classes/TagUnknown.gd | 11 - src/data_classes/TagUnrecognized.gd | 8 + src/data_classes/Transform.gd | 75 ++++ src/data_classes/XNode.gd | 3 + src/parsers/ColorParser.gd | 2 +- src/parsers/IDParser.gd | 2 +- src/parsers/PathDataParser.gd | 24 +- src/parsers/SVGHighlighter.gd | 29 +- src/parsers/SVGParser.gd | 97 ++-- src/parsers/TransformListParser.gd | 46 +- src/ui_elements/BetterLineEdit.gd | 1 + .../{context_popup.gd => ContextPopup.gd} | 23 +- src/ui_elements/LineEditButton.gd | 26 +- src/ui_elements/color_field.gd | 49 +- src/ui_elements/color_swatch_config.gd | 2 +- src/ui_elements/enum_field.gd | 40 +- src/ui_elements/flag_field.tscn | 9 +- src/ui_elements/id_field.gd | 45 ++ src/ui_elements/id_field.tscn | 15 + src/ui_elements/number_field.gd | 27 +- src/ui_elements/number_field_with_slider.gd | 42 +- src/ui_elements/palette_config.gd | 6 +- src/ui_elements/pathdata_field.gd | 98 ++-- src/ui_elements/setting_shortcut.gd | 12 +- src/ui_elements/tag_content_basic_shape.gd | 38 ++ ...rcle.tscn => tag_content_basic_shape.tscn} | 4 +- src/ui_elements/tag_content_circle.gd | 46 -- src/ui_elements/tag_content_ellipse.gd | 46 -- src/ui_elements/tag_content_g.gd | 38 ++ ...g_content_line.tscn => tag_content_g.tscn} | 8 +- src/ui_elements/tag_content_line.gd | 46 -- .../tag_content_linear_gradient.gd | 40 ++ ....tscn => tag_content_linear_gradient.tscn} | 8 +- src/ui_elements/tag_content_path.gd | 57 ++- .../tag_content_radial_gradient.gd | 40 ++ ....tscn => tag_content_radial_gradient.tscn} | 8 +- src/ui_elements/tag_content_rect.gd | 46 -- src/ui_elements/tag_content_stop.gd | 57 ++- ...unknown.gd => tag_content_unrecognized.gd} | 1 - ...own.tscn => tag_content_unrecognized.tscn} | 2 +- src/ui_elements/transform_field.gd | 23 +- src/ui_elements/transform_popup.gd | 81 ++-- src/ui_elements/unknown_field.gd | 33 -- src/ui_elements/unrecognized_field.gd | 29 ++ ...own_field.tscn => unrecognized_field.tscn} | 2 +- src/ui_parts/DeltaHandle.gd | 50 +-- src/ui_parts/Handle.gd | 4 +- src/ui_parts/PathHandle.gd | 43 +- src/ui_parts/XYHandle.gd | 36 +- src/ui_parts/about_menu.gd | 13 +- src/ui_parts/camera.gd | 2 +- src/ui_parts/code_editor.gd | 9 +- src/ui_parts/display.gd | 13 +- src/ui_parts/display_texture.gd | 10 +- src/ui_parts/handles_manager.gd | 255 +++++------ src/ui_parts/import_warning_dialog.gd | 45 +- src/ui_parts/import_warning_dialog.tscn | 2 - src/ui_parts/inspector.gd | 28 +- src/ui_parts/move_to_overlay.gd | 12 +- src/ui_parts/root_tag_editor.gd | 144 ++++-- src/ui_parts/root_tag_editor.tscn | 55 +-- src/ui_parts/settings_menu.gd | 121 +++-- src/ui_parts/settings_menu.tscn | 93 ++-- src/ui_parts/tag_container.gd | 85 ++-- src/ui_parts/tag_frame.gd | 182 +++++--- src/ui_parts/viewport.gd | 8 +- translations/GodSVG.pot | 90 ++-- translations/bg.po | 110 +++-- translations/de.po | 103 +++-- translations/en.po | 90 ++-- translations/ru.po | 127 +++--- translations/uk.po | 113 ++--- visual/icons/ApplyMatrix.svg | 2 +- visual/icons/Clear.svg | 2 +- visual/icons/CodeOptions.svg | 2 +- visual/icons/Compress.svg | 2 +- visual/icons/CreateFolder.svg | 2 +- visual/icons/DirDocuments.svg | 2 +- visual/icons/DirMovies.svg | 2 +- visual/icons/DirMusic.svg | 2 +- visual/icons/DirPictures.svg | 2 +- visual/icons/Edit.svg | 2 +- visual/icons/FileBroken.svg | 2 +- visual/icons/InsertAfter.svg | 2 +- visual/icons/InsertBefore.svg | 2 +- visual/icons/NoneColor.svg | 2 +- visual/icons/OpenFile.svg | 2 +- visual/icons/OpenFolder.svg | 2 +- visual/icons/Redo.svg | 2 +- visual/icons/Reference.svg | 2 +- visual/icons/Rotate.svg | 2 +- visual/icons/Save.svg | 2 +- visual/icons/Scale.svg | 2 +- visual/icons/SideSliderArrow.svg | 2 +- visual/icons/SkewX.svg | 2 +- visual/icons/SkewY.svg | 2 +- visual/icons/SliderArrow.svg | 2 +- visual/icons/Snap.svg | 2 +- visual/icons/TagWarning.svg | 1 + visual/icons/TagWarning.svg.import | 37 ++ visual/icons/Undo.svg | 2 +- visual/icons/backgrounds/ColorButtonBG.svg | 2 +- visual/icons/foreign_logos/KoFiLogo.svg | 2 +- visual/icons/tag/g.svg | 2 +- visual/icons/tag/linearGradient.svg | 2 +- visual/icons/tag/radialGradient.svg | 2 +- visual/icons/tag/stop.svg | 2 +- .../tag/{unknown.svg => unrecognized.svg} | 0 ...own.svg.import => unrecognized.svg.import} | 6 +- 143 files changed, 3392 insertions(+), 2773 deletions(-) delete mode 100644 AppInfo.gd create mode 100644 app_info.json create mode 100644 src/data_classes/AttributeID.gd rename src/data_classes/{AttributePath.gd => AttributePathdata.gd} (78%) delete mode 100644 src/data_classes/AttributeTransform.gd create mode 100644 src/data_classes/AttributeTransformList.gd create mode 100644 src/data_classes/TagG.gd create mode 100644 src/data_classes/TagLinearGradient.gd create mode 100644 src/data_classes/TagRadialGradient.gd create mode 100644 src/data_classes/TagRoot.gd create mode 100644 src/data_classes/TagShape.gd delete mode 100644 src/data_classes/TagUnknown.gd create mode 100644 src/data_classes/TagUnrecognized.gd create mode 100644 src/data_classes/Transform.gd create mode 100644 src/data_classes/XNode.gd rename src/ui_elements/{context_popup.gd => ContextPopup.gd} (92%) create mode 100644 src/ui_elements/id_field.gd create mode 100644 src/ui_elements/id_field.tscn create mode 100644 src/ui_elements/tag_content_basic_shape.gd rename src/ui_elements/{tag_content_circle.tscn => tag_content_basic_shape.tscn} (75%) delete mode 100644 src/ui_elements/tag_content_circle.gd delete mode 100644 src/ui_elements/tag_content_ellipse.gd create mode 100644 src/ui_elements/tag_content_g.gd rename src/ui_elements/{tag_content_line.tscn => tag_content_g.tscn} (53%) delete mode 100644 src/ui_elements/tag_content_line.gd create mode 100644 src/ui_elements/tag_content_linear_gradient.gd rename src/ui_elements/{tag_content_ellipse.tscn => tag_content_linear_gradient.tscn} (50%) create mode 100644 src/ui_elements/tag_content_radial_gradient.gd rename src/ui_elements/{tag_content_rect.tscn => tag_content_radial_gradient.tscn} (50%) delete mode 100644 src/ui_elements/tag_content_rect.gd rename src/ui_elements/{tag_content_unknown.gd => tag_content_unrecognized.gd} (58%) rename src/ui_elements/{tag_content_unknown.tscn => tag_content_unrecognized.tscn} (87%) delete mode 100644 src/ui_elements/unknown_field.gd create mode 100644 src/ui_elements/unrecognized_field.gd rename src/ui_elements/{unknown_field.tscn => unrecognized_field.tscn} (81%) create mode 100644 visual/icons/TagWarning.svg create mode 100644 visual/icons/TagWarning.svg.import rename visual/icons/tag/{unknown.svg => unrecognized.svg} (100%) rename visual/icons/tag/{unknown.svg.import => unrecognized.svg.import} (73%) diff --git a/AppInfo.gd b/AppInfo.gd deleted file mode 100644 index 6ce44157..00000000 --- a/AppInfo.gd +++ /dev/null @@ -1,41 +0,0 @@ -# Stores basic information about GodSVG. -class_name AppInfo extends RefCounted - -const project_founder_and_manager: Array[String] = ["MewPurPur"] - - -# The developers and artists who have contributed significant patches to the MIT-licensed -# source code of GodSVG are listed here. What counts as significant is decided arbitrarily. -# The translators for each locale are listed in their corresponding PO files. -# Entries are formatted as follows: -# Option 1: Real Name (GitHub username) -# Option 2: GitHub username - -const authors: Array[String] = [ - "Aaron Franke (aaronfranke)", - "ajreckof", - "aladvs", - "Alex2782", - "DevPoodle", - "ilikefrogs101", - "Kiisu-Master", - "MewPurPur", - "Qainguin", - "Serem Titus (SeremTitus)", - "Swarkin", - "thiagola92", - "Tom Blackwell (Volts-s)", - "WeaverSong", -] - -# GodSVG is a non-profit project developed by voluntary contributors. -# Thousands of hours of work have gone into it so far. -# Everyone who has generously sponsored this effort is listed here. - -const donors: Array[String] = [] - -const golden_donors: Array[String] = [] - -const diamond_donors: Array[String] = [ - "Aaron Franke (aaronfranke)", -] diff --git a/app_info.json b/app_info.json new file mode 100644 index 00000000..a3bc3720 --- /dev/null +++ b/app_info.json @@ -0,0 +1,28 @@ +{ + "project_founder_and_manager": [ + "MewPurPur" + ], + "authors": [ + "Aaron Franke (aaronfranke)", + "ajreckof", + "aladvs", + "Alex2782", + "DevPoodle", + "ilikefrogs101", + "Kiisu-Master", + "MewPurPur", + "Qainguin", + "Serem Titus (SeremTitus)", + "Swarkin", + "thiagola92", + "Tom Blackwell (Volts-s)", + "WeaverSong}" + ], + "donors": [ + "Racer911dash1" + ], + "golden_donors": [], + "diamond_donors": [ + "Aaron Franke (aaronfranke)" + ] +} diff --git a/export_presets.cfg b/export_presets.cfg index 858a3f1b..b4795455 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -3,6 +3,7 @@ name="Windows Desktop" platform="Windows Desktop" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -13,6 +14,7 @@ encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.0.options] @@ -20,10 +22,8 @@ custom_template/debug="" custom_template/release="" debug/export_console_wrapper=1 binary_format/embed_pck=true -texture_format/bptc=true -texture_format/s3tc=true -texture_format/etc=false -texture_format/etc2=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false binary_format/architecture="x86_64" codesign/enable=false codesign/timestamp=true @@ -32,7 +32,7 @@ codesign/digest_algorithm=1 codesign/description="" codesign/custom_options=PackedStringArray() application/modify_resources=true -application/icon="" +application/icon="res://visual/icon.ico" application/console_wrapper_icon="" application/icon_interpolation=4 application/file_version="" @@ -43,6 +43,8 @@ application/file_description="GodSVG" application/copyright="2023-present GodSVG contributors" application/trademarks="" application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true ssh_remote_deploy/enabled=false ssh_remote_deploy/host="user@host_ip" ssh_remote_deploy/port="22" @@ -60,12 +62,17 @@ Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorActi ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue Remove-Item -Recurse -Force '{temp_dir}'" +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false [preset.1] name="Linux/X11" -platform="Linux/X11" +platform="Linux" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -76,6 +83,7 @@ encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.1.options] @@ -83,10 +91,8 @@ custom_template/debug="" custom_template/release="" debug/export_console_wrapper=1 binary_format/embed_pck=true -texture_format/bptc=true -texture_format/s3tc=true -texture_format/etc=false -texture_format/etc2=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false binary_format/architecture="x86_64" ssh_remote_deploy/enabled=false ssh_remote_deploy/host="user@host_ip" @@ -100,12 +106,17 @@ unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") rm -rf \"{temp_dir}\"" +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false [preset.2] name="macOS" platform="macOS" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -116,6 +127,7 @@ encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.2.options] @@ -136,6 +148,7 @@ application/copyright_localized={} application/min_macos_version="10.12" application/export_angle=0 display/high_res=true +application/additional_plist_content="" xcode/platform_build="14C18" xcode/sdk_version="13.1" xcode/sdk_build="22C55" @@ -194,6 +207,148 @@ privacy/network_volumes_usage_description="" privacy/network_volumes_usage_description_localized={} privacy/removable_volumes_usage_description="" privacy/removable_volumes_usage_description_localized={} +privacy/tracking_enabled=false +privacy/tracking_domains=PackedStringArray() +privacy/collected_data/name/collected=false +privacy/collected_data/name/linked_to_user=false +privacy/collected_data/name/used_for_tracking=false +privacy/collected_data/name/collection_purposes=0 +privacy/collected_data/email_address/collected=false +privacy/collected_data/email_address/linked_to_user=false +privacy/collected_data/email_address/used_for_tracking=false +privacy/collected_data/email_address/collection_purposes=0 +privacy/collected_data/phone_number/collected=false +privacy/collected_data/phone_number/linked_to_user=false +privacy/collected_data/phone_number/used_for_tracking=false +privacy/collected_data/phone_number/collection_purposes=0 +privacy/collected_data/physical_address/collected=false +privacy/collected_data/physical_address/linked_to_user=false +privacy/collected_data/physical_address/used_for_tracking=false +privacy/collected_data/physical_address/collection_purposes=0 +privacy/collected_data/other_contact_info/collected=false +privacy/collected_data/other_contact_info/linked_to_user=false +privacy/collected_data/other_contact_info/used_for_tracking=false +privacy/collected_data/other_contact_info/collection_purposes=0 +privacy/collected_data/health/collected=false +privacy/collected_data/health/linked_to_user=false +privacy/collected_data/health/used_for_tracking=false +privacy/collected_data/health/collection_purposes=0 +privacy/collected_data/fitness/collected=false +privacy/collected_data/fitness/linked_to_user=false +privacy/collected_data/fitness/used_for_tracking=false +privacy/collected_data/fitness/collection_purposes=0 +privacy/collected_data/payment_info/collected=false +privacy/collected_data/payment_info/linked_to_user=false +privacy/collected_data/payment_info/used_for_tracking=false +privacy/collected_data/payment_info/collection_purposes=0 +privacy/collected_data/credit_info/collected=false +privacy/collected_data/credit_info/linked_to_user=false +privacy/collected_data/credit_info/used_for_tracking=false +privacy/collected_data/credit_info/collection_purposes=0 +privacy/collected_data/other_financial_info/collected=false +privacy/collected_data/other_financial_info/linked_to_user=false +privacy/collected_data/other_financial_info/used_for_tracking=false +privacy/collected_data/other_financial_info/collection_purposes=0 +privacy/collected_data/precise_location/collected=false +privacy/collected_data/precise_location/linked_to_user=false +privacy/collected_data/precise_location/used_for_tracking=false +privacy/collected_data/precise_location/collection_purposes=0 +privacy/collected_data/coarse_location/collected=false +privacy/collected_data/coarse_location/linked_to_user=false +privacy/collected_data/coarse_location/used_for_tracking=false +privacy/collected_data/coarse_location/collection_purposes=0 +privacy/collected_data/sensitive_info/collected=false +privacy/collected_data/sensitive_info/linked_to_user=false +privacy/collected_data/sensitive_info/used_for_tracking=false +privacy/collected_data/sensitive_info/collection_purposes=0 +privacy/collected_data/contacts/collected=false +privacy/collected_data/contacts/linked_to_user=false +privacy/collected_data/contacts/used_for_tracking=false +privacy/collected_data/contacts/collection_purposes=0 +privacy/collected_data/emails_or_text_messages/collected=false +privacy/collected_data/emails_or_text_messages/linked_to_user=false +privacy/collected_data/emails_or_text_messages/used_for_tracking=false +privacy/collected_data/emails_or_text_messages/collection_purposes=0 +privacy/collected_data/photos_or_videos/collected=false +privacy/collected_data/photos_or_videos/linked_to_user=false +privacy/collected_data/photos_or_videos/used_for_tracking=false +privacy/collected_data/photos_or_videos/collection_purposes=0 +privacy/collected_data/audio_data/collected=false +privacy/collected_data/audio_data/linked_to_user=false +privacy/collected_data/audio_data/used_for_tracking=false +privacy/collected_data/audio_data/collection_purposes=0 +privacy/collected_data/gameplay_content/collected=false +privacy/collected_data/gameplay_content/linked_to_user=false +privacy/collected_data/gameplay_content/used_for_tracking=false +privacy/collected_data/gameplay_content/collection_purposes=0 +privacy/collected_data/customer_support/collected=false +privacy/collected_data/customer_support/linked_to_user=false +privacy/collected_data/customer_support/used_for_tracking=false +privacy/collected_data/customer_support/collection_purposes=0 +privacy/collected_data/other_user_content/collected=false +privacy/collected_data/other_user_content/linked_to_user=false +privacy/collected_data/other_user_content/used_for_tracking=false +privacy/collected_data/other_user_content/collection_purposes=0 +privacy/collected_data/browsing_history/collected=false +privacy/collected_data/browsing_history/linked_to_user=false +privacy/collected_data/browsing_history/used_for_tracking=false +privacy/collected_data/browsing_history/collection_purposes=0 +privacy/collected_data/search_hhistory/collected=false +privacy/collected_data/search_hhistory/linked_to_user=false +privacy/collected_data/search_hhistory/used_for_tracking=false +privacy/collected_data/search_hhistory/collection_purposes=0 +privacy/collected_data/user_id/collected=false +privacy/collected_data/user_id/linked_to_user=false +privacy/collected_data/user_id/used_for_tracking=false +privacy/collected_data/user_id/collection_purposes=0 +privacy/collected_data/device_id/collected=false +privacy/collected_data/device_id/linked_to_user=false +privacy/collected_data/device_id/used_for_tracking=false +privacy/collected_data/device_id/collection_purposes=0 +privacy/collected_data/purchase_history/collected=false +privacy/collected_data/purchase_history/linked_to_user=false +privacy/collected_data/purchase_history/used_for_tracking=false +privacy/collected_data/purchase_history/collection_purposes=0 +privacy/collected_data/product_interaction/collected=false +privacy/collected_data/product_interaction/linked_to_user=false +privacy/collected_data/product_interaction/used_for_tracking=false +privacy/collected_data/product_interaction/collection_purposes=0 +privacy/collected_data/advertising_data/collected=false +privacy/collected_data/advertising_data/linked_to_user=false +privacy/collected_data/advertising_data/used_for_tracking=false +privacy/collected_data/advertising_data/collection_purposes=0 +privacy/collected_data/other_usage_data/collected=false +privacy/collected_data/other_usage_data/linked_to_user=false +privacy/collected_data/other_usage_data/used_for_tracking=false +privacy/collected_data/other_usage_data/collection_purposes=0 +privacy/collected_data/crash_data/collected=false +privacy/collected_data/crash_data/linked_to_user=false +privacy/collected_data/crash_data/used_for_tracking=false +privacy/collected_data/crash_data/collection_purposes=0 +privacy/collected_data/performance_data/collected=false +privacy/collected_data/performance_data/linked_to_user=false +privacy/collected_data/performance_data/used_for_tracking=false +privacy/collected_data/performance_data/collection_purposes=0 +privacy/collected_data/other_diagnostic_data/collected=false +privacy/collected_data/other_diagnostic_data/linked_to_user=false +privacy/collected_data/other_diagnostic_data/used_for_tracking=false +privacy/collected_data/other_diagnostic_data/collection_purposes=0 +privacy/collected_data/environment_scanning/collected=false +privacy/collected_data/environment_scanning/linked_to_user=false +privacy/collected_data/environment_scanning/used_for_tracking=false +privacy/collected_data/environment_scanning/collection_purposes=0 +privacy/collected_data/hands/collected=false +privacy/collected_data/hands/linked_to_user=false +privacy/collected_data/hands/used_for_tracking=false +privacy/collected_data/hands/collection_purposes=0 +privacy/collected_data/head/collected=false +privacy/collected_data/head/linked_to_user=false +privacy/collected_data/head/used_for_tracking=false +privacy/collected_data/head/collection_purposes=0 +privacy/collected_data/other_data_types/collected=false +privacy/collected_data/other_data_types/linked_to_user=false +privacy/collected_data/other_data_types/used_for_tracking=false +privacy/collected_data/other_data_types/collection_purposes=0 ssh_remote_deploy/enabled=false ssh_remote_deploy/host="user@host_ip" ssh_remote_deploy/port="22" @@ -211,6 +366,7 @@ rm -rf \"{temp_dir}\"" name="Web" platform="Web" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="exclude" @@ -222,12 +378,14 @@ encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.3.options] custom_template/debug="" custom_template/release="/home/volter/Desktop/godot/bin/godot.web.template_release.wasm32.zip" variant/extensions_support=false +variant/thread_support=true vram_texture_compression/for_desktop=true vram_texture_compression/for_mobile=false html/export_icon=true @@ -237,6 +395,7 @@ html/canvas_resize_policy=2 html/focus_canvas_on_start=true html/experimental_virtual_keyboard=false progressive_web_app/enabled=false +progressive_web_app/ensure_cross_origin_isolation_headers=true progressive_web_app/offline_page="" progressive_web_app/display=1 progressive_web_app/orientation=0 @@ -250,6 +409,7 @@ progressive_web_app/background_color=Color(0, 0, 0, 1) name="Android" platform="Android" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -260,12 +420,16 @@ encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.4.options] custom_template/debug="" custom_template/release="" gradle_build/use_gradle_build=false +gradle_build/gradle_build_directory="" +gradle_build/android_source_template="" +gradle_build/compress_native_libraries=false gradle_build/export_format=0 gradle_build/min_sdk="" gradle_build/target_sdk="" diff --git a/godot_only/scripts/tests.gd b/godot_only/scripts/tests.gd index d019c71f..7c96348c 100644 --- a/godot_only/scripts/tests.gd +++ b/godot_only/scripts/tests.gd @@ -32,7 +32,7 @@ func pathdata_tests(print_success := false) -> bool: var tests_passed := true for test in tests.keys(): - var result := PathDataParser.path_data_to_arrays(test) + var result := PathdataParser.pathdata_to_arrays(test) var expected: Array = tests[test] if result != expected: tests_passed = false diff --git a/src/DB.gd b/src/DB.gd index f9a9344c..f4e04e38 100644 --- a/src/DB.gd +++ b/src/DB.gd @@ -1,50 +1,84 @@ class_name DB extends RefCounted -const known_tags = ["svg", "circle", "ellipse", "rect", "path", "line", "stop"] +enum AttributeType {NUMERIC, COLOR, LIST, PATHDATA, ENUM, TRANSFORM_LIST, ID, UNKNOWN} -const known_tag_attributes = { # Dictionary{String: Array[String]} - "svg": TagSVG.known_attributes, - "circle": TagCircle.known_attributes, - "ellipse": TagEllipse.known_attributes, - "rect": TagRect.known_attributes, - "path": TagPath.known_attributes, - "line": TagLine.known_attributes, - "stop": TagStop.known_attributes, +const recognized_tags = ["svg", "g", "circle", "ellipse", "rect", "path", "line", "stop", + "linearGradient", "radialGradient"] + +const tag_icons = { + "circle": preload("res://visual/icons/tag/circle.svg"), + "ellipse": preload("res://visual/icons/tag/ellipse.svg"), + "rect": preload("res://visual/icons/tag/rect.svg"), + "path": preload("res://visual/icons/tag/path.svg"), + "line": preload("res://visual/icons/tag/line.svg"), + "g": preload("res://visual/icons/tag/g.svg"), + "linearGradient": preload("res://visual/icons/tag/linearGradient.svg"), + "radialGradient": preload("res://visual/icons/tag/radialGradient.svg"), + "stop": preload("res://visual/icons/tag/stop.svg"), } +const unrecognized_tag_icon = preload("res://visual/icons/tag/unrecognized.svg") -const attribute_defaults = { - "viewBox": "", - "width": "0", - "height": "0", - "x": "0", - "y": "0", - "x1": "0", - "y1": "0", - "x2": "0", - "y2": "0", - "cx": "0", - "cy": "0", - "r": "0", - "rx": "0", - "ry": "0", - "opacity": "1", - "fill": "black", - "fill-opacity": "1", - "stroke": "none", - "stroke-opacity": "1", - "stroke-width": "1", - "stroke-linecap": "butt", - "stroke-linejoin": "miter", - "d": "", - "transform": "", - "offset": "0", - "stop-color": "black", - "stop-opacity": "1", +const recognized_attributes = { # Dictionary{String: Array[String]} + # TODO this is just propagated_attributes, but it ruins the const because of Godot bug. + "svg": ["xmlns", "width", "height", "viewBox", "fill", "fill-opacity", "stroke", + "stroke-opacity", "stroke-width", "stroke-linecap", "stroke-linejoin"], + "g": ["transform", "opacity", "fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "stroke-linecap", "stroke-linejoin"], + "linearGradient": ["id", "gradientTransform", "gradientUnits", "x1", "y1", "x2", "y2"], + "radialGradient": ["id", "gradientTransform", "gradientUnits", "cx", "cy", "r"], + "circle": ["transform", "opacity", "fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "cx", "cy", "r"], + "ellipse": ["transform", "opacity", "fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "cx", "cy", "rx", "ry"], + "rect": ["transform", "opacity", "fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "stroke-linejoin", "x", "y", "width", "height", "rx", "ry"], + "path": ["transform", "opacity", "fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "stroke-linecap", "stroke-linejoin", "d"], + "line": ["transform", "opacity", "stroke", "stroke-opacity", "stroke-width", + "stroke-linecap", "x1", "y1", "x2", "y2"], + "stop": ["offset", "stop-color", "stop-opacity"], +} + +const propagated_attributes = ["fill", "fill-opacity", "stroke", "stroke-opacity", + "stroke-width", "stroke-linecap", "stroke-linejoin"] + +const attribute_types = { + "viewBox": AttributeType.LIST, + "width": AttributeType.NUMERIC, + "height": AttributeType.NUMERIC, + "x": AttributeType.NUMERIC, + "y": AttributeType.NUMERIC, + "x1": AttributeType.NUMERIC, + "y1": AttributeType.NUMERIC, + "x2": AttributeType.NUMERIC, + "y2": AttributeType.NUMERIC, + "cx": AttributeType.NUMERIC, + "cy": AttributeType.NUMERIC, + "r": AttributeType.NUMERIC, + "rx": AttributeType.NUMERIC, + "ry": AttributeType.NUMERIC, + "opacity": AttributeType.NUMERIC, + "fill": AttributeType.COLOR, + "fill-opacity": AttributeType.NUMERIC, + "stroke": AttributeType.COLOR, + "stroke-opacity": AttributeType.NUMERIC, + "stroke-width": AttributeType.NUMERIC, + "stroke-linecap": AttributeType.ENUM, + "stroke-linejoin": AttributeType.ENUM, + "d": AttributeType.PATHDATA, + "transform": AttributeType.TRANSFORM_LIST, + "offset": AttributeType.NUMERIC, + "stop-color": AttributeType.COLOR, + "stop-opacity": AttributeType.NUMERIC, + "id": AttributeType.ID, + "gradientTransform": AttributeType.TRANSFORM_LIST, + "gradientUnits": AttributeType.ENUM, } const attribute_enum_values = { "stroke-linecap": ["butt", "round", "square"], "stroke-linejoin": ["miter", "round", "bevel"], + "gradientUnits": ["userSpaceOnUse", "objectBoundingBox"] } const attribute_numeric_bounds = { @@ -70,51 +104,43 @@ const attribute_numeric_bounds = { } -static func is_tag_known(tag_name: String) -> bool: - return tag_name in known_tags - -static func is_attribute_known(tag_name: String, attribute_name: String) -> bool: - if not known_tag_attributes.has(tag_name): - return false - return attribute_name in known_tag_attributes[tag_name] +static func is_attribute_recognized(tag_name: String, attribute_name: String) -> bool: + return recognized_attributes.has(tag_name) and\ + attribute_name in recognized_attributes[tag_name] static func get_tag_icon(tag_name: String) -> Texture2D: - match tag_name: - "circle": return TagCircle.icon - "ellipse": return TagEllipse.icon - "rect": return TagRect.icon - "path": return TagPath.icon - "line": return TagLine.icon - "stop": return TagStop.icon - _: return TagUnknown.icon + return tag_icons[tag_name] if tag_icons.has(tag_name) else unrecognized_tag_icon +static func get_attribute_type(attribute_name: String) -> AttributeType: + return attribute_types[attribute_name] if attribute_types.has(attribute_name)\ + else AttributeType.UNKNOWN + +# Creates an attribute with a certain value. static func attribute(name: String, initial_value := "") -> Attribute: - match name: - "viewBox": return AttributeList.new(name, initial_value) - "width": return AttributeNumeric.new(name, initial_value) - "height": return AttributeNumeric.new(name, initial_value) - "x": return AttributeNumeric.new(name, initial_value) - "y": return AttributeNumeric.new(name, initial_value) - "x1": return AttributeNumeric.new(name, initial_value) - "y1": return AttributeNumeric.new(name, initial_value) - "x2": return AttributeNumeric.new(name, initial_value) - "y2": return AttributeNumeric.new(name, initial_value) - "cx": return AttributeNumeric.new(name, initial_value) - "cy": return AttributeNumeric.new(name, initial_value) - "r": return AttributeNumeric.new(name, initial_value) - "rx": return AttributeNumeric.new(name, initial_value) - "ry": return AttributeNumeric.new(name, initial_value) - "opacity": return AttributeNumeric.new(name, initial_value) - "fill": return AttributeColor.new(name, initial_value) - "fill-opacity": return AttributeNumeric.new(name, initial_value) - "stroke": return AttributeColor.new(name, initial_value) - "stroke-opacity": return AttributeNumeric.new(name, initial_value) - "stroke-width": return AttributeNumeric.new(name, initial_value) - "stroke-linecap": return AttributeEnum.new(name, initial_value) - "stroke-linejoin": return AttributeEnum.new(name, initial_value) - "d": return AttributePath.new(name, initial_value) - "transform": return AttributeTransform.new(name, initial_value) - "offset": return AttributeNumeric.new(name, initial_value) - "stop-color": return AttributeColor.new(name, initial_value) - "stop-opacity": return AttributeNumeric.new(name, initial_value) + match get_attribute_type(name): + AttributeType.NUMERIC: return AttributeNumeric.new(name, initial_value) + AttributeType.COLOR: return AttributeColor.new(name, initial_value) + AttributeType.LIST: return AttributeList.new(name, initial_value) + AttributeType.PATHDATA: return AttributePathdata.new(name, initial_value) + AttributeType.ENUM: return AttributeEnum.new(name, initial_value) + AttributeType.TRANSFORM_LIST: return AttributeTransformList.new(name, initial_value) + AttributeType.ID: return AttributeID.new(name, initial_value) _: return Attribute.new(name, initial_value) + +static func tag(name: String, user_setup_value = null) -> Tag: + var new_tag: Tag + match name: + "svg": new_tag = TagSVG.new() + "g": new_tag = TagG.new() + "circle": new_tag = TagCircle.new() + "ellipse": new_tag = TagEllipse.new() + "rect": new_tag = TagRect.new() + "path": new_tag = TagPath.new() + "line": new_tag = TagLine.new() + "linearGradient": new_tag = TagLinearGradient.new() + "radialGradient": new_tag = TagRadialGradient.new() + "stop": new_tag = TagStop.new() + _: new_tag = TagUnrecognized.new(name) + if user_setup_value != null: + new_tag.user_setup(user_setup_value) + return new_tag diff --git a/src/FileUtils.gd b/src/FileUtils.gd index 15b9a20d..58351a15 100644 --- a/src/FileUtils.gd +++ b/src/FileUtils.gd @@ -3,7 +3,7 @@ class_name FileUtils extends RefCounted const GoodFileDialogType = preload("res://src/ui_parts/good_file_dialog.gd") -const AlertDialog := preload("res://src/ui_parts/alert_dialog.tscn") +const AlertDialog = preload("res://src/ui_parts/alert_dialog.tscn") const ImportWarningDialog = preload("res://src/ui_parts/import_warning_dialog.tscn") const GoodFileDialog = preload("res://src/ui_parts/good_file_dialog.tscn") const ExportDialog = preload("res://src/ui_parts/export_dialog.tscn") @@ -42,16 +42,17 @@ quality := 0.8, lossless := true) -> void: static func generate_image_from_tags(upscale_amount := 1.0) -> Image: var export_svg := SVG.root_tag.duplicate() - if export_svg.attributes.viewBox.get_list().is_empty(): - export_svg.attributes.viewBox.set_list([0, 0, export_svg.width, export_svg.height]) + if export_svg.get_attribute_list("viewBox").is_empty(): + export_svg.set_attribute("viewBox", + PackedFloat32Array([0, 0, export_svg.width, export_svg.height])) # First ensure there are dimensions. # Otherwise changing one side could influence the other. - export_svg.attributes.width.set_num(export_svg.width) - export_svg.attributes.height.set_num(export_svg.height) - export_svg.attributes.width.set_num(export_svg.width * upscale_amount) - export_svg.attributes.height.set_num(export_svg.height * upscale_amount) + export_svg.set_attribute("width", export_svg.width) + export_svg.set_attribute("height", export_svg.height) + export_svg.set_attribute("width", export_svg.width * upscale_amount) + export_svg.set_attribute("height", export_svg.height * upscale_amount) var img := Image.new() - img.load_svg_from_string(SVGParser.svg_to_text(export_svg)) + img.load_svg_from_string(SVGParser.root_to_text(export_svg)) img.fix_alpha_edges() # See godot issue 82579. return img @@ -111,12 +112,12 @@ static func open_import_dialog() -> void: HandlerGUI.add_overlay(svg_import_dialog) svg_import_dialog.file_selected.connect(apply_svg_from_path) -static func open_reference_load_dialog() -> void: +static func open_reference_load_dialog(callable: Callable) -> void: if FileUtils._is_native_preferred(): DisplayServer.file_dialog_show(TranslationServer.translate("Load an image file"), Utils.get_last_dir(), "", false, DisplayServer.FILE_DIALOG_MODE_OPEN_FILE, PackedStringArray(["*.png,*.jpeg,*.jpg,*.webp,*.svg"]), - native_reference_image_load) + native_reference_image_load.bind(callable)) # TODO: Add Web Support #elif OS.has_feature("web"): #HandlerGUI.web_load_reference_image() @@ -126,7 +127,7 @@ static func open_reference_load_dialog() -> void: GoodFileDialogType.FileMode.SELECT, PackedStringArray(["png", "jpeg", "jpg", "webp", "svg"])) HandlerGUI.add_overlay(image_import_dialog) - image_import_dialog.file_selected.connect(load_reference_image) + image_import_dialog.file_selected.connect(load_reference_image.bind(callable)) static func native_svg_import(has_selected: bool, files: PackedStringArray, _filter_idx: int) -> void: @@ -134,15 +135,15 @@ _filter_idx: int) -> void: apply_svg_from_path(files[0]) static func native_reference_image_load(has_selected: bool, files: PackedStringArray, -_filter_idx: int) -> void: +_filter_idx: int, callable: Callable) -> void: if has_selected: - load_reference_image(files[0]) + load_reference_image(files[0], callable) -static func load_reference_image(path: String) -> void: +static func load_reference_image(path: String, callable: Callable) -> void: var img = Image.new() img.load(path) img.save_png("user://reference_image.png") - Indications.imported_reference.emit() + callable.call() static func apply_svg_from_path(path: String) -> int: var svg_file := FileAccess.open(path, FileAccess.READ) diff --git a/src/GlobalSettings.gd b/src/GlobalSettings.gd index d2537387..48f8b0e1 100644 --- a/src/GlobalSettings.gd +++ b/src/GlobalSettings.gd @@ -11,11 +11,12 @@ const config_path = "user://config.tres" # Don't have the language setting here, so it's not reset. const default_config = { - "autoformat": { + "formatting": { "general_number_precision": 3, "general_angle_precision": 1, "xml_add_trailing_newline": false, "xml_shorthand_tags": true, + "xml_pretty_formatting": false, "number_enable_autoformatting": false, "number_remove_zero_padding": false, "number_remove_leading_zero": true, @@ -25,13 +26,15 @@ const default_config = { "color_convert_named_to_hex": true, "color_use_shorthand_hex_code": true, "color_use_short_named_colors": false, - "path_compress_numbers": true, - "path_minimize_spacing": true, - "path_remove_spacing_after_flags": false, - "path_remove_consecutive_commands": true, - "transform_compress_numbers": true, - "transform_minimize_spacing": true, - "transform_remove_unnecessary_params": true, + "pathdata_enable_autoformatting": true, + "pathdata_compress_numbers": true, + "pathdata_minimize_spacing": true, + "pathdata_remove_spacing_after_flags": false, + "pathdata_remove_consecutive_commands": true, + "transform_list_enable_autoformatting": true, + "transform_list_compress_numbers": true, + "transform_list_minimize_spacing": true, + "transform_list_remove_unnecessary_params": true, }, "theming": { "highlighting_symbol_color": Color("abc9ff"), @@ -66,6 +69,7 @@ const default_config = { # No way to fetch defaults otherwise. var default_input_events := {} # Dictionary{String: Array[InputEvent]} +# Stores whether the keybinds should be modifiable. const keybinds_dict = { "file": { "import": true, @@ -146,6 +150,7 @@ var general_number_precision := 3 var general_angle_precision := 1 var xml_add_trailing_newline := false var xml_shorthand_tags := true +var xml_pretty_formatting := false var number_enable_autoformatting := false var number_remove_zero_padding := true var number_remove_leading_zero := false @@ -154,13 +159,15 @@ var color_convert_rgb_to_hex := false var color_convert_named_to_hex := true var color_use_shorthand_hex_code := true var color_use_short_named_colors := false -var path_compress_numbers := true -var path_minimize_spacing := true -var path_remove_spacing_after_flags := false -var path_remove_consecutive_commands := true -var transform_compress_numbers := true -var transform_minimize_spacing := true -var transform_remove_unnecessary_params := true +var pathdata_enable_autoformatting := true +var pathdata_compress_numbers := true +var pathdata_minimize_spacing := true +var pathdata_remove_spacing_after_flags := false +var pathdata_remove_consecutive_commands := true +var transform_list_enable_autoformatting := true +var transform_list_compress_numbers := true +var transform_list_minimize_spacing := true +var transform_list_remove_unnecessary_params := true # Theming var highlighting_symbol_color := Color("abc9ff") @@ -291,6 +298,5 @@ func reset_palettes() -> void: # Just a helper. func get_validity_color(error_condition: bool, warning_condition := false) -> Color: - return GlobalSettings.basic_color_error if error_condition else\ - GlobalSettings.basic_color_warning if warning_condition else\ - GlobalSettings.basic_color_valid + return basic_color_error if error_condition else\ + basic_color_warning if warning_condition else basic_color_valid diff --git a/src/HandlerGUI.gd b/src/HandlerGUI.gd index 7e377839..b6a93592 100644 --- a/src/HandlerGUI.gd +++ b/src/HandlerGUI.gd @@ -15,6 +15,7 @@ var popup_overlay_stack: Array[Control] func _enter_tree() -> void: + process_mode = PROCESS_MODE_ALWAYS get_tree().root.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED get_window().files_dropped.connect(_on_files_dropped) get_window().dpi_changed.connect(update_ui_scale) diff --git a/src/Indications.gd b/src/Indications.gd index 25ca7f91..99faf813 100644 --- a/src/Indications.gd +++ b/src/Indications.gd @@ -20,32 +20,30 @@ signal hover_changed signal selection_changed signal proposed_drop_changed -signal requested_scroll_to_tag_editor(tid: PackedInt32Array) +signal requested_scroll_to_tag_editor(xid: PackedInt32Array) # The viewport listens for this signal to put you in handle-placing mode. signal handle_added -signal imported_reference - -# The PackedInt32Array holds the hierarchical orders. TID means Tag ID. +# The PackedInt32Array holds the hierarchical orders. XID means XMLNode ID. # For example, the 5th child of the 2nd child of the root tag would be (1, 4). # PackedInt32Array() means it's invalid. -var hovered_tid := PackedInt32Array() -var selected_tids: Array[PackedInt32Array] = [] -var selection_pivot_tid := PackedInt32Array() +var hovered_xid := PackedInt32Array() +var selected_xids: Array[PackedInt32Array] = [] +var selection_pivot_xid := PackedInt32Array() # Semi-hovered means the tag has inner selections, but it is not selected itself. # For example, individual path commands. # Note that you can't have a selected tag and an inner selection simultaneously! -var semi_hovered_tid := PackedInt32Array() -var semi_selected_tid := PackedInt32Array() +var semi_hovered_xid := PackedInt32Array() +var semi_selected_xid := PackedInt32Array() # Inner stuff aren't in a tree, so they use an int. -1 means invalid. var inner_hovered := -1 var inner_selections: Array[int] = [] var inner_selection_pivot := -1 # When dragging tags in the inspector. -var proposed_drop_tid := PackedInt32Array() +var proposed_drop_xid := PackedInt32Array() signal zoom_changed @@ -66,37 +64,37 @@ func set_viewport_size(new_value: Vector2i) -> void: func _ready() -> void: - SVG.root_tag.tags_added.connect(_on_tags_added) - SVG.root_tag.tags_deleted.connect(_on_tags_deleted) - SVG.root_tag.tags_moved_in_parent.connect(_on_tags_moved_in_parent) - SVG.root_tag.tags_moved_to.connect(_on_tags_moved_to) - SVG.root_tag.changed_unknown.connect(clear_all_selections) + SVG.tags_added.connect(_on_tags_added) + SVG.tags_deleted.connect(_on_tags_deleted) + SVG.tags_moved_in_parent.connect(_on_tags_moved_in_parent) + SVG.tags_moved_to.connect(_on_tags_moved_to) + SVG.changed_unknown.connect(clear_all_selections) # Override the selected tags with a single new selected tag. # If inner_idx is given, this will be an inner selection. -func normal_select(tid: PackedInt32Array, inner_idx := -1) -> void: - if tid.is_empty(): +func normal_select(xid: PackedInt32Array, inner_idx := -1) -> void: + if xid.is_empty(): return if inner_idx == -1: - var old_selected_tids := selected_tids.duplicate() - if not semi_selected_tid.is_empty(): - semi_selected_tid.clear() + var old_selected_xids := selected_xids.duplicate() + if not semi_selected_xid.is_empty(): + semi_selected_xid.clear() inner_selections.clear() - if selected_tids.size() == 1 and selected_tids[0] == tid: + if selected_xids.size() == 1 and selected_xids[0] == xid: return - selection_pivot_tid = tid.duplicate() - selected_tids = [tid.duplicate()] - if old_selected_tids != selected_tids: + selection_pivot_xid = xid.duplicate() + selected_xids = [xid.duplicate()] + if old_selected_xids != selected_xids: selection_changed.emit() else: - selected_tids.clear() + selected_xids.clear() var old_inner_selections := inner_selections.duplicate() - if semi_selected_tid == tid and\ + if semi_selected_xid == xid and\ inner_selections.size() == 1 and inner_selections[0] == inner_idx: return - semi_selected_tid = tid.duplicate() + semi_selected_xid = xid.duplicate() inner_selection_pivot = inner_idx inner_selections = [inner_idx] if inner_selections != old_inner_selections: @@ -104,25 +102,25 @@ func normal_select(tid: PackedInt32Array, inner_idx := -1) -> void: # If the tag was selected, unselect it. If it was unselected, select it. # If inner_idx is given, this will be an inner selection. -func ctrl_select(tid: PackedInt32Array, inner_idx := -1) -> void: - if tid.is_empty(): +func ctrl_select(xid: PackedInt32Array, inner_idx := -1) -> void: + if xid.is_empty(): return if inner_idx == -1: inner_selections.clear() - var tid_idx := selected_tids.find(tid) - if tid_idx == -1: - selection_pivot_tid = tid.duplicate() - selected_tids.append(tid.duplicate()) + var xid_idx := selected_xids.find(xid) + if xid_idx == -1: + selection_pivot_xid = xid.duplicate() + selected_xids.append(xid.duplicate()) else: - selected_tids.remove_at(tid_idx) - if selected_tids.is_empty(): - selection_pivot_tid = PackedInt32Array() + selected_xids.remove_at(xid_idx) + if selected_xids.is_empty(): + selection_pivot_xid = PackedInt32Array() else: - if semi_selected_tid != tid: - normal_select(tid, inner_idx) + if semi_selected_xid != xid: + normal_select(xid, inner_idx) else: - selected_tids.clear() + selected_xids.clear() var idx_idx := inner_selections.find(inner_idx) if idx_idx == -1: inner_selection_pivot = inner_idx @@ -136,47 +134,47 @@ func ctrl_select(tid: PackedInt32Array, inner_idx := -1) -> void: # Select all tags with the same depth from the tag to the last selected tag. # Similarly for inner selections if inner_idx is given, but without tree logic. -func shift_select(tid: PackedInt32Array, inner_idx := -1) -> void: - if tid.is_empty(): +func shift_select(xid: PackedInt32Array, inner_idx := -1) -> void: + if xid.is_empty(): return if inner_idx == -1: - if selection_pivot_tid.is_empty(): - if selected_tids.is_empty(): - normal_select(tid, inner_idx) + if selection_pivot_xid.is_empty(): + if selected_xids.is_empty(): + normal_select(xid, inner_idx) return - if tid == selection_pivot_tid: + if xid == selection_pivot_xid: return - var old_selected_tids := selected_tids.duplicate() + var old_selected_xids := selected_xids.duplicate() - if tid.size() != selection_pivot_tid.size(): - if not tid in selected_tids: - selected_tids.append(tid) + if xid.size() != selection_pivot_xid.size(): + if not xid in selected_xids: + selected_xids.append(xid) selection_changed.emit() return - var parent_tag := tid.duplicate() + var parent_tag := xid.duplicate() parent_tag.resize(parent_tag.size() - 1) - var tid_idx := tid[-1] - var selection_pivot_tid_idx := selection_pivot_tid[-1] + var xid_idx := xid[-1] + var selection_pivot_xid_idx := selection_pivot_xid[-1] - var first_idx := mini(tid_idx, selection_pivot_tid_idx) - var last_idx := maxi(tid_idx, selection_pivot_tid_idx) + var first_idx := mini(xid_idx, selection_pivot_xid_idx) + var last_idx := maxi(xid_idx, selection_pivot_xid_idx) for i in range(first_idx, last_idx + 1): - var new_tid := parent_tag.duplicate() - new_tid.append(i) - if not new_tid in selected_tids: - selected_tids.append(new_tid) + var new_xid := parent_tag.duplicate() + new_xid.append(i) + if not new_xid in selected_xids: + selected_xids.append(new_xid) - if selected_tids == old_selected_tids: + if selected_xids == old_selected_xids: return else: if inner_selection_pivot == -1: if inner_selections.is_empty(): - normal_select(tid, inner_idx) + normal_select(xid, inner_idx) return var old_inner_selections := inner_selections.duplicate() @@ -194,79 +192,80 @@ func shift_select(tid: PackedInt32Array, inner_idx := -1) -> void: # Select all tags. func select_all() -> void: clear_inner_selection() - var tid_list := SVG.root_tag.get_all_tids() - if selected_tids == tid_list: + var tag_list: Array[Tag] = SVG.root_tag.get_all_tags() + var xid_list: Array[PackedInt32Array] = tag_list.map(func(tag): return tag.xid) + if selected_xids == xid_list: return - for tid in SVG.root_tag.get_all_tids(): - if not tid in selected_tids: - selected_tids.append(tid) + for xid in xid_list: + if not xid in selected_xids: + selected_xids.append(xid) selection_changed.emit() # Clear the selected tags. func clear_selection() -> void: - if not selected_tids.is_empty(): - selected_tids.clear() - selection_pivot_tid.clear() + if not selected_xids.is_empty(): + selected_xids.clear() + selection_pivot_xid.clear() selection_changed.emit() # Clear the inner selection. func clear_inner_selection() -> void: - if not inner_selections.is_empty() or not semi_selected_tid.is_empty(): + if not inner_selections.is_empty() or not semi_selected_xid.is_empty(): inner_selections.clear() - semi_selected_tid.clear() + semi_selected_xid.clear() inner_selection_pivot = -1 selection_changed.emit() # Clear the selected tags or the inner selection. func clear_all_selections() -> void: - if not inner_selections.is_empty() or not semi_selected_tid.is_empty() or\ - not selected_tids.is_empty(): - selected_tids.clear() + if not inner_selections.is_empty() or not semi_selected_xid.is_empty() or\ + not selected_xids.is_empty(): + selected_xids.clear() inner_selections.clear() - semi_selected_tid.clear() + semi_selected_xid.clear() selection_changed.emit() # Set the hovered tag. -func set_hovered(tid: PackedInt32Array, inner_idx := -1) -> void: +func set_hovered(xid: PackedInt32Array, inner_idx := -1) -> void: if inner_idx == -1: - if hovered_tid != tid: - hovered_tid = tid.duplicate() - if not tid.is_empty(): + if hovered_xid != xid: + hovered_xid = xid.duplicate() + if not xid.is_empty(): inner_hovered = -1 - semi_hovered_tid = PackedInt32Array() + semi_hovered_xid = PackedInt32Array() hover_changed.emit() else: - if semi_hovered_tid != tid: - semi_hovered_tid = tid.duplicate() + if semi_hovered_xid != xid: + semi_hovered_xid = xid.duplicate() inner_hovered = inner_idx - if not tid.is_empty(): - hovered_tid.clear() + if not xid.is_empty(): + hovered_xid.clear() hover_changed.emit() elif inner_hovered != inner_idx: inner_hovered = inner_idx - if not tid.is_empty(): - hovered_tid.clear() + if not xid.is_empty(): + hovered_xid.clear() hover_changed.emit() # If the tag is hovered, make it not hovered. -func remove_hovered(tid: PackedInt32Array, inner_idx := -1) -> void: +func remove_hovered(xid: PackedInt32Array, inner_idx := -1) -> void: if inner_idx == -1: - if hovered_tid == tid: - hovered_tid.clear() + if hovered_xid == xid: + hovered_xid.clear() hover_changed.emit() else: - if semi_hovered_tid == tid and inner_hovered == inner_idx: - semi_hovered_tid.clear() + if semi_hovered_xid == xid and inner_hovered == inner_idx: + semi_hovered_xid.clear() inner_hovered = -1 hover_changed.emit() # Clear the hovered tag. func clear_hovered() -> void: - if not hovered_tid.is_empty(): - hovered_tid.clear() + if not hovered_xid.is_empty(): + hovered_xid.clear() hover_changed.emit() # Clear the inner hover. @@ -276,108 +275,108 @@ func clear_inner_hovered() -> void: hover_changed.emit() # Returns whether the given tag or inner editor is hovered. -func is_hovered(tid: PackedInt32Array, cmd_idx := -1, propagate := false) -> bool: +func is_hovered(xid: PackedInt32Array, cmd_idx := -1, propagate := false) -> bool: if propagate: if cmd_idx == -1: - return Utils.is_tid_parent_or_self(hovered_tid, tid) + return Utils.is_xid_parent_or_self(hovered_xid, xid) else: - return Utils.is_tid_parent_or_self(hovered_tid, tid) or\ - (semi_hovered_tid == tid and inner_hovered == cmd_idx) + return Utils.is_xid_parent_or_self(hovered_xid, xid) or\ + (semi_hovered_xid == xid and inner_hovered == cmd_idx) else: if cmd_idx == -1: - return hovered_tid == tid + return hovered_xid == xid else: - return semi_hovered_tid == tid and inner_hovered == cmd_idx + return semi_hovered_xid == xid and inner_hovered == cmd_idx # Returns whether the given tag or inner editor is selected. -func is_selected(tid: PackedInt32Array, cmd_idx := -1, propagate := false) -> bool: +func is_selected(xid: PackedInt32Array, cmd_idx := -1, propagate := false) -> bool: if propagate: if cmd_idx == -1: - for selected_tid in selected_tids: - if Utils.is_tid_parent_or_self(selected_tid, tid): + for selected_xid in selected_xids: + if Utils.is_xid_parent_or_self(selected_xid, xid): return true return false else: - for selected_tid in selected_tids: - if Utils.is_tid_parent_or_self(selected_tid, tid): + for selected_xid in selected_xids: + if Utils.is_xid_parent_or_self(selected_xid, xid): return true - return semi_selected_tid == tid and cmd_idx in inner_selections + return semi_selected_xid == xid and cmd_idx in inner_selections else: if cmd_idx == -1: - return tid in selected_tids + return xid in selected_xids else: - return semi_selected_tid == tid and cmd_idx in inner_selections + return semi_selected_xid == xid and cmd_idx in inner_selections -func set_proposed_drop_tid(tid: PackedInt32Array) -> void: - if proposed_drop_tid != tid: - proposed_drop_tid = tid.duplicate() +func set_proposed_drop_xid(xid: PackedInt32Array) -> void: + if proposed_drop_xid != xid: + proposed_drop_xid = xid.duplicate() proposed_drop_changed.emit() -func clear_proposed_drop_tid() -> void: - if not proposed_drop_tid.is_empty(): - proposed_drop_tid.clear() +func clear_proposed_drop_xid() -> void: + if not proposed_drop_xid.is_empty(): + proposed_drop_xid.clear() proposed_drop_changed.emit() -func _on_tags_added(tids: Array[PackedInt32Array]) -> void: - selected_tids = tids.duplicate() +func _on_tags_added(xids: Array[PackedInt32Array]) -> void: + selected_xids = xids.duplicate() # If selected tags were deleted, remove them from the list of selected tags. -func _on_tags_deleted(tids: Array[PackedInt32Array]) -> void: - tids = tids.duplicate() # For some reason, it breaks without this. - var old_selected_tids := selected_tids.duplicate() - for deleted_tid in tids: - for i in range(selected_tids.size() - 1, -1, -1): - var tid := selected_tids[i] - if Utils.is_tid_parent_or_self(deleted_tid, tid): - selected_tids.remove_at(i) - if old_selected_tids != selected_tids: +func _on_tags_deleted(xids: Array[PackedInt32Array]) -> void: + xids = xids.duplicate() # For some reason, it breaks without this. + var old_selected_xids := selected_xids.duplicate() + for deleted_xid in xids: + for i in range(selected_xids.size() - 1, -1, -1): + var xid := selected_xids[i] + if Utils.is_xid_parent_or_self(deleted_xid, xid): + selected_xids.remove_at(i) + if old_selected_xids != selected_xids: selection_changed.emit() -# If selected tags were moved up or down, change the TIDs and their children. -func _on_tags_moved_in_parent(parent_tid: PackedInt32Array, indices: Array[int]) -> void: - var old_selected_tids := selected_tids.duplicate() - var tids_to_select: Array[PackedInt32Array] = [] - var tids_to_unselect: Array[PackedInt32Array] = [] +# If selected tags were moved up or down, change the XIDs and their children. +func _on_tags_moved_in_parent(parent_xid: PackedInt32Array, indices: Array[int]) -> void: + var old_selected_xids := selected_xids.duplicate() + var xids_to_select: Array[PackedInt32Array] = [] + var xids_to_unselect: Array[PackedInt32Array] = [] for index_idx in indices.size(): if index_idx == indices[index_idx]: continue # For the tags that have moved, get their old. - var old_moved_tid := parent_tid.duplicate() - old_moved_tid.append(indices[index_idx]) + var old_moved_xid := parent_xid.duplicate() + old_moved_xid.append(indices[index_idx]) - # If the TID or a child of it is found, append it. - for tid in selected_tids: - if Utils.is_tid_parent_or_self(old_moved_tid, tid): - var new_selected_tid := tid.duplicate() - new_selected_tid[parent_tid.size()] = index_idx - tids_to_unselect.append(tid) - tids_to_select.append(new_selected_tid) - for tid in tids_to_unselect: - selected_tids.erase(tid) - selected_tids += tids_to_select + # If the XID or a child of it is found, append it. + for xid in selected_xids: + if Utils.is_xid_parent_or_self(old_moved_xid, xid): + var new_selected_xid := xid.duplicate() + new_selected_xid[parent_xid.size()] = index_idx + xids_to_unselect.append(xid) + xids_to_select.append(new_selected_xid) + for xid in xids_to_unselect: + selected_xids.erase(xid) + selected_xids += xids_to_select - if old_selected_tids != selected_tids: + if old_selected_xids != selected_xids: selection_changed.emit() -# If selected tags were moved to a location, change the TIDs and their children. -func _on_tags_moved_to(tids: Array[PackedInt32Array], location: PackedInt32Array) -> void: - tids = tids.duplicate() - var new_selected_tids: Array[PackedInt32Array] = [] - for moved_idx in tids.size(): - var moved_tid := tids[moved_idx] - for tid in selected_tids: - if Utils.is_tid_parent_or_self(moved_tid, tid): - var new_location := Utils.get_parent_tid(location) +# If selected tags were moved to a location, change the XIDs and their children. +func _on_tags_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Array) -> void: + xids = xids.duplicate() + var new_selected_xids: Array[PackedInt32Array] = [] + for moved_idx in xids.size(): + var moved_xid := xids[moved_idx] + for xid in selected_xids: + if Utils.is_xid_parent_or_self(moved_xid, xid): + var new_location := Utils.get_parent_xid(location) new_location.append(moved_idx + location[-1]) - for ii in range(moved_tid.size(), tid.size()): - new_location.append(tid[ii]) - new_selected_tids.append(new_location) - if selected_tids != new_selected_tids: - selected_tids = new_selected_tids + for ii in range(moved_xid.size(), xid.size()): + new_location.append(xid[ii]) + new_selected_xids.append(new_location) + if selected_xids != new_selected_xids: + selected_xids = new_selected_xids selection_changed.emit() @@ -385,10 +384,10 @@ func respond_to_key_input(event: InputEventKey) -> void: # Path commands using keys. if inner_selections.is_empty() or event.is_command_or_control_pressed(): # If a single path tag is selected, add the new command at the end. - if selected_tids.size() == 1: - var tag_ref := SVG.root_tag.get_tag(selected_tids[0]) + if selected_xids.size() == 1: + var tag_ref := SVG.root_tag.get_tag(selected_xids[0]) if tag_ref.name == "path": - var path_attrib: AttributePath = tag_ref.attributes.d + var path_attrib: AttributePathdata = tag_ref.get_attribute("d") for action_name in path_actions_dict.keys(): if event.is_action_pressed(action_name): var path_cmd_count := path_attrib.get_command_count() @@ -399,27 +398,25 @@ func respond_to_key_input(event: InputEventKey) -> void: path_attrib.get_command(path_cmd_count - 1) is\ PathCommand.CloseCommand): return - path_attrib.insert_command(path_cmd_count, path_cmd_char, Vector2.ZERO, - Attribute.SyncMode.INTERMEDIATE) - normal_select(selected_tids[0], path_cmd_count) + path_attrib.insert_command(path_cmd_count, path_cmd_char, Vector2.ZERO) + normal_select(selected_xids[0], path_cmd_count) handle_added.emit() break return # If path commands are selected, insert after the last one. for action_name in path_actions_dict.keys(): - var tag_ref := SVG.root_tag.get_tag(semi_selected_tid) + var tag_ref := SVG.root_tag.get_tag(semi_selected_xid) if tag_ref.name == "path": if event.is_action_pressed(action_name): - var path_attrib: AttributePath = tag_ref.attributes.d + var path_attrib: AttributePathdata = tag_ref.get_attribute("d") var path_cmd_char: String = path_actions_dict[action_name] var last_selection: int = inner_selections.max() # Z after a Z is syntactically invalid. if path_attrib.get_command(last_selection) is PathCommand.CloseCommand and\ path_cmd_char in "Zz": return - path_attrib.insert_command(last_selection + 1, path_cmd_char, Vector2.ZERO, - Attribute.SyncMode.INTERMEDIATE) - normal_select(semi_selected_tid, last_selection + 1) + path_attrib.insert_command(last_selection + 1, path_cmd_char, Vector2.ZERO) + normal_select(semi_selected_xid, last_selection + 1) handle_added.emit() break @@ -427,58 +424,58 @@ func respond_to_key_input(event: InputEventKey) -> void: # Operations on selected tags. func delete_selected() -> void: - if not selected_tids.is_empty(): - SVG.root_tag.delete_tags(selected_tids) - elif not inner_selections.is_empty() and not semi_selected_tid.is_empty(): + if not selected_xids.is_empty(): + SVG.root_tag.delete_tags(selected_xids) + elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): inner_selections.sort() inner_selections.reverse() - var tag_ref := SVG.root_tag.get_tag(semi_selected_tid) + var tag_ref := SVG.root_tag.get_tag(semi_selected_xid) match tag_ref.name: - "path": tag_ref.attributes.d.delete_commands(inner_selections) + "path": tag_ref.get_attribute("d").delete_commands(inner_selections) clear_inner_selection() clear_inner_hovered() func move_up_selected() -> void: - SVG.root_tag.move_tags_in_parent(selected_tids, false) + SVG.root_tag.move_tags_in_parent(selected_xids, false) func move_down_selected() -> void: - SVG.root_tag.move_tags_in_parent(selected_tids, true) + SVG.root_tag.move_tags_in_parent(selected_xids, true) -func view_in_list(tid: PackedInt32Array) -> void: - if tid.is_empty(): +func view_in_list(xid: PackedInt32Array) -> void: + if xid.is_empty(): return - requested_scroll_to_tag_editor.emit(tid) + requested_scroll_to_tag_editor.emit(xid) func duplicate_selected() -> void: - SVG.root_tag.duplicate_tags(selected_tids) + SVG.root_tag.duplicate_tags(selected_xids) func insert_inner_after_selection(new_command: String) -> void: - var tag_ref := SVG.root_tag.get_tag(semi_selected_tid) + var tag_ref := SVG.root_tag.get_tag(semi_selected_xid) match tag_ref.name: "path": - var path_attrib: AttributePath = tag_ref.attributes.d + var path_attrib: AttributePathdata = tag_ref.get_attribute("d") var last_selection: int = inner_selections.max() # Z after a Z is syntactically invalid. if path_attrib.get_command(last_selection) is PathCommand.CloseCommand and\ new_command in "Zz": return path_attrib.insert_command(last_selection + 1, new_command) - normal_select(semi_selected_tid, last_selection + 1) + normal_select(semi_selected_xid, last_selection + 1) -enum SelectionContext { +enum Context { VIEWPORT, TAG_EDITOR } -func get_selection_context(popup_method: Callable, context: SelectionContext) -> ContextPopup: +func get_selection_context(popup_method: Callable, context: Context) -> ContextPopup: var btn_arr: Array[Button] = [] - if not selected_tids.is_empty(): - var filtered_tids := Utils.filter_descendant_tids(selected_tids) + if not selected_xids.is_empty(): + var filtered_xids := Utils.filter_descendant_xids(selected_xids) var can_move_down := true var can_move_up := true - for base_tid in filtered_tids: - if not Utils.are_tid_parents_same(base_tid, filtered_tids[0]): + for base_xid in filtered_xids: + if not Utils.are_xid_parents_same(base_xid, filtered_xids[0]): can_move_down = false can_move_up = false break @@ -486,26 +483,26 @@ func get_selection_context(popup_method: Callable, context: SelectionContext) -> if can_move_up or can_move_down: can_move_down = false can_move_up = false - var parent_tid := Utils.get_parent_tid(filtered_tids[0]) - var filtered_count := filtered_tids.size() - var parent_child_count := SVG.root_tag.get_tag(parent_tid).get_child_count() - for base_tid in filtered_tids: - if not can_move_up and base_tid[-1] >= filtered_count: + var parent_xid := Utils.get_parent_xid(filtered_xids[0]) + var filtered_count := filtered_xids.size() + var parent_child_count := SVG.root_tag.get_tag(parent_xid).get_child_count() + for base_xid in filtered_xids: + if not can_move_up and base_xid[-1] >= filtered_count: can_move_up = true - if not can_move_down and base_tid[-1] < parent_child_count - filtered_count: + if not can_move_down and base_xid[-1] < parent_child_count - filtered_count: can_move_down = true - if context == SelectionContext.VIEWPORT: + if context == Context.VIEWPORT: btn_arr.append(ContextPopup.create_button( TranslationServer.translate("View In List"), - view_in_list.bind(selected_tids[0]), false, + view_in_list.bind(selected_xids[0]), false, load("res://visual/icons/ViewInList.svg"))) btn_arr.append(ContextPopup.create_button(TranslationServer.translate("Duplicate"), duplicate_selected, false, load("res://visual/icons/Duplicate.svg"), "duplicate")) - if selected_tids.size() == 1 and not SVG.root_tag.get_tag( - selected_tids[0]).possible_conversions.is_empty(): + if selected_xids.size() == 1 and not SVG.root_tag.get_tag( + selected_xids[0]).possible_conversions.is_empty(): btn_arr.append(ContextPopup.create_button( TranslationServer.translate("Convert To"), popup_convert_to_context.bind(popup_method), false, @@ -524,11 +521,11 @@ func get_selection_context(popup_method: Callable, context: SelectionContext) -> btn_arr.append(ContextPopup.create_button(TranslationServer.translate("Delete"), delete_selected, false, load("res://visual/icons/Delete.svg"), "delete")) - elif not inner_selections.is_empty() and not semi_selected_tid.is_empty(): - if context == SelectionContext.VIEWPORT: + elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): + if context == Context.VIEWPORT: btn_arr.append(ContextPopup.create_button( TranslationServer.translate("View In List"), - view_in_list.bind(semi_selected_tid), false, + view_in_list.bind(semi_selected_xid), false, load("res://visual/icons/ViewInList.svg"))) if inner_selections.size() == 1: btn_arr.append(ContextPopup.create_button( @@ -549,21 +546,21 @@ func get_selection_context(popup_method: Callable, context: SelectionContext) -> func popup_convert_to_context(popup_method: Callable) -> void: # The "Convert To" context popup. - if not selected_tids.is_empty(): + if not selected_xids.is_empty(): var btn_arr: Array[Button] = [] - var tag := SVG.root_tag.get_tag(selected_tids[0]) + var tag := SVG.root_tag.get_tag(selected_xids[0]) for tag_name in tag.possible_conversions: var btn := ContextPopup.create_button(tag_name, convert_selected_tag_to.bind(tag_name), !tag.can_replace(tag_name), - load("res://visual/icons/tag/%s.svg" % tag_name)) + DB.get_tag_icon(tag_name)) btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf")) btn_arr.append(btn) var context_popup := ContextPopup.new() context_popup.setup(btn_arr, true) popup_method.call(context_popup) - elif not inner_selections.is_empty() and not semi_selected_tid.is_empty(): - var cmd_char: String = SVG.root_tag.get_tag(semi_selected_tid).\ - attributes.d.get_command(inner_selections[0]).command_char + elif not inner_selections.is_empty() and not semi_selected_xid.is_empty(): + var cmd_char: String = SVG.root_tag.get_tag(semi_selected_xid).\ + get_attribute("d").get_command(inner_selections[0]).command_char var command_picker := PathCommandPopup.instantiate() popup_method.call(command_picker) command_picker.force_relativity(Utils.is_string_lower(cmd_char)) @@ -571,7 +568,7 @@ func popup_convert_to_context(popup_method: Callable) -> void: command_picker.path_command_picked.connect(convert_selected_command_to) func popup_insert_command_after_context(popup_method: Callable) -> void: - var cmd_char: String = SVG.root_tag.get_tag(semi_selected_tid).attributes.d.\ + var cmd_char: String = SVG.root_tag.get_tag(semi_selected_xid).get_attribute("d").\ get_command(inner_selections.max()).command_char var command_picker := PathCommandPopup.instantiate() @@ -585,10 +582,10 @@ func popup_insert_command_after_context(popup_method: Callable) -> void: "Q", "T": command_picker.disable_invalid(["S"]) func convert_selected_tag_to(tag_name: String) -> void: - var tid := selected_tids[0] - SVG.root_tag.replace_tag(tid, SVG.root_tag.get_tag(tid).get_replacement(tag_name)) + var xid := selected_xids[0] + SVG.root_tag.replace_tag(xid, SVG.root_tag.get_tag(xid).get_replacement(tag_name)) func convert_selected_command_to(cmd_type: String) -> void: - var tag_ref := SVG.root_tag.get_tag(semi_selected_tid) + var tag_ref := SVG.root_tag.get_tag(semi_selected_xid) match tag_ref.name: - "path": tag_ref.attributes.d.convert_command(inner_selections[0], cmd_type) + "path": tag_ref.get_attribute("d").convert_command(inner_selections[0], cmd_type) diff --git a/src/SVG.gd b/src/SVG.gd index d847f3d1..49d95d76 100644 --- a/src/SVG.gd +++ b/src/SVG.gd @@ -2,26 +2,31 @@ # The SVG text, and the native TagSVG representation. extends Node +signal changed_unknown +signal resized + +signal attribute_somewhere_changed(xid: PackedInt32Array) +signal tags_added(xids: Array[PackedInt32Array]) +signal tags_deleted(xids: Array[PackedInt32Array]) +signal tags_moved_in_parent(parent_xid: PackedInt32Array, old_indices: Array[int]) +signal tags_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Array) +signal tag_layout_changed # Emitted together with any of the above 5. + signal parsing_finished(error_id: SVGParser.ParseError) -signal svg_text_changed() -const DEFAULT = '' +const DEFAULT = '' -var text := "": - set(value): - text = value - svg_text_changed.emit() +var text := "" -var root_tag := TagSVG.new() +var root_tag := TagRoot.new() var UR := UndoRedo.new() func _ready() -> void: UR.version_changed.connect(_on_undo_redo) - root_tag.changed_unknown.connect(update_text.bind(false)) - root_tag.attribute_changed.connect(update_text) - root_tag.child_attribute_changed.connect(update_text) - root_tag.tag_layout_changed.connect(update_text) + changed_unknown.connect(update_text.bind(false)) + attribute_somewhere_changed.connect(update_text.unbind(1)) + tag_layout_changed.connect(update_text) var cmdline_args := OS.get_cmdline_args() var load_cmdl := false @@ -43,45 +48,50 @@ func _ready() -> void: UR.clear_history() - func _exit_tree() -> void: UR.free() -func update_tags() -> void: - var svg_parse_result := SVGParser.text_to_svg(text) +func sync() -> void: + var old_size := root_tag.get_size() + var svg_parse_result := SVGParser.text_to_root(text) parsing_finished.emit(svg_parse_result.error) if svg_parse_result.error == SVGParser.ParseError.OK: - root_tag.replace_self(svg_parse_result.svg) + root_tag = svg_parse_result.svg + root_tag.attribute_somewhere_changed.connect(attribute_somewhere_changed.emit) + root_tag.tags_added.connect(tags_added.emit) + root_tag.tags_deleted.connect(tags_deleted.emit) + root_tag.tags_moved_in_parent.connect(tags_moved_in_parent.emit) + root_tag.tags_moved_to.connect(tags_moved_to.emit) + root_tag.tag_layout_changed.connect(tag_layout_changed.emit) + changed_unknown.emit() + if root_tag.get_size() != old_size: + resized.emit() func update_text(undo_redo := true) -> void: if undo_redo: UR.create_action("") - UR.add_do_property(self, "text", SVGParser.svg_to_text(root_tag)) + UR.add_do_property(self, "text", SVGParser.root_to_text(root_tag)) UR.add_undo_property(self, "text", GlobalSettings.save_data.svg_text) UR.commit_action() GlobalSettings.modify_save_data("svg_text", text) else: - text = SVGParser.svg_to_text(root_tag) + text = SVGParser.root_to_text(root_tag) func undo() -> void: if UR.has_undo(): UR.undo() - update_tags() + sync() func redo() -> void: if UR.has_redo(): UR.redo() - update_tags() + sync() func _on_undo_redo() -> void: GlobalSettings.modify_save_data("svg_text", text) - -func refresh() -> void: - SVG.root_tag.replace_self(SVG.root_tag.duplicate()) - -func apply_svg_text(svg_text: String,) -> void: +func apply_svg_text(svg_text: String) -> void: text = svg_text GlobalSettings.modify_save_data("svg_text", text) - update_tags() + sync() diff --git a/src/ThemeGenerator.gd b/src/ThemeGenerator.gd index 46ff40bf..f1493eaa 100644 --- a/src/ThemeGenerator.gd +++ b/src/ThemeGenerator.gd @@ -13,7 +13,6 @@ const common_text_color = Color("ddeeff") const common_subtle_text_color = Color("ffffff55") const common_inner_color_disabled = Color("0e0e12") const common_border_color_disabled = Color("1e1f24") -const common_separator_color = Color("414159", 0.6) const common_button_inner_color_normal = Color("1c1e38") const common_button_border_color_normal = Color("313859") @@ -247,6 +246,8 @@ static func setup_button(theme: Theme) -> void: normal_left_connected_button_stylebox.bg_color = connected_button_inner_color_normal normal_left_connected_button_stylebox.border_color = connected_button_border_color_normal theme.set_stylebox("normal", "LeftConnectedButton", normal_left_connected_button_stylebox) + # Disabled theme is not currently used, but is needed for correct spacing. + theme.set_stylebox("disabled", "LeftConnectedButton", normal_left_connected_button_stylebox) var hover_left_connected_button_stylebox := left_connected_button_stylebox.duplicate() hover_left_connected_button_stylebox.bg_color = connected_button_inner_color_hover @@ -311,6 +312,8 @@ static func setup_button(theme: Theme) -> void: normal_right_connected_button_stylebox.bg_color = connected_button_inner_color_normal normal_right_connected_button_stylebox.border_color = connected_button_border_color_normal theme.set_stylebox("normal", "RightConnectedButton", normal_right_connected_button_stylebox) + # Disabled theme is not currently used, but is needed for correct spacing. + theme.set_stylebox("disabled", "RightConnectedButton", normal_right_connected_button_stylebox) var hover_right_connected_button_stylebox := right_connected_button_stylebox.duplicate() hover_right_connected_button_stylebox.bg_color = connected_button_inner_color_hover @@ -709,6 +712,7 @@ static func setup_lineedit(theme: Theme) -> void: var empty_stylebox := StyleBoxEmpty.new() theme.set_stylebox("hover", "GoodColorPickerLineEdit", empty_stylebox) theme.set_stylebox("focus", "GoodColorPickerLineEdit", empty_stylebox) + theme.set_stylebox("read_only", "GoodColorPickerLineEdit", empty_stylebox) static func setup_scrollbar(theme: Theme) -> void: theme.add_type("HScrollBar") @@ -764,13 +768,14 @@ static func setup_scrollbar(theme: Theme) -> void: static func setup_separator(theme: Theme) -> void: theme.add_type("HSeparator") var stylebox := StyleBoxLine.new() - stylebox.color = common_separator_color + stylebox.color = common_panel_border_color stylebox.thickness = 2 theme.set_stylebox("separator", "HSeparator", stylebox) theme.add_type("SmallHSeparator") theme.set_type_variation("SmallHSeparator", "HSeparator") var small_stylebox := stylebox.duplicate() + small_stylebox.color = Color(common_panel_border_color, 0.5) small_stylebox.grow_begin = -3 small_stylebox.grow_end = -3 theme.set_stylebox("separator", "SmallHSeparator", small_stylebox) diff --git a/src/Utils.gd b/src/Utils.gd index 350dcd8c..fcab334f 100644 --- a/src/Utils.gd +++ b/src/Utils.gd @@ -5,17 +5,15 @@ enum CustomNotification { LANGUAGE_CHANGED = 300, UI_SCALE_CHANGED = 301, THEME_CHANGED = 302, - NUMBER_PRECISION_CHANGED = 303, - HIGHLIGHT_COLORS_CHANGED = 304, - BASIC_COLORS_CHANGED = 305, - HANDLE_VISUALS_CHANGED = 306, + SHORTCUTS_CHANGED = 303, + NUMBER_PRECISION_CHANGED = 304, + HIGHLIGHT_COLORS_CHANGED = 305, + BASIC_COLORS_CHANGED = 306, + HANDLE_VISUALS_CHANGED = 307, } -# Enum with values to be used for set_value() of attribute editors. -# REGULAR means that the attribute will update if the new value is different. -# INTERMEDIATE and FINAL cause the attribute update to have the corresponding sync mode. -# FINAL also causes the equivalence check to be skipped. -enum UpdateType {REGULAR, INTERMEDIATE, FINAL} +static func custom_notify(notif: CustomNotification) -> void: + Engine.get_main_loop().get_root().propagate_notification(notif) enum InteractionType {NONE = 0, HOVERED = 1, SELECTED = 2, HOVERED_SELECTED = 3} @@ -53,6 +51,16 @@ cp3: Vector2) -> PackedVector2Array: return Utils.get_cubic_bezier_points( cp1, 2/3.0 * (cp2 - cp1), 2/3.0 * (cp2 - cp3), cp3) +# Calculate quadratic bezier point coordinate along an axis. +static func quadratic_bezier_point(p0: float, p1: float, p2: float, t: float) -> float: + var u := 1.0 - t + return u * u * p0 + 2 * u * t * p1 + t * t * p2 + +# Calculate cubic bezier point coordinate along an axis. +static func cubic_bezier_point(p0: float, p1: float, p2: float, p3: float, t: float) -> float: + var u := 1.0 - t + return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3 + # Ellipse parametric equation. static func E(c: Vector2, r: Vector2, cosine: float, sine: float, t: float) -> Vector2: var xt := r.x * cos(t) @@ -67,20 +75,20 @@ static func Et(r: Vector2, cosine: float, sine: float, t: float) -> Vector2: # [1] > [1, 2] > [1, 0] > [0] -static func compare_tids(tid1: PackedInt32Array, tid2: PackedInt32Array) -> bool: - var smaller_tid_size := mini(tid1.size(), tid2.size()) - for i in smaller_tid_size: - if tid1[i] < tid2[i]: +static func compare_xids(xid1: PackedInt32Array, xid2: PackedInt32Array) -> bool: + var smaller_xid_size := mini(xid1.size(), xid2.size()) + for i in smaller_xid_size: + if xid1[i] < xid2[i]: return true - elif tid1[i] > tid2[i]: + elif xid1[i] > xid2[i]: return false - return tid1.size() > smaller_tid_size + return xid1.size() > smaller_xid_size -static func compare_tids_r(tid1: PackedInt32Array, tid2: PackedInt32Array) -> bool: - return compare_tids(tid2, tid1) +static func compare_xids_r(xid1: PackedInt32Array, xid2: PackedInt32Array) -> bool: + return compare_xids(xid2, xid1) # Indirect parent, i.e. ancestor. Passing the root tag as parent will return false. -static func is_tid_parent(parent: PackedInt32Array, child: PackedInt32Array) -> bool: +static func is_xid_parent(parent: PackedInt32Array, child: PackedInt32Array) -> bool: if parent.is_empty(): return false var parent_size := parent.size() @@ -92,38 +100,38 @@ static func is_tid_parent(parent: PackedInt32Array, child: PackedInt32Array) -> return false return true -static func is_tid_parent_or_self(parent: PackedInt32Array, +static func is_xid_parent_or_self(parent: PackedInt32Array, child: PackedInt32Array) -> bool: - return is_tid_parent(parent, child) or parent == child + return is_xid_parent(parent, child) or parent == child -static func get_parent_tid(tid: PackedInt32Array) -> PackedInt32Array: - var parent_tid := tid.duplicate() - parent_tid.resize(tid.size() - 1) - return parent_tid +static func get_parent_xid(xid: PackedInt32Array) -> PackedInt32Array: + var parent_xid := xid.duplicate() + parent_xid.resize(xid.size() - 1) + return parent_xid -static func are_tid_parents_same(tid1: PackedInt32Array, tid2: PackedInt32Array) -> bool: - if tid1.size() != tid2.size(): +static func are_xid_parents_same(xid1: PackedInt32Array, xid2: PackedInt32Array) -> bool: + if xid1.size() != xid2.size(): return false - for i in tid1.size() - 1: - if tid1[i] != tid2[i]: + for i in xid1.size() - 1: + if xid1[i] != xid2[i]: return false return true # Filter out all descendants. -static func filter_descendant_tids(tids: Array[PackedInt32Array]) -> Array[PackedInt32Array]: - var new_tids: Array[PackedInt32Array] = tids.duplicate() - new_tids.sort_custom(Utils.compare_tids_r) +static func filter_descendant_xids(xids: Array[PackedInt32Array]) -> Array[PackedInt32Array]: + var new_xids: Array[PackedInt32Array] = xids.duplicate() + new_xids.sort_custom(Utils.compare_xids_r) # Linear scan to filter out the descendants. - var last_accepted := new_tids[0] + var last_accepted := new_xids[0] var i := 1 - while i < new_tids.size(): - var tid := new_tids[i] - if Utils.is_tid_parent_or_self(last_accepted, tid): - new_tids.remove_at(i) + while i < new_xids.size(): + var xid := new_xids[i] + if Utils.is_xid_parent_or_self(last_accepted, xid): + new_xids.remove_at(i) else: - last_accepted = new_tids[i] + last_accepted = new_xids[i] i += 1 - return new_tids + return new_xids static func is_event_drag(event: InputEvent) -> bool: diff --git a/src/data_classes/Attribute.gd b/src/data_classes/Attribute.gd index 2781188f..6f13836b 100644 --- a/src/data_classes/Attribute.gd +++ b/src/data_classes/Attribute.gd @@ -2,58 +2,34 @@ # If the Attribute's data type is known, one of the inheriting classes should be used. class_name Attribute extends RefCounted -signal value_changed(new_value: String) -signal propagate_value_changed(undo_redo: bool) +signal value_changed(changed: bool, save_changed: bool) var name: String var _value: String +var _saved_value: String # Value that is stored in UndoRedo. -enum SyncMode {LOUD, INTERMEDIATE, FINAL, NO_PROPAGATION, SILENT} - -# LOUD means the attribute will emit value_changed and be noticed everywhere. - -# INTERMEDIATE is the same as LOUD, but doesn't create an UndoRedo action. -# Can be used to update an attribute continuously (i.e. dragging a color). - -# FINAL is the same as LOUD, but it runs even if the new value is the same. -# This can be used to force an UndoRedo action after some intermediate changes. -# Note that the attribute is not responsible for making sure the new value is -# different from the previous one in the UndoRedo, this must be handled in the widgets. - -# NO_PROPAGATION means the tag won't learn about it. This can allow the attribute change -# to be noted by an attribute editor without the SVG text being updated. -# This can be used, for example, to update two attributes corresponding to 2D coordinates -# without the first one causing an update to the SVG text. - -# SILENT means the attribute update is ignored fully. It only makes sense -# if there is logic for updating the corresponding attribute editor despite that. - -func set_value(new_value: String, sync_mode := SyncMode.LOUD) -> void: +func set_value(new_value: String, save := true) -> void: var proposed_new_value := format(new_value) - if proposed_new_value != _value or sync_mode == SyncMode.FINAL: + var changed := false + var saved_changed := false + if proposed_new_value != _value: _value = proposed_new_value + changed = true _sync() - if sync_mode != SyncMode.SILENT: - value_changed.emit(proposed_new_value) - if sync_mode != SyncMode.NO_PROPAGATION: - propagate_value_changed.emit(sync_mode != SyncMode.INTERMEDIATE) + if save and proposed_new_value != _saved_value: + _saved_value = proposed_new_value + saved_changed = true + value_changed.emit(changed, saved_changed) func get_value() -> String: return _value -# Override these functions in extending classes to update the non-string representation. func _sync() -> void: - return + pass func format(text: String) -> String: return text -func get_default() -> String: - if name in DB.attribute_defaults: - return DB.attribute_defaults[name] - else: - return "" - func _init(new_name: String, init_value := "") -> void: name = new_name - set_value(init_value, SyncMode.FINAL) + set_value(init_value) diff --git a/src/data_classes/AttributeColor.gd b/src/data_classes/AttributeColor.gd index 1a69d101..07570f42 100644 --- a/src/data_classes/AttributeColor.gd +++ b/src/data_classes/AttributeColor.gd @@ -3,24 +3,17 @@ class_name AttributeColor extends Attribute # No direct color representation for this attribute type. There are too many quirks. -func set_value(new_value: String, sync_mode := SyncMode.LOUD) -> void: - super(new_value if (new_value.is_empty() or ColorParser.is_valid(new_value))\ - else get_default(), sync_mode) +func set_value(new_value: String, save := true) -> void: + super(new_value if ColorParser.is_valid(new_value) else "", save) func format(text: String) -> String: if GlobalSettings.color_enable_autoformatting: - var new_text := ColorParser.format_text(text) - return get_default() if ColorParser.are_colors_same(new_text, get_default()) else\ - new_text + return ColorParser.format_text(text) else: return text - func get_color() -> Color: - if _value.is_empty(): - return ColorParser.string_to_color(DB.attribute_defaults[name]) - else: - return ColorParser.string_to_color(_value) + return ColorParser.string_to_color(_value) const special_colors = ["none", "currentColor"] diff --git a/src/data_classes/AttributeEnum.gd b/src/data_classes/AttributeEnum.gd index 3aba6ee3..9806e2ba 100644 --- a/src/data_classes/AttributeEnum.gd +++ b/src/data_classes/AttributeEnum.gd @@ -1,8 +1,6 @@ # An attribute with only a set of meaningful values. class_name AttributeEnum extends Attribute -func set_value(new_value: String, sync_mode := SyncMode.LOUD) -> void: - if new_value.is_empty() or new_value in DB.attribute_enum_values[name]: - super(new_value, sync_mode) - else: - super(get_default(), sync_mode) +func set_value(new_value: String, save := true) -> void: + super(new_value if (new_value.is_empty() or\ + new_value in DB.attribute_enum_values[name]) else "", save) diff --git a/src/data_classes/AttributeID.gd b/src/data_classes/AttributeID.gd new file mode 100644 index 00000000..f955082a --- /dev/null +++ b/src/data_classes/AttributeID.gd @@ -0,0 +1,6 @@ +# An attribute representing an element's id +class_name AttributeID extends Attribute + +func set_value(new_value: String, save := true) -> void: + super(new_value if IDParser.get_validity(new_value) != IDParser.ValidityLevel.INVALID\ + else "", save) diff --git a/src/data_classes/AttributeList.gd b/src/data_classes/AttributeList.gd index 21784376..edd2305e 100644 --- a/src/data_classes/AttributeList.gd +++ b/src/data_classes/AttributeList.gd @@ -6,9 +6,9 @@ var _list: PackedFloat32Array func _sync() -> void: _list = ListParser.string_to_list(get_value()) -func set_list(new_list: PackedFloat32Array, sync_mode := SyncMode.LOUD) -> void: +func set_list(new_list: PackedFloat32Array, save := true) -> void: _list = new_list - super.set_value(ListParser.list_to_string(new_list), sync_mode) + super.set_value(ListParser.list_to_string(new_list), save) func get_list() -> PackedFloat32Array: return _list @@ -17,14 +17,14 @@ func get_list_size() -> int: return _list.size() # Just a helper to handle Rect2. -func set_rect(new_rect: Rect2, sync_mode := SyncMode.LOUD) -> void: +func set_rect(new_rect: Rect2, save := true) -> void: set_list(PackedFloat32Array([new_rect.position.x, new_rect.position.y, - new_rect.size.x, new_rect.size.y]), sync_mode) + new_rect.size.x, new_rect.size.y]), save) -func set_list_element(idx: int, new_value: float, sync_mode := SyncMode.LOUD) -> void: +func set_list_element(idx: int, new_value: float, save := true) -> void: _list[idx] = new_value - set_value(ListParser.list_to_string(_list), sync_mode) + set_value(ListParser.list_to_string(_list), save) func get_list_element(idx: int) -> float: return _list[idx] if idx < _list.size() else NAN diff --git a/src/data_classes/AttributeNumeric.gd b/src/data_classes/AttributeNumeric.gd index ef9f4d7f..10ce298a 100644 --- a/src/data_classes/AttributeNumeric.gd +++ b/src/data_classes/AttributeNumeric.gd @@ -4,9 +4,7 @@ class_name AttributeNumeric extends Attribute var _number := NAN func _sync() -> void: - if _value.is_empty(): - _number = NumberParser.text_to_num(DB.attribute_defaults[name]) - else: + if not _value.is_empty(): _number = NumberParser.text_to_num(_value) func format(text: String) -> String: @@ -15,10 +13,10 @@ func format(text: String) -> String: else: return text -func set_num(new_number: float, sync_mode := SyncMode.LOUD) -> void: +func set_num(new_number: float, save := true) -> void: _number = new_number super.set_value(NumberParser.num_to_text(new_number) if is_finite(_number) else "", - sync_mode) + save) func get_num() -> float: return _number diff --git a/src/data_classes/AttributePath.gd b/src/data_classes/AttributePathdata.gd similarity index 78% rename from src/data_classes/AttributePath.gd rename to src/data_classes/AttributePathdata.gd index 2ca375aa..185337bb 100644 --- a/src/data_classes/AttributePath.gd +++ b/src/data_classes/AttributePathdata.gd @@ -1,22 +1,25 @@ # The "d" attribute of [TagPath]. -class_name AttributePath extends Attribute +class_name AttributePathdata extends Attribute var _commands: Array[PathCommand] func _sync() -> void: - _commands = PathDataParser.parse_path_data(get_value()) + _commands = PathdataParser.parse_pathdata(get_value()) locate_start_points() func autoformat(text: String) -> String: - return PathDataParser.path_commands_to_text(PathDataParser.parse_path_data(text)) + return PathdataParser.path_commands_to_text(PathdataParser.parse_pathdata(text)) -func set_commands(new_commands: Array[PathCommand], sync_mode := SyncMode.LOUD) -> void: +func get_commands() -> Array[PathCommand]: + return _commands + +func set_commands(new_commands: Array[PathCommand], save := true) -> void: _commands = new_commands - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) -func sync_after_commands_change(sync_mode := SyncMode.LOUD) -> void: - set_value(PathDataParser.path_commands_to_text(_commands), sync_mode) +func sync_after_commands_change(save := true) -> void: + set_value(PathdataParser.path_commands_to_text(_commands), save) func locate_start_points() -> void: @@ -113,51 +116,50 @@ func get_implied_T_control(idx: int) -> Vector2: func set_command_property(idx: int, property: String, new_value: float, -sync_mode := SyncMode.LOUD) -> void: +save := true) -> void: var cmd := get_command(idx) - if cmd.get(property) != new_value or sync_mode == SyncMode.FINAL: + if cmd.get(property) != new_value: cmd.set(property, new_value) - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) -func insert_command(idx: int, command_char: String, vec := Vector2.ZERO, -sync_mode := SyncMode.LOUD) -> void: - var new_cmd: PathCommand = PathCommand.translation_dict[command_char.to_upper()].new() - var relative := Utils.is_string_lower(command_char) +func insert_command(idx: int, cmd_char: String, vec := Vector2.ZERO, save := true) -> void: + var new_cmd: PathCommand = PathCommand.translation_dict[cmd_char.to_upper()].new() + var relative := Utils.is_string_lower(cmd_char) if relative: new_cmd.toggle_relative() _commands.insert(idx, new_cmd) locate_start_points() - if not command_char in "Zz": - if not command_char in "Vv": + if not cmd_char in "Zz": + if not cmd_char in "Vv": new_cmd.x = vec.x - if not command_char in "Hh": + if not cmd_char in "Hh": new_cmd.y = vec.y - if command_char in "Qq": + if cmd_char in "Qq": new_cmd.x1 = lerpf(0.0 if relative else new_cmd.start.x, vec.x, 0.5) new_cmd.y1 = lerpf(0.0 if relative else new_cmd.start.y, vec.y, 0.5) - elif command_char in "Ss": + elif cmd_char in "Ss": new_cmd.x2 = lerpf(0.0 if relative else new_cmd.start.x, vec.x, 2/3.0) new_cmd.y2 = lerpf(0.0 if relative else new_cmd.start.y, vec.y, 2/3.0) - elif command_char in "Cc": + elif cmd_char in "Cc": new_cmd.x1 = lerpf(0.0 if relative else new_cmd.start.x, vec.x, 1/3.0) new_cmd.y1 = lerpf(0.0 if relative else new_cmd.start.y, vec.y, 1/3.0) new_cmd.x2 = lerpf(0.0 if relative else new_cmd.start.x, vec.x, 2/3.0) new_cmd.y2 = lerpf(0.0 if relative else new_cmd.start.y, vec.y, 2/3.0) - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) -func convert_command(idx: int, command_char: String, sync_mode := SyncMode.LOUD) -> void: +func convert_command(idx: int, cmd_char: String, save := true) -> void: var old_cmd := get_command(idx) - if old_cmd.command_char == command_char: + if old_cmd.command_char == cmd_char: return - var cmd_absolute_char := command_char.to_upper() + var cmd_absolute_char := cmd_char.to_upper() var new_cmd: PathCommand = PathCommand.translation_dict[cmd_absolute_char].new() for property in ["x", "y", "x1", "y1", "x2", "y2"]: if property in old_cmd and property in new_cmd: new_cmd[property] = old_cmd[property] - var relative := Utils.is_string_lower(command_char) + var relative := Utils.is_string_lower(cmd_char) if "x" in new_cmd and not "x" in old_cmd: new_cmd.x = 0.0 if relative else old_cmd.start.x @@ -192,10 +194,10 @@ func convert_command(idx: int, command_char: String, sync_mode := SyncMode.LOUD) _commands.insert(idx, new_cmd) if relative: _commands[idx].toggle_relative() - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) -func delete_commands(indices: Array[int], sync_mode := SyncMode.LOUD) -> void: +func delete_commands(indices: Array[int], save := true) -> void: if indices.is_empty(): return @@ -204,8 +206,8 @@ func delete_commands(indices: Array[int], sync_mode := SyncMode.LOUD) -> void: indices.reverse() for idx in indices: _commands.remove_at(idx) - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) -func toggle_relative_command(idx: int, sync_mode := SyncMode.LOUD) -> void: +func toggle_relative_command(idx: int, save := true) -> void: _commands[idx].toggle_relative() - sync_after_commands_change(sync_mode) + sync_after_commands_change(save) diff --git a/src/data_classes/AttributeTransform.gd b/src/data_classes/AttributeTransform.gd deleted file mode 100644 index 10911659..00000000 --- a/src/data_classes/AttributeTransform.gd +++ /dev/null @@ -1,141 +0,0 @@ -# An attribute representing a list of transformations. -class_name AttributeTransform extends Attribute - -class Transform extends RefCounted: - func compute_transform() -> Transform2D: - return Transform2D.IDENTITY - -class TransformMatrix extends Transform: - var x1: float - var x2: float - var y1: float - var y2: float - var o1: float - var o2: float - - func _init(new_x1: float, new_x2: float, new_y1: float, new_y2: float, new_o1: float, - new_o2: float) -> void: - x1 = new_x1 - x2 = new_x2 - y1 = new_y1 - y2 = new_y2 - o1 = new_o1 - o2 = new_o2 - - func compute_transform() -> Transform2D: - return Transform2D(Vector2(x1, x2), Vector2(y1, y2), Vector2(o1, o2)) - -class TransformTranslate extends Transform: - var x: float - var y: float - - func _init(new_x: float, new_y: float) -> void: - x = new_x - y = new_y - - func compute_transform() -> Transform2D: - return Transform2D(Vector2.RIGHT, Vector2.DOWN, Vector2(x, y)) - -class TransformRotate extends Transform: - var deg: float - var x: float - var y: float - - func _init(new_deg: float, new_x: float, new_y: float) -> void: - deg = new_deg - x = new_x - y = new_y - - func compute_transform() -> Transform2D: - var pt := Vector2(x, y) - return Transform2D.IDENTITY.translated(-pt).rotated(deg_to_rad(deg)).translated(pt) - -class TransformScale extends Transform: - var x: float - var y: float - - func _init(new_x: float, new_y: float) -> void: - x = new_x - y = new_y - - func compute_transform() -> Transform2D: - return Transform2D(Vector2.RIGHT * x, Vector2.DOWN * y, Vector2.ZERO) - -class TransformSkewX extends Transform: - var x: float - - func _init(new_x: float) -> void: - x = new_x - - func compute_transform() -> Transform2D: - return Transform2D(Vector2.RIGHT, Vector2(tan(deg_to_rad(x)), 1), Vector2.ZERO) - -class TransformSkewY extends Transform: - var y: float - - func _init(new_y: float) -> void: - y = new_y - - func compute_transform() -> Transform2D: - return Transform2D(Vector2(1, tan(deg_to_rad(y))), Vector2.DOWN, Vector2.ZERO) - - -var _transform_list: Array[Transform] = [] -var _final_transform := Transform2D.IDENTITY - -func _sync() -> void: - _transform_list = TransformListParser.text_to_transform_list(get_value()) - _final_transform = AttributeTransform.compute_final_transform(_transform_list) - -func sync_after_transforms_change(sync_mode := SyncMode.LOUD) -> void: - super.set_value(TransformListParser.transform_list_to_text(_transform_list), sync_mode) - -func format(text: String) -> String: - return TransformListParser.transform_list_to_text( - TransformListParser.text_to_transform_list(text)) - -func set_transform_list(new_transform_list: Array[Transform], -sync_mode := SyncMode.LOUD) -> void: - _transform_list = new_transform_list - _final_transform = AttributeTransform.compute_final_transform(new_transform_list) - super.set_value(TransformListParser.transform_list_to_text(new_transform_list), - sync_mode) - -func set_transform_property(idx: int, property: String, new_value: float, -sync_mode := SyncMode.LOUD) -> void: - if _transform_list[idx].get(property) != new_value or sync_mode == SyncMode.FINAL: - _transform_list[idx].set(property, new_value) - sync_after_transforms_change(sync_mode) - -func get_transform_list() -> Array[Transform]: - return _transform_list - -func get_transform_count() -> int: - return _transform_list.size() - -func get_transform(idx: int) -> Transform: - return _transform_list[idx] - -func get_final_transform() -> Transform2D: - return _final_transform - - -static func compute_final_transform(transform_list: Array[Transform]) -> Transform2D: - var final_transform := Transform2D.IDENTITY - for t in transform_list: - final_transform *= t.compute_transform() - return final_transform - -func delete_transform(idx: int) -> void: - _transform_list.remove_at(idx) - sync_after_transforms_change() - -func insert_transform(idx: int, type: String) -> void: - match type: - "matrix": _transform_list.insert(idx, TransformMatrix.new(1, 0, 0, 1, 0, 0)) - "translate": _transform_list.insert(idx, TransformTranslate.new(0, 0)) - "rotate": _transform_list.insert(idx, TransformRotate.new(0, 0, 0)) - "scale": _transform_list.insert(idx, TransformScale.new(1, 1)) - "skewX": _transform_list.insert(idx, TransformSkewX.new(0)) - "skewY": _transform_list.insert(idx, TransformSkewY.new(0)) - sync_after_transforms_change() diff --git a/src/data_classes/AttributeTransformList.gd b/src/data_classes/AttributeTransformList.gd new file mode 100644 index 00000000..b3215734 --- /dev/null +++ b/src/data_classes/AttributeTransformList.gd @@ -0,0 +1,60 @@ +# An attribute representing a list of transformations. +class_name AttributeTransformList extends Attribute + +var _transform_list: Array[Transform] = [] +var _final_transform := Transform2D.IDENTITY + +func _sync() -> void: + _transform_list = TransformListParser.text_to_transform_list(get_value()) + _final_transform = compute_final_transform(_transform_list) + +func sync_after_transforms_change(save := true) -> void: + super.set_value(TransformListParser.transform_list_to_text(_transform_list), save) + +func format(text: String) -> String: + return TransformListParser.transform_list_to_text( + TransformListParser.text_to_transform_list(text)) + +func set_transform_list(new_transform_list: Array[Transform], save := true) -> void: + _transform_list = new_transform_list + _final_transform = compute_final_transform(new_transform_list) + super.set_value(TransformListParser.transform_list_to_text(new_transform_list), save) + +func set_transform_property(idx: int, property: String, new_value: float, +save := true) -> void: + if _transform_list[idx].get(property) != new_value or save: + _transform_list[idx].set(property, new_value) + sync_after_transforms_change(save) + +func get_transform_list() -> Array[Transform]: + return _transform_list + +func get_transform_count() -> int: + return _transform_list.size() + +func get_transform(idx: int) -> Transform: + return _transform_list[idx] + +func get_final_transform() -> Transform2D: + return _final_transform + + +static func compute_final_transform(transform_list: Array[Transform]) -> Transform2D: + var final_transform := Transform2D.IDENTITY + for t in transform_list: + final_transform *= t.compute_transform() + return final_transform + +func delete_transform(idx: int) -> void: + _transform_list.remove_at(idx) + sync_after_transforms_change() + +func insert_transform(idx: int, type: String) -> void: + match type: + "matrix": _transform_list.insert(idx, Transform.TransformMatrix.new(1, 0, 0, 1, 0, 0)) + "translate": _transform_list.insert(idx, Transform.TransformTranslate.new(0, 0)) + "rotate": _transform_list.insert(idx, Transform.TransformRotate.new(0, 0, 0)) + "scale": _transform_list.insert(idx, Transform.TransformScale.new(1, 1)) + "skewX": _transform_list.insert(idx, Transform.TransformSkewX.new(0)) + "skewY": _transform_list.insert(idx, Transform.TransformSkewY.new(0)) + sync_after_transforms_change() diff --git a/src/data_classes/Tag.gd b/src/data_classes/Tag.gd index e266bbb4..f7ed8a49 100644 --- a/src/data_classes/Tag.gd +++ b/src/data_classes/Tag.gd @@ -1,54 +1,149 @@ # An SVG tag, standalone () or container (). -class_name Tag extends RefCounted +class_name Tag extends XNode -var child_tags: Array[Tag] +signal attribute_changed(name: String) +signal ancestor_attribute_changed(name: String) -signal attribute_changed(undo_redo: bool) +var parent: Tag = null +var svg: TagSVG = null +var root: TagRoot = null +var child_tags: Array[Tag] var attributes: Dictionary # Dictionary{String: Attribute} -# Attributes that aren't recognized (usually because GodSVG doesn't support them). -var unknown_attributes: Array[Attribute] +func _init() -> void: + attribute_changed.connect(_on_attribute_changed) + ancestor_attribute_changed.connect(_on_ancestor_attribute_changed) + +func _on_attribute_changed(attribute_name: String) -> void: + for child_tag in child_tags: + child_tag.ancestor_attribute_changed.emit(attribute_name) + if root != null: + root.attribute_somewhere_changed.emit(xid) + +func _on_ancestor_attribute_changed(attribute_name: String) -> void: + for child_tag in child_tags: + child_tag.ancestor_attribute_changed.emit(attribute_name) + +func _notification(what: int) -> void: + if what == NOTIFICATION_POSTINITIALIZE: + root.attribute_somewhere_changed.connect(update_cache.unbind(1)) + update_cache() func is_standalone() -> bool: return child_tags.is_empty() -func _init() -> void: - for attribute: Attribute in attributes.values(): - attribute.propagate_value_changed.connect(emit_attribute_changed) +func get_child_count() -> int: + return child_tags.size() func user_setup(_what = null) -> void: - printerr("Unimplemented function") - breakpoint + return -func set_unknown_attributes(attribs: Array[Attribute]) -> void: - unknown_attributes = attribs.duplicate() - for attribute in unknown_attributes: - attribute.propagate_value_changed.connect(emit_attribute_changed) -func emit_attribute_changed(undo_redo: bool) -> void: - attribute_changed.emit(undo_redo) +func get_attribute(attribute_name: String) -> Attribute: + if attributes.has(attribute_name): + return attributes[attribute_name] + elif DB.propagated_attributes.has(attribute_name): + if is_parent_g(): + return parent.get_attribute(attribute_name) + elif svg != null: + return svg.get_attribute(attribute_name) + return DB.attribute(attribute_name) -func get_child_count() -> int: - return child_tags.size() + +func get_attribute_value(attribute_name: String, only_self := true) -> String: + if only_self and not attributes.has(attribute_name): + return "" + return get_attribute(attribute_name).get_value() + +func get_attribute_num(attribute_name: String) -> float: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.NUMERIC: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_num() + +func get_attribute_rect(attribute_name: String) -> float: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.LIST: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_rect() + +func get_attribute_list(attribute_name: String) -> PackedFloat32Array: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.LIST: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_list() + +func get_attribute_commands(attribute_name: String) -> Array[PathCommand]: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.PATHDATA: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_commands() + +func get_attribute_transforms(attribute_name: String) -> Array[Transform]: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.TRANSFORM_LIST: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_transform_list() + +func get_attribute_final_transform(attribute_name: String) -> Transform2D: + if DB.get_attribute_type(attribute_name) != DB.AttributeType.TRANSFORM_LIST: + push_error("Attribute not the correct type.") + return get_attribute(attribute_name).get_final_transform() + + +func set_attribute(attribute_name: String, value: Variant, save := true) -> void: + var has_attribute := attributes.has(attribute_name) + + var attrib: Attribute + if has_attribute: + attrib = get_attribute(attribute_name) + else: + attrib = DB.attribute(attribute_name) + attrib.value_changed.connect(update_cache.unbind(2)) + attrib.value_changed.connect(attribute_changed.emit.bind(attribute_name).unbind(2)) + + var value_type := typeof(value) + + if value_type == TYPE_STRING: + attrib.set_value(value, save) + else: + match DB.get_attribute_type(attribute_name): + DB.AttributeType.NUMERIC: + if value_type in [TYPE_FLOAT, TYPE_INT]: attrib.set_num(value, save) + else: push_error("Invalid value set to attribute.") + DB.AttributeType.LIST: + if value_type in [TYPE_RECT2, TYPE_RECT2I]: attrib.set_rect(value, save) + elif value_type == TYPE_PACKED_FLOAT32_ARRAY: attrib.set_list(value, save) + else: push_error("Invalid value set to attribute.") + DB.AttributeType.PATHDATA: + if value_type == TYPE_ARRAY: attrib.set_commands(value, save) + else: push_error("Invalid value set to attribute.") + DB.AttributeType.TRANSFORM_LIST: + if value_type == TYPE_ARRAY: attrib.set_commands(value, save) + else: push_error("Invalid value set to attribute.") + _: push_error("Invalid value set to attribute.") + + if has_attribute and attrib.get_value().is_empty(): + attributes.erase(attribute_name) + elif not has_attribute and not attrib.get_value().is_empty(): + attributes[attribute_name] = attrib + + +func get_default(attribute_name: String) -> String: + if attribute_name in DB.propagated_attributes: + if is_parent_g(): + return parent.get_default(attribute_name) + elif svg != null: + return svg.get_default(attribute_name) + return get_own_default(attribute_name) # Why is there no way to duplicate RefCounteds, again? func duplicate(include_children := true) -> Tag: var type: GDScript = get_script() var new_tag: Tag - if type == TagUnknown: - new_tag = TagUnknown.new(self.name) + if type == TagUnrecognized: + new_tag = TagUnrecognized.new(self.name) else: new_tag = type.new() - for attribute in new_tag.attributes: - new_tag.attributes[attribute].set_value(attributes[attribute].get_value()) - var unknown_attributes_array: Array[Attribute] = [] - for attribute in unknown_attributes: - var new_attrib := Attribute.new(attribute.name) - new_attrib.set_value(attribute.get_value()) - unknown_attributes_array.append(new_attrib) - new_tag.set_unknown_attributes(unknown_attributes_array) + for attribute in attributes: + new_tag.set_attribute(attribute, attributes[attribute].get_value()) if include_children: # Iterate this over all children. @@ -60,8 +155,34 @@ func duplicate(include_children := true) -> Tag: # To be overridden in extending classes. +func get_own_default(_attribute_name: String) -> String: + return "" + +func update_cache() -> void: + pass + func can_replace(_new_tag: String) -> bool: return false func get_replacement(_new_tag: String) -> Tag: return null + +func get_config_warnings() -> PackedStringArray: + return PackedStringArray() + +# Helpers +func is_parent_g() -> bool: + return parent != null and parent is TagG + +func set_parent(tag: Tag) -> void: + parent = tag + svg = parent if parent is TagSVG else tag.svg + root = tag.root + +func get_transform() -> Transform2D: + var result := Transform2D.IDENTITY + if is_parent_g(): + result *= parent.get_transform() + if attributes.has("transform"): + result *= attributes.transform.get_final_transform() + return result diff --git a/src/data_classes/TagCircle.gd b/src/data_classes/TagCircle.gd index 20d29d7e..523ea8ee 100644 --- a/src/data_classes/TagCircle.gd +++ b/src/data_classes/TagCircle.gd @@ -1,23 +1,14 @@ # A tag. -class_name TagCircle extends Tag +class_name TagCircle extends TagShape const name = "circle" const possible_conversions = ["ellipse", "rect", "path"] -const icon = preload("res://visual/icons/tag/circle.svg") - -const known_attributes = ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width", "cx", "cy", "r"] - -func _init() -> void: - for attrib_name in known_attributes: - attributes[attrib_name] = DB.attribute(attrib_name) - super() func user_setup(pos := Vector2.ZERO) -> void: - attributes.r.set_num(1.0) + set_attribute("r", 1.0) if pos != Vector2.ZERO: - attributes.cx.set_num(pos.x) - attributes.cy.set_num(pos.y) + set_attribute("cx", pos.x) + set_attribute("cy", pos.y) func can_replace(new_tag: String) -> bool: return new_tag in ["ellipse", "rect", "path"] @@ -27,44 +18,54 @@ func get_replacement(new_tag: String) -> Tag: return null var tag: Tag - var retained_attributes: Array[String] = [] + var dropped_attributes: PackedStringArray match new_tag: "ellipse": tag = TagEllipse.new() - retained_attributes = ["cx", "cy", "transform", "opacity", "fill", - "fill-opacity", "stroke", "stroke-opacity", "stroke-width"] - tag.attributes.rx.set_num(attributes.r.get_num(), Attribute.SyncMode.SILENT) - tag.attributes.ry.set_num(attributes.r.get_num(), Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["r"]) + tag.set_attribute("rx", get_attribute_num("r")) + tag.set_attribute("ry", get_attribute_num("r")) "rect": tag = TagRect.new() - retained_attributes = ["transform", "opacity", "fill", "fill-opacity", "stroke", - "stroke-opacity", "stroke-width"] - tag.attributes.x.set_num(attributes.cx.get_num() - attributes.r.get_num(), - Attribute.SyncMode.SILENT) - tag.attributes.y.set_num(attributes.cy.get_num() - attributes.r.get_num(), - Attribute.SyncMode.SILENT) - tag.attributes.width.set_num(attributes.r.get_num() * 2, - Attribute.SyncMode.SILENT) - tag.attributes.height.set_num(attributes.r.get_num() * 2, - Attribute.SyncMode.SILENT) - tag.attributes.rx.set_num(attributes.r.get_num(), Attribute.SyncMode.SILENT) - tag.attributes.ry.set_num(attributes.r.get_num(), Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["r", "cx", "cy"]) + tag.set_attribute("x", get_attribute_num("cx") - get_attribute_num("r")) + tag.set_attribute("y", get_attribute_num("cy") - get_attribute_num("r")) + tag.set_attribute("width", get_attribute_num("r")) + tag.set_attribute("height", get_attribute_num("r")) + tag.set_attribute("rx", get_attribute_num("r")) + tag.set_attribute("ry", get_attribute_num("r")) "path": tag = TagPath.new() - retained_attributes = ["transform", "opacity", "fill", "fill-opacity", "stroke", - "stroke-opacity", "stroke-width"] + dropped_attributes = PackedStringArray(["r", "cx", "cy"]) var commands: Array[PathCommand] = [] - commands.append(PathCommand.MoveCommand.new(attributes.cx.get_num(), - attributes.cy.get_num() - attributes.r.get_num(), true)) - commands.append(PathCommand.EllipticalArcCommand.new(attributes.r.get_num(), - attributes.r.get_num(), 0, 0, 0, 0, attributes.r.get_num() * 2, true)) - commands.append(PathCommand.EllipticalArcCommand.new(attributes.r.get_num(), - attributes.r.get_num(), 0, 0, 0, 0, -attributes.r.get_num() * 2, true)) + commands.append(PathCommand.MoveCommand.new(get_attribute_num("cx"), + get_attribute_num("cy") - get_attribute_num("r"), true)) + commands.append(PathCommand.EllipticalArcCommand.new(get_attribute_num("r"), + get_attribute_num("r"), 0, 0, 0, 0, get_attribute_num("r") * 2, true)) + commands.append(PathCommand.EllipticalArcCommand.new(get_attribute_num("r"), + get_attribute_num("r"), 0, 0, 0, 0, -get_attribute_num("r") * 2, true)) commands.append(PathCommand.CloseCommand.new(true)) - tag.attributes.d.set_commands(commands, Attribute.SyncMode.SILENT) + tag.set_attribute("d", commands) - for k in retained_attributes: - tag.attributes[k].set_value(attributes[k].get_value(), Attribute.SyncMode.SILENT) - tag.child_tags = child_tags + for attribute_name in attributes: + if not attribute_name in dropped_attributes: + tag.set_attribute(attribute_name, attributes[attribute_name]) + tag.child_tags = child_tags return tag + + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "cx", "cy": return "0" + "r": return "0" + "opacity": return "1" + _: return "" + +func get_bounding_box() -> Rect2: + var bounding_box := Rect2() + bounding_box.position.x = get_attribute_num("cx") - get_attribute_num("r") + bounding_box.position.y = get_attribute_num("cy") - get_attribute_num("r") + bounding_box.size.x = get_attribute_num("r") * 2 + bounding_box.size.y = get_attribute_num("r") * 2 + return bounding_box diff --git a/src/data_classes/TagEllipse.gd b/src/data_classes/TagEllipse.gd index 1c8084df..f148080f 100644 --- a/src/data_classes/TagEllipse.gd +++ b/src/data_classes/TagEllipse.gd @@ -1,28 +1,22 @@ # An tag. -class_name TagEllipse extends Tag +class_name TagEllipse extends TagShape + +var rx: float +var ry: float const name = "ellipse" const possible_conversions = ["circle", "rect", "path"] -const icon = preload("res://visual/icons/tag/ellipse.svg") - -const known_attributes = ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width", "cx", "cy", "rx", "ry"] - -func _init() -> void: - for attrib_name in known_attributes: - attributes[attrib_name] = DB.attribute(attrib_name) - super() func user_setup(pos := Vector2.ZERO) -> void: - attributes.rx.set_num(1.0) - attributes.ry.set_num(1.0) + set_attribute("rx", 1.0) + set_attribute("ry", 1.0) if pos != Vector2.ZERO: - attributes.cx.set_num(pos.x) - attributes.cy.set_num(pos.y) + set_attribute("cx", pos.x) + set_attribute("cy", pos.y) func can_replace(new_tag: String) -> bool: if new_tag == "circle": - return attributes.rx.get_num() == attributes.ry.get_num() + return get_attribute_num("rx") == get_attribute_num("ry") else: return new_tag in ["rect", "path"] @@ -31,41 +25,54 @@ func get_replacement(new_tag: String) -> Tag: return null var tag: Tag - var retained_attributes: Array[String] = [] + var dropped_attributes: PackedStringArray match new_tag: "circle": tag = TagCircle.new() - retained_attributes = ["cx", "cy", "transform", "opacity", "fill", - "fill-opacity", "stroke", "stroke-opacity", "stroke-width"] - tag.attributes.r.set_num(attributes.rx.get_num(), Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["rx", "ry"]) + tag.set_attribute("r", get_attribute_num("rx")) "rect": tag = TagRect.new() - retained_attributes = ["rx", "ry", "transform", "opacity", "fill", - "fill-opacity", "stroke", "stroke-opacity", "stroke-width"] - tag.attributes.x.set_num(attributes.cx.get_num() - attributes.rx.get_num(), - Attribute.SyncMode.SILENT) - tag.attributes.y.set_num(attributes.cy.get_num() - attributes.ry.get_num(), - Attribute.SyncMode.SILENT) - tag.attributes.width.set_num(attributes.rx.get_num() * 2, - Attribute.SyncMode.SILENT) - tag.attributes.height.set_num(attributes.ry.get_num() * 2, - Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["cx", "cy"]) + tag.set_attribute("x", get_attribute_num("cx") - get_attribute_num("rx")) + tag.set_attribute("y", get_attribute_num("cy") - get_attribute_num("ry")) + tag.set_attribute("width", get_attribute_num("rx") * 2) + tag.set_attribute("height", get_attribute_num("ry") * 2) "path": tag = TagPath.new() - retained_attributes = ["transform", "opacity", "fill", - "fill-opacity", "stroke", "stroke-opacity", "stroke-width"] + dropped_attributes = PackedStringArray(["cx", "cy", "rx", "ry"]) var commands: Array[PathCommand] = [] - commands.append(PathCommand.MoveCommand.new(attributes.cx.get_num(), - attributes.cy.get_num() - attributes.ry.get_num(), true)) - commands.append(PathCommand.EllipticalArcCommand.new(attributes.rx.get_num(), - attributes.ry.get_num(), 0, 0, 0, 0, attributes.ry.get_num() * 2, true)) - commands.append(PathCommand.EllipticalArcCommand.new(attributes.rx.get_num(), - attributes.ry.get_num(), 0, 0, 0, 0, -attributes.ry.get_num() * 2, true)) + commands.append(PathCommand.MoveCommand.new(get_attribute_num("cx"), + get_attribute_num("cy") - ry, true)) + commands.append(PathCommand.EllipticalArcCommand.new(rx, ry, 0, 0, 0, 0, + ry * 2, true)) + commands.append(PathCommand.EllipticalArcCommand.new(rx, ry, 0, 0, 0, 0, + -ry * 2, true)) commands.append(PathCommand.CloseCommand.new(true)) - tag.attributes.d.set_commands(commands, Attribute.SyncMode.SILENT) + tag.set_attribute("d", commands) - for k in retained_attributes: - tag.attributes[k].set_value(attributes[k].get_value(), Attribute.SyncMode.SILENT) - tag.child_tags = child_tags + for attribute_name in attributes: + if not attribute_name in dropped_attributes: + tag.set_attribute(attribute_name, attributes[attribute_name]) + tag.child_tags = child_tags return tag + +func update_cache() -> void: + rx = get_attribute_num("rx") if attributes.has("rx") else get_attribute_num("ry") + ry = get_attribute_num("ry") if attributes.has("ry") else get_attribute_num("rx") + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "cx", "cy": return "0" + "rx": return "auto" + "ry": return "auto" + "opacity": return "1" + _: return "" + +func get_bounding_box() -> Rect2: + var bounding_box := Rect2() + bounding_box.position.x = get_attribute_num("cx") - rx + bounding_box.position.y = get_attribute_num("cy") - ry + bounding_box.size = Vector2(rx, ry) * 2 + return bounding_box diff --git a/src/data_classes/TagG.gd b/src/data_classes/TagG.gd new file mode 100644 index 00000000..75cd9afb --- /dev/null +++ b/src/data_classes/TagG.gd @@ -0,0 +1,16 @@ +class_name TagG extends Tag + +const name = "g" +const possible_conversions = [] + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "opacity": return "1" + _: return "" + +func get_config_warnings() -> PackedStringArray: + var warnings := super() + if not (parent is TagG or parent is TagSVG or parent is TagUnrecognized): + warnings.append(TranslationServer.translate("{tag} must be a child of {allowed} to have any effect.").format( + {"tag": self.name, "allowed": "[svg, g]"})) + return warnings diff --git a/src/data_classes/TagLine.gd b/src/data_classes/TagLine.gd index bde59192..1bd56d11 100644 --- a/src/data_classes/TagLine.gd +++ b/src/data_classes/TagLine.gd @@ -1,25 +1,16 @@ # A tag. -class_name TagLine extends Tag +class_name TagLine extends TagShape const name = "line" const possible_conversions = ["path"] -const icon = preload("res://visual/icons/tag/line.svg") - -const known_attributes = ["transform", "opacity", "stroke", "stroke-opacity", - "stroke-width", "stroke-linecap", "x1", "y1", "x2", "y2", ] - -func _init() -> void: - for attrib_name in known_attributes: - attributes[attrib_name] = DB.attribute(attrib_name) - super() func user_setup(pos := Vector2.ZERO) -> void: if pos != Vector2.ZERO: - attributes.x1.set_num(pos.x) - attributes.y1.set_num(pos.y) - attributes.y2.set_num(pos.y) - attributes.x2.set_num(pos.x + 1) - attributes.stroke.set_value("black") + set_attribute("x1", pos.x) + set_attribute("y1", pos.y) + set_attribute("y2", pos.y) + set_attribute("x2", pos.x + 1) + set_attribute("stroke", "black") func can_replace(new_tag: String) -> bool: return new_tag == "path" @@ -29,22 +20,37 @@ func get_replacement(new_tag: String) -> Tag: return null var tag: Tag - var retained_attributes: Array[String] = [] + var dropped_attributes: PackedStringArray match new_tag: "path": tag = TagPath.new() - retained_attributes = ["transform", "opacity", "stroke", "stroke-opacity", - "stroke-width", "stroke-linecap"] + dropped_attributes = PackedStringArray(["x1", "y1", "x2", "y2"]) var commands: Array[PathCommand] = [] - commands.append(PathCommand.MoveCommand.new(attributes.x1.get_num(), - attributes.y1.get_num(), true)) + commands.append(PathCommand.MoveCommand.new(get_attribute_num("x1"), + get_attribute_num("y1"), true)) commands.append(PathCommand.LineCommand.new( - attributes.x2.get_num() - attributes.x1.get_num(), - attributes.y2.get_num() - attributes.y1.get_num(), true)) - tag.attributes.d.set_commands(commands, Attribute.SyncMode.SILENT) + get_attribute_num("x2") - get_attribute_num("x1"), + get_attribute_num("y2") - get_attribute_num("y1"), true)) + tag.set_attribute("d", commands) - for k in retained_attributes: - tag.attributes[k].set_value(attributes[k].get_value(), Attribute.SyncMode.SILENT) - tag.child_tags = child_tags + for attribute_name in attributes: + if not attribute_name in dropped_attributes: + tag.set_attribute(attribute_name, attributes[attribute_name]) + tag.child_tags = child_tags return tag + + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "x1", "y1", "x2", "y2": return "0" + "opacity": return "1" + _: return "" + +func get_bounding_box() -> Rect2: + var bounding_box := Rect2() + bounding_box.position.x = minf(get_attribute_num("x1"), get_attribute_num("x2")) + bounding_box.position.y = minf(get_attribute_num("y1"), get_attribute_num("y2")) + bounding_box.end.x = maxf(get_attribute_num("y1"), get_attribute_num("y2")) + bounding_box.end.y = maxf(get_attribute_num("y1"), get_attribute_num("y2")) + return bounding_box diff --git a/src/data_classes/TagLinearGradient.gd b/src/data_classes/TagLinearGradient.gd new file mode 100644 index 00000000..b9bbf376 --- /dev/null +++ b/src/data_classes/TagLinearGradient.gd @@ -0,0 +1,28 @@ +class_name TagLinearGradient extends Tag + +const name = "linearGradient" +const possible_conversions = [] + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "x1", "y1": return "0%" + "x2", "y2": return "100%" + _: return "" + +func get_config_warnings() -> PackedStringArray: + var warnings := super() + if not (parent is TagG or parent is TagSVG or parent is TagUnrecognized): + warnings.append(TranslationServer.translate("{tag} must be a child of {allowed} to have any effect.").format( + {"tag": name, "allowed": "[g, svg]"})) + if not attributes.has("id"): + warnings.append(TranslationServer.translate("No id attribute defined.")) + + var has_stops := false + for child in child_tags: + if child is TagStop: + has_stops = true + break + if not has_stops: + warnings.append(TranslationServer.translate("No stop tags under this gradient.")) + + return warnings diff --git a/src/data_classes/TagPath.gd b/src/data_classes/TagPath.gd index 62622745..98bbba7d 100644 --- a/src/data_classes/TagPath.gd +++ b/src/data_classes/TagPath.gd @@ -1,30 +1,189 @@ # A tag. -class_name TagPath extends Tag +class_name TagPath extends TagShape const name = "path" const possible_conversions = [] -const icon = preload("res://visual/icons/tag/path.svg") - -const known_attributes = ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width", "stroke-linecap", - "stroke-linejoin", "d"] - -func _init() -> void: - for attrib_name in known_attributes: - attributes[attrib_name] = DB.attribute(attrib_name) - super() func user_setup(pos := Vector2.ZERO) -> void: if pos != Vector2.ZERO: - attributes.d.insert_command(0, "M") - attributes.d.set_command_property(0, "x", pos.x) - attributes.d.set_command_property(0, "y", pos.y) + var attrib := get_attribute("d") + attrib.insert_command(0, "M") + attrib.set_command_property(0, "x", pos.x) + attrib.set_command_property(0, "y", pos.y) -func can_replace(_new_tag: String) -> bool: - return false +func get_own_default(attribute_name: String) -> String: + if attribute_name == "opacity": + return "1" + return "" -func get_replacement(new_tag: String) -> Tag: - if not can_replace(new_tag): - return null +func get_bounding_box() -> Rect2: + if not attributes.has("d"): + return Rect2() + + var pathdata: AttributePathdata = get_attribute("d") + var min_x := INF + var min_y := INF + var max_x := -INF + var max_y := -INF - return null + for cmd_idx in pathdata.get_command_count(): + var cmd := pathdata.get_command(cmd_idx) + var relative := cmd.relative + var cmd_char := cmd.command_char.to_upper() + match cmd_char: + "M", "L": + # Move / Line + var v := Vector2(cmd.x, cmd.y) + var end := cmd.start + v if relative else v + min_x = minf(min_x, end.x) + min_y = minf(min_y, end.y) + max_x = maxf(max_x, end.x) + max_y = maxf(max_y, end.y) + "H": + # Horizontal line + var v := Vector2(cmd.x, 0) + var end := cmd.start + v if relative else v + min_x = minf(min_x, end.x) + max_x = maxf(max_x, end.x) + "V": + # Vertical line + var v := Vector2(0, cmd.y) + var end := cmd.start + v if relative else v + min_y = minf(min_y, end.y) + max_y = maxf(max_y, end.y) + "C", "S": + # Cubic Bezier curve + var v := Vector2(cmd.x, cmd.y) + var v1 := Vector2(cmd.x1, cmd.y1) if cmd_char == "C" else\ + pathdata.get_implied_S_control(cmd_idx) + var v2 := Vector2(cmd.x2, cmd.y2) + var cp1 := cmd.start + var cp4 := cp1 + v if relative else v + var cp2 := cp1 + v1 if relative else v1 + var cp3 := cp1 + v2 if relative else v2 + + min_x = minf(min_x, cp4.x) + min_y = minf(min_y, cp4.y) + max_x = maxf(max_x, cp4.x) + max_y = maxf(max_y, cp4.y) + + var i := cp2 - cp1 + var j := cp3 - cp2 + var k := cp4 - cp3 + + var a := 3 * i - 6 * j + 3 * k + var b := 6 * j - 6 * i + var c := 3 * i + + var sol_x = solve_quadratic(a.x, b.x, c.x) + for sol in sol_x: + if sol != null and sol > 0 and sol < 1: + var pt := Utils.cubic_bezier_point(cp1.x, cp2.x, cp3.x, cp4.x, sol) + min_x = minf(pt, min_x) + max_x = maxf(pt, max_x) + + var sol_y = solve_quadratic(a.y, b.y, c.y) + for sol in sol_y: + if sol != null and sol > 0 and sol < 1: + var pt := Utils.cubic_bezier_point(cp1.y, cp2.y, cp3.y, cp4.y, sol) + min_y = minf(pt, min_y) + max_y = maxf(pt, max_y) + "Q", "T": + # Quadratic Bezier curve + var v := Vector2(cmd.x, cmd.y) + var v1 := Vector2(cmd.x1, cmd.y1) if cmd_char == "Q" else\ + pathdata.get_implied_T_control(cmd_idx) + + var cp1 := cmd.start + var cp2 := cp1 + v1 if relative else v1 + var cp3 := cp1 + v if relative else v + + min_x = minf(min_x, cp3.x) + min_y = minf(min_y, cp3.y) + max_x = maxf(max_x, cp3.x) + max_y = maxf(max_y, cp3.y) + + var t_x := (cp1.x - cp2.x) / (cp1.x - 2 * cp2.x + cp3.x) + if 0 <= t_x and t_x <= 1: + var x_extrema := Utils.quadratic_bezier_point(cp1.x, cp2.x, cp3.x, t_x) + min_x = minf(min_x, x_extrema) + max_x = maxf(max_x, x_extrema) + + var t_y := (cp1.y - cp2.y) / (cp1.y - 2 * cp2.y + cp3.y) + if 0 <= t_y and t_y <= 1: + var y_extrema := Utils.quadratic_bezier_point(cp1.y, cp2.y, cp3.y, t_y) + min_y = minf(min_y, y_extrema) + max_y = maxf(max_y, y_extrema) + "A": + # Elliptical arc. + var start := cmd.start + var v := Vector2(cmd.x, cmd.y) + var end := start + v if relative else v + + min_x = minf(min_x, end.x) + min_y = minf(min_y, end.y) + max_x = maxf(max_x, end.x) + max_y = maxf(max_y, end.y) + + if start == end or cmd.rx == 0 or cmd.ry == 0: + continue + + var r := Vector2(cmd.rx, cmd.ry).abs() + # Obtain center parametrization. + var rot := deg_to_rad(cmd.rot) + var cosine := cos(rot) + var sine := sin(rot) + var half := (start - end) / 2 + var x1 := half.x * cosine + half.y * sine + var y1 := -half.x * sine + half.y * cosine + var r2 := Vector2(r.x * r.x, r.y * r.y) + var x12 := x1 * x1 + var y12 := y1 * y1 + var cr := x12 / r2.x + y12 / r2.y + if cr > 1: + cr = sqrt(cr) + r *= cr + r2 = Vector2(r.x * r.x, r.y * r.y) + + var dq := r2.x * y12 + r2.y * x12 + var pq := (r2.x * r2.y - dq) / dq + var sc := sqrt(maxf(0, pq)) + if cmd.large_arc_flag == cmd.sweep_flag: + sc = -sc + + var ct := Vector2(r.x * sc * y1 / r.y, -r.y * sc * x1 / r.x) + var c := Vector2(ct.x * cosine - ct.y * sine, + ct.x * sine + ct.y * cosine) + start.lerp(end, 0.5) + var tv := Vector2(x1 - ct.x, y1 - ct.y) / r + var theta1 := tv.angle() + var delta_theta := fposmod(tv.angle_to( + Vector2(-x1 - ct.x, -y1 - ct.y) / r), TAU) + if cmd.sweep_flag == 0: + theta1 += delta_theta + delta_theta = TAU - delta_theta + theta1 = fposmod(theta1, TAU) + + var rot_h := tan(rot) + var extreme1 := atan2(-r.y * rot_h, r.x) + var extreme2 := atan2(r.y, r.x * rot_h) + for angle in [extreme1, extreme2, PI + extreme1, PI + extreme2]: + if (angle < theta1 or angle > theta1 + delta_theta) and\ + (angle + TAU < theta1 or angle + TAU > theta1 + delta_theta): + continue + var extreme_point := Vector2( + c.x + r.x * cos(angle) * cosine - r.y * sin(angle) * sine, + c.y + r.x * cos(angle) * sine + r.y * sin(angle) * cosine) + min_x = minf(min_x, extreme_point.x) + min_y = minf(min_y, extreme_point.y) + max_x = maxf(max_x, extreme_point.x) + max_y = maxf(max_y, extreme_point.y) + + return Rect2(min_x, min_y, max_x - min_x, max_y - min_y) + + +static func solve_quadratic(a: float, b: float, c: float) -> Array: + var D = sqrt(b * b - 4 * a * c) + if is_nan(D): + return [null, null] + else: + return [(-b + D) / (2 * a), (-b - D) / (2 * a)] diff --git a/src/data_classes/TagRadialGradient.gd b/src/data_classes/TagRadialGradient.gd new file mode 100644 index 00000000..78abff9b --- /dev/null +++ b/src/data_classes/TagRadialGradient.gd @@ -0,0 +1,27 @@ +class_name TagRadialGradient extends Tag + +const name = "radialGradient" +const possible_conversions = [] + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "cx", "cy", "r": return "50%" + _: return "" + +func get_config_warnings() -> PackedStringArray: + var warnings := super() + if not (parent is TagG or parent is TagSVG or parent is TagUnrecognized): + warnings.append(TranslationServer.translate("{tag} must be a child of {allowed} to have any effect.").format( + {"tag": name, "allowed": "[g, svg]"})) + if not attributes.has("id"): + warnings.append(TranslationServer.translate("No id attribute defined.")) + + var has_stops := false + for child in child_tags: + if child is TagStop: + has_stops = true + break + if not has_stops: + warnings.append(TranslationServer.translate("No stop tags under this gradient.")) + + return warnings diff --git a/src/data_classes/TagRect.gd b/src/data_classes/TagRect.gd index 754db6a4..93587432 100644 --- a/src/data_classes/TagRect.gd +++ b/src/data_classes/TagRect.gd @@ -1,44 +1,25 @@ # A tag. -class_name TagRect extends Tag +class_name TagRect extends TagShape + +var rx: float +var ry: float const name = "rect" const possible_conversions = ["circle", "ellipse", "path"] -const icon = preload("res://visual/icons/tag/rect.svg") - -const known_attributes = ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width", "stroke-linejoin", - "x", "y", "width", "height", "rx", "ry",] - -func _init() -> void: - for attrib_name in known_attributes: - attributes[attrib_name] = DB.attribute(attrib_name) - super() func user_setup(pos := Vector2.ZERO) -> void: - attributes.width.set_num(1.0) - attributes.height.set_num(1.0) + set_attribute("width", 1.0) + set_attribute("height", 1.0) if pos != Vector2.ZERO: - attributes.x.set_num(pos.x) - attributes.y.set_num(pos.y) + set_attribute("x", pos.x) + set_attribute("y", pos.y) func can_replace(new_tag: String) -> bool: if new_tag == "ellipse": - var rx: float = attributes.rx.get_num() - var ry: float = attributes.ry.get_num() - if rx == 0: - rx = ry - elif ry == 0: - ry = rx - return rx >= attributes.width.get_num() / 2 and ry >= attributes.height.get_num() / 2 + return rx >= get_attribute_num("width") / 2 and ry >= get_attribute_num("height") / 2 elif new_tag == "circle": - var rx: float = attributes.rx.get_num() - var ry: float = attributes.ry.get_num() - if rx == 0: - rx = ry - elif ry == 0: - ry = rx - var side: float = attributes.width.get_num() - return attributes.height.get_num() == side and rx >= side / 2 and ry >= side / 2 + var side := get_attribute_num("width") + return get_attribute_num("height") == side and rx >= side / 2 and ry >= side / 2 else: return new_tag == "path" @@ -47,57 +28,41 @@ func get_replacement(new_tag: String) -> Tag: return null var tag: Tag - var retained_attributes: Array[String] = [] + var dropped_attributes: PackedStringArray match new_tag: "ellipse": tag = TagEllipse.new() - retained_attributes = ["transform", "opacity", "fill", "fill-opacity", "stroke", - "stroke-opacity", "stroke-width"] - tag.attributes.rx.set_num(attributes.width.get_num() / 2, - Attribute.SyncMode.SILENT) - tag.attributes.ry.set_num(attributes.height.get_num() / 2, - Attribute.SyncMode.SILENT) - tag.attributes.cx.set_num(attributes.x.get_num() +\ - attributes.width.get_num() / 2, Attribute.SyncMode.SILENT) - tag.attributes.cy.set_num(attributes.y.get_num() +\ - attributes.height.get_num() / 2, Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["x", "y", "width", "height"]) + tag.set_attribute("rx", get_attribute_num("width") / 2) + tag.set_attribute("ry", get_attribute_num("height") / 2) + tag.set_attribute("cx", get_attribute_num("x") + get_attribute_num("width") / 2) + tag.set_attribute("cy", get_attribute_num("y") + get_attribute_num("height") / 2) "circle": tag = TagCircle.new() - retained_attributes = ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width"] - tag.attributes.r.set_num(attributes.width.get_num() / 2, - Attribute.SyncMode.SILENT) - tag.attributes.cx.set_num(attributes.x.get_num() +\ - attributes.width.get_num() / 2, Attribute.SyncMode.SILENT) - tag.attributes.cy.set_num(attributes.y.get_num() +\ - attributes.height.get_num() / 2, Attribute.SyncMode.SILENT) + dropped_attributes = PackedStringArray(["x", "y", "width", "height", "rx", "ry"]) + tag.set_attribute("r", get_attribute_num("width") / 2) + tag.set_attribute("cx", get_attribute_num("x") + get_attribute_num("width") / 2) + tag.set_attribute("cy", get_attribute_num("y") + get_attribute_num("height") / 2) "path": tag = TagPath.new() - retained_attributes = ["transform", "opacity", "fill", "fill-opacity", "stroke", - "stroke-opacity", "stroke-width", "stroke-linejoin"] - var rx := minf(attributes.rx.get_num(), attributes.width.get_num() / 2) - var ry := minf(attributes.ry.get_num(), attributes.height.get_num() / 2) + dropped_attributes = PackedStringArray(["x", "y", "width", "height", "rx", "ry"]) var commands: Array[PathCommand] = [] if rx == 0 and ry == 0: - commands.append(PathCommand.MoveCommand.new(attributes.x.get_num(), - attributes.y.get_num(), true)) + commands.append(PathCommand.MoveCommand.new(get_attribute_num("x"), + get_attribute_num("y"), true)) commands.append(PathCommand.HorizontalLineCommand.new( - attributes.width.get_num(), true)) + get_attribute_num("width"), true)) commands.append(PathCommand.VerticalLineCommand.new( - attributes.height.get_num(), true)) + get_attribute_num("height"), true)) commands.append(PathCommand.HorizontalLineCommand.new( - -attributes.width.get_num(), true)) + -get_attribute_num("width"), true)) commands.append(PathCommand.CloseCommand.new(true)) else: - if rx == 0: - rx = ry - elif ry == 0: - ry = rx - var w: float = attributes.width.get_num() - rx * 2 - var h: float = attributes.height.get_num() - ry * 2 + var w := get_attribute_num("width") - rx * 2 + var h := get_attribute_num("height") - ry * 2 - commands.append(PathCommand.MoveCommand.new(attributes.x.get_num(), - attributes.y.get_num() + ry, true)) + commands.append(PathCommand.MoveCommand.new(get_attribute_num("x"), + get_attribute_num("y") + ry, true)) commands.append(PathCommand.EllipticalArcCommand.new( rx, ry, 0, 0, 1, rx, -ry, true)) if w > 0.0: @@ -113,10 +78,31 @@ func get_replacement(new_tag: String) -> Tag: commands.append(PathCommand.EllipticalArcCommand.new( rx, ry, 0, 0, 1, -rx, -ry, true)) commands.append(PathCommand.CloseCommand.new(true)) - tag.attributes.d.set_commands(commands, Attribute.SyncMode.SILENT) - - for k in retained_attributes: - tag.attributes[k].set_value(attributes[k].get_value(), Attribute.SyncMode.SILENT) - tag.child_tags = child_tags + tag.set_attribute("d", commands) + + for attribute_name in attributes: + if not attribute_name in dropped_attributes: + tag.set_attribute(attribute_name, attributes[attribute_name]) + tag.child_tags = child_tags return tag + +func update_cache() -> void: + rx = ry if !attributes.has("rx") else minf(get_attribute_num("rx"), + get_attribute_num("width") / 2) + ry = rx if !attributes.has("ry") else minf(get_attribute_num("ry"), + get_attribute_num("height") / 2) + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "x", "y", "width", "height": return "0" + "rx": return "auto" + "ry": return "auto" + "opacity": return "1" + _: return "" + +func get_bounding_box() -> Rect2: + var bounding_box := Rect2() + bounding_box.position = Vector2(get_attribute_num("x"), get_attribute_num("y")) + bounding_box.size = Vector2(get_attribute_num("width"), get_attribute_num("height")) + return bounding_box diff --git a/src/data_classes/TagRoot.gd b/src/data_classes/TagRoot.gd new file mode 100644 index 00000000..42578c10 --- /dev/null +++ b/src/data_classes/TagRoot.gd @@ -0,0 +1,255 @@ +class_name TagRoot extends TagSVG + +@warning_ignore("unused_signal") +signal attribute_somewhere_changed(xid: PackedInt32Array) + +signal tags_added(xids: Array[PackedInt32Array]) +signal tags_deleted(xids: Array[PackedInt32Array]) +signal tags_moved_in_parent(parent_xid: PackedInt32Array, old_indices: Array[int]) +signal tags_moved_to(xids: Array[PackedInt32Array], location: PackedInt32Array) +signal tag_layout_changed # Emitted together with any of the above 5. + + +func get_all_tags() -> Array[Tag]: + var tags: Array[Tag] = [] + var unchecked_tags: Array[Tag] = child_tags.duplicate() + + while not unchecked_tags.is_empty(): + var checked_tag: Tag = unchecked_tags.pop_back() + for child_tag in checked_tag.child_tags: + unchecked_tags.append(child_tag) + tags.append(checked_tag) + return tags + +func get_tag(id: PackedInt32Array) -> Tag: + var current_tag: Tag = self + for idx in id: + if idx >= current_tag.child_tags.size(): + return null + current_tag = current_tag.child_tags[idx] + return current_tag + + +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "fill": return "black" + "fill-opacity": return "1" + "stroke": return "none" + "stroke-opacity": return "1" + "stroke-width": return "1" + "stroke-linecap": return "butt" + "stroke-linejoin": return "miter" + _: return "" + + +func add_tag(new_tag: Tag, new_xid: PackedInt32Array) -> void: + var parent_tag := get_tag(Utils.get_parent_xid(new_xid)) + new_tag.xid = new_xid + new_tag.set_parent(parent_tag) + parent_tag.child_tags.insert(new_xid[-1], new_tag) + var new_xid_array: Array[PackedInt32Array] = [new_xid] + tags_added.emit(new_xid_array) + tag_layout_changed.emit() + +func delete_tags(xids: Array[PackedInt32Array]) -> void: + if xids.is_empty(): + return + + xids = Utils.filter_descendant_xids(xids) + for id in xids: + var parent_tag := get_tag(Utils.get_parent_xid(id)) + if is_instance_valid(parent_tag): + var tag_idx := id[-1] + if tag_idx < parent_tag.get_child_count(): + parent_tag.child_tags.remove_at(tag_idx) + tags_deleted.emit(xids) + tag_layout_changed.emit() + +# Moves tags up or down, not to an arbitrary position. +func move_tags_in_parent(xids: Array[PackedInt32Array], down: bool) -> void: + if xids.is_empty(): + return + + # For moving, all these tags must be direct children of the same parent. + xids = Utils.filter_descendant_xids(xids) + var depth := xids[0].size() + var parent_xid := Utils.get_parent_xid(xids[0]) + for id in xids: + if id.size() != depth or Utils.get_parent_xid(id) != parent_xid: + return + + var xid_indices: Array[int] = [] # The last indices of the XIDs. + for id in xids: + xid_indices.append(id[-1]) + + var parent_tag := get_tag(parent_xid) + var parent_child_count := parent_tag.get_child_count() + var old_indices: Array[int] = [] + for i in parent_child_count: + old_indices.append(i) + # Do the moving. + if down: + var i := parent_child_count - 1 + while i >= 0: + if not i in xid_indices and (i - 1) in xid_indices: + old_indices.remove_at(i) + var moved_i := i + var moved_tag: Tag = parent_tag.child_tags.pop_at(i) + while (i - 1) in xid_indices: + i -= 1 + old_indices.insert(i, moved_i) + parent_tag.child_tags.insert(i, moved_tag) + i -= 1 + else: + var i := 0 + while i < parent_child_count: + if not i in xid_indices and (i + 1) in xid_indices: + old_indices.remove_at(i) + var moved_i := i + var moved_tag: Tag = parent_tag.child_tags.pop_at(i) + while (i + 1) in xid_indices: + i += 1 + old_indices.insert(i, moved_i) + parent_tag.child_tags.insert(i, moved_tag) + i += 1 + # Check if indices were really changed after the operation. + if old_indices != range(old_indices.size()): + tags_moved_in_parent.emit(parent_xid, old_indices) + tag_layout_changed.emit() + +# Moves tags to an arbitrary position. The first moved tag will move to the location XID. +func move_tags_to(xids: Array[PackedInt32Array], location: PackedInt32Array) -> void: + xids = Utils.filter_descendant_xids(xids) + # A tag can't move deeper inside itself. Remove the descendants of the location. + for i in range(xids.size() - 1, -1, -1): + if Utils.is_xid_parent(xids[i], location): + xids.remove_at(i) + + # Remove tags from their old locations. + var xids_stored: Array[PackedInt32Array] = [] + var tags_stored: Array[Tag] = [] + for id in xids: + # Shift the new location if tags before it were removed. A tag is "before" + # if it has the same parent as the new location, but is before that location. + if id.size() <= location.size(): + var before := true + for i in id.size() - 1: + if id[i] != location[i]: + before = false + break + if before and id[-1] < location[id.size() - 1]: + location[id.size() - 1] -= 1 + xids_stored.append(id) + tags_stored.append(get_tag(Utils.get_parent_xid(id)).child_tags.pop_at(id[-1])) + # Add the tags back in the new location. + for tag in tags_stored: + get_tag(Utils.get_parent_xid(location)).child_tags.insert(location[-1], tag) + # Check if this actually chagned the layout. + for id in xids_stored: + if not Utils.are_xid_parents_same(id, location) or id[-1] < location[-1] or\ + id[-1] >= location[-1] + xids_stored.size(): + # If this condition is passed, then there was a layout change. + tags_moved_to.emit(xids, location) + tag_layout_changed.emit() + return + +# Duplicates tags and puts them below. +func duplicate_tags(xids: Array[PackedInt32Array]) -> void: + if xids.is_empty(): + return + + xids = Utils.filter_descendant_xids(xids) + var xids_added: Array[PackedInt32Array] = [] + # Used to offset previously added XIDs in xids_added after duplicating a tag before. + var last_parent := PackedInt32Array([-1]) # Start with a XID that can't be matched. + var added_to_last_parent := 0 + + for id in xids: + var new_tag := get_tag(id).duplicate() + # Add the new tag. + var new_xid := id.duplicate() + new_xid[-1] += 1 + var parent_xid := Utils.get_parent_xid(new_xid) + get_tag(parent_xid).child_tags.insert(new_xid[-1], new_tag) + # Add the XID and offset the other ones from the same parent. + var added_xid_idx := xids_added.size() + xids_added.append(new_xid) + if last_parent == parent_xid: + added_to_last_parent += 1 + else: + last_parent = parent_xid + added_to_last_parent = 0 + for xid_idx in range(added_xid_idx - added_to_last_parent , added_xid_idx): + xids_added[xid_idx][-1] += 1 + tags_added.emit(xids_added) + tag_layout_changed.emit() + +func replace_tag(id: PackedInt32Array, new_tag: Tag) -> void: + get_tag(Utils.get_parent_xid(id)).child_tags[id[-1]] = new_tag + tag_layout_changed.emit() + +# Optimizes the SVG text in more ways than what formatting attributes allows. +# The return value is true if the SVG can be optimized, otherwise false. +# If apply_changes is false, you'll only get the return value. +func optimize(not_applied := false) -> bool: + for tag in get_all_tags(): + match tag.name: + "ellipse": + # If possible, turn ellipses into circles. + if tag.can_replace("circle"): + if not_applied: + return true + replace_tag(tag.xid, tag.get_replacement("circle")) + "line": + # Turn lines into paths. + if not_applied: + return true + replace_tag(tag.xid, tag.get_replacement("path")) + "rect": + # If possible, turn rounded rects into circles or ellipses. + if tag.can_replace("circle"): + if not_applied: + return true + replace_tag(tag.xid, tag.get_replacement("circle")) + elif tag.can_replace("ellipse"): + if not_applied: + return true + replace_tag(tag.xid, tag.get_replacement("ellipse")) + elif tag.rx == 0: + # If the rectangle is not rounded, turn it into a path. + if not_applied: + return true + replace_tag(tag.xid, tag.get_replacement("path")) + "path": + var pathdata: AttributePathdata = tag.get_attribute("d") + # Simplify A rotation to 0 degrees for circular arcs. + for cmd_idx in pathdata.get_command_count(): + var command := pathdata.get_command(cmd_idx) + var cmd_char := command.command_char + if cmd_char in "Aa" and command.rx == command.ry and command.rot != 0: + if not_applied: + return true + pathdata.set_command_property(cmd_idx, "rot", 0) + # Replace L with H or V when possible. + for cmd_idx in pathdata.get_command_count(): + var command := pathdata.get_command(cmd_idx) + var cmd_char := command.command_char + if cmd_char == "l": + if command.x == 0: + if not_applied: + return true + pathdata.convert_command(cmd_idx, "v") + elif command.y == 0: + if not_applied: + return true + pathdata.convert_command(cmd_idx, "h") + elif cmd_char == "L": + if command.x == command.start.x: + if not_applied: + return true + pathdata.convert_command(cmd_idx, "V") + elif command.y == command.start.y: + if not_applied: + return true + pathdata.convert_command(cmd_idx, "H") + return false diff --git a/src/data_classes/TagSVG.gd b/src/data_classes/TagSVG.gd index 03c64b91..2ee2a81e 100644 --- a/src/data_classes/TagSVG.gd +++ b/src/data_classes/TagSVG.gd @@ -6,68 +6,43 @@ var height: float var viewbox: Rect2 var canvas_transform: Transform2D -# The difference between attribute_changed() and resized() is that -# resized() will emit even after unknown changes. -signal resized - -signal child_attribute_changed(undo_redo: bool) -signal changed_unknown - -signal tags_added(tids: Array[PackedInt32Array]) -signal tags_deleted(tids: Array[PackedInt32Array]) -signal tags_moved_in_parent(parent_tid: PackedInt32Array, old_indices: Array[int]) -signal tags_moved_to(tids: Array[PackedInt32Array], location: PackedInt32Array) -signal tag_layout_changed # Emitted together with any of the above 5. - -# This list is currently only used by the highlighter, so xmlns is here. -const known_attributes = ["width", "height", "viewBox", "xmlns"] const name = "svg" -func _init() -> void: - for attrib_name in ["width", "height", "viewBox"]: - attributes[attrib_name] = DB.attribute(attrib_name) - unknown_attributes.append(Attribute.new("xmlns", "http://www.w3.org/2000/svg")) - attribute_changed.connect(update_cache.unbind(1)) - changed_unknown.connect(update_cache) - update_cache() - super() - func update_cache() -> void: - var has_valid_width: bool = !attributes.width.get_value().is_empty() - var has_valid_height: bool = !attributes.height.get_value().is_empty() - var has_valid_viewbox: bool = attributes.viewBox.get_list_size() >= 4 + var has_valid_width := attributes.has("width") + var has_valid_height := attributes.has("height") + var has_valid_viewbox := attributes.has("viewBox") # Return early on invalid input. if not has_valid_viewbox and not (has_valid_width and has_valid_height): - width = attributes.width.get_num() if has_valid_width else 0 - height = attributes.height.get_num() if has_valid_height else 0 + width = get_attribute_num("width") if has_valid_width else 0.0 + height = get_attribute_num("height") if has_valid_height else 0.0 viewbox = Rect2(0, 0, 0, 0) canvas_transform = Transform2D.IDENTITY return # From now on we're sure the input is valid. Cache width and height. if has_valid_width: - width = attributes.width.get_num() + width = get_attribute_num("width") if not has_valid_height: - height = attributes.width.get_num() / attributes.viewBox.get_list_element(2) *\ + height = width / attributes.viewBox.get_list_element(2) *\ attributes.viewBox.get_list_element(3) else: - height = attributes.height.get_num() + height = get_attribute_num("height") elif has_valid_height: - height = attributes.height.get_num() - width = attributes.height.get_num() / attributes.viewBox.get_list_element(3) *\ + height = get_attribute_num("height") + width = height / attributes.viewBox.get_list_element(3) *\ attributes.viewBox.get_list_element(2) else: width = attributes.viewBox.get_list_element(2) height = attributes.viewBox.get_list_element(3) # Cache viewbox. - if attributes.viewBox.get_list_size() >= 4: - viewbox = Rect2(attributes.viewBox.get_list_element(0), - attributes.viewBox.get_list_element(1), - attributes.viewBox.get_list_element(2), - attributes.viewBox.get_list_element(3)) + if has_valid_viewbox and attributes.viewBox.get_list_size() >= 4: + var viewbox_attrib: AttributeList = attributes.viewBox + viewbox = Rect2(viewbox_attrib.get_list_element(0), viewbox_attrib.get_list_element(1), + viewbox_attrib.get_list_element(2), viewbox_attrib.get_list_element(3)) else: - viewbox = Rect2(0, 0, attributes.width.get_num(), attributes.height.get_num()) + viewbox = Rect2(0, 0, get_attribute_num("width"), get_attribute_num("height")) # Cache canvas transform. var width_ratio := width / viewbox.size.x var height_ratio := height / viewbox.size.y @@ -89,277 +64,5 @@ func canvas_to_world(pos: Vector2) -> Vector2: func world_to_canvas(pos: Vector2) -> Vector2: return canvas_transform.affine_inverse() * pos - func get_size() -> Vector2: return Vector2(width, height) - - -func get_all_tids() -> Array[PackedInt32Array]: - var tids: Array[PackedInt32Array] = [] - var unchecked_tids: Array[PackedInt32Array] = [] - for idx in get_child_count(): - unchecked_tids.append(PackedInt32Array([idx])) - - while not unchecked_tids.is_empty(): - var checked_tid: PackedInt32Array = unchecked_tids.pop_back() - for idx in get_tag(checked_tid).get_child_count(): - var new_tid := checked_tid.duplicate() - new_tid.append(idx) - unchecked_tids.append(new_tid) - tids.append(checked_tid) - return tids - -func get_tag(tid: PackedInt32Array) -> Tag: - var current_tag: Tag = self - for idx in tid: - if idx >= current_tag.child_tags.size(): - return null - current_tag = current_tag.child_tags[idx] - return current_tag - - -func add_tag(new_tag: Tag, new_tid: PackedInt32Array) -> void: - var parent_tid := Utils.get_parent_tid(new_tid) - get_tag(parent_tid).child_tags.insert(new_tid[-1], new_tag) - new_tag.attribute_changed.connect(emit_child_attribute_changed) - var new_tid_array: Array[PackedInt32Array] = [new_tid] - tags_added.emit(new_tid_array) - tag_layout_changed.emit() - -func replace_self(new_tag: Tag) -> void: - var old_size := get_size() - for attrib in attributes: - attributes[attrib].set_value(new_tag.attributes[attrib].get_value(), - Attribute.SyncMode.SILENT) - - unknown_attributes.clear() - for attrib in new_tag.unknown_attributes: - unknown_attributes.append(attrib) - child_tags.clear() - - for tag in new_tag.child_tags: - child_tags.append(tag) - - for tid in get_all_tids(): - get_tag(tid).attribute_changed.connect(emit_child_attribute_changed) - - changed_unknown.emit() - if old_size != get_size(): - resized.emit() - -func delete_tags(tids: Array[PackedInt32Array]) -> void: - if tids.is_empty(): - return - - tids = Utils.filter_descendant_tids(tids) - for tid in tids: - var parent_tid := Utils.get_parent_tid(tid) - var parent_tag := get_tag(parent_tid) - if is_instance_valid(parent_tag): - var tag_idx := tid[-1] - if tag_idx < parent_tag.get_child_count(): - parent_tag.child_tags.remove_at(tag_idx) - tags_deleted.emit(tids) - tag_layout_changed.emit() - -# Moves tags up or down, not to an arbitrary position. -func move_tags_in_parent(tids: Array[PackedInt32Array], down: bool) -> void: - if tids.is_empty(): - return - - # For moving, all these tags must be direct children of the same parent. - tids = Utils.filter_descendant_tids(tids) - var depth := tids[0].size() - var parent_tid := Utils.get_parent_tid(tids[0]) - for tid in tids: - if tid.size() != depth or Utils.get_parent_tid(tid) != parent_tid: - return - - var tid_indices: Array[int] = [] # The last indices of the TIDs. - for tid in tids: - tid_indices.append(tid[-1]) - - var parent_tag := get_tag(parent_tid) - var parent_child_count := parent_tag.get_child_count() - var old_indices: Array[int] = [] - for i in parent_child_count: - old_indices.append(i) - # Do the moving. - if down: - var i := parent_child_count - 1 - while i >= 0: - if not i in tid_indices and (i - 1) in tid_indices: - old_indices.remove_at(i) - var moved_i := i - var moved_tag: Tag = parent_tag.child_tags.pop_at(i) - while (i - 1) in tid_indices: - i -= 1 - old_indices.insert(i, moved_i) - parent_tag.child_tags.insert(i, moved_tag) - i -= 1 - else: - var i := 0 - while i < parent_child_count: - if not i in tid_indices and (i + 1) in tid_indices: - old_indices.remove_at(i) - var moved_i := i - var moved_tag: Tag = parent_tag.child_tags.pop_at(i) - while (i + 1) in tid_indices: - i += 1 - old_indices.insert(i, moved_i) - parent_tag.child_tags.insert(i, moved_tag) - i += 1 - # Check if indices were really changed after the operation. - if old_indices != range(old_indices.size()): - tags_moved_in_parent.emit(parent_tid, old_indices) - tag_layout_changed.emit() - -# Moves tags to an arbitrary position. The first moved tag will move to the location TID. -func move_tags_to(tids: Array[PackedInt32Array], location: PackedInt32Array) -> void: - tids = Utils.filter_descendant_tids(tids) - # A tag can't move deeper inside itself. Remove the descendants of the location. - for i in range(tids.size() - 1, -1, -1): - if Utils.is_tid_parent(tids[i], location): - tids.remove_at(i) - - # Remove tags from their old locations. - var tids_stored: Array[PackedInt32Array] = [] - var tags_stored: Array[Tag] = [] - for tid in tids: - # Shift the new location if tags before it were removed. A tag is "before" - # if it has the same parent as the new location, but is before that location. - if tid.size() <= location.size(): - var before := true - for i in tid.size() - 1: - if tid[i] != location[i]: - before = false - break - if before and tid[-1] < location[tid.size() - 1]: - location[tid.size() - 1] -= 1 - tids_stored.append(tid) - tags_stored.append(get_tag(Utils.get_parent_tid(tid)).child_tags.pop_at(tid[-1])) - # Add the tags back in the new location. - for tag in tags_stored: - get_tag(Utils.get_parent_tid(location)).child_tags.insert(location[-1], tag) - # Check if this actually chagned the layout. - for tid in tids_stored: - if not Utils.are_tid_parents_same(tid, location) or tid[-1] < location[-1] or\ - tid[-1] >= location[-1] + tids_stored.size(): - # If this condition is passed, then there was a layout change. - tags_moved_to.emit(tids, location) - tag_layout_changed.emit() - return - -# Duplicates tags and puts them below. -func duplicate_tags(tids: Array[PackedInt32Array]) -> void: - if tids.is_empty(): - return - - tids = Utils.filter_descendant_tids(tids) - var tids_added: Array[PackedInt32Array] = [] - # Used to offset previously added TIDs in tids_added after duplicating a tag before. - var last_parent := PackedInt32Array([-1]) # Start with a TID that can't be matched. - var added_to_last_parent := 0 - - for tid in tids: - var new_tag := get_tag(tid).duplicate() - # Add the new tag. - var new_tid := tid.duplicate() - new_tid[-1] += 1 - var parent_tid := Utils.get_parent_tid(new_tid) - get_tag(parent_tid).child_tags.insert(new_tid[-1], new_tag) - new_tag.attribute_changed.connect(emit_child_attribute_changed) - # Add the TID and offset the other ones from the same parent. - var added_tid_idx := tids_added.size() - tids_added.append(new_tid) - if last_parent == parent_tid: - added_to_last_parent += 1 - else: - last_parent = parent_tid - added_to_last_parent = 0 - for tid_idx in range(added_tid_idx - added_to_last_parent , added_tid_idx): - tids_added[tid_idx][-1] += 1 - tags_added.emit(tids_added) - tag_layout_changed.emit() - -func replace_tag(tid: PackedInt32Array, new_tag: Tag) -> void: - if tid.is_empty(): - replace_self(new_tag) - get_tag(Utils.get_parent_tid(tid)).child_tags[tid[-1]] = new_tag - new_tag.attribute_changed.connect(emit_child_attribute_changed) - tag_layout_changed.emit() - -func emit_child_attribute_changed(undo_redo: bool) -> void: - child_attribute_changed.emit(undo_redo) - -func emit_attribute_changed(undo_redo: bool) -> void: - super(undo_redo) - resized.emit() - - -# Optimizes the SVG text in more ways than what formatting attributes allows. -# The return value is true if the SVG can be optimized, otherwise false. -# If apply_changes is false, you'll only get the return value. -func optimize(not_applied := false) -> bool: - for tid in get_all_tids(): - var tag := get_tag(tid) - match tag.name: - "ellipse": - # If possible, turn ellipses into circles. - if tag.can_replace("circle"): - if not_applied: - return true - replace_tag(tid, get_tag(tid).get_replacement("circle")) - "line": - # Turn lines into paths. - if not_applied: - return true - replace_tag(tid, get_tag(tid).get_replacement("path")) - "rect": - # If possible, turn rounded rects into circles or ellipses. - if tag.can_replace("circle"): - if not_applied: - return true - replace_tag(tid, get_tag(tid).get_replacement("circle")) - elif tag.can_replace("ellipse"): - if not_applied: - return true - replace_tag(tid, get_tag(tid).get_replacement("ellipse")) - elif tag.attributes.rx.get_num() == 0 and tag.attributes.ry.get_num() == 0: - # If the rectangle is not rounded, turn it into a path. - if not_applied: - return true - replace_tag(tid, get_tag(tid).get_replacement("path")) - "path": - var pathdata: AttributePath = tag.attributes.d - # Simplify A rotation to 0 degrees for circular arcs. - for cmd_idx in pathdata.get_command_count(): - var command := pathdata.get_command(cmd_idx) - var cmd_char := command.command_char - if cmd_char in "Aa" and command.rx == command.ry and command.rot != 0: - if not_applied: - return true - pathdata.set_command_property(cmd_idx, "rot", 0) - # Replace L with H or V when possible. - for cmd_idx in pathdata.get_command_count(): - var command := pathdata.get_command(cmd_idx) - var cmd_char := command.command_char - if cmd_char == "l": - if command.x == 0: - if not_applied: - return true - pathdata.convert_command(cmd_idx, "v") - elif command.y == 0: - if not_applied: - return true - pathdata.convert_command(cmd_idx, "h") - elif cmd_char == "L": - if command.x == command.start.x: - if not_applied: - return true - pathdata.convert_command(cmd_idx, "V") - elif command.y == command.start.y: - if not_applied: - return true - pathdata.convert_command(cmd_idx, "H") - return false diff --git a/src/data_classes/TagShape.gd b/src/data_classes/TagShape.gd new file mode 100644 index 00000000..b826b0a4 --- /dev/null +++ b/src/data_classes/TagShape.gd @@ -0,0 +1,12 @@ +class_name TagShape extends Tag + +# To be overridden. +func get_bounding_box() -> Rect2: + return Rect2(0, 0, 0, 0) + +func get_config_warnings() -> PackedStringArray: + var warnings := super() + if not (parent is TagG or parent is TagSVG or parent is TagUnrecognized): + warnings.append(TranslationServer.translate("{tag} must be a child of {allowed} to have any effect.").format( + {"tag": self.name, "allowed": "[svg, g]"})) + return warnings diff --git a/src/data_classes/TagStop.gd b/src/data_classes/TagStop.gd index 7a473672..e4b983a2 100644 --- a/src/data_classes/TagStop.gd +++ b/src/data_classes/TagStop.gd @@ -3,10 +3,17 @@ class_name TagStop extends Tag const name = "stop" const possible_conversions = [] -const known_attributes = ["offset", "stop-color", "stop-opacity"] -const icon = preload("res://visual/icons/tag/stop.svg") -func _init() -> void: - for attrib_name in ["offset", "stop-color", "stop-opacity"]: - attributes[attrib_name] = DB.attribute(attrib_name) - super() +func get_own_default(attribute_name: String) -> String: + match attribute_name: + "offset": return "0" + "stop-color": return "black" + "stop-opacity": return "1" + _: return "" + +func get_config_warnings() -> PackedStringArray: + var warnings := super() + if not (parent is TagLinearGradient or parent is TagRadialGradient): + warnings.append(TranslationServer.translate("{tag} must be a child of {allowed} to have any effect.").format( + {"tag": name, "allowed": "[linearGradient, radialGradient]"})) + return warnings diff --git a/src/data_classes/TagUnknown.gd b/src/data_classes/TagUnknown.gd deleted file mode 100644 index 341c54c1..00000000 --- a/src/data_classes/TagUnknown.gd +++ /dev/null @@ -1,11 +0,0 @@ -# A tag that GodSVG doesn't recognize. -class_name TagUnknown extends Tag - -var name: String -const possible_conversions = [] -const known_shape_attributes = [] -const known_inheritable_attributes = [] -const icon = preload("res://visual/icons/tag/unknown.svg") - -func _init(new_name: String) -> void: - name = new_name diff --git a/src/data_classes/TagUnrecognized.gd b/src/data_classes/TagUnrecognized.gd new file mode 100644 index 00000000..48c3be84 --- /dev/null +++ b/src/data_classes/TagUnrecognized.gd @@ -0,0 +1,8 @@ +# A tag that GodSVG doesn't recognize. +class_name TagUnrecognized extends Tag + +var name: String +const possible_conversions = [] + +func _init(new_name: String) -> void: + name = new_name diff --git a/src/data_classes/Transform.gd b/src/data_classes/Transform.gd new file mode 100644 index 00000000..d04a7835 --- /dev/null +++ b/src/data_classes/Transform.gd @@ -0,0 +1,75 @@ +class_name Transform extends RefCounted + +class TransformMatrix extends Transform: + var x1: float + var x2: float + var y1: float + var y2: float + var o1: float + var o2: float + + func _init(new_x1: float, new_x2: float, new_y1: float, new_y2: float, new_o1: float, + new_o2: float) -> void: + x1 = new_x1 + x2 = new_x2 + y1 = new_y1 + y2 = new_y2 + o1 = new_o1 + o2 = new_o2 + + func compute_transform() -> Transform2D: + return Transform2D(Vector2(x1, x2), Vector2(y1, y2), Vector2(o1, o2)) + +class TransformTranslate extends Transform: + var x: float + var y: float + + func _init(new_x: float, new_y: float) -> void: + x = new_x + y = new_y + + func compute_transform() -> Transform2D: + return Transform2D(Vector2.RIGHT, Vector2.DOWN, Vector2(x, y)) + +class TransformRotate extends Transform: + var deg: float + var x: float + var y: float + + func _init(new_deg: float, new_x: float, new_y: float) -> void: + deg = new_deg + x = new_x + y = new_y + + func compute_transform() -> Transform2D: + var pt := Vector2(x, y) + return Transform2D.IDENTITY.translated(-pt).rotated(deg_to_rad(deg)).translated(pt) + +class TransformScale extends Transform: + var x: float + var y: float + + func _init(new_x: float, new_y: float) -> void: + x = new_x + y = new_y + + func compute_transform() -> Transform2D: + return Transform2D(Vector2.RIGHT * x, Vector2.DOWN * y, Vector2.ZERO) + +class TransformSkewX extends Transform: + var x: float + + func _init(new_x: float) -> void: + x = new_x + + func compute_transform() -> Transform2D: + return Transform2D(Vector2.RIGHT, Vector2(tan(deg_to_rad(x)), 1), Vector2.ZERO) + +class TransformSkewY extends Transform: + var y: float + + func _init(new_y: float) -> void: + y = new_y + + func compute_transform() -> Transform2D: + return Transform2D(Vector2(1, tan(deg_to_rad(y))), Vector2.DOWN, Vector2.ZERO) diff --git a/src/data_classes/XNode.gd b/src/data_classes/XNode.gd new file mode 100644 index 00000000..3bdf3efa --- /dev/null +++ b/src/data_classes/XNode.gd @@ -0,0 +1,3 @@ +class_name XNode extends RefCounted + +var xid: PackedInt32Array diff --git a/src/parsers/ColorParser.gd b/src/parsers/ColorParser.gd index 02fbede3..629b14d6 100644 --- a/src/parsers/ColorParser.gd +++ b/src/parsers/ColorParser.gd @@ -77,7 +77,7 @@ static func is_valid_url(color: String) -> bool: if not color.begins_with("url(") or not color.ends_with(")"): return false var id := color.substr(4, color.length() - 5).strip_edges().trim_prefix("#") - return IDParser.get_id_validity(id) != IDParser.ValidityLevel.INVALID + return IDParser.get_validity(id) != IDParser.ValidityLevel.INVALID # URL doesn't have a color interpretation, so it'll give the backup. static func string_to_color(color: String, backup := Color.BLACK, diff --git a/src/parsers/IDParser.gd b/src/parsers/IDParser.gd index 1fc9143d..f3db349c 100644 --- a/src/parsers/IDParser.gd +++ b/src/parsers/IDParser.gd @@ -5,7 +5,7 @@ class_name IDParser extends RefCounted enum ValidityLevel {VALID, INVALID_XML_NAMETOKEN, INVALID} -static func get_id_validity(id: String) -> ValidityLevel: +static func get_validity(id: String) -> ValidityLevel: if id.is_empty() or id[0] == "#": return ValidityLevel.INVALID diff --git a/src/parsers/PathDataParser.gd b/src/parsers/PathDataParser.gd index 8948c687..b688afdc 100644 --- a/src/parsers/PathDataParser.gd +++ b/src/parsers/PathDataParser.gd @@ -1,12 +1,12 @@ -class_name PathDataParser extends RefCounted +class_name PathdataParser extends RefCounted const translation_dict = PathCommand.translation_dict -static func parse_path_data(text: String) -> Array[PathCommand]: - return path_commands_from_parsed_data(path_data_to_arrays(text)) +static func parse_pathdata(text: String) -> Array[PathCommand]: + return path_commands_from_parsed_data(pathdata_to_arrays(text)) # godot_only/tests.gd has a test for this. -static func path_data_to_arrays(text: String) -> Array[Array]: +static func pathdata_to_arrays(text: String) -> Array[Array]: var new_commands: Array[Array] = [] var curr_command := "" var prev_command := "" @@ -173,21 +173,21 @@ static func path_commands_from_parsed_data(data: Array[Array]) -> Array[PathComm static func path_commands_to_text(commands_arr: Array[PathCommand]) -> String: var output := "" var num_parser := NumberArrayParser.new() - num_parser.compress_numbers = GlobalSettings.path_compress_numbers - num_parser.minimize_spacing = GlobalSettings.path_minimize_spacing + num_parser.compress_numbers = GlobalSettings.pathdata_compress_numbers + num_parser.minimize_spacing = GlobalSettings.pathdata_minimize_spacing var last_command := "" for i in commands_arr.size(): var cmd := commands_arr[i] var cmd_char_capitalized := cmd.command_char.to_upper() - if not (GlobalSettings.path_remove_consecutive_commands and\ + if not (GlobalSettings.pathdata_remove_consecutive_commands and\ ((cmd_char_capitalized != "M" and last_command == cmd.command_char) or\ (last_command == "m" and cmd.command_char == "l") or\ (last_command == "M" and cmd.command_char == "L"))): output += cmd.command_char - if not GlobalSettings.path_minimize_spacing: + if not GlobalSettings.pathdata_minimize_spacing: output += " " - elif i > 0 and GlobalSettings.path_minimize_spacing: + elif i > 0 and GlobalSettings.pathdata_minimize_spacing: var current_char := "" var prev_numstr := "" match cmd_char_capitalized: @@ -209,7 +209,7 @@ static func path_commands_to_text(commands_arr: Array[PathCommand]) -> String: "V": current_char = num_parser.num_to_text(cmd.y)[0] prev_numstr = num_parser.num_to_text(+commands_arr[i - 1].y) - if not GlobalSettings.path_minimize_spacing or not\ + if not GlobalSettings.pathdata_minimize_spacing or not\ (("." in prev_numstr and current_char == ".") or current_char in "-+"): output += " " @@ -218,7 +218,7 @@ static func path_commands_to_text(commands_arr: Array[PathCommand]) -> String: "A": output += num_parser.numstr_arr_to_text([num_parser.num_to_text(cmd.rx), num_parser.num_to_text(cmd.ry), num_parser.num_to_text(cmd.rot, true)]) - if GlobalSettings.path_remove_spacing_after_flags: + if GlobalSettings.pathdata_remove_spacing_after_flags: output += (" 0" if cmd.large_arc_flag == 0 else " 1") +\ ("0" if cmd.sweep_flag == 0 else "1") else: @@ -247,7 +247,7 @@ static func path_commands_to_text(commands_arr: Array[PathCommand]) -> String: "V": output += num_parser.num_to_text(cmd.y) _: continue - if not GlobalSettings.path_minimize_spacing: + if not GlobalSettings.pathdata_minimize_spacing: output += " " output = output.rstrip(" ") diff --git a/src/parsers/SVGHighlighter.gd b/src/parsers/SVGHighlighter.gd index 7105ee31..df9e82ee 100644 --- a/src/parsers/SVGHighlighter.gd +++ b/src/parsers/SVGHighlighter.gd @@ -10,12 +10,12 @@ class_name SVGHighlighter extends SyntaxHighlighter @export var cdata_color := Color("ffeda1ac") @export var error_color := Color("ff866b") -var unknown_tag_color: Color -var unknown_attribute_color: Color +var unrecognized_tag_color: Color +var unrecognized_attribute_color: Color func setup_extra_colors() -> void: - unknown_tag_color = Color(tag_color, tag_color.a * 0.7) - unknown_attribute_color = Color(attribute_color, attribute_color.a * 0.7) + unrecognized_tag_color = Color(tag_color, tag_color.a * 2 / 3.0) + unrecognized_attribute_color = Color(attribute_color, attribute_color.a * 2 / 3.0) func _get_line_syntax_highlighting(line: int) -> Dictionary: @@ -39,7 +39,7 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: return color_map else: color_map[offset] = {"color": - tag_color if DB.is_tag_known(tag_name) else unknown_tag_color} + tag_color if tag_name in DB.recognized_tags else unrecognized_tag_color} offset += tag_name.length() # Attribute names can't be directly after a quotation or after the tag name. @@ -70,20 +70,20 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: elif not current_attribute_name.is_empty(): if c in " \t\n\r": color_map[offset - current_attribute_name.length()] = {"color": - attribute_color if DB.is_attribute_known(tag_name, - current_attribute_name) else unknown_attribute_color} + attribute_color if DB.is_attribute_recognized(tag_name, + current_attribute_name) else unrecognized_attribute_color} current_attribute_name = "" expecting_equal_sign = true elif c in "/>": color_map[offset - current_attribute_name.length()] = {"color": - attribute_color if DB.is_attribute_known(tag_name, - current_attribute_name) else unknown_attribute_color} + attribute_color if DB.is_attribute_recognized(tag_name, + current_attribute_name) else unrecognized_attribute_color} color_map[offset] = {"color": error_color} return color_map elif c == "=": color_map[offset - current_attribute_name.length()] = {"color": - attribute_color if DB.is_attribute_known(tag_name, - current_attribute_name) else unknown_attribute_color} + attribute_color if DB.is_attribute_recognized(tag_name, + current_attribute_name) else unrecognized_attribute_color} color_map[offset] = {"color": symbol_color} current_attribute_name = "" expecting_attribute_value = true @@ -123,15 +123,15 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: offset += 1 if not current_attribute_name.is_empty(): color_map[svg_text.length() - current_attribute_name.length() - 1] =\ - {"color": attribute_color if DB.is_attribute_known(tag_name, - current_attribute_name) else unknown_attribute_color} + {"color": attribute_color if DB.is_attribute_recognized(tag_name, + current_attribute_name) else unrecognized_attribute_color} XMLParser.NODE_ELEMENT_END: offset = svg_text.find("<", offset) var tag_name := parser.get_node_name() color_map[offset] = {"color": symbol_color} offset += 2 color_map[offset] = {"color": - tag_color if DB.is_tag_known(tag_name) else unknown_tag_color} + tag_color if tag_name in DB.recognized_tags else unrecognized_tag_color} offset += tag_name.length() color_map[offset] = {"color": symbol_color} XMLParser.NODE_TEXT: @@ -142,4 +142,3 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: color_map[offset] = {"color": comment_color} return color_map - diff --git a/src/parsers/SVGParser.gd b/src/parsers/SVGParser.gd index a3bddc4e..64d526c5 100644 --- a/src/parsers/SVGParser.gd +++ b/src/parsers/SVGParser.gd @@ -4,54 +4,54 @@ class_name SVGParser extends RefCounted const shorthand_tag_exceptions = ["svg", "g", "linearGradient, radialGradient"] # For rendering only a section of the SVG. -static func svg_to_text_custom(svg_tag: TagSVG, custom_width: float, +static func root_to_text_custom(svg_tag: TagSVG, custom_width: float, custom_height: float, custom_viewbox: Rect2) -> String: var new_svg_tag: TagSVG = svg_tag.duplicate(false) - new_svg_tag.attributes.viewBox.set_rect(custom_viewbox) - new_svg_tag.attributes.width.set_num(custom_width) - new_svg_tag.attributes.height.set_num(custom_height) + new_svg_tag.set_attribute("viewBox", custom_viewbox) + new_svg_tag.set_attribute("width", custom_width) + new_svg_tag.set_attribute("height", custom_height) var text := _tag_to_text(new_svg_tag) - text = text.left(-6) # Removee the at the end. + text = text.left(-6) # Remove the at the end. for child_idx in svg_tag.get_child_count(): text += _tag_to_text(svg_tag.get_tag(PackedInt32Array([child_idx]))) return text + "" -static func svg_to_text(tag: TagSVG) -> String: +static func root_to_text(tag: TagRoot) -> String: + var text := _tag_to_text(tag).trim_suffix('\n') if GlobalSettings.xml_add_trailing_newline: - return _tag_to_text(tag) + "\n" - else: - return _tag_to_text(tag) + text += "\n" + return text static func _tag_to_text(tag: Tag) -> String: var text := "" + if GlobalSettings.xml_pretty_formatting: + text += '\t'.repeat(tag.xid.size()) text += '<' + tag.name for attribute_key in tag.attributes: var attribute: Attribute = tag.attributes[attribute_key] var value := attribute.get_value() - if value.is_empty(): - continue - if not '"' in value: text += ' %s="%s"' % [attribute_key, value] else: text += " %s='%s'" % [attribute_key, value] - for attribute in tag.unknown_attributes: - var value := attribute.get_value() - if not '"' in value: - text += ' %s="%s"' % [attribute.name, value] - else: - text += " %s='%s'" % [attribute.name, value] - if tag.is_standalone() and GlobalSettings.xml_shorthand_tags and\ not tag.name in shorthand_tag_exceptions: text += '/>' + if GlobalSettings.xml_pretty_formatting: + text += '\n' else: text += '>' + if GlobalSettings.xml_pretty_formatting: + text += '\n' for child_tag in tag.child_tags: text += _tag_to_text(child_tag) - text += '' + if GlobalSettings.xml_pretty_formatting: + text += '\t'.repeat(tag.xid.size()) + text += '' % tag.name + if GlobalSettings.xml_pretty_formatting: + text += '\n' return text @@ -60,7 +60,7 @@ enum ParseError {OK, ERR_NOT_SVG, ERR_IMPROPER_NESTING} class ParseResult extends RefCounted: var error: SVGParser.ParseError - var svg: TagSVG + var svg: TagRoot func _init(err_id: SVGParser.ParseError, result: TagSVG = null) -> void: error = err_id @@ -74,11 +74,14 @@ static func get_error_string(parse_error: ParseError) -> String: return TranslationServer.translate("Improper nesting.") _: return "" -static func text_to_svg(text: String) -> ParseResult: +static func text_to_root(text: String) -> ParseResult: if text.is_empty(): return ParseResult.new(ParseError.ERR_NOT_SVG) - var svg_tag := TagSVG.new() + var root_tag := TagRoot.new() + root_tag.xid = PackedInt32Array() + root_tag.root = root_tag + root_tag.svg = null var parser := XMLParser.new() parser.open_buffer(text.to_utf8_buffer()) var unclosed_tag_stack: Array[Tag] = [] @@ -97,34 +100,17 @@ static func text_to_svg(text: String) -> ParseResult: var attrib_dict := {} for i in parser.get_attribute_count(): attrib_dict[parser.get_attribute_name(i)] = parser.get_attribute_value(i) - # width, height, and viewBox don't have defaults. - if attrib_dict.has("width"): - svg_tag.attributes.width.set_value(attrib_dict["width"], - Attribute.SyncMode.SILENT) - if attrib_dict.has("height"): - svg_tag.attributes.height.set_value(attrib_dict["height"], - Attribute.SyncMode.SILENT) - if attrib_dict.has("viewBox"): - svg_tag.attributes.viewBox.set_value(attrib_dict["viewBox"], - Attribute.SyncMode.SILENT) - svg_tag.update_cache() - var unknown: Array[Attribute] = [] for element in attrib_dict: - if svg_tag.attributes.has(element): - var attribute: Attribute = svg_tag.attributes[element] - attribute.set_value(attrib_dict[element], Attribute.SyncMode.SILENT) - else: - unknown.append(Attribute.new(element, attrib_dict[element])) - svg_tag.set_unknown_attributes(unknown) + root_tag.set_attribute(element, attrib_dict[element]) var node_offset := parser.get_node_offset() var closure_pos := text.find("/>", node_offset) if closure_pos == -1 or closure_pos > text.find(">", node_offset): - unclosed_tag_stack.append(svg_tag) + unclosed_tag_stack.append(root_tag) break else: - return ParseResult.new(ParseError.OK, svg_tag) + return ParseResult.new(ParseError.OK, root_tag) if not describes_svg: return ParseResult.new(ParseError.ERR_NOT_SVG) @@ -138,32 +124,23 @@ static func text_to_svg(text: String) -> ParseResult: for i in parser.get_attribute_count(): attrib_dict[parser.get_attribute_name(i)] = parser.get_attribute_value(i) - var tag: Tag - match node_name: - "circle": tag = TagCircle.new() - "ellipse": tag = TagEllipse.new() - "rect": tag = TagRect.new() - "path": tag = TagPath.new() - "line": tag = TagLine.new() - "stop": tag = TagStop.new() - _: tag = TagUnknown.new(node_name) + var tag := DB.tag(node_name) + tag.set_parent(unclosed_tag_stack.back()) + tag.xid = tag.parent.xid.duplicate() + tag.xid.append(tag.parent.get_child_count()) - var unknown: Array[Attribute] = [] for element in attrib_dict: - if tag.attributes.has(element): - var attribute: Attribute = tag.attributes[element] - attribute.set_value(attrib_dict[element], Attribute.SyncMode.SILENT) - else: - unknown.append(Attribute.new(element, attrib_dict[element])) - tag.set_unknown_attributes(unknown) + tag.set_attribute(element, attrib_dict[element]) # Check if we're entering or exiting the tag. var node_offset := parser.get_node_offset() var closure_pos := text.find("/>", node_offset) + if closure_pos == -1 or closure_pos > text.find(">", node_offset): unclosed_tag_stack.append(tag) else: unclosed_tag_stack.back().child_tags.append(tag) + XMLParser.NODE_ELEMENT_END: if unclosed_tag_stack.is_empty(): return ParseResult.new(ParseError.ERR_IMPROPER_NESTING) @@ -179,4 +156,4 @@ static func text_to_svg(text: String) -> ParseResult: if not unclosed_tag_stack.is_empty(): return ParseResult.new(ParseError.ERR_IMPROPER_NESTING) - return ParseResult.new(ParseError.OK, svg_tag) + return ParseResult.new(ParseError.OK, root_tag) diff --git a/src/parsers/TransformListParser.gd b/src/parsers/TransformListParser.gd index ce4f8892..5351cb2d 100644 --- a/src/parsers/TransformListParser.gd +++ b/src/parsers/TransformListParser.gd @@ -1,48 +1,48 @@ class_name TransformListParser extends RefCounted static func transform_list_to_text( -transform_list: Array[AttributeTransform.Transform]) -> String: +transform_list: Array[Transform]) -> String: var output := "" var num_parser := NumberArrayParser.new() - num_parser.compress_numbers = GlobalSettings.transform_compress_numbers - num_parser.minimize_spacing = GlobalSettings.transform_minimize_spacing + num_parser.compress_numbers = GlobalSettings.transform_list_compress_numbers + num_parser.minimize_spacing = GlobalSettings.transform_list_minimize_spacing for t in transform_list: - if t is AttributeTransform.TransformMatrix: + if t is Transform.TransformMatrix: output += "matrix(%s) " % num_parser.numstr_arr_to_text([ num_parser.num_to_text(t.x1), num_parser.num_to_text(t.x2), num_parser.num_to_text(t.y1), num_parser.num_to_text(t.y2), num_parser.num_to_text(t.o1), num_parser.num_to_text(t.o2)]) - elif t is AttributeTransform.TransformTranslate: - if t.y == 0 and GlobalSettings.transform_remove_unnecessary_params: + elif t is Transform.TransformTranslate: + if t.y == 0 and GlobalSettings.transform_list_remove_unnecessary_params: output += "translate(%s) " % num_parser.num_to_text(t.x) else: output += "translate(%s) " % num_parser.numstr_arr_to_text([ num_parser.num_to_text(t.x), num_parser.num_to_text(t.y)]) - elif t is AttributeTransform.TransformRotate: - if t.x == 0 and t.y == 0 and GlobalSettings.transform_remove_unnecessary_params: + elif t is Transform.TransformRotate: + if t.x == 0 and t.y == 0 and GlobalSettings.transform_list_remove_unnecessary_params: output += "rotate(%s) " % num_parser.num_to_text(t.deg, true) else: output += "rotate(%s) " % num_parser.numstr_arr_to_text([ num_parser.num_to_text(t.deg, true), num_parser.num_to_text(t.x), num_parser.num_to_text(t.y)]) - elif t is AttributeTransform.TransformScale: - if t.x == t.y and GlobalSettings.transform_remove_unnecessary_params: + elif t is Transform.TransformScale: + if t.x == t.y and GlobalSettings.transform_list_remove_unnecessary_params: output += "scale(%s) " % num_parser.num_to_text(t.x) else: output += "scale(%s) " % num_parser.numstr_arr_to_text([ num_parser.num_to_text(t.x), num_parser.num_to_text(t.y)]) - elif t is AttributeTransform.TransformSkewX: + elif t is Transform.TransformSkewX: output += "skewX(%s) " % num_parser.num_to_text(t.x, true) - elif t is AttributeTransform.TransformSkewY: + elif t is Transform.TransformSkewY: output += "skewY(%s) " % num_parser.num_to_text(t.y, true) return output.trim_suffix(" ") -static func text_to_transform_list(text: String) -> Array[AttributeTransform.Transform]: +static func text_to_transform_list(text: String) -> Array[Transform]: if text.is_empty(): return [] - var output: Array[AttributeTransform.Transform] = [] + var output: Array[Transform] = [] text = text.strip_edges() var transforms := text.split(")", false) for transform in transforms: @@ -127,40 +127,40 @@ static func text_to_transform_list(text: String) -> Array[AttributeTransform.Tra match transform_info[0].strip_edges(): "matrix": if nums.size() == 6: - output.append(AttributeTransform.TransformMatrix.new(nums[0], nums[1], + output.append(Transform.TransformMatrix.new(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5])) else: return [] "translate": if nums.size() == 1: - output.append(AttributeTransform.TransformTranslate.new(nums[0], 0.0)) + output.append(Transform.TransformTranslate.new(nums[0], 0.0)) elif nums.size() == 2: - output.append(AttributeTransform.TransformTranslate.new(nums[0], nums[1])) + output.append(Transform.TransformTranslate.new(nums[0], nums[1])) else: return [] "rotate": if nums.size() == 1: - output.append(AttributeTransform.TransformRotate.new(nums[0], 0.0, 0.0)) + output.append(Transform.TransformRotate.new(nums[0], 0.0, 0.0)) elif nums.size() == 3: - output.append(AttributeTransform.TransformRotate.new( + output.append(Transform.TransformRotate.new( nums[0], nums[1], nums[2])) else: return [] "scale": if nums.size() == 1: - output.append(AttributeTransform.TransformScale.new(nums[0], nums[0])) + output.append(Transform.TransformScale.new(nums[0], nums[0])) elif nums.size() == 2: - output.append(AttributeTransform.TransformScale.new(nums[0], nums[1])) + output.append(Transform.TransformScale.new(nums[0], nums[1])) else: return [] "skewX": if nums.size() == 1: - output.append(AttributeTransform.TransformSkewX.new(nums[0])) + output.append(Transform.TransformSkewX.new(nums[0])) else: return [] "skewY": if nums.size() == 1: - output.append(AttributeTransform.TransformSkewY.new(nums[0])) + output.append(Transform.TransformSkewY.new(nums[0])) else: return [] _: diff --git a/src/ui_elements/BetterLineEdit.gd b/src/ui_elements/BetterLineEdit.gd index 87109039..a6bb3eb3 100644 --- a/src/ui_elements/BetterLineEdit.gd +++ b/src/ui_elements/BetterLineEdit.gd @@ -71,6 +71,7 @@ func _input(event: InputEvent) -> void: return if event is InputEventMouseButton: + # TODO This causes part of #798. if event.is_pressed() and not get_global_rect().has_point(event.position) and\ HandlerGUI.popup_overlay_stack.is_empty(): release_focus() diff --git a/src/ui_elements/context_popup.gd b/src/ui_elements/ContextPopup.gd similarity index 92% rename from src/ui_elements/context_popup.gd rename to src/ui_elements/ContextPopup.gd index 709b89b2..13dbba84 100644 --- a/src/ui_elements/context_popup.gd +++ b/src/ui_elements/ContextPopup.gd @@ -15,7 +15,7 @@ icon: Texture2D = null, shortcut := "") -> Button: if not shortcut.is_empty(): if not InputMap.has_action(shortcut): - printerr("A non-existent shortcut was passed to ContextPopup.create_button().") + push_error("Non-existent shortcut was passed to ContextPopup.create_button().") elif InputMap.has_action(shortcut): var events := InputMap.action_get_events(shortcut) if not events.is_empty(): @@ -26,11 +26,10 @@ icon: Texture2D = null, shortcut := "") -> Button: if disabled: main_button.disabled = true ret_button.disabled = true - main_button.add_theme_stylebox_override("disabled", - main_button.get_theme_stylebox("normal", "ContextButton")) else: ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - main_button.add_theme_stylebox_override("normal", + for theme_item in ["normal", "hover", "pressed", "disabled"]: + main_button.add_theme_stylebox_override(theme_item, main_button.get_theme_stylebox("normal", "ContextButton")) var internal_hbox := HBoxContainer.new() main_button.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable. @@ -43,8 +42,10 @@ icon: Texture2D = null, shortcut := "") -> Button: var label := Label.new() label.text = events[0].as_text_keycode() label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - label.add_theme_color_override("font_color", - ThemeGenerator.common_subtle_text_color) + var shortcut_text_color := ThemeGenerator.common_subtle_text_color + if disabled: + shortcut_text_color.a *= 0.75 + label.add_theme_color_override("font_color", shortcut_text_color) label.add_theme_font_size_override("font_size", main_button.get_theme_font_size("font_size")) @@ -80,7 +81,7 @@ start_pressed: bool, shortcut := "") -> CheckBox: if not shortcut.is_empty(): if not InputMap.has_action(shortcut): - printerr("A non-existent shortcut was passed to ContextPopup.create_checkbox().") + push_error("Non-existent shortcut was passed to ContextPopup.create_checkbox().") elif InputMap.has_action(shortcut): var events := InputMap.action_get_events(shortcut) if not events.is_empty(): @@ -102,8 +103,10 @@ start_pressed: bool, shortcut := "") -> CheckBox: var label := Label.new() label.text = events[0].as_text_keycode() label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - label.add_theme_color_override("font_color", - ThemeGenerator.common_subtle_text_color) + var shortcut_text_color := ThemeGenerator.common_subtle_text_color + #if disabled: + #shortcut_text_color.a *= 0.75 + label.add_theme_color_override("font_color", shortcut_text_color) label.add_theme_font_size_override("font_size", checkbox.get_theme_font_size("font_size")) @@ -176,7 +179,7 @@ min_width := -1.0, separator_indices: Array[int] = []) -> void: stylebox.content_margin_left = 8 stylebox.content_margin_right = 8 stylebox.border_width_bottom = 2 - stylebox.border_color = ThemeGenerator.common_separator_color + stylebox.border_color = ThemeGenerator.common_panel_border_color title_container.add_theme_stylebox_override("panel", stylebox) var title_label := Label.new() title_label.text = top_title diff --git a/src/ui_elements/LineEditButton.gd b/src/ui_elements/LineEditButton.gd index d5e20d8b..0686a599 100644 --- a/src/ui_elements/LineEditButton.gd +++ b/src/ui_elements/LineEditButton.gd @@ -117,9 +117,9 @@ func _setup() -> void: if font_color != Color.TRANSPARENT: temp_line_edit.add_theme_color_override("font_color", _get_font_color()) temp_line_edit.add_theme_font_override("font", _get_font()) - temp_line_edit.text_change_canceled.connect(emit_text_change_canceled) - temp_line_edit.text_changed.connect(emit_text_changed) - temp_line_edit.text_submitted.connect(emit_text_submitted) + temp_line_edit.text_change_canceled.connect(text_change_canceled.emit) + temp_line_edit.text_changed.connect(text_changed.emit) + temp_line_edit.text_submitted.connect(text_submitted.emit) temp_line_edit.focus_entered.connect(_on_underlying_control_focused) temp_line_edit.focus_exited.connect(_on_underlying_control_unfocused) add_child(temp_line_edit) @@ -134,8 +134,8 @@ func _setup() -> void: temp_button.theme_type_variation = "LeftConnectedButton" else: temp_button.flat = true - temp_button.pressed.connect(emit_pressed) - temp_button.gui_input.connect(emit_button_gui_input) + temp_button.pressed.connect(pressed.emit) + temp_button.gui_input.connect(button_gui_input.emit) temp_button.button_down.connect(_on_underlying_control_focused) temp_button.button_up.connect(_on_underlying_control_unfocused) add_child(temp_button) @@ -176,22 +176,6 @@ func _draw() -> void: (size.y - icon_side) / 2, icon_side, icon_side), false) -func emit_pressed() -> void: - pressed.emit() - -func emit_text_change_canceled() -> void: - text_change_canceled.emit() - -func emit_text_changed(new_text: String) -> void: - text_changed.emit(new_text) - -func emit_text_submitted(new_text: String) -> void: - text_submitted.emit(new_text) - -func emit_button_gui_input(event: InputEvent) -> void: - button_gui_input.emit(event) - - # Helpers func _get_font() -> Font: diff --git a/src/ui_elements/color_field.gd b/src/ui_elements/color_field.gd index d2fe6f8a..2d7068c4 100644 --- a/src/ui_elements/color_field.gd +++ b/src/ui_elements/color_field.gd @@ -1,14 +1,16 @@ # An editor to be tied to a color attribute. extends LineEditButton -var attribute: AttributeColor +var tag: Tag +var attribute_name: String const ColorPopup = preload("res://src/ui_elements/color_popup.tscn") const checkerboard = preload("res://visual/icons/backgrounds/ColorButtonBG.svg") @onready var color_popup: Control -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: +func set_value(new_value: String, save := true) -> void: + var attribute := tag.get_attribute(attribute_name) if not new_value.is_empty(): # Validate the value. if not is_valid(new_value): @@ -18,28 +20,35 @@ func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> vo sync(attribute.format(new_value)) # Update the attribute. - if attribute.get_value() != new_value or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) + +func setup_default() -> void: + placeholder_text = tag.get_default(attribute_name) func _ready() -> void: - set_value(attribute.get_value()) - tooltip_text = attribute.name - placeholder_text = attribute.get_default() - attribute.value_changed.connect(set_value) + set_value(tag.get_attribute_value(attribute_name, true)) + tag.attribute_changed.connect(_on_tag_attribute_changed) + if attribute_name in DB.propagated_attributes: + tag.ancestor_attribute_changed.connect(_on_tag_ancestor_attribute_changed) text_submitted.connect(set_value) focus_entered.connect(reset_font_color) + tooltip_text = attribute_name + setup_default() + + +func _on_tag_attribute_changed(attribute_changed: String) -> void: + if attribute_name == attribute_changed: + set_value(tag.get_attribute_value(attribute_name, true), false) +func _on_tag_ancestor_attribute_changed(attribute_changed: String) -> void: + if attribute_name == attribute_changed: + setup_default() func _on_pressed() -> void: color_popup = ColorPopup.instantiate() - color_popup.current_value = attribute.get_value() + color_popup.current_value = tag.get_attribute(attribute_name).get_value() color_popup.color_picked.connect(_on_color_picked) HandlerGUI.popup_under_rect(color_popup, get_global_rect(), get_viewport()) @@ -48,7 +57,7 @@ func _draw() -> void: var stylebox := StyleBoxFlat.new() stylebox.corner_radius_top_right = 5 stylebox.corner_radius_bottom_right = 5 - stylebox.bg_color = attribute.get_color() + stylebox.bg_color = tag.get_attribute(attribute_name).get_color() checkerboard.draw(ci, Vector2(size.x - BUTTON_WIDTH, 1)) stylebox.draw(ci, Rect2(size.x - BUTTON_WIDTH, 1, BUTTON_WIDTH - 1, size.y - 2)) if is_instance_valid(temp_button) and temp_button.button_pressed: @@ -61,15 +70,15 @@ func _draw() -> void: func _on_text_change_canceled() -> void: - sync(attribute.get_value()) + sync(tag.get_attribute(attribute_name).get_value()) func _on_color_picked(new_color: String, close_picker: bool) -> void: if close_picker: color_popup.queue_free() - set_value(new_color, Utils.UpdateType.FINAL) + set_value(new_color, true) else: - set_value(new_color, Utils.UpdateType.INTERMEDIATE) + set_value(new_color, false) func is_valid(color_text: String) -> bool: return ColorParser.is_valid(ColorParser.add_hash_if_hex(color_text)) @@ -80,7 +89,7 @@ func _on_text_changed(new_text: String) -> void: func sync(new_value: String) -> void: reset_font_color() - if new_value == attribute.get_default(): + if new_value == tag.get_default(attribute_name): font_color = GlobalSettings.basic_color_warning text = new_value.trim_prefix("#") queue_redraw() diff --git a/src/ui_elements/color_swatch_config.gd b/src/ui_elements/color_swatch_config.gd index dbf11635..dc20276a 100644 --- a/src/ui_elements/color_swatch_config.gd +++ b/src/ui_elements/color_swatch_config.gd @@ -44,7 +44,7 @@ func _draw() -> void: # Draw the drag-and-drop indicator. var drop_sb := StyleBoxFlat.new() drop_sb.draw_center = false - drop_sb.border_color = Color.YELLOW + drop_sb.border_color = Color.GREEN drop_sb.set_corner_radius_all(3) if drop_idx == idx: drop_sb.border_width_left = 2 diff --git a/src/ui_elements/enum_field.gd b/src/ui_elements/enum_field.gd index 9a683db5..67240df2 100644 --- a/src/ui_elements/enum_field.gd +++ b/src/ui_elements/enum_field.gd @@ -1,41 +1,39 @@ # An editor to be tied to an enum attribute. extends LineEditButton -var attribute: AttributeEnum +var tag: Tag +var attribute_name: String const bold_font = preload("res://visual/fonts/FontBold.ttf") const reload_icon = preload("res://visual/icons/Reload.svg") -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: +func set_value(new_value: String, save := true) -> void: sync(new_value) - if attribute.get_value() != new_value or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) + var attribute := tag.get_attribute(attribute_name) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) func _ready() -> void: + var attribute: AttributeEnum = tag.get_attribute(attribute_name) set_value(attribute.get_value()) - tooltip_text = attribute.name - placeholder_text = attribute.get_default() + attribute.value_changed.connect(set_value) + tooltip_text = attribute_name + placeholder_text = tag.get_default(attribute_name) focus_entered.connect(reset_font_color) func _on_pressed() -> void: var btn_arr: Array[Button] = [] # Add a default. var reset_btn := ContextPopup.create_button("", set_value.bind(""), - attribute.get_value().is_empty(), reload_icon) + tag.get_attribute(attribute_name).get_value().is_empty(), reload_icon) reset_btn.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER btn_arr.append(reset_btn) # Add a button for each enum value. - for enum_constant in DB.attribute_enum_values[attribute.name]: + for enum_constant in DB.attribute_enum_values[attribute_name]: var btn := ContextPopup.create_button(enum_constant, set_value.bind(enum_constant), - enum_constant == attribute.get_value()) - if enum_constant == attribute.get_default(): + enum_constant == tag.get_attribute(attribute_name).get_value()) + if enum_constant == tag.get_default(attribute_name): btn.add_theme_font_override("font", bold_font) btn_arr.append(btn) var value_picker := ContextPopup.new() @@ -44,23 +42,23 @@ func _on_pressed() -> void: func _on_text_submitted(new_text: String) -> void: - if new_text.is_empty() or new_text in DB.attribute_enum_values[attribute.name]: + if new_text.is_empty() or new_text in DB.attribute_enum_values[attribute_name]: set_value(new_text) else: - sync(attribute.get_value()) + sync(tag.get_attribute(attribute_name).get_value()) func _on_text_change_canceled() -> void: - sync(attribute.get_value()) + sync(tag.get_attribute(attribute_name).get_value()) func _on_text_changed(new_text: String) -> void: font_color = GlobalSettings.get_validity_color( - not new_text in DB.attribute_enum_values[attribute.name]) + not new_text in DB.attribute_enum_values[attribute_name]) func sync(new_value: String) -> void: text = new_value reset_font_color() - if new_value == attribute.get_default(): + if new_value == tag.get_default(attribute_name): font_color = GlobalSettings.basic_color_warning func _notification(what: int) -> void: diff --git a/src/ui_elements/flag_field.tscn b/src/ui_elements/flag_field.tscn index a60bc271..6d28f146 100644 --- a/src/ui_elements/flag_field.tscn +++ b/src/ui_elements/flag_field.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=6 format=3 uid="uid://br8g7w38jguh4"] +[gd_scene load_steps=7 format=3 uid="uid://br8g7w38jguh4"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="1_p8s8y"] [ext_resource type="Script" path="res://src/ui_elements/flag_field.gd" id="2_0bhg4"] @@ -8,6 +8,8 @@ base_font = ExtResource("1_p8s8y") spacing_top = -1 spacing_bottom = -1 +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kfgda"] + [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oo47u"] content_margin_left = 5.0 content_margin_top = 0.0 @@ -37,13 +39,14 @@ offset_bottom = 18.0 focus_mode = 0 mouse_filter = 1 mouse_default_cursor_shape = 2 -theme_override_colors/font_color = Color(1, 0.75, 0.75, 1) theme_override_colors/font_pressed_color = Color(0.74902, 1, 0.74902, 1) +theme_override_colors/font_color = Color(1, 0.75, 0.75, 1) theme_override_fonts/font = SubResource("FontVariation_46ud6") theme_override_font_sizes/font_size = 14 -theme_override_styles/normal = SubResource("StyleBoxFlat_oo47u") +theme_override_styles/disabled = SubResource("StyleBoxEmpty_kfgda") theme_override_styles/hover = SubResource("StyleBoxFlat_oo47u") theme_override_styles/pressed = SubResource("StyleBoxFlat_uepoa") +theme_override_styles/normal = SubResource("StyleBoxFlat_oo47u") toggle_mode = true text = "0" script = ExtResource("2_0bhg4") diff --git a/src/ui_elements/id_field.gd b/src/ui_elements/id_field.gd new file mode 100644 index 00000000..cf000f74 --- /dev/null +++ b/src/ui_elements/id_field.gd @@ -0,0 +1,45 @@ +# An editor to be tied to a numeric attribute. +extends BetterLineEdit + +var tag: Tag +var attribute_name: String + +func set_value(new_value: String, save := true) -> void: + var attribute := tag.get_attribute(attribute_name) + if not new_value.is_empty(): + sync(attribute.format(new_value)) + + # Update the attribute. + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) + + +func _ready() -> void: + super() + var attribute: AttributeID = tag.get_attribute(attribute_name) + set_value(attribute.get_value()) + attribute.value_changed.connect(set_value) + tooltip_text = attribute_name + placeholder_text = tag.get_default(attribute_name) + text_submitted.connect(set_value) + +func _on_focus_entered() -> void: + remove_theme_color_override("font_color") + +func _on_text_change_canceled() -> void: + sync(tag.get_attribute(attribute_name).get_value()) + +func _on_text_changed(new_text: String) -> void: + var validity_level := IDParser.get_validity(new_text) + var font_color := GlobalSettings.get_validity_color( + validity_level == IDParser.ValidityLevel.INVALID, + validity_level == IDParser.ValidityLevel.INVALID_XML_NAMETOKEN) + add_theme_color_override("font_color", font_color) + +func sync(new_value: String) -> void: + text = new_value + remove_theme_color_override("font_color") + +func _notification(what: int) -> void: + if what == Utils.CustomNotification.BASIC_COLORS_CHANGED: + sync(text) diff --git a/src/ui_elements/id_field.tscn b/src/ui_elements/id_field.tscn new file mode 100644 index 00000000..45f1c31f --- /dev/null +++ b/src/ui_elements/id_field.tscn @@ -0,0 +1,15 @@ +[gd_scene load_steps=2 format=3 uid="uid://6tbfb2t0tyfh"] + +[ext_resource type="Script" path="res://src/ui_elements/id_field.gd" id="1_dqgkn"] + +[node name="IdField" type="LineEdit"] +custom_minimum_size = Vector2(54, 22) +offset_right = 35.8125 +offset_bottom = 21.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +script = ExtResource("1_dqgkn") + +[connection signal="focus_entered" from="." to="." method="_on_focus_entered"] +[connection signal="text_change_canceled" from="." to="." method="_on_text_change_canceled"] +[connection signal="text_changed" from="." to="." method="_on_text_changed"] diff --git a/src/ui_elements/number_field.gd b/src/ui_elements/number_field.gd index 7ac90138..ee0d5c51 100644 --- a/src/ui_elements/number_field.gd +++ b/src/ui_elements/number_field.gd @@ -1,14 +1,16 @@ # An editor to be tied to a numeric attribute. extends BetterLineEdit -var attribute: AttributeNumeric +var tag: Tag +var attribute_name: String var min_value := 0.0 var max_value := 1.0 var allow_lower := true var allow_higher := true -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: +func set_value(new_value: String, save := true) -> void: + var attribute := tag.get_attribute(attribute_name) if not new_value.is_empty(): var numeric_value := NumberParser.evaluate(new_value) # Validate the value. @@ -27,34 +29,29 @@ func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> vo sync(attribute.format(new_value)) # Update the attribute. - if new_value != attribute.get_value() or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) func _ready() -> void: - set_value(attribute.get_value()) super() + var attribute: AttributeNumeric = tag.get_attribute(attribute_name) + set_value(attribute.get_value()) attribute.value_changed.connect(set_value) - tooltip_text = attribute.name - placeholder_text = attribute.get_default() + tooltip_text = attribute_name + placeholder_text = tag.get_default(attribute_name) text_submitted.connect(set_value) func _on_focus_entered() -> void: remove_theme_color_override("font_color") func _on_text_change_canceled() -> void: - sync(attribute.get_value()) + sync(tag.get_attribute(attribute_name).get_value()) func sync(new_value: String) -> void: text = new_value remove_theme_color_override("font_color") - if new_value == attribute.get_default(): + if new_value == tag.get_default(attribute_name): add_theme_color_override("font_color", GlobalSettings.basic_color_warning) func _notification(what: int) -> void: diff --git a/src/ui_elements/number_field_with_slider.gd b/src/ui_elements/number_field_with_slider.gd index 3d20ad5e..d81c9762 100644 --- a/src/ui_elements/number_field_with_slider.gd +++ b/src/ui_elements/number_field_with_slider.gd @@ -1,7 +1,8 @@ # An editor to be tied to a numeric attribute, plus a slider widget. extends LineEditButton -var attribute: AttributeNumeric +var tag: Tag +var attribute_name: String var slider_step := 0.01 var min_value := 0.0 @@ -9,7 +10,8 @@ var max_value := 1.0 var allow_lower := true var allow_higher := true -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: +func set_value(new_value: String, save := true) -> void: + var attribute := tag.get_attribute(attribute_name) if not new_value.is_empty(): var numeric_value := NumberParser.evaluate(new_value) # Validate the value. @@ -29,34 +31,29 @@ func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> vo sync(attribute.format(new_value)) # Update the attribute. - if new_value != attribute.get_value() or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) + if new_value != attribute.get_value(): + attribute.set_value(new_value, save) -func set_num(new_number: float, update_type := Utils.UpdateType.REGULAR) -> void: - set_value(NumberParser.num_to_text(new_number), update_type) +func set_num(new_number: float, save := true) -> void: + set_value(NumberParser.num_to_text(new_number), save) func _ready() -> void: + var attribute: AttributeNumeric = tag.get_attribute(attribute_name) set_value(attribute.get_value()) attribute.value_changed.connect(set_value) - tooltip_text = attribute.name - placeholder_text = attribute.get_default() + tooltip_text = attribute_name + placeholder_text = tag.get_default(attribute_name) text_submitted.connect(set_value) focus_entered.connect(reset_font_color) func _on_text_change_canceled() -> void: - sync(attribute.get_value()) + set_value(tag.get_attribute(attribute_name).get_value()) func sync(new_value: String) -> void: text = new_value reset_font_color() - if new_value == attribute.get_default(): + if new_value == tag.get_default(attribute_name): font_color = GlobalSettings.basic_color_warning queue_redraw() @@ -95,7 +92,8 @@ func _draw() -> void: stylebox.bg_color = get_theme_stylebox("normal", "LineEdit").bg_color stylebox.draw(ci, Rect2(get_size().x - BUTTON_WIDTH, 1, BUTTON_WIDTH - 2, get_size().y - 2)) - var fill_height := (get_size().y - 4) * (attribute.get_num() - min_value) / max_value + var fill_height: float = (get_size().y - 4) *\ + (tag.get_attribute(attribute_name).get_num() - min_value) / max_value # Create a stylebox that'll occupy the exact amount of space. var fill_stylebox := StyleBoxFlat.new() fill_stylebox.bg_color = Color("#def") @@ -128,21 +126,21 @@ func _on_slider_gui_input(event: InputEvent) -> void: slider_hovered = true if Utils.is_event_drag_start(event): slider_dragged = true - initial_slider_value = attribute.get_num() - set_num(get_slider_value_at_y(event.position.y), Utils.UpdateType.INTERMEDIATE) + initial_slider_value = tag.get_attribute(attribute_name).get_num() + set_num(get_slider_value_at_y(event.position.y), false) else: if Utils.is_event_drag(event): - set_num(get_slider_value_at_y(event.position.y), Utils.UpdateType.INTERMEDIATE) + set_num(get_slider_value_at_y(event.position.y), false) elif Utils.is_event_drag_end(event): slider_dragged = false var final_slider_value := get_slider_value_at_y(event.position.y) if initial_slider_value != final_slider_value: - set_num(final_slider_value, Utils.UpdateType.FINAL) + set_num(final_slider_value, true) func _unhandled_input(event: InputEvent) -> void: if slider_dragged and Utils.is_event_cancel(event): slider_dragged = false - set_num(initial_slider_value, Utils.UpdateType.INTERMEDIATE) + set_num(initial_slider_value, false) accept_event() func get_slider_value_at_y(y_coord: float) -> float: diff --git a/src/ui_elements/palette_config.gd b/src/ui_elements/palette_config.gd index 4f6c85bb..b9d87067 100644 --- a/src/ui_elements/palette_config.gd +++ b/src/ui_elements/palette_config.gd @@ -53,11 +53,7 @@ func rebuild_colors() -> void: swatch.pressed.emit() # Add the add button. var fake_swatch := Button.new() - fake_swatch.begin_bulk_theme_override() - for stylebox_type in ["normal", "hover", "pressed"]: - fake_swatch.add_theme_stylebox_override(stylebox_type, - fake_swatch.get_theme_stylebox(stylebox_type, "Swatch")) - fake_swatch.end_bulk_theme_override() + fake_swatch.theme_type_variation = "Swatch" fake_swatch.icon = plus_icon fake_swatch.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER fake_swatch.focus_mode = Control.FOCUS_NONE diff --git a/src/ui_elements/pathdata_field.gd b/src/ui_elements/pathdata_field.gd index 8e7abf86..229e83d8 100644 --- a/src/ui_elements/pathdata_field.gd +++ b/src/ui_elements/pathdata_field.gd @@ -1,6 +1,9 @@ # An editor to be tied to a pathdata attribute. extends VBoxContainer +var tag: Tag +const attribute_name = "d" + # So, about this editor. Most of this code is about implementing a huge optimization. # All the path commands are a single node that draws fake-outs in order to prevent # adding too many nodes to the scene tree. The real controls are only created when @@ -16,8 +19,6 @@ const COMMAND_HEIGHT = 22.0 @export var relative_button_pressed: StyleBoxFlat signal focused -var attribute: AttributePath -var tid: PackedInt32Array # The path field has inner selectables, so it needs this. const MiniNumberField = preload("mini_number_field.tscn") const FlagField = preload("flag_field.tscn") @@ -51,27 +52,25 @@ var current_hovered: int = -1 var add_move_button: Control -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: - sync(attribute.format(new_value)) - if attribute.get_value() != new_value or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) +func set_value(new_value: String, save := true) -> void: + sync(new_value) + var attribute := tag.get_attribute(attribute_name) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) + +func update_value() -> void: + set_value(tag.get_attribute(attribute_name).get_value()) func _notification(what: int) -> void: if what == Utils.CustomNotification.LANGUAGE_CHANGED: update_translation() -func set_attribute(new_attribute: AttributePath) -> void: - attribute = new_attribute +func setup() -> void: + var attribute: AttributePathdata = tag.get_attribute(attribute_name) set_value(attribute.get_value()) - attribute.value_changed.connect(set_value) - line_edit.tooltip_text = attribute.name + attribute.value_changed.connect(update_value.unbind(2)) + line_edit.tooltip_text = attribute_name line_edit.text_submitted.connect(set_value) line_edit.text_changed.connect(setup_font) line_edit.focus_entered.connect(_on_line_edit_focus_entered) @@ -97,7 +96,7 @@ func sync(new_value: String) -> void: line_edit.text = new_value setup_font(new_value) # A plus button for adding a move command if empty. - var cmd_count := attribute.get_command_count() + var cmd_count: int = tag.get_attribute(attribute_name).get_command_count() if cmd_count == 0 and not is_instance_valid(add_move_button): add_move_button = Button.new() add_move_button.icon = plus_icon @@ -107,11 +106,12 @@ func sync(new_value: String) -> void: add_move_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND add_move_button.theme_type_variation = "FlatButton" add_child(add_move_button) - add_move_button.pressed.connect(attribute.insert_command.bind(0, "M")) + add_move_button.pressed.connect( + tag.get_attribute(attribute_name).insert_command.bind(0, "M")) add_move_button.pressed.connect(add_move_button.queue_free) # Rebuild the path commands. commands_container.custom_minimum_size.y = cmd_count * COMMAND_HEIGHT - if hovered_idx >= attribute.get_command_count(): + if hovered_idx >= tag.get_attribute(attribute_name).get_command_count(): activate_hovered(-1) var mm := InputEventMouseMotion.new() mm.position = commands_container.get_local_mouse_position() @@ -119,11 +119,11 @@ func sync(new_value: String) -> void: commands_container.queue_redraw() -func update_value(new_value: float, property: String, idx: int) -> void: - attribute.set_command_property(idx, property, new_value) +func update_parameter(new_value: float, property: String, idx: int) -> void: + tag.get_attribute(attribute_name).set_command_property(idx, property, new_value) func _on_relative_button_pressed() -> void: - attribute.toggle_relative_command(hovered_idx) + tag.get_attribute(attribute_name).toggle_relative_command(hovered_idx) reactivate_hovered() @@ -131,10 +131,10 @@ func _on_relative_button_pressed() -> void: func _on_selections_or_hover_changed() -> void: var new_selections: Array[int] = [] - if Indications.semi_selected_tid == tid: + if Indications.semi_selected_xid == tag.xid: new_selections = Indications.inner_selections var new_hovered: int = -1 - if Indications.semi_hovered_tid == tid: + if Indications.semi_hovered_xid == tag.xid: new_hovered = Indications.inner_hovered # Only redraw if selections or hovered changed. if new_selections != current_selections: @@ -147,8 +147,8 @@ func _on_selections_or_hover_changed() -> void: func _on_commands_mouse_exited() -> void: var cmd_idx := Indications.inner_hovered - Indications.remove_hovered(tid, cmd_idx) - if Indications.semi_hovered_tid == tid: + Indications.remove_hovered(tag.xid, cmd_idx) + if Indications.semi_hovered_xid == tag.xid: activate_hovered(-1) @@ -174,9 +174,9 @@ func respond_to_mouse_input(event: InputEventMouse) -> void: if event is InputEventMouseMotion and event.button_mask == 0: if cmd_idx != -1: - Indications.set_hovered(tid, cmd_idx) + Indications.set_hovered(tag.xid, cmd_idx) else: - Indications.remove_hovered(tid, cmd_idx) + Indications.remove_hovered(tag.xid, cmd_idx) activate_hovered(cmd_idx) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: @@ -184,40 +184,39 @@ func respond_to_mouse_input(event: InputEventMouse) -> void: if event.double_click: # Unselect the tags so we can use Ctrl select. Indications.clear_inner_selection() - var subpath_range: Vector2i =\ - SVG.root_tag.get_tag(tid).attributes.d.get_subpath(cmd_idx) + var subpath_range: Vector2i = tag.get_attribute("d").get_subpath(cmd_idx) for idx in range(subpath_range.x, subpath_range.y + 1): - Indications.ctrl_select(tid, idx) + Indications.ctrl_select(tag.xid, idx) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(tid, cmd_idx) + Indications.ctrl_select(tag.xid, cmd_idx) elif event.shift_pressed: - Indications.shift_select(tid, cmd_idx) + Indications.shift_select(tag.xid, cmd_idx) elif not cmd_idx in Indications.inner_selections: - Indications.normal_select(tid, cmd_idx) + Indications.normal_select(tag.xid, cmd_idx) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and not event.double_click and\ Indications.inner_selections.size() > 1 and\ cmd_idx in Indications.inner_selections: - Indications.normal_select(tid, cmd_idx) + Indications.normal_select(tag.xid, cmd_idx) elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if Indications.semi_selected_tid != tid or\ + if Indications.semi_selected_xid != tag.xid or\ not cmd_idx in Indications.inner_selections: - Indications.normal_select(tid, cmd_idx) + Indications.normal_select(tag.xid, cmd_idx) # Popup the actions. var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() HandlerGUI.popup_under_pos(Indications.get_selection_context( HandlerGUI.popup_under_pos.bind(popup_pos, viewport), - Indications.SelectionContext.TAG_EDITOR), popup_pos, viewport) + Indications.Context.TAG_EDITOR), popup_pos, viewport) func commands_draw() -> void: RenderingServer.canvas_item_clear(ci) - for i in attribute.get_command_count(): + for i: int in tag.get_attribute(attribute_name).get_command_count(): var v_offset := COMMAND_HEIGHT * i # Draw the background hover or selection stylebox. - var hovered := Indications.is_hovered(tid, i) - var selected := Indications.is_selected(tid, i) + var hovered := Indications.is_hovered(tag.xid, i) + var selected := Indications.is_selected(tag.xid, i) if selected or hovered: var stylebox := StyleBoxFlat.new() stylebox.set_corner_radius_all(3) @@ -233,7 +232,7 @@ func commands_draw() -> void: if i == hovered_idx or i == focused_idx: continue - var cmd := attribute.get_command(i) + var cmd: PathCommand = tag.get_attribute(attribute_name).get_command(i) var cmd_char := cmd.command_char # Draw the action button. more_icon.draw_rect(ci, Rect2(Vector2(commands_container.size.x - 19, 4 + v_offset), @@ -303,7 +302,7 @@ path_command: PathCommand) -> void: func activate_hovered(idx: int) -> void: - if idx != hovered_idx and idx < attribute.get_command_count(): + if idx != hovered_idx and idx < tag.get_attribute(attribute_name).get_command_count(): activate_hovered_shared_logic(idx) func reactivate_hovered() -> void: @@ -346,7 +345,7 @@ func setup_path_command_controls(idx: int) -> Control: if idx < 0: return null - var cmd := attribute.get_command(idx) + var cmd: PathCommand = tag.get_attribute(attribute_name).get_command(idx) var cmd_char := cmd.command_char var is_absolute := Utils.is_string_upper(cmd_char) @@ -364,11 +363,14 @@ func setup_path_command_controls(idx: int) -> Control: relative_button.add_theme_font_override("font", code_font) relative_button.add_theme_font_size_override("font_size", 13) relative_button.add_theme_color_override("font_color", Color(1, 1, 1)) + # Disabled styleboxes aren unused, but must be set for the correct content margins. if is_absolute: + relative_button.add_theme_stylebox_override("disabled", absolute_button_normal) relative_button.add_theme_stylebox_override("normal", absolute_button_normal) relative_button.add_theme_stylebox_override("hover", absolute_button_hovered) relative_button.add_theme_stylebox_override("pressed", absolute_button_pressed) else: + relative_button.add_theme_stylebox_override("disabled", relative_button_normal) relative_button.add_theme_stylebox_override("normal", relative_button_normal) relative_button.add_theme_stylebox_override("hover", relative_button_hovered) relative_button.add_theme_stylebox_override("pressed", relative_button_pressed) @@ -445,7 +447,7 @@ func setup_path_command_controls(idx: int) -> Control: var property_name := property_names[i] field.set_value(cmd.get(property_name)) field.tooltip_text = property_name - field.value_changed.connect(update_value.bind(property_name, idx)) + field.value_changed.connect(update_parameter.bind(property_name, idx)) field.focus_entered.connect(activate_focused.bind(idx)) field.focus_exited.connect(check_focused, CONNECT_DEFERRED) container.add_child(field) @@ -458,16 +460,16 @@ func setup_path_command_controls(idx: int) -> Control: func numfield(cmd_idx: int) -> BetterLineEdit: var new_field := MiniNumberField.instantiate() - new_field.focus_entered.connect(Indications.normal_select.bind(tid, cmd_idx)) + new_field.focus_entered.connect(Indications.normal_select.bind(tag.xid, cmd_idx)) return new_field func _on_action_button_pressed(action_button_ref: Button) -> void: # Update the selection immediately, since if this path command is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(tid, hovered_idx) + Indications.normal_select(tag.xid, hovered_idx) var viewport := get_viewport() var action_button_rect := action_button_ref.get_global_rect() HandlerGUI.popup_under_rect_center(Indications.get_selection_context( HandlerGUI.popup_under_rect_center.bind(action_button_rect, viewport), - Indications.SelectionContext.TAG_EDITOR), action_button_rect, viewport) + Indications.Context.TAG_EDITOR), action_button_rect, viewport) diff --git a/src/ui_elements/setting_shortcut.gd b/src/ui_elements/setting_shortcut.gd index e609a4b5..fad128e2 100644 --- a/src/ui_elements/setting_shortcut.gd +++ b/src/ui_elements/setting_shortcut.gd @@ -111,9 +111,13 @@ func cancel_listening() -> void: func delete_shortcut(idx: int) -> void: events.remove_at(idx) - GlobalSettings.modify_keybind(action, events) + update_shortcut() sync() +func update_shortcut() -> void: + Utils.custom_notify(Utils.CustomNotification.SHORTCUTS_CHANGED) + GlobalSettings.modify_keybind(action, events) + func _input(event: InputEvent) -> void: if not (listening_idx >= 0 and event is InputEventKey): return @@ -134,14 +138,14 @@ func _input(event: InputEvent) -> void: elif event.is_released(): if listening_idx < events.size(): events[listening_idx] = event - GlobalSettings.modify_keybind(action, events) + update_shortcut() else: events.append(event) - GlobalSettings.modify_keybind(action, events) + update_shortcut() sync() listening_idx = -1 func _on_reset_button_pressed() -> void: events = GlobalSettings.default_input_events[action].duplicate(true) - GlobalSettings.modify_keybind(action, events) + update_shortcut() sync() diff --git a/src/ui_elements/tag_content_basic_shape.gd b/src/ui_elements/tag_content_basic_shape.gd new file mode 100644 index 00000000..9c20e763 --- /dev/null +++ b/src/ui_elements/tag_content_basic_shape.gd @@ -0,0 +1,38 @@ +extends VBoxContainer + +const TransformField = preload("res://src/ui_elements/transform_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") + +@onready var attribute_container: HFlowContainer = $AttributeContainer + +var tag: Tag + +func _ready() -> void: + for attribute_key in DB.recognized_attributes[tag.name]: + var input_field: Control + match DB.get_attribute_type(attribute_key): + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute_key].x + var max_value: float = DB.attribute_numeric_bounds[attribute_key].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + input_field.attribute_name = attribute_key + input_field.tag = tag + input_field.focus_entered.connect(Indications.normal_select.bind(tag.xid)) + attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_circle.tscn b/src/ui_elements/tag_content_basic_shape.tscn similarity index 75% rename from src/ui_elements/tag_content_circle.tscn rename to src/ui_elements/tag_content_basic_shape.tscn index fd7e682d..a7f66295 100644 --- a/src/ui_elements/tag_content_circle.tscn +++ b/src/ui_elements/tag_content_basic_shape.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=2 format=3 uid="uid://b0srm51hsvpel"] -[ext_resource type="Script" path="res://src/ui_elements/tag_content_circle.gd" id="1_tg77v"] +[ext_resource type="Script" path="res://src/ui_elements/tag_content_basic_shape.gd" id="1_tg77v"] -[node name="TagContentCircle" type="VBoxContainer"] +[node name="TagContentBasicShape" type="VBoxContainer"] offset_right = 40.0 offset_bottom = 40.0 script = ExtResource("1_tg77v") diff --git a/src/ui_elements/tag_content_circle.gd b/src/ui_elements/tag_content_circle.gd deleted file mode 100644 index 67ce9155..00000000 --- a/src/ui_elements/tag_content_circle.gd +++ /dev/null @@ -1,46 +0,0 @@ -extends VBoxContainer - -const TransformField = preload("res://src/ui_elements/transform_field.tscn") -const NumberField = preload("res://src/ui_elements/number_field.tscn") -const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") -const ColorField = preload("res://src/ui_elements/color_field.tscn") -const EnumField = preload("res://src/ui_elements/enum_field.tscn") - -@onready var attribute_container: HFlowContainer = $AttributeContainer - -var tag: Tag -var tid: PackedInt32Array - -func _ready() -> void: - for attribute_key in ["transform", "opacity", "cx", "cy", "r", - "fill", "fill-opacity", "stroke", "stroke-opacity", "stroke-width"]: - var attribute: Attribute = tag.attributes[attribute_key] - var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): - input_field.allow_lower = false - input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute - # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) - attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_ellipse.gd b/src/ui_elements/tag_content_ellipse.gd deleted file mode 100644 index 63849b62..00000000 --- a/src/ui_elements/tag_content_ellipse.gd +++ /dev/null @@ -1,46 +0,0 @@ -extends VBoxContainer - -const TransformField = preload("res://src/ui_elements/transform_field.tscn") -const NumberField = preload("res://src/ui_elements/number_field.tscn") -const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") -const ColorField = preload("res://src/ui_elements/color_field.tscn") -const EnumField = preload("res://src/ui_elements/enum_field.tscn") - -@onready var attribute_container: HFlowContainer = $AttributeContainer - -var tag: Tag -var tid: PackedInt32Array - -func _ready() -> void: - for attribute_key in ["transform", "opacity", "cx", "cy", "rx", "ry", - "fill", "fill-opacity", "stroke", "stroke-opacity", "stroke-width"]: - var attribute: Attribute = tag.attributes[attribute_key] - var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): - input_field.allow_lower = false - input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute - # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) - attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_g.gd b/src/ui_elements/tag_content_g.gd new file mode 100644 index 00000000..7024a8f2 --- /dev/null +++ b/src/ui_elements/tag_content_g.gd @@ -0,0 +1,38 @@ +extends VBoxContainer + +const TransformField = preload("res://src/ui_elements/transform_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") + +@onready var attribute_container: HFlowContainer = $AttributeContainer + +var tag: Tag + +func _ready() -> void: + for attribute_key in DB.recognized_attributes["g"]: + var input_field: Control + match DB.get_attribute_type(attribute_key): + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute_key].x + var max_value: float = DB.attribute_numeric_bounds[attribute_key].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + input_field.attribute_name = attribute_key + input_field.tag = tag + input_field.focus_entered.connect(Indications.normal_select.bind(tag.xid)) + attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_line.tscn b/src/ui_elements/tag_content_g.tscn similarity index 53% rename from src/ui_elements/tag_content_line.tscn rename to src/ui_elements/tag_content_g.tscn index 0be00872..23d7aca6 100644 --- a/src/ui_elements/tag_content_line.tscn +++ b/src/ui_elements/tag_content_g.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=2 format=3 uid="uid://b6ra3mw3qludw"] +[gd_scene load_steps=2 format=3 uid="uid://bmbrrcuxvyfv3"] -[ext_resource type="Script" path="res://src/ui_elements/tag_content_line.gd" id="1_xvs53"] +[ext_resource type="Script" path="res://src/ui_elements/tag_content_g.gd" id="1_wpyt2"] -[node name="TagContentLine" type="VBoxContainer"] +[node name="TagContentG" type="VBoxContainer"] offset_right = 40.0 offset_bottom = 40.0 -script = ExtResource("1_xvs53") +script = ExtResource("1_wpyt2") [node name="AttributeContainer" type="HFlowContainer" parent="."] layout_mode = 2 diff --git a/src/ui_elements/tag_content_line.gd b/src/ui_elements/tag_content_line.gd deleted file mode 100644 index 78356a09..00000000 --- a/src/ui_elements/tag_content_line.gd +++ /dev/null @@ -1,46 +0,0 @@ -extends VBoxContainer - -const TransformField = preload("res://src/ui_elements/transform_field.tscn") -const NumberField = preload("res://src/ui_elements/number_field.tscn") -const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") -const ColorField = preload("res://src/ui_elements/color_field.tscn") -const EnumField = preload("res://src/ui_elements/enum_field.tscn") - -@onready var attribute_container: HFlowContainer = $AttributeContainer - -var tag: Tag -var tid: PackedInt32Array - -func _ready() -> void: - for attribute_key in ["transform", "opacity", "x1", "y1", "x2", "y2", - "stroke", "stroke-opacity", "stroke-width", "stroke-linecap"]: - var attribute: Attribute = tag.attributes[attribute_key] - var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): - input_field.allow_lower = false - input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute - # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) - attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_linear_gradient.gd b/src/ui_elements/tag_content_linear_gradient.gd new file mode 100644 index 00000000..94c81f33 --- /dev/null +++ b/src/ui_elements/tag_content_linear_gradient.gd @@ -0,0 +1,40 @@ +extends VBoxContainer + +const TransformField = preload("res://src/ui_elements/transform_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") +const IdField = preload("res://src/ui_elements/id_field.tscn") + +@onready var attribute_container: HFlowContainer = $AttributeContainer + +var tag: Tag + +func _ready() -> void: + for attribute_key in DB.recognized_attributes["linearGradient"]: + var input_field: Control + match DB.get_attribute_type(attribute_key): + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.ID: input_field = IdField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute_key].x + var max_value: float = DB.attribute_numeric_bounds[attribute_key].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + input_field.attribute_name = attribute_key + input_field.tag = tag + input_field.focus_entered.connect(Indications.normal_select.bind(tag.xid)) + attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_ellipse.tscn b/src/ui_elements/tag_content_linear_gradient.tscn similarity index 50% rename from src/ui_elements/tag_content_ellipse.tscn rename to src/ui_elements/tag_content_linear_gradient.tscn index d0c4e00f..d7798ebc 100644 --- a/src/ui_elements/tag_content_ellipse.tscn +++ b/src/ui_elements/tag_content_linear_gradient.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=2 format=3 uid="uid://dyyd1lfrkl303"] +[gd_scene load_steps=2 format=3 uid="uid://bt0kb3188plwn"] -[ext_resource type="Script" path="res://src/ui_elements/tag_content_ellipse.gd" id="1_dysu6"] +[ext_resource type="Script" path="res://src/ui_elements/tag_content_linear_gradient.gd" id="1_q43hc"] -[node name="TagContentEllipse" type="VBoxContainer"] +[node name="TagContentLinearGradient" type="VBoxContainer"] offset_right = 40.0 offset_bottom = 40.0 -script = ExtResource("1_dysu6") +script = ExtResource("1_q43hc") [node name="AttributeContainer" type="HFlowContainer" parent="."] layout_mode = 2 diff --git a/src/ui_elements/tag_content_path.gd b/src/ui_elements/tag_content_path.gd index 4a6570df..fed4ec37 100644 --- a/src/ui_elements/tag_content_path.gd +++ b/src/ui_elements/tag_content_path.gd @@ -10,40 +10,37 @@ const EnumField = preload("res://src/ui_elements/enum_field.tscn") @onready var path_field: VBoxContainer = $PathField var tag: Tag -var tid: PackedInt32Array func _ready() -> void: - path_field.set_attribute(tag.attributes.d) - path_field.tid = tid - for attribute_key in ["transform", "opacity", "fill", "fill-opacity", - "stroke", "stroke-opacity", "stroke-width", "stroke-linecap", "stroke-linejoin"]: - var attribute: Attribute = tag.attributes[attribute_key] + path_field.tag = tag + path_field.setup() + path_field.focused.connect(Indications.normal_select.bind(tag.xid)) + + for attribute_key in DB.recognized_attributes["path"]: + if attribute_key == "d": + continue var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): + match DB.get_attribute_type(attribute_key): + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute_key].x + var max_value: float = DB.attribute_numeric_bounds[attribute_key].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() input_field.allow_lower = false + input_field.allow_higher = false input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute + input_field.max_value = max_value + input_field.slider_step = 0.01 + input_field.attribute_name = attribute_key + input_field.tag = tag # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) + input_field.focus_entered.connect(Indications.normal_select.bind(tag.xid)) attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_radial_gradient.gd b/src/ui_elements/tag_content_radial_gradient.gd new file mode 100644 index 00000000..99ec65c1 --- /dev/null +++ b/src/ui_elements/tag_content_radial_gradient.gd @@ -0,0 +1,40 @@ +extends VBoxContainer + +const TransformField = preload("res://src/ui_elements/transform_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") +const IdField = preload("res://src/ui_elements/id_field.tscn") + +@onready var attribute_container: HFlowContainer = $AttributeContainer + +var tag: Tag + +func _ready() -> void: + for attribute_key in DB.recognized_attributes["radialGradient"]: + var input_field: Control + match DB.get_attribute_type(attribute_key): + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.ID: input_field = IdField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute_key].x + var max_value: float = DB.attribute_numeric_bounds[attribute_key].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + input_field.attribute_name = attribute_key + input_field.tag = tag + input_field.focus_entered.connect(Indications.normal_select.bind(tag.xid)) + attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_rect.tscn b/src/ui_elements/tag_content_radial_gradient.tscn similarity index 50% rename from src/ui_elements/tag_content_rect.tscn rename to src/ui_elements/tag_content_radial_gradient.tscn index f2873d8f..bd7b6886 100644 --- a/src/ui_elements/tag_content_rect.tscn +++ b/src/ui_elements/tag_content_radial_gradient.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=2 format=3 uid="uid://dxeni0ewiuop8"] +[gd_scene load_steps=2 format=3 uid="uid://bethkcrbf0tf7"] -[ext_resource type="Script" path="res://src/ui_elements/tag_content_rect.gd" id="1_bhc7f"] +[ext_resource type="Script" path="res://src/ui_elements/tag_content_radial_gradient.gd" id="1_le714"] -[node name="TagContentRect" type="VBoxContainer"] +[node name="TagContentRadialGradient" type="VBoxContainer"] offset_right = 40.0 offset_bottom = 40.0 -script = ExtResource("1_bhc7f") +script = ExtResource("1_le714") [node name="AttributeContainer" type="HFlowContainer" parent="."] layout_mode = 2 diff --git a/src/ui_elements/tag_content_rect.gd b/src/ui_elements/tag_content_rect.gd deleted file mode 100644 index e0ebe310..00000000 --- a/src/ui_elements/tag_content_rect.gd +++ /dev/null @@ -1,46 +0,0 @@ -extends VBoxContainer - -const TransformField = preload("res://src/ui_elements/transform_field.tscn") -const NumberField = preload("res://src/ui_elements/number_field.tscn") -const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") -const ColorField = preload("res://src/ui_elements/color_field.tscn") -const EnumField = preload("res://src/ui_elements/enum_field.tscn") - -@onready var attribute_container: HFlowContainer = $AttributeContainer - -var tag: Tag -var tid: PackedInt32Array - -func _ready() -> void: - for attribute_key in ["transform", "opacity", "x", "y", "width", "height", "rx", "ry", - "fill", "fill-opacity", "stroke", "stroke-opacity", "stroke-width", "stroke-linejoin"]: - var attribute: Attribute = tag.attributes[attribute_key] - var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): - input_field.allow_lower = false - input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute - # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) - attribute_container.add_child(input_field) diff --git a/src/ui_elements/tag_content_stop.gd b/src/ui_elements/tag_content_stop.gd index 30512209..66db631e 100644 --- a/src/ui_elements/tag_content_stop.gd +++ b/src/ui_elements/tag_content_stop.gd @@ -9,37 +9,30 @@ const EnumField = preload("res://src/ui_elements/enum_field.tscn") @onready var attribute_container: HFlowContainer = $AttributeContainer var tag: Tag -var tid: PackedInt32Array func _ready() -> void: - for attribute_key in ["offset", "stop-color", "stop-opacity"]: - var attribute: Attribute = tag.attributes[attribute_key] - var input_field: Control - if attribute is AttributeTransform: - input_field = TransformField.instantiate() - elif attribute is AttributeNumeric: - var min_value: float = DB.attribute_numeric_bounds[attribute_key].x - var max_value: float = DB.attribute_numeric_bounds[attribute_key].y - if is_inf(max_value): - input_field = NumberField.instantiate() - if not is_inf(min_value): - input_field.allow_lower = false - input_field.min_value = min_value - else: - input_field = NumberSlider.instantiate() - input_field.allow_lower = false - input_field.allow_higher = false - input_field.min_value = min_value - input_field.max_value = max_value - input_field.slider_step = 0.01 - elif attribute is AttributeColor: - input_field = ColorField.instantiate() - elif attribute is AttributeEnum: - input_field = EnumField.instantiate() - input_field.attribute = attribute - # Focused signal for pathdata attribute. - if input_field.has_signal("focused"): - input_field.focused.connect(Indications.normal_select.bind(tid)) - else: - input_field.focus_entered.connect(Indications.normal_select.bind(tid)) - attribute_container.add_child(input_field) + var offset_input_field: Control = NumberSlider.instantiate() + offset_input_field.allow_lower = false + offset_input_field.allow_higher = false + offset_input_field.min_value = DB.attribute_numeric_bounds["offset"].x + offset_input_field.max_value = DB.attribute_numeric_bounds["offset"].y + offset_input_field.slider_step = 0.01 + offset_input_field.tag = tag + offset_input_field.attribute_name = "offset" + + var color_input_field: Control = ColorField.instantiate() + color_input_field.tag = tag + color_input_field.attribute_name = "stop-color" + + var opacity_input_field: Control = NumberSlider.instantiate() + opacity_input_field.allow_lower = false + opacity_input_field.allow_higher = false + opacity_input_field.min_value = DB.attribute_numeric_bounds["stop-opacity"].x + opacity_input_field.max_value = DB.attribute_numeric_bounds["stop-opacity"].y + opacity_input_field.slider_step = 0.01 + opacity_input_field.tag = tag + opacity_input_field.attribute_name = "stop-opacity" + + attribute_container.add_child(offset_input_field) + attribute_container.add_child(color_input_field) + attribute_container.add_child(opacity_input_field) diff --git a/src/ui_elements/tag_content_unknown.gd b/src/ui_elements/tag_content_unrecognized.gd similarity index 58% rename from src/ui_elements/tag_content_unknown.gd rename to src/ui_elements/tag_content_unrecognized.gd index 19ac58c8..f0d4eb0e 100644 --- a/src/ui_elements/tag_content_unknown.gd +++ b/src/ui_elements/tag_content_unrecognized.gd @@ -1,4 +1,3 @@ extends VBoxContainer var tag: Tag -var tid: PackedInt32Array diff --git a/src/ui_elements/tag_content_unknown.tscn b/src/ui_elements/tag_content_unrecognized.tscn similarity index 87% rename from src/ui_elements/tag_content_unknown.tscn rename to src/ui_elements/tag_content_unrecognized.tscn index ac8dc8f2..0efeaa01 100644 --- a/src/ui_elements/tag_content_unknown.tscn +++ b/src/ui_elements/tag_content_unrecognized.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b70dhqt8ldcmc"] -[ext_resource type="Script" path="res://src/ui_elements/tag_content_unknown.gd" id="1_vy7fr"] +[ext_resource type="Script" path="res://src/ui_elements/tag_content_unrecognized.gd" id="1_vy7fr"] [node name="TagContentUnknown" type="VBoxContainer"] size_flags_horizontal = 3 diff --git a/src/ui_elements/transform_field.gd b/src/ui_elements/transform_field.gd index 13be79a3..ba64e143 100644 --- a/src/ui_elements/transform_field.gd +++ b/src/ui_elements/transform_field.gd @@ -1,20 +1,16 @@ # An editor to be tied to a transform list attribute. extends LineEditButton -var attribute: AttributeTransform +var tag: Tag +var attribute_name: String const TransformPopup = preload("res://src/ui_elements/transform_popup.tscn") -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: - sync(attribute.format(new_value)) - if attribute.get_value() != new_value or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) +func set_value(new_value: String, save := true) -> void: + sync(new_value) + var attribute := tag.get_attribute(attribute_name) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) func _notification(what: int) -> void: @@ -22,9 +18,10 @@ func _notification(what: int) -> void: update_translation() func _ready() -> void: + var attribute: AttributeTransformList = tag.get_attribute(attribute_name) set_value(attribute.get_value()) attribute.value_changed.connect(set_value) - tooltip_text = attribute.name + tooltip_text = attribute_name text_submitted.connect(set_value) text_changed.connect(setup_font) update_translation() @@ -42,7 +39,7 @@ func sync(new_value: String) -> void: func _on_pressed() -> void: var transform_popup := TransformPopup.instantiate() - transform_popup.attribute_ref = attribute + transform_popup.attribute_ref = tag.get_attribute(attribute_name) HandlerGUI.popup_under_rect(transform_popup, get_global_rect(), get_viewport()) diff --git a/src/ui_elements/transform_popup.gd b/src/ui_elements/transform_popup.gd index 336e1882..bf13e1c9 100644 --- a/src/ui_elements/transform_popup.gd +++ b/src/ui_elements/transform_popup.gd @@ -18,7 +18,7 @@ const icons_dict := { "skewY": preload("res://visual/icons/SkewY.svg"), } -var attribute_ref: AttributeTransform +var attribute_ref: AttributeTransformList var UR := UndoRedo.new() @onready var x1_edit: NumberEditType = %FinalMatrix/X1 @@ -55,15 +55,14 @@ func rebuild() -> void: if i >= transform_count: break var t := attribute_ref.get_transform(i) - if t is AttributeTransform.TransformMatrix and transform_editor.type == "matrix": + if t is Transform.TransformMatrix and transform_editor.type == "matrix": transform_editor.fields[0].set_value(t.x1, true) transform_editor.fields[1].set_value(t.x2, true) transform_editor.fields[2].set_value(t.y1, true) transform_editor.fields[3].set_value(t.y2, true) transform_editor.fields[4].set_value(t.o1, true) transform_editor.fields[5].set_value(t.o2, true) - elif t is AttributeTransform.TransformTranslate and\ - transform_editor.type == "translate": + elif t is Transform.TransformTranslate and transform_editor.type == "translate": transform_editor.fields[0].set_value(t.x, true) transform_editor.fields[1].set_value(t.y, true) if t.y == 0: @@ -72,7 +71,7 @@ func rebuild() -> void: DEFAULT_VALUE_OPACITY)) else: transform_editor.fields[1].remove_theme_color_override("font_color") - elif t is AttributeTransform.TransformRotate and transform_editor.type == "rotate": + elif t is Transform.TransformRotate and transform_editor.type == "rotate": transform_editor.fields[0].set_value(t.deg, true) transform_editor.fields[1].set_value(t.x, true) transform_editor.fields[2].set_value(t.y, true) @@ -86,7 +85,7 @@ func rebuild() -> void: else: transform_editor.fields[1].remove_theme_color_override("font_color") transform_editor.fields[2].remove_theme_color_override("font_color") - elif t is AttributeTransform.TransformScale and transform_editor.type == "scale": + elif t is Transform.TransformScale and transform_editor.type == "scale": transform_editor.fields[0].set_value(t.x, true) transform_editor.fields[1].set_value(t.y, true) if t.x == t.y: @@ -95,9 +94,9 @@ func rebuild() -> void: DEFAULT_VALUE_OPACITY)) else: transform_editor.fields[1].remove_theme_color_override("font_color") - elif t is AttributeTransform.TransformSkewX and transform_editor.type == "skewX": + elif t is Transform.TransformSkewX and transform_editor.type == "skewX": transform_editor.fields[0].set_value(t.x, true) - elif t is AttributeTransform.TransformSkewY and transform_editor.type == "skewY": + elif t is Transform.TransformSkewY and transform_editor.type == "skewY": transform_editor.fields[0].set_value(t.y, true) else: break @@ -111,24 +110,24 @@ func rebuild() -> void: var t_editor := TransformEditor.instantiate() transform_list.add_child(t_editor) # Setup top panel - if t is AttributeTransform.TransformMatrix: + if t is Transform.TransformMatrix: t_editor.type = "matrix" - elif t is AttributeTransform.TransformTranslate: + elif t is Transform.TransformTranslate: t_editor.type = "translate" - elif t is AttributeTransform.TransformRotate: + elif t is Transform.TransformRotate: t_editor.type = "rotate" - elif t is AttributeTransform.TransformScale: + elif t is Transform.TransformScale: t_editor.type = "scale" - elif t is AttributeTransform.TransformSkewX: + elif t is Transform.TransformSkewX: t_editor.type = "skewX" - elif t is AttributeTransform.TransformSkewY: + elif t is Transform.TransformSkewY: t_editor.type = "skewY" t_editor.transform_label.text = t_editor.type t_editor.transform_icon.texture = icons_dict[t_editor.type] t_editor.more_button.pressed.connect( popup_transform_actions.bind(i, t_editor.more_button)) # Setup fields. - if t is AttributeTransform.TransformMatrix: + if t is Transform.TransformMatrix: var field_x1 := create_mini_number_field(t, i, "x1") var field_x2 := create_mini_number_field(t, i, "x2") var field_y1 := create_mini_number_field(t, i, "y1") @@ -149,7 +148,7 @@ func rebuild() -> void: transform_fields_additional.add_child(field_o2) t_editor.transform_list.add_child(transform_fields) t_editor.transform_list.add_child(transform_fields_additional) - elif t is AttributeTransform.TransformTranslate: + elif t is Transform.TransformTranslate: var field_x := create_mini_number_field(t, i, "x") var field_y := create_mini_number_field(t, i, "y") t_editor.fields = [field_x, field_y] as Array[BetterLineEdit] @@ -158,7 +157,7 @@ func rebuild() -> void: transform_fields.add_child(field_x) transform_fields.add_child(field_y) t_editor.transform_list.add_child(transform_fields) - elif t is AttributeTransform.TransformRotate: + elif t is Transform.TransformRotate: var field_deg := create_mini_number_field(t, i, "deg") field_deg.mode = field_deg.Mode.ANGLE var field_x := create_mini_number_field(t, i, "x") @@ -170,7 +169,7 @@ func rebuild() -> void: transform_fields.add_child(field_x) transform_fields.add_child(field_y) t_editor.transform_list.add_child(transform_fields) - elif t is AttributeTransform.TransformScale: + elif t is Transform.TransformScale: var field_x := create_mini_number_field(t, i, "x") var field_y := create_mini_number_field(t, i, "y") t_editor.fields = [field_x, field_y] as Array[BetterLineEdit] @@ -179,14 +178,14 @@ func rebuild() -> void: transform_fields.add_child(field_x) transform_fields.add_child(field_y) t_editor.transform_list.add_child(transform_fields) - elif t is AttributeTransform.TransformSkewX: + elif t is Transform.TransformSkewX: var field_x := create_mini_number_field(t, i, "x") t_editor.fields = [field_x] as Array[BetterLineEdit] var transform_fields := HBoxContainer.new() transform_fields.alignment = BoxContainer.ALIGNMENT_CENTER transform_fields.add_child(field_x) t_editor.transform_list.add_child(transform_fields) - elif t is AttributeTransform.TransformSkewY: + elif t is Transform.TransformSkewY: var field_y := create_mini_number_field(t, i, "y") t_editor.fields = [field_y] as Array[BetterLineEdit] var transform_fields := HBoxContainer.new() @@ -199,15 +198,15 @@ func rebuild() -> void: add_button.visible = (transform_count == 0) update_final_transform() -func create_mini_number_field(transform: AttributeTransform.Transform, idx: int, +func create_mini_number_field(transform: Transform, idx: int, property: String) -> BetterLineEdit: var field := MiniNumberField.instantiate() field.custom_minimum_size.x = 44 field.set_value(transform.get(property)) - if (transform is AttributeTransform.TransformTranslate and transform.y == 0 and\ - property == "y") or (transform is AttributeTransform.TransformRotate and\ - transform.x == 0 and transform.y == 0 and (property == "x" or property == "y")) or\ - (transform is AttributeTransform.TransformScale and transform.x == transform.y and\ + if (transform is Transform.TransformTranslate and transform.y == 0 and\ + property == "y") or (transform is Transform.TransformRotate and transform.x == 0 and\ + transform.y == 0 and (property == "x" or property == "y")) or\ + (transform is Transform.TransformScale and transform.x == transform.y and\ property == "y"): field.add_theme_color_override("font_color", Color( field.get_theme_color("font_color"), DEFAULT_VALUE_OPACITY)) @@ -244,9 +243,9 @@ func _on_apply_matrix_pressed() -> void: var final_transform := attribute_ref.get_final_transform() UR.create_action("") UR.add_do_method(attribute_ref.set_transform_list.bind([ - AttributeTransform.TransformMatrix.new(final_transform.x.x, final_transform.x.y, + Transform.TransformMatrix.new(final_transform.x.x, final_transform.x.y, final_transform.y.x, final_transform.y.y, final_transform.origin.x, - final_transform.origin.y)] as Array[AttributeTransform.Transform])) + final_transform.origin.y)] as Array[Transform])) UR.add_do_method(rebuild) UR.add_undo_method(attribute_ref.set_transform_list.bind(get_transform_list())) UR.add_undo_method(rebuild) @@ -303,20 +302,20 @@ func _unhandled_input(event: InputEvent) -> void: # So I have to rebuild this in its entirety to keep the references safe or something... -func get_transform_list() -> Array[AttributeTransform.Transform]: - var t_list: Array[AttributeTransform.Transform] = [] +func get_transform_list() -> Array[Transform]: + var t_list: Array[Transform] = [] for t in attribute_ref.get_transform_list(): - if t is AttributeTransform.TransformMatrix: - t_list.append(AttributeTransform.TransformMatrix.new( + if t is Transform.TransformMatrix: + t_list.append(Transform.TransformMatrix.new( t.x1, t.x2, t.y1, t.y2, t.o1, t.o2)) - elif t is AttributeTransform.TransformTranslate: - t_list.append(AttributeTransform.TransformTranslate.new(t.x, t.y)) - elif t is AttributeTransform.TransformRotate: - t_list.append(AttributeTransform.TransformRotate.new(t.deg, t.x, t.y)) - elif t is AttributeTransform.TransformScale: - t_list.append(AttributeTransform.TransformScale.new(t.x, t.y)) - elif t is AttributeTransform.TransformSkewX: - t_list.append(AttributeTransform.TransformSkewX.new(t.x)) - elif t is AttributeTransform.TransformSkewY: - t_list.append(AttributeTransform.TransformSkewY.new(t.y)) + elif t is Transform.TransformTranslate: + t_list.append(Transform.TransformTranslate.new(t.x, t.y)) + elif t is Transform.TransformRotate: + t_list.append(Transform.TransformRotate.new(t.deg, t.x, t.y)) + elif t is Transform.TransformScale: + t_list.append(Transform.TransformScale.new(t.x, t.y)) + elif t is Transform.TransformSkewX: + t_list.append(Transform.TransformSkewX.new(t.x)) + elif t is Transform.TransformSkewY: + t_list.append(Transform.TransformSkewY.new(t.y)) return t_list diff --git a/src/ui_elements/unknown_field.gd b/src/ui_elements/unknown_field.gd deleted file mode 100644 index 860cfde0..00000000 --- a/src/ui_elements/unknown_field.gd +++ /dev/null @@ -1,33 +0,0 @@ -# An editor to be tied to an attribute GodSVG can't recognize, allowing to still edit it. -extends BetterLineEdit - -var attribute: Attribute - -func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: - sync(new_value) - if attribute.get_value() != new_value or update_type == Utils.UpdateType.FINAL: - match update_type: - Utils.UpdateType.INTERMEDIATE: - attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) - Utils.UpdateType.FINAL: - attribute.set_value(new_value, Attribute.SyncMode.FINAL) - _: - attribute.set_value(new_value) - - -func _notification(what: int) -> void: - if what == Utils.CustomNotification.LANGUAGE_CHANGED: - update_translation() - -func _ready() -> void: - super() - set_value(attribute.get_value()) - update_translation() - text_submitted.connect(set_value) - -func sync(new_value: String) -> void: - text = new_value - -func update_translation() -> void: - tooltip_text = attribute.name + "\n(%s)" %\ - TranslationServer.translate("GodSVG doesn’t recognize this attribute") diff --git a/src/ui_elements/unrecognized_field.gd b/src/ui_elements/unrecognized_field.gd new file mode 100644 index 00000000..7998ef55 --- /dev/null +++ b/src/ui_elements/unrecognized_field.gd @@ -0,0 +1,29 @@ +# An editor to be tied to an attribute GodSVG can't recognize, allowing to still edit it. +extends BetterLineEdit + +var tag: Tag +var attribute_name: String + +func set_value(new_value: String, save := true) -> void: + sync(new_value) + var attribute := tag.get_attribute(attribute_name) + if attribute.get_value() != new_value: + attribute.set_value(new_value, save) + + +func _notification(what: int) -> void: + if what == Utils.CustomNotification.LANGUAGE_CHANGED: + update_translation() + +func _ready() -> void: + super() + set_value(tag.get_attribute(attribute_name).get_value()) + update_translation() + text_submitted.connect(set_value) + +func sync(new_value: String) -> void: + text = new_value + +func update_translation() -> void: + tooltip_text = attribute_name + "\n(%s)" %\ + TranslationServer.translate("GodSVG doesn’t recognize this attribute") diff --git a/src/ui_elements/unknown_field.tscn b/src/ui_elements/unrecognized_field.tscn similarity index 81% rename from src/ui_elements/unknown_field.tscn rename to src/ui_elements/unrecognized_field.tscn index 9e9fe94e..70b094f9 100644 --- a/src/ui_elements/unknown_field.tscn +++ b/src/ui_elements/unrecognized_field.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://mr2c1hti43k4"] -[ext_resource type="Script" path="res://src/ui_elements/unknown_field.gd" id="1_decfb"] +[ext_resource type="Script" path="res://src/ui_elements/unrecognized_field.gd" id="1_decfb"] [ext_resource type="Texture2D" uid="uid://crx4kcj4o01bs" path="res://visual/icons/SmallQuestionMark.svg" id="1_sd7sr"] [node name="UnknownField" type="LineEdit"] diff --git a/src/ui_parts/DeltaHandle.gd b/src/ui_parts/DeltaHandle.gd index 9868828c..e4207b66 100644 --- a/src/ui_parts/DeltaHandle.gd +++ b/src/ui_parts/DeltaHandle.gd @@ -2,41 +2,39 @@ class_name DeltaHandle extends Handle # Required. -var x_attribute: AttributeNumeric -var y_attribute: AttributeNumeric -var t_attribute: AttributeTransform - +var x_name: String +var y_name: String +var d_name: String var horizontal: bool -var d_attribute: AttributeNumeric -func _init(id: PackedInt32Array, xref: AttributeNumeric, yref: AttributeNumeric, -tref: AttributeTransform, dref: AttributeNumeric, p_horizontal: bool) -> void: - tid = id - x_attribute = xref - y_attribute = yref - t_attribute = tref - d_attribute = dref +func _init(new_tag: Tag, xref: String, yref: String, dref: String, p_horizontal: bool) -> void: + tag = new_tag + x_name = xref + y_name = yref + d_name = dref horizontal = p_horizontal display_mode = Display.SMALL sync() -func set_pos(new_pos: Vector2, undo_redo := false) -> void: - if initial_pos != new_pos and undo_redo: - d_attribute.set_num(absf(new_pos.x - x_attribute.get_num() if horizontal else\ - new_pos.y - y_attribute.get_num()), Attribute.SyncMode.FINAL) - else: - d_attribute.set_num(absf(new_pos.x - x_attribute.get_num() if horizontal else\ - new_pos.y - y_attribute.get_num()), Attribute.SyncMode.FINAL if undo_redo\ - else Attribute.SyncMode.INTERMEDIATE) - pos = Vector2(x_attribute.get_num(), y_attribute.get_num()) +func set_pos(new_pos: Vector2, save := true) -> void: if horizontal: - pos += Vector2(d_attribute.get_num(), 0.0) + new_pos = Vector2(tag.get_attribute(x_name).get_num() +\ + tag.get_attribute(d_name).get_num(), tag.get_attribute(y_name).get_num()) else: - pos += Vector2(0.0, d_attribute.get_num()) + new_pos = Vector2(tag.get_attribute(x_name).get_num(), + tag.get_attribute(y_name).get_num() + tag.get_attribute(d_name).get_num()) + + if pos != new_pos: + pos = new_pos + tag.get_attribute(d_name).set_num( + absf(new_pos.x - tag.get_attribute(x_name).get_num() if horizontal\ + else new_pos.y - tag.get_attribute(y_name).get_num()), save) func sync() -> void: if horizontal: - pos = Vector2(x_attribute.get_num() + d_attribute.get_num(), y_attribute.get_num()) + pos = Vector2(tag.get_attribute(x_name).get_num() +\ + tag.get_attribute(d_name).get_num(), tag.get_attribute(y_name).get_num()) else: - pos = Vector2(x_attribute.get_num(), y_attribute.get_num() + d_attribute.get_num()) - transform = t_attribute.get_final_transform() + pos = Vector2(tag.get_attribute(x_name).get_num(), + tag.get_attribute(y_name).get_num() + tag.get_attribute(d_name).get_num()) + super() diff --git a/src/ui_parts/Handle.gd b/src/ui_parts/Handle.gd index e4d7a550..8fe862d5 100644 --- a/src/ui_parts/Handle.gd +++ b/src/ui_parts/Handle.gd @@ -4,7 +4,7 @@ class_name Handle extends RefCounted enum Display {BIG, SMALL} var display_mode := Display.BIG -var tid := PackedInt32Array() +var tag: Tag var pos: Vector2 var transform: Transform2D var initial_pos: Vector2 # The position of a handle when it started being dragged. @@ -13,7 +13,7 @@ func _init() -> void: pass func sync() -> void: - pass + transform = tag.get_transform() func set_pos(_new_pos: Vector2, _undo_redo := false) -> void: pass diff --git a/src/ui_parts/PathHandle.gd b/src/ui_parts/PathHandle.gd index 3fb27695..ae080852 100644 --- a/src/ui_parts/PathHandle.gd +++ b/src/ui_parts/PathHandle.gd @@ -1,51 +1,30 @@ # A handle that binds to one or two path parameters. class_name PathHandle extends Handle -var path_attribute: AttributePath -var t_attribute: AttributeTransform +const pathdata_name = "d" var command_index: int var x_param: String var y_param: String -func _init(id: PackedInt32Array, path_ref: Attribute, t_ref: AttributeTransform, -command_idx: int, x_name := "x", y_name := "y") -> void: - path_attribute = path_ref - t_attribute = t_ref - tid = id +func _init(new_tag: Tag, command_idx: int, x_name := "x", y_name := "y") -> void: + tag = new_tag command_index = command_idx x_param = x_name y_param = y_name sync() -func set_pos(new_pos: Vector2, undo_redo := false) -> void: +func set_pos(new_pos: Vector2, save := false) -> void: + var path_attribute: AttributePathdata = tag.get_attribute(pathdata_name) var command := path_attribute.get_command(command_index) var new_coords := new_pos - command.start if command.relative else new_pos - if undo_redo: - if initial_pos != new_pos: - path_attribute.set_command_property(command_index, x_param, new_coords.x, - Attribute.SyncMode.NO_PROPAGATION) - path_attribute.set_command_property(command_index, y_param, new_coords.y, - Attribute.SyncMode.FINAL) - else: - if x_param in command: - # Don't emit commands_changed for the X change if there'll be a Y change too. - path_attribute.set_command_property(command_index, x_param, new_coords.x, - Attribute.SyncMode.NO_PROPAGATION if (y_param in command and\ - command.get(y_param) != new_coords.y) else Attribute.SyncMode.INTERMEDIATE) - pos.x = new_pos.x - else: - pos.x = command.start.x - if y_param in command: - if command.get(y_param) != new_coords.y: - path_attribute.set_command_property(command_index, y_param, new_coords.y, - Attribute.SyncMode.INTERMEDIATE) - pos.y = new_pos.y - else: - pos.y = command.start.y + if pos != new_pos: + pos = new_pos + path_attribute.set_command_property(command_index, x_param, new_coords.x, save) + path_attribute.set_command_property(command_index, y_param, new_coords.y, save) func sync() -> void: - var command := path_attribute.get_command(command_index) + var command: PathCommand = tag.get_attribute(pathdata_name).get_command(command_index) if x_param in command: var command_x: float = command.get(x_param) pos.x = command.start.x + command_x if command.relative else command_x @@ -56,4 +35,4 @@ func sync() -> void: pos.y = command.start.y + command_y if command.relative else command_y else: pos.y = command.start.y - transform = t_attribute.get_final_transform() + super() diff --git a/src/ui_parts/XYHandle.gd b/src/ui_parts/XYHandle.gd index 4b96f9dd..e0f9970b 100644 --- a/src/ui_parts/XYHandle.gd +++ b/src/ui_parts/XYHandle.gd @@ -1,31 +1,21 @@ # A handle that binds to two numeric attributes. class_name XYHandle extends Handle -var x_attribute: AttributeNumeric -var y_attribute: AttributeNumeric -var t_attribute: AttributeTransform +var x_name: String +var y_name: String -func _init(id: PackedInt32Array, xref: AttributeNumeric, yref: AttributeNumeric, -tref: AttributeTransform) -> void: - tid = id - x_attribute = xref - y_attribute = yref - t_attribute = tref +func _init(new_tag: Tag, xref: String, yref: String) -> void: + tag = new_tag + x_name = xref + y_name = yref sync() -func set_pos(new_pos: Vector2, undo_redo := false) -> void: - if undo_redo: - if initial_pos != new_pos: - x_attribute.set_num(new_pos.x, Attribute.SyncMode.NO_PROPAGATION) - y_attribute.set_num(new_pos.y, Attribute.SyncMode.FINAL) - else: - if new_pos.x != pos.x: - x_attribute.set_num(new_pos.x, Attribute.SyncMode.INTERMEDIATE if\ - new_pos.y == pos.y else Attribute.SyncMode.NO_PROPAGATION) - if new_pos.y != pos.y: - y_attribute.set_num(new_pos.y, Attribute.SyncMode.INTERMEDIATE) - pos = new_pos +func set_pos(new_pos: Vector2, save := false) -> void: + if pos != new_pos: + pos = new_pos + tag.get_attribute(x_name).set_num(new_pos.x, save) + tag.get_attribute(y_name).set_num(new_pos.y, save) func sync() -> void: - pos = Vector2(x_attribute.get_num(), y_attribute.get_num()) - transform = t_attribute.get_final_transform() + pos = Vector2(tag.get_attribute(x_name).get_num(), tag.get_attribute(y_name).get_num()) + super() diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd index a0f4143a..a68683a9 100644 --- a/src/ui_parts/about_menu.gd +++ b/src/ui_parts/about_menu.gd @@ -1,5 +1,7 @@ extends PanelContainer +const app_info_json = preload("res://app_info.json") + @onready var version_label: Label = %VersionLabel @onready var close_button: Button = $VBoxContainer/CloseButton @onready var translations_list: VBoxContainer = %Translations/List @@ -30,16 +32,17 @@ func update_translation() -> void: %Components.text = TranslationServer.translate("Godot third-party components") func _ready() -> void: + var app_info: Dictionary = app_info_json.data version_label.text = "GodSVG v" + ProjectSettings.get_setting("application/config/version") - project_founder_list.items = AppInfo.project_founder_and_manager + project_founder_list.items = app_info.project_founder_and_manager project_founder_list.setup() - authors_list.items = AppInfo.authors + authors_list.items = app_info.authors authors_list.setup() - donors_list.items = AppInfo.donors + donors_list.items = app_info.donors donors_list.setup() - golden_donors_list.items = AppInfo.golden_donors + golden_donors_list.items = app_info.golden_donors golden_donors_list.setup() - diamond_donors_list.items = AppInfo.diamond_donors + diamond_donors_list.items = app_info.diamond_donors diamond_donors_list.setup() # There can be multiple translators for a single locale. for lang in TranslationServer.get_loaded_locales(): diff --git a/src/ui_parts/camera.gd b/src/ui_parts/camera.gd index e29646e4..85faa59f 100644 --- a/src/ui_parts/camera.gd +++ b/src/ui_parts/camera.gd @@ -18,7 +18,7 @@ var surface := RenderingServer.canvas_item_create() # Used for drawing the numb func _ready() -> void: RenderingServer.canvas_item_set_parent(surface, ci) - SVG.root_tag.resized.connect(queue_redraw) + SVG.resized.connect(queue_redraw) Indications.zoom_changed.connect(change_zoom) Indications.zoom_changed.connect(queue_redraw) diff --git a/src/ui_parts/code_editor.gd b/src/ui_parts/code_editor.gd index 9f254195..73176e38 100644 --- a/src/ui_parts/code_editor.gd +++ b/src/ui_parts/code_editor.gd @@ -23,10 +23,9 @@ func _ready() -> void: setup_theme() setup_highlighter() code_edit.clear_undo_history() - SVG.root_tag.attribute_changed.connect(auto_update_text.unbind(1)) - SVG.root_tag.child_attribute_changed.connect(auto_update_text.unbind(1)) - SVG.root_tag.tag_layout_changed.connect(auto_update_text) - SVG.root_tag.changed_unknown.connect(auto_update_text) + SVG.attribute_somewhere_changed.connect(auto_update_text.unbind(1)) + SVG.tag_layout_changed.connect(auto_update_text) + SVG.changed_unknown.connect(auto_update_text) GlobalSettings.save_data.current_file_path_changed.connect(update_file_button) import_button.pressed.connect(ShortcutUtils.fn("import")) export_button.pressed.connect(ShortcutUtils.fn("export")) @@ -124,7 +123,7 @@ func set_new_text(svg_text: String) -> void: func _on_svg_code_edit_text_changed() -> void: SVG.text = code_edit.text - SVG.update_tags() + SVG.sync() func update_size_label() -> void: diff --git a/src/ui_parts/display.gd b/src/ui_parts/display.gd index 4c0732dc..60fd1e97 100644 --- a/src/ui_parts/display.gd +++ b/src/ui_parts/display.gd @@ -40,7 +40,6 @@ func _ready() -> void: update_theme() update_snap_config() get_window().window_input.connect(_update_input_debug) - Indications.imported_reference.connect(finish_reference_load) view_settings_updated.emit(grid_visuals.visible, controls.visible, viewport.display_texture.rasterized) @@ -145,6 +144,9 @@ func _on_more_options_pressed() -> void: "about_info") about_btn.expand_icon = true var buttons_arr: Array[Button] = [ + ContextPopup.create_button(TranslationServer.translate("Check for updates"), + ShortcutUtils.fn("check_updates"), false, + load("res://visual/icons/Reload.svg"), "check_updates"), about_btn, ContextPopup.create_button(TranslationServer.translate("Donate…"), ShortcutUtils.fn("about_donate"), false, load("res://visual/icons/Heart.svg"), @@ -154,11 +156,8 @@ func _on_more_options_pressed() -> void: "about_repo"), ContextPopup.create_button(TranslationServer.translate("GodSVG website"), ShortcutUtils.fn("about_website"), false, load("res://visual/icons/Link.svg"), - "about_website"), - ContextPopup.create_button(TranslationServer.translate("Check for updates"), - ShortcutUtils.fn("check_updates"), false, - load("res://visual/icons/Reload.svg"), "check_updates")] - var separator_indices: Array[int] = [2, 4] + "about_website")] + var separator_indices: Array[int] = [1, 3] var more_popup := ContextPopup.new() more_popup.setup(buttons_arr, true, -1, separator_indices) @@ -182,7 +181,7 @@ func toggle_rasterization() -> void: viewport.display_texture.rasterized) func load_reference_image() -> void: - FileUtils.open_reference_load_dialog() + FileUtils.open_reference_load_dialog(finish_reference_load) func toggle_reference_image() -> void: reference_texture.visible = not reference_texture.visible diff --git a/src/ui_parts/display_texture.gd b/src/ui_parts/display_texture.gd index 0754068e..41bc297c 100644 --- a/src/ui_parts/display_texture.gd +++ b/src/ui_parts/display_texture.gd @@ -17,10 +17,9 @@ var update_pending := false func _ready() -> void: - SVG.root_tag.tag_layout_changed.connect(queue_update) - SVG.root_tag.changed_unknown.connect(queue_update) - SVG.root_tag.attribute_changed.connect(queue_update.unbind(1)) - SVG.root_tag.child_attribute_changed.connect(queue_update.unbind(1)) + SVG.tag_layout_changed.connect(queue_update) + SVG.changed_unknown.connect(queue_update) + SVG.attribute_somewhere_changed.connect(queue_update.unbind(1)) Indications.zoom_changed.connect(queue_update) queue_update() @@ -47,10 +46,9 @@ func svg_update() -> void: display_rect.end.x = minf(display_rect.end.x, SVG.root_tag.width) display_rect.end.y = minf(display_rect.end.y, SVG.root_tag.height) - var svg_text := SVGParser.svg_to_text_custom(SVG.root_tag, display_rect.size.x, + var svg_text := SVGParser.root_to_text_custom(SVG.root_tag, display_rect.size.x, display_rect.size.y, Rect2(SVG.root_tag.world_to_canvas(display_rect.position), display_rect.size / SVG.root_tag.canvas_transform.get_scale())) - var img := Image.new() var err := img.load_svg_from_string(svg_text, image_zoom) if err == OK: diff --git a/src/ui_parts/handles_manager.gd b/src/ui_parts/handles_manager.gd index ca2193fb..d5b6ef81 100644 --- a/src/ui_parts/handles_manager.gd +++ b/src/ui_parts/handles_manager.gd @@ -75,11 +75,10 @@ func render_handle_textures() -> void: func _ready() -> void: render_handle_textures() RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) - SVG.root_tag.attribute_changed.connect(queue_update.unbind(1)) - SVG.root_tag.child_attribute_changed.connect(queue_redraw.unbind(1)) - SVG.root_tag.child_attribute_changed.connect(sync_handles.unbind(1)) - SVG.root_tag.tag_layout_changed.connect(queue_update) - SVG.root_tag.changed_unknown.connect(queue_update) + SVG.attribute_somewhere_changed.connect(queue_redraw.unbind(1)) + SVG.attribute_somewhere_changed.connect(sync_handles.unbind(1)) + SVG.tag_layout_changed.connect(queue_update) + SVG.changed_unknown.connect(queue_update) Indications.selection_changed.connect(queue_redraw) Indications.hover_changed.connect(queue_redraw) Indications.zoom_changed.connect(queue_redraw) @@ -102,32 +101,24 @@ func _process(_delta: float) -> void: func update_handles() -> void: handles.clear() - for tid in SVG.root_tag.get_all_tids(): - var tag := SVG.root_tag.get_tag(tid) + for tag in SVG.root_tag.get_all_tags(): match tag.name: "circle": - handles.append(generate_xy_handle(tid, tag, "cx", "cy", "transform")) - handles.append(generate_delta_handle(tid, tag, "cx", "cy", "transform", - "r", true)) + handles.append(XYHandle.new(tag, "cx", "cy")) + handles.append(DeltaHandle.new(tag, "cx", "cy", "r", true)) "ellipse": - handles.append(generate_xy_handle(tid, tag, "cx", "cy", "transform")) - handles.append(generate_delta_handle(tid, tag, "cx", "cy", "transform", - "rx", true)) - handles.append(generate_delta_handle(tid, tag, "cx", "cy", "transform", - "ry", false)) + handles.append(XYHandle.new(tag, "cx", "cy")) + handles.append(DeltaHandle.new(tag, "cx", "cy", "rx", true)) + handles.append(DeltaHandle.new(tag, "cx", "cy", "ry", false)) "rect": - handles.append(generate_xy_handle(tid, tag, "x", "y", "transform")) - handles.append(generate_xy_handle(tid, tag, "x", "y", "transform")) - handles.append(generate_delta_handle(tid, tag, "x", "y", "transform", - "width", true)) - handles.append(generate_delta_handle(tid, tag, "x", "y", "transform", - "height", false)) + handles.append(XYHandle.new(tag, "x", "y")) + handles.append(DeltaHandle.new(tag, "x", "y", "width", true)) + handles.append(DeltaHandle.new(tag, "x", "y", "height", false)) "line": - handles.append(generate_xy_handle(tid, tag, "x1", "y1", "transform")) - handles.append(generate_xy_handle(tid, tag, "x2", "y2", "transform")) + handles.append(XYHandle.new(tag, "x1", "y1")) + handles.append(XYHandle.new(tag, "x2", "y2")) "path": - handles += generate_path_handles(tid, tag.attributes.d, - tag.attributes.transform) + handles += generate_path_handles(tag) # Pretend the mouse was moved to update the hovering. var mouse_motion_event := InputEventMouseMotion.new() mouse_motion_event.position = get_viewport().get_mouse_position() @@ -146,42 +137,28 @@ func sync_handles() -> void: else: handle.sync() - for tid in SVG.root_tag.get_all_tids(): - var tag := SVG.root_tag.get_tag(tid) + for tag in SVG.root_tag.get_all_tags(): if tag.name == "path": - handles += generate_path_handles(tid, tag.attributes.d, tag.attributes.transform) + handles += generate_path_handles(tag) queue_redraw() -func generate_path_handles(tid: PackedInt32Array, data_attrib: AttributePath, -t_attrib: AttributeTransform) -> Array[Handle]: +func generate_path_handles(tag: Tag) -> Array[Handle]: var path_handles: Array[Handle] = [] + var data_attrib: AttributePathdata = tag.get_attribute("d") for idx in data_attrib.get_command_count(): var path_command := data_attrib.get_command(idx) if not path_command.command_char in "Zz": - path_handles.append(PathHandle.new(tid, data_attrib, t_attrib, idx)) + path_handles.append(PathHandle.new(tag, idx)) if path_command.command_char in "CcQq": - var tangent := PathHandle.new(tid, data_attrib, t_attrib, idx, "x1", "y1") + var tangent := PathHandle.new(tag, idx, "x1", "y1") tangent.display_mode = Handle.Display.SMALL path_handles.append(tangent) if path_command.command_char in "CcSs": - var tangent := PathHandle.new(tid, data_attrib, t_attrib, idx, "x2", "y2") + var tangent := PathHandle.new(tag, idx, "x2", "y2") tangent.display_mode = Handle.Display.SMALL path_handles.append(tangent) return path_handles -# Helpers for generating the handles when the tag is at hand. -func generate_xy_handle(tid: PackedInt32Array, tag: Tag, x_attrib_name: String,\ -y_attrib_name: String, t_attrib_name: String) -> XYHandle: - return XYHandle.new(tid, tag.attributes[x_attrib_name], - tag.attributes[y_attrib_name], tag.attributes[t_attrib_name]) - -func generate_delta_handle(tid: PackedInt32Array, tag: Tag, x_attrib_name: String,\ -y_attrib_name: String, t_attrib_name: String, delta_attrib_name: String,\ -horizontal: bool) -> DeltaHandle: - return DeltaHandle.new(tid, tag.attributes[x_attrib_name], - tag.attributes[y_attrib_name], tag.attributes[t_attrib_name], - tag.attributes[delta_attrib_name], horizontal) - func _draw() -> void: # Store contours of shapes. @@ -195,18 +172,15 @@ func _draw() -> void: var hovered_multiline := PackedVector2Array() var hovered_selected_multiline := PackedVector2Array() - for tid in SVG.root_tag.get_all_tids(): - var tag := SVG.root_tag.get_tag(tid) - var attribs := tag.attributes - + for tag: Tag in SVG.root_tag.get_all_tags(): # Determine if the tag is hovered/selected or has a hovered/selected parent. - var tag_hovered := Indications.is_hovered(tid, -1, true) - var tag_selected := Indications.is_selected(tid, -1, true) + var tag_hovered := Indications.is_hovered(tag.xid, -1, true) + var tag_selected := Indications.is_selected(tag.xid, -1, true) match tag.name: "circle": - var c := Vector2(attribs.cx.get_num(), attribs.cy.get_num()) - var r: float = attribs.r.get_num() + var c := Vector2(tag.get_attribute_num("cx"), tag.get_attribute_num("cy")) + var r: float = tag.get_attribute_num("r") var points := PackedVector2Array() points.resize(181) @@ -215,8 +189,9 @@ func _draw() -> void: points[i] = c + Vector2(cos(d), sin(d)) * r points[180] = points[0] var extras := PackedVector2Array([c, c + Vector2(r, 0)]) - points = attribs.transform.get_final_transform() * points - extras = attribs.transform.get_final_transform() * extras + var final_transform := tag.get_transform() + points = final_transform * points + extras = final_transform * extras if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -232,20 +207,19 @@ func _draw() -> void: normal_multiline += extras "ellipse": - var c := Vector2(attribs.cx.get_num(), attribs.cy.get_num()) - var rx: float = attribs.rx.get_num() - var ry: float = attribs.ry.get_num() + var c := Vector2(tag.get_attribute_num("cx"), tag.get_attribute_num("cy")) # Squished circle. var points := PackedVector2Array() points.resize(181) for i in 180: var d := i * TAU/180 - points[i] = c + Vector2(cos(d) * rx, sin(d) * ry) + points[i] = c + Vector2(cos(d) * tag.rx, sin(d) * tag.ry) points[180] = points[0] var extras := PackedVector2Array([ - c, c + Vector2(rx, 0), c, c + Vector2(0, ry)]) - points = attribs.transform.get_final_transform() * points - extras = attribs.transform.get_final_transform() * extras + c, c + Vector2(tag.rx, 0), c, c + Vector2(0, tag.ry)]) + var final_transform := tag.get_transform() + points = final_transform * points + extras = final_transform * extras if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -261,12 +235,12 @@ func _draw() -> void: normal_multiline += extras "rect": - var x: float = attribs.x.get_num() - var y: float = attribs.y.get_num() - var rect_width: float = attribs.width.get_num() - var rect_height: float = attribs.height.get_num() - var rx: float = attribs.rx.get_num() - var ry: float = attribs.ry.get_num() + var x := tag.get_attribute_num("x") + var y := tag.get_attribute_num("y") + var rect_width := tag.get_attribute_num("width") + var rect_height := tag.get_attribute_num("height") + var rx: float = tag.rx + var ry: float = tag.ry var points := PackedVector2Array() if rx == 0 and ry == 0: # Basic rectangle. @@ -306,8 +280,9 @@ func _draw() -> void: points[185] = points[0] var extras := PackedVector2Array([Vector2(x, y), Vector2(x + rect_width, y), Vector2(x, y), Vector2(x, y + rect_height)]) - points = attribs.transform.get_final_transform() * points - extras = attribs.transform.get_final_transform() * extras + var final_transform := tag.get_transform() + points = final_transform * points + extras = final_transform * extras if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -323,13 +298,13 @@ func _draw() -> void: normal_multiline += extras "line": - var x1: float = attribs.x1.get_num() - var y1: float = attribs.y1.get_num() - var x2: float = attribs.x2.get_num() - var y2: float = attribs.y2.get_num() + var x1 := tag.get_attribute_num("x1") + var y1 := tag.get_attribute_num("y1") + var x2 := tag.get_attribute_num("x2") + var y2 := tag.get_attribute_num("y2") var points := PackedVector2Array([Vector2(x1, y1), Vector2(x2, y2)]) - points = attribs.transform.get_final_transform() * points + points = tag.get_transform() * points if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -341,7 +316,7 @@ func _draw() -> void: normal_polylines.append(points) "path": - var pathdata: AttributePath = attribs.d + var pathdata: AttributePathdata = tag.get_attribute("d") if pathdata.get_command_count() == 0 or\ not pathdata.get_command(0).command_char in "Mm": continue # Nothing to draw. @@ -356,10 +331,10 @@ func _draw() -> void: var relative := cmd.relative current_mode = Utils.InteractionType.NONE - if Indications.is_hovered(tid, cmd_idx, true): + if Indications.is_hovered(tag.xid, cmd_idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.HOVERED - if Indications.is_selected(tid, cmd_idx, true): + if Indications.is_selected(tag.xid, cmd_idx, true): @warning_ignore("int_as_enum_without_cast") current_mode += Utils.InteractionType.SELECTED @@ -526,8 +501,10 @@ func _draw() -> void: points = PackedVector2Array([cmd.start, end]) "M": continue - points = attribs.transform.get_final_transform() * points - tangent_points = attribs.transform.get_final_transform() * tangent_points + + var final_transform := tag.get_transform() + points = final_transform * points + tangent_points = final_transform * tangent_points match current_mode: Utils.InteractionType.NONE: normal_polylines.append(points) @@ -550,37 +527,15 @@ func _draw() -> void: RenderingServer.canvas_item_set_transform(surface, Transform2D(0.0, Vector2(1 / Indications.zoom, 1 / Indications.zoom), 0.0, Vector2.ZERO)) - for polyline in normal_polylines: - draw_polyline(polyline, normal_color, contour_width, true) - for polyline in selected_polylines: - draw_polyline(polyline, selected_color, contour_width, true) - for polyline in hovered_polylines: - draw_polyline(polyline, hovered_color, contour_width, true) - for polyline in hovered_selected_polylines: - draw_polyline(polyline, hovered_selected_color, contour_width, true) - - if not normal_multiline.is_empty(): - draw_multiline(normal_multiline, - Color(normal_color, tangent_alpha), tangent_width, true) - if not selected_multiline.is_empty(): - draw_multiline(selected_multiline, - Color(selected_color, tangent_alpha), tangent_width, true) - if not hovered_multiline.is_empty(): - draw_multiline(hovered_multiline, - Color(hovered_color, tangent_alpha), tangent_width, true) - if not hovered_selected_multiline.is_empty(): - draw_multiline(hovered_selected_multiline, - Color(hovered_selected_color, tangent_alpha), tangent_width, true) - - # First gather all handles in 4 categories, then draw them in the right order. + # First gather all handles in 4 categories, to then draw them in the right order. var normal_handles: Array[Handle] = [] var selected_handles: Array[Handle] = [] var hovered_handles: Array[Handle] = [] var hovered_selected_handles: Array[Handle] = [] for handle in handles: var cmd_idx: int = handle.command_index if handle is PathHandle else -1 - var is_hovered := Indications.is_hovered(handle.tid, cmd_idx, true) - var is_selected := Indications.is_selected(handle.tid, cmd_idx, true) + var is_hovered := Indications.is_hovered(handle.tag.xid, cmd_idx, true) + var is_selected := Indications.is_selected(handle.tag.xid, cmd_idx, true) if is_hovered and is_selected: hovered_selected_handles.append(handle) @@ -592,22 +547,57 @@ func _draw() -> void: normal_handles.append(handle) RenderingServer.canvas_item_clear(surface) + + for polyline in normal_polylines: + draw_polyline(polyline, normal_color, contour_width, true) + if not normal_multiline.is_empty(): + draw_multiline(normal_multiline, + Color(normal_color, tangent_alpha), tangent_width, true) for handle in normal_handles: var texture: Texture2D = normal_handle_textures[handle.display_mode] texture.draw(surface, SVG.root_tag.canvas_to_world(handle.transform * handle.pos) *\ Indications.zoom - texture.get_size() / 2) + + for polyline in selected_polylines: + draw_polyline(polyline, selected_color, contour_width, true) + if not selected_multiline.is_empty(): + draw_multiline(selected_multiline, + Color(selected_color, tangent_alpha), tangent_width, true) for handle in selected_handles: var texture: Texture2D = selected_handle_textures[handle.display_mode] texture.draw(surface, SVG.root_tag.canvas_to_world(handle.transform * handle.pos) *\ Indications.zoom - texture.get_size() / 2) + + for polyline in hovered_polylines: + draw_polyline(polyline, hovered_color, contour_width, true) + if not hovered_multiline.is_empty(): + draw_multiline(hovered_multiline, + Color(hovered_color, tangent_alpha), tangent_width, true) for handle in hovered_handles: var texture: Texture2D = hovered_handle_textures[handle.display_mode] texture.draw(surface, SVG.root_tag.canvas_to_world(handle.transform * handle.pos) *\ Indications.zoom - texture.get_size() / 2) + + for polyline in hovered_selected_polylines: + draw_polyline(polyline, hovered_selected_color, contour_width, true) + if not hovered_selected_multiline.is_empty(): + draw_multiline(hovered_selected_multiline, + Color(hovered_selected_color, tangent_alpha), tangent_width, true) for handle in hovered_selected_handles: var texture: Texture2D = hovered_selected_handle_textures[handle.display_mode] texture.draw(surface, SVG.root_tag.canvas_to_world(handle.transform * handle.pos) *\ Indications.zoom - texture.get_size() / 2) + + for tag: Tag in SVG.root_tag.get_all_tags(): + if tag is TagShape and tag.xid in Indications.selected_xids: + var bounding_box: Rect2 = tag.get_bounding_box() + if bounding_box != Rect2(): + var transform := tag.get_attribute_final_transform("transform") + var transform_scale := transform.get_scale() + draw_set_transform_matrix(transform.scaled(Vector2(1, 1) / transform_scale)) + bounding_box *= Transform2D(0, transform_scale, 0, Vector2.ZERO) + draw_rect(bounding_box.grow(1.0 / Indications.zoom), Color.YELLOW, false, + 1.0 / Indications.zoom, true) var dragged_handle: Handle = null @@ -632,9 +622,9 @@ func respond_to_input_event(event: InputEvent) -> void: if is_instance_valid(nearest_handle): hovered_handle = nearest_handle if hovered_handle is PathHandle: - Indications.set_hovered(hovered_handle.tid, hovered_handle.command_index) + Indications.set_hovered(hovered_handle.tag.xid, hovered_handle.command_index) else: - Indications.set_hovered(hovered_handle.tid) + Indications.set_hovered(hovered_handle.tag.xid) else: hovered_handle = null Indications.clear_hovered() @@ -673,23 +663,23 @@ func respond_to_input_event(event: InputEvent) -> void: dragged_handle = hovered_handle dragged_handle.initial_pos = dragged_handle.pos var inner_idx := -1 - var dragged_tid := dragged_handle.tid + var dragged_xid := dragged_handle.tag.xid if dragged_handle is PathHandle: inner_idx = dragged_handle.command_index if event.double_click and inner_idx != -1: # Unselect the tag, so then it's selected again in the subpath. - Indications.ctrl_select(dragged_tid, inner_idx) + Indications.ctrl_select(dragged_xid, inner_idx) var subpath_range: Vector2i =\ - dragged_handle.path_attribute.get_subpath(inner_idx) + dragged_handle.tag.get_attribute("d").get_subpath(inner_idx) for idx in range(subpath_range.x, subpath_range.y + 1): - Indications.ctrl_select(dragged_tid, idx) + Indications.ctrl_select(dragged_xid, idx) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(dragged_tid, inner_idx) + Indications.ctrl_select(dragged_xid, inner_idx) elif event.shift_pressed: - Indications.shift_select(dragged_tid, inner_idx) + Indications.shift_select(dragged_xid, inner_idx) else: - Indications.normal_select(dragged_tid, inner_idx) + Indications.normal_select(dragged_xid, inner_idx) elif is_instance_valid(dragged_handle) and event.is_released(): if was_handle_moved: var new_pos := dragged_handle.transform.affine_inverse() *\ @@ -709,18 +699,18 @@ func respond_to_input_event(event: InputEvent) -> void: Indications.clear_all_selections() HandlerGUI.popup_under_pos(create_tag_context(event_pos), popup_pos, vp) else: - var hovered_tid := hovered_handle.tid + var hovered_xid := hovered_handle.tag.xid var inner_idx := -1 if hovered_handle is PathHandle: inner_idx = hovered_handle.command_index - if (Indications.semi_selected_tid != hovered_tid or\ + if (Indications.semi_selected_xid != hovered_xid or\ not inner_idx in Indications.inner_selections) and\ - not hovered_tid in Indications.selected_tids: - Indications.normal_select(hovered_tid, inner_idx) + not hovered_xid in Indications.selected_xids: + Indications.normal_select(hovered_xid, inner_idx) HandlerGUI.popup_under_pos(Indications.get_selection_context( HandlerGUI.popup_under_pos.bind(popup_pos, vp), - Indications.SelectionContext.VIEWPORT), popup_pos, vp) + Indications.Context.VIEWPORT), popup_pos, vp) func find_nearest_handle(event_pos: Vector2) -> Handle: var nearest_handle: Handle = null @@ -737,16 +727,16 @@ func find_nearest_handle(event_pos: Vector2) -> Handle: func _on_handle_added() -> void: if not get_viewport_rect().has_point(get_viewport().get_mouse_position()): - if not Indications.semi_selected_tid.is_empty(): - SVG.root_tag.get_tag(Indications.semi_selected_tid).attributes.d.\ - sync_after_commands_change(Attribute.SyncMode.FINAL) + if not Indications.semi_selected_xid.is_empty(): + SVG.root_tag.get_tag(Indications.semi_selected_xid).get_attribute("d").\ + sync_after_commands_change(true) return update_handles() for handle in handles: - if handle is PathHandle and handle.tid == Indications.semi_selected_tid and\ + if handle is PathHandle and handle.tag.xid == Indications.semi_selected_xid and\ handle.command_index == Indications.inner_selections[0]: - Indications.set_hovered(handle.tid, handle.command_index) + Indications.set_hovered(handle.tag.xid, handle.command_index) dragged_handle = handle # Move the handle that's being dragged. var mouse_pos := get_global_mouse_position() @@ -769,16 +759,11 @@ func create_tag_context(pos: Vector2) -> ContextPopup: btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf")) btn_array.append(btn) var tag_context := ContextPopup.new() - tag_context.setup_with_title(btn_array, TranslationServer.translate("New tag"), true) + var separation_indices: Array[int] = [1] + tag_context.setup_with_title(btn_array, TranslationServer.translate("New shape"), true, + -1, separation_indices) return tag_context func add_tag_at_pos(tag_name: String, pos: Vector2) -> void: - var tag: Tag - match tag_name: - "path": tag = TagPath.new() - "circle": tag = TagCircle.new() - "ellipse": tag = TagEllipse.new() - "rect": tag = TagRect.new() - "line": tag = TagLine.new() - tag.user_setup(pos) - SVG.root_tag.add_tag(tag, PackedInt32Array([SVG.root_tag.get_child_count()])) + SVG.root_tag.add_tag(DB.tag(tag_name, pos), + PackedInt32Array([SVG.root_tag.get_child_count()])) diff --git a/src/ui_parts/import_warning_dialog.gd b/src/ui_parts/import_warning_dialog.gd index ea4141f5..69f9584d 100644 --- a/src/ui_parts/import_warning_dialog.gd +++ b/src/ui_parts/import_warning_dialog.gd @@ -14,19 +14,20 @@ func _notification(what: int) -> void: if what == Utils.CustomNotification.LANGUAGE_CHANGED: setup() - func _ready() -> void: + imported.connect(queue_free) + ok_button.pressed.connect(imported.emit) setup() + func setup() -> void: - imported.connect(queue_free) # Convert forward and backward to show how GodSVG would display the given SVG. - var imported_text_parse_result := SVGParser.text_to_svg(imported_text) - var preview_text := SVGParser.svg_to_text(imported_text_parse_result.svg) - var preview_parse_result := SVGParser.text_to_svg(preview_text) + var imported_text_parse_result := SVGParser.text_to_root(imported_text) + var preview_text := SVGParser.root_to_text(imported_text_parse_result.svg) + var preview_parse_result := SVGParser.text_to_root(preview_text) var preview := preview_parse_result.svg if is_instance_valid(preview): - texture_preview.setup(SVGParser.svg_to_text(preview), preview.get_size()) + texture_preview.setup(SVGParser.root_to_text(preview), preview.get_size()) if imported_text_parse_result.error != SVGParser.ParseError.OK: texture_preview.hide() @@ -54,20 +55,22 @@ func set_svg(text: String) -> void: imported_text = text -func get_svg_warnings(svg_tag: TagSVG) -> Array[String]: - var warnings: Array[String] = [] - var tids := svg_tag.get_all_tids() - for tid in tids: - var tag := svg_tag.get_tag(tid) - if tag is TagUnknown: - warnings.append("%s: %s" % [TranslationServer.translate("Unknown tag"), - tag.name]) +func get_svg_warnings(svg_tag: TagSVG) -> PackedStringArray: + var unrecognized_tags := PackedStringArray() + var unrecognized_attributes := PackedStringArray() + for tag in svg_tag.get_all_tags(): + if tag is TagUnrecognized: + if not tag.name in unrecognized_tags: + unrecognized_tags.append(tag.name) else: - for unknown_attrib in tag.unknown_attributes: - warnings.append("%s: %s" % [TranslationServer.translate("Unknown attribute"), - unknown_attrib.name]) + for attribute_key in tag.attributes: + if not attribute_key in DB.recognized_attributes[tag.name] and\ + not attribute_key in unrecognized_attributes: + unrecognized_attributes.append(attribute_key) + var warnings := PackedStringArray() + for tag in unrecognized_tags: + warnings.append("%s: %s" % [TranslationServer.translate("Unrecognized tag"), tag]) + for attribute in unrecognized_attributes: + warnings.append("%s: %s" % [TranslationServer.translate("Unrecognized attribute"), + attribute]) return warnings - - -func _on_ok_button_pressed() -> void: - imported.emit() diff --git a/src/ui_parts/import_warning_dialog.tscn b/src/ui_parts/import_warning_dialog.tscn index e8a221ed..a79bd763 100644 --- a/src/ui_parts/import_warning_dialog.tscn +++ b/src/ui_parts/import_warning_dialog.tscn @@ -77,5 +77,3 @@ layout_mode = 2 size_flags_horizontal = 6 mouse_default_cursor_shape = 2 text = "Import" - -[connection signal="pressed" from="VBoxContainer/ButtonContainer/OKButton" to="." method="_on_ok_button_pressed"] diff --git a/src/ui_parts/inspector.gd b/src/ui_parts/inspector.gd index 63568a2f..5b131fc0 100644 --- a/src/ui_parts/inspector.gd +++ b/src/ui_parts/inspector.gd @@ -12,13 +12,13 @@ func _notification(what: int) -> void: func _ready() -> void: update_translation() - SVG.root_tag.tag_layout_changed.connect(full_rebuild) - SVG.root_tag.changed_unknown.connect(full_rebuild) + SVG.tag_layout_changed.connect(full_rebuild) + SVG.changed_unknown.connect(full_rebuild) full_rebuild() func update_translation() -> void: - $AddButton.text = TranslationServer.translate("Add new tag") + add_button.text = TranslationServer.translate("Add element") func full_rebuild() -> void: @@ -28,30 +28,26 @@ func full_rebuild() -> void: for tag_idx in SVG.root_tag.get_child_count(): var tag_editor := TagFrame.instantiate() tag_editor.tag = SVG.root_tag.child_tags[tag_idx] - tag_editor.tid = PackedInt32Array([tag_idx]) tags_container.add_child(tag_editor) func add_tag(tag_name: String) -> void: - var new_tid := PackedInt32Array([SVG.root_tag.get_child_count()]) - var new_tag: Tag - match tag_name: - "circle": new_tag = TagCircle.new() - "ellipse": new_tag = TagEllipse.new() - "rect": new_tag = TagRect.new() - "path": new_tag = TagPath.new() - "line": new_tag = TagLine.new() - new_tag.user_setup() - SVG.root_tag.add_tag(new_tag, new_tid) + var new_tag := DB.tag(tag_name) + if tag_name in ["linearGradient", "radialGradient"]: + SVG.root_tag.add_tag(new_tag, PackedInt32Array([0])) + else: + SVG.root_tag.add_tag(new_tag, PackedInt32Array([SVG.root_tag.get_child_count()])) func _on_add_button_pressed() -> void: var btn_array: Array[Button] = [] - for tag_name in ["path", "circle", "ellipse", "rect", "line"]: + for tag_name in PackedStringArray(["path", "circle", "ellipse", "rect", "line", + "g", "linearGradient", "radialGradient"]): var btn := ContextPopup.create_button(tag_name, add_tag.bind(tag_name), false, DB.get_tag_icon(tag_name)) btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf")) btn_array.append(btn) + var separator_indices: Array[int] = [1, 5] var add_popup := ContextPopup.new() - add_popup.setup(btn_array, true, add_button.size.x) + add_popup.setup(btn_array, true, add_button.size.x, separator_indices) HandlerGUI.popup_under_rect(add_popup, add_button.get_global_rect(), get_viewport()) diff --git a/src/ui_parts/move_to_overlay.gd b/src/ui_parts/move_to_overlay.gd index 4283a17b..3ce96f01 100644 --- a/src/ui_parts/move_to_overlay.gd +++ b/src/ui_parts/move_to_overlay.gd @@ -1,16 +1,16 @@ extends Control -# Runs every time the mouse moves. Returning true means you can drop the TIDs. +# Runs every time the mouse moves. Returning true means you can drop the XIDs. func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: if not data is Array[PackedInt32Array]: return false - get_parent().update_proposed_tid() - for tid in data: - if Utils.is_tid_parent(tid, Indications.proposed_drop_tid): + get_parent().update_proposed_xid() + for xid in data: + if Utils.is_xid_parent(xid, Indications.proposed_drop_xid): return false return true -# Runs when you drop the TIDs. +# Runs when you drop the XIDs. func _drop_data(_at_position: Vector2, data: Variant) -> void: if data is Array[PackedInt32Array]: - SVG.root_tag.move_tags_to(data, Indications.proposed_drop_tid) + SVG.root_tag.move_tags_to(data, Indications.proposed_drop_xid) diff --git a/src/ui_parts/root_tag_editor.gd b/src/ui_parts/root_tag_editor.gd index 66d4e930..048df1dc 100644 --- a/src/ui_parts/root_tag_editor.gd +++ b/src/ui_parts/root_tag_editor.gd @@ -1,9 +1,17 @@ -extends CenterContainer +extends VBoxContainer # So, about this editor. Width and height don't have default values, so they use NAN and # use NumberEdit, rather than NumberField. Viewbox is a list and it also doesn't have a # default value, so it uses 4 NumberEdits. +const UnrecognizedField = preload("res://src/ui_elements/unrecognized_field.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const IDField = preload("res://src/ui_elements/id_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") +const TransformField = preload("res://src/ui_elements/transform_field.tscn") + const NumberEditType = preload("res://src/ui_elements/number_edit.gd") @onready var width_button: Button = %Size/Width/WidthButton @@ -15,27 +23,76 @@ const NumberEditType = preload("res://src/ui_elements/number_edit.gd") @onready var viewbox_edit_y: NumberEditType = %Viewbox/Rect/ViewboxEditY @onready var viewbox_edit_w: NumberEditType = %Viewbox/Rect/ViewboxEditW @onready var viewbox_edit_h: NumberEditType = %Viewbox/Rect/ViewboxEditH +@onready var unknown_container: HFlowContainer + +var root_tag: TagRoot func _ready() -> void: - SVG.root_tag.resized.connect(update_attributes) - SVG.root_tag.changed_unknown.connect(update_attributes) + root_tag = SVG.root_tag + SVG.resized.connect(update_attributes) + SVG.changed_unknown.connect(update_attributes) update_attributes() func update_attributes() -> void: - width_edit.set_value(SVG.root_tag.width, false) - height_edit.set_value(SVG.root_tag.height, false) - viewbox_edit_x.set_value(SVG.root_tag.viewbox.position.x, false) - viewbox_edit_y.set_value(SVG.root_tag.viewbox.position.y, false) - viewbox_edit_w.set_value(SVG.root_tag.viewbox.size.x, false) - viewbox_edit_h.set_value(SVG.root_tag.viewbox.size.y, false) + # If there are unknown attributes, they would always be on top. + if is_instance_valid(unknown_container): + for child in unknown_container.get_children(): + child.queue_free() + var has_unrecognized_attributes := false + for attribute in root_tag.attributes.values(): + # TODO separate unrecognized attributes from global defaults. + if not attribute.name in ["width", "height", "viewBox", "xmlns"]: + if not has_unrecognized_attributes: + has_unrecognized_attributes = true + if is_instance_valid(unknown_container): + unknown_container.queue_free() + unknown_container = HFlowContainer.new() + add_child(unknown_container) + move_child(unknown_container, 0) + + var input_field: Control + match DB.get_attribute_type(attribute.name): + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.ID: input_field = IDField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute.name].x + var max_value: float = DB.attribute_numeric_bounds[attribute.name].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + _: input_field = UnrecognizedField.instantiate() + input_field.tag = root_tag + input_field.attribute_name = attribute.name + unknown_container.add_child(input_field) + if not has_unrecognized_attributes and is_instance_valid(unknown_container): + unknown_container.queue_free() + + width_edit.set_value(root_tag.width, false) + height_edit.set_value(root_tag.height, false) + viewbox_edit_x.set_value(root_tag.viewbox.position.x, false) + viewbox_edit_y.set_value(root_tag.viewbox.position.y, false) + viewbox_edit_w.set_value(root_tag.viewbox.size.x, false) + viewbox_edit_h.set_value(root_tag.viewbox.size.y, false) update_editable() func update_editable() -> void: - var is_width_valid: bool = !SVG.root_tag.attributes.width.get_value().is_empty() - var is_height_valid: bool = !SVG.root_tag.attributes.height.get_value().is_empty() - var is_viewbox_valid: bool = SVG.root_tag.attributes.viewBox.get_list_size() >= 4 + var is_width_valid := root_tag.attributes.has("width") + var is_height_valid := root_tag.attributes.has("height") + var is_viewbox_valid: bool = root_tag.attributes.has("viewBox") and\ + root_tag.get_attribute("viewBox").get_list_size() >= 4 width_button.set_pressed_no_signal(is_width_valid) height_button.set_pressed_no_signal(is_height_valid) @@ -50,65 +107,64 @@ func update_editable() -> void: func _on_width_edit_value_changed(new_value: float) -> void: - if is_finite(new_value) and SVG.root_tag.attributes.width.get_num() != new_value: - SVG.root_tag.width = new_value - SVG.root_tag.attributes.width.set_num(new_value) + if is_finite(new_value) and root_tag.get_attribute("width").get_num() != new_value: + root_tag.width = new_value + root_tag.set_attribute("width", new_value) else: - SVG.root_tag.attributes.width.set_num(SVG.root_tag.width, false) + root_tag.set_attribute("width", root_tag.width, false) func _on_height_edit_value_changed(new_value: float) -> void: - if is_finite(new_value) and SVG.root_tag.attributes.height.get_num() != new_value: - SVG.root_tag.height = new_value - SVG.root_tag.attributes.height.set_num(new_value) + if is_finite(new_value) and root_tag.get_attribute("height").get_num() != new_value: + root_tag.height = new_value + root_tag.set_attribute("height", new_value) else: - SVG.root_tag.attributes.height.set_num(SVG.root_tag.height, false) + root_tag.set_attribute("height", root_tag.height, false) func _on_viewbox_edit_x_value_changed(new_value: float) -> void: - if SVG.root_tag.attributes.viewBox.get_value() != null: - SVG.root_tag.viewbox.position.x = new_value - SVG.root_tag.attributes.viewBox.set_list_element(0, new_value) + if root_tag.attributes.has("viewBox"): + root_tag.viewbox.position.x = new_value + root_tag.get_attribute("viewBox").set_list_element(0, new_value) func _on_viewbox_edit_y_value_changed(new_value: float) -> void: - if SVG.root_tag.attributes.viewBox.get_value() != null: - SVG.root_tag.viewbox.position.y = new_value - SVG.root_tag.attributes.viewBox.set_list_element(1, new_value) + if root_tag.attributes.has("viewBox"): + root_tag.viewbox.position.y = new_value + root_tag.get_attribute("viewBox").set_list_element(1, new_value) func _on_viewbox_edit_w_value_changed(new_value: float) -> void: - if SVG.root_tag.attributes.viewBox.get_value() != null and\ - SVG.root_tag.attributes.viewBox.get_list_element(2) != new_value: - SVG.root_tag.viewbox.size.x = new_value - SVG.root_tag.attributes.viewBox.set_list_element(2, new_value) + if root_tag.attributes.has("viewBox") and\ + root_tag.get_attribute("viewBox").get_list_element(2) != new_value: + root_tag.viewbox.size.x = new_value + root_tag.get_attribute("viewBox").set_list_element(2, new_value) func _on_viewbox_edit_h_value_changed(new_value: float) -> void: - if SVG.root_tag.attributes.viewBox.get_value() != null and\ - SVG.root_tag.attributes.viewBox.get_list_element(3) != new_value: - SVG.root_tag.viewbox.size.y = new_value - SVG.root_tag.attributes.viewBox.set_list_element(3, new_value) + if root_tag.attributes.has("viewBox") and\ + root_tag.get_attribute("viewBox").get_list_element(3) != new_value: + root_tag.viewbox.size.y = new_value + root_tag.get_attribute("viewBox").set_list_element(3, new_value) func _on_width_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_tag.attributes.width.set_num(SVG.root_tag.width) + root_tag.set_attribute("width", root_tag.width) else: - if SVG.root_tag.attributes.viewBox.get_list_size() == 4: - SVG.root_tag.attributes.width.set_num(NAN) + if root_tag.get_attribute("viewBox").get_list_size() == 4: + root_tag.set_attribute("width", NAN) else: width_button.set_pressed_no_signal(true) func _on_height_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_tag.attributes.height.set_num(SVG.root_tag.height) + root_tag.set_attribute("height", root_tag.height) else: - if SVG.root_tag.attributes.viewBox.get_list_size() == 4: - SVG.root_tag.attributes.height.set_num(NAN) + if root_tag.get_attribute("viewBox").get_list_size() == 4: + root_tag.set_attribute("height", NAN) else: height_button.set_pressed_no_signal(true) func _on_viewbox_button_toggled(toggled_on: bool) -> void: if toggled_on: - SVG.root_tag.attributes.viewBox.set_rect(SVG.root_tag.viewbox) + root_tag.set_attribute("viewBox", root_tag.viewbox) else: - if not SVG.root_tag.attributes.width.get_value().is_empty() and\ - not SVG.root_tag.attributes.height.get_value().is_empty(): - SVG.root_tag.attributes.viewBox.set_value("") + if not root_tag.attributes.has("width") and not root_tag.attributes.has("height"): + root_tag.get_attribute_mutable("viewBox").set_value("") else: viewbox_button.set_pressed_no_signal(true) diff --git a/src/ui_parts/root_tag_editor.tscn b/src/ui_parts/root_tag_editor.tscn index 7af4f1c4..1dafe3d6 100644 --- a/src/ui_parts/root_tag_editor.tscn +++ b/src/ui_parts/root_tag_editor.tscn @@ -5,25 +5,28 @@ [ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_elements/number_edit.tscn" id="3_1gu7n"] [ext_resource type="Script" path="res://src/ui_elements/BetterToggleButton.gd" id="4_7r848"] -[node name="RootTagEditor" type="CenterContainer"] -offset_right = 470.0 +[node name="RootTagEditor" type="VBoxContainer"] +offset_right = 452.0 offset_bottom = 56.0 script = ExtResource("1_xgyg0") -[node name="Edits" type="HBoxContainer" parent="."] +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 2 + +[node name="Edits" type="HBoxContainer" parent="CenterContainer"] layout_mode = 2 theme_override_constants/separation = 30 -[node name="Size" type="HBoxContainer" parent="Edits"] +[node name="Size" type="HBoxContainer" parent="CenterContainer/Edits"] unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 8 -[node name="Width" type="VBoxContainer" parent="Edits/Size"] +[node name="Width" type="VBoxContainer" parent="CenterContainer/Edits/Size"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="WidthButton" type="Button" parent="Edits/Size/Width"] +[node name="WidthButton" type="Button" parent="CenterContainer/Edits/Size/Width"] layout_mode = 2 size_flags_horizontal = 4 focus_mode = 0 @@ -35,16 +38,16 @@ text = "width" script = ExtResource("4_7r848") hover_pressed_font_color = Color(1, 1, 1, 0.4) -[node name="WidthEdit" parent="Edits/Size/Width" instance=ExtResource("3_1gu7n")] +[node name="WidthEdit" parent="CenterContainer/Edits/Size/Width" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 allow_lower = false -[node name="Height" type="VBoxContainer" parent="Edits/Size"] +[node name="Height" type="VBoxContainer" parent="CenterContainer/Edits/Size"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="HeightButton" type="Button" parent="Edits/Size/Height"] +[node name="HeightButton" type="Button" parent="CenterContainer/Edits/Size/Height"] layout_mode = 2 size_flags_horizontal = 4 focus_mode = 0 @@ -56,17 +59,17 @@ text = "height" script = ExtResource("4_7r848") hover_pressed_font_color = Color(1, 1, 1, 0.4) -[node name="HeightEdit" parent="Edits/Size/Height" instance=ExtResource("3_1gu7n")] +[node name="HeightEdit" parent="CenterContainer/Edits/Size/Height" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 allow_lower = false -[node name="Viewbox" type="VBoxContainer" parent="Edits"] +[node name="Viewbox" type="VBoxContainer" parent="CenterContainer/Edits"] unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 0 -[node name="ViewboxButton" type="Button" parent="Edits/Viewbox"] +[node name="ViewboxButton" type="Button" parent="CenterContainer/Edits/Viewbox"] layout_mode = 2 size_flags_horizontal = 4 focus_mode = 0 @@ -78,33 +81,33 @@ text = "viewBox" script = ExtResource("4_7r848") hover_pressed_font_color = Color(1, 1, 1, 0.4) -[node name="Rect" type="HBoxContainer" parent="Edits/Viewbox"] +[node name="Rect" type="HBoxContainer" parent="CenterContainer/Edits/Viewbox"] layout_mode = 2 -[node name="ViewboxEditX" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +[node name="ViewboxEditX" parent="CenterContainer/Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 -[node name="ViewboxEditY" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +[node name="ViewboxEditY" parent="CenterContainer/Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 -[node name="ViewboxEditW" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +[node name="ViewboxEditW" parent="CenterContainer/Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 allow_lower = false -[node name="ViewboxEditH" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +[node name="ViewboxEditH" parent="CenterContainer/Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] custom_minimum_size = Vector2(48, 22) layout_mode = 2 allow_lower = false -[connection signal="toggled" from="Edits/Size/Width/WidthButton" to="." method="_on_width_button_toggled"] -[connection signal="value_changed" from="Edits/Size/Width/WidthEdit" to="." method="_on_width_edit_value_changed"] -[connection signal="toggled" from="Edits/Size/Height/HeightButton" to="." method="_on_height_button_toggled"] -[connection signal="value_changed" from="Edits/Size/Height/HeightEdit" to="." method="_on_height_edit_value_changed"] -[connection signal="toggled" from="Edits/Viewbox/ViewboxButton" to="." method="_on_viewbox_button_toggled"] -[connection signal="value_changed" from="Edits/Viewbox/Rect/ViewboxEditX" to="." method="_on_viewbox_edit_x_value_changed"] -[connection signal="value_changed" from="Edits/Viewbox/Rect/ViewboxEditY" to="." method="_on_viewbox_edit_y_value_changed"] -[connection signal="value_changed" from="Edits/Viewbox/Rect/ViewboxEditW" to="." method="_on_viewbox_edit_w_value_changed"] -[connection signal="value_changed" from="Edits/Viewbox/Rect/ViewboxEditH" to="." method="_on_viewbox_edit_h_value_changed"] +[connection signal="toggled" from="CenterContainer/Edits/Size/Width/WidthButton" to="CenterContainer" method="_on_width_button_toggled"] +[connection signal="value_changed" from="CenterContainer/Edits/Size/Width/WidthEdit" to="CenterContainer" method="_on_width_edit_value_changed"] +[connection signal="toggled" from="CenterContainer/Edits/Size/Height/HeightButton" to="CenterContainer" method="_on_height_button_toggled"] +[connection signal="value_changed" from="CenterContainer/Edits/Size/Height/HeightEdit" to="CenterContainer" method="_on_height_edit_value_changed"] +[connection signal="toggled" from="CenterContainer/Edits/Viewbox/ViewboxButton" to="CenterContainer" method="_on_viewbox_button_toggled"] +[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditX" to="CenterContainer" method="_on_viewbox_edit_x_value_changed"] +[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditY" to="CenterContainer" method="_on_viewbox_edit_y_value_changed"] +[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditW" to="CenterContainer" method="_on_viewbox_edit_w_value_changed"] +[connection signal="value_changed" from="CenterContainer/Edits/Viewbox/Rect/ViewboxEditH" to="CenterContainer" method="_on_viewbox_edit_h_value_changed"] diff --git a/src/ui_parts/settings_menu.gd b/src/ui_parts/settings_menu.gd index 6ac1d71b..d93f591e 100644 --- a/src/ui_parts/settings_menu.gd +++ b/src/ui_parts/settings_menu.gd @@ -56,10 +56,8 @@ func setup_setting_labels() -> void: %GeneralVBox/SectionLabel.text = TranslationServer.translate("General") %NumberVBox/SectionLabel.text = TranslationServer.translate("Numbers") %ColorVBox/SectionLabel.text = TranslationServer.translate("Colors") - %PathVBox/SectionLabel.text = TranslationServer.translate("Paths") - %PathVBox/Note.text = TranslationServer.translate("Always active.") - %TransformVBox/SectionLabel.text = TranslationServer.translate("Transforms") - %TransformVBox/Note.text = TranslationServer.translate("Always active.") + %PathdataVBox/SectionLabel.text = TranslationServer.translate("Path data") + %TransformListVBox/SectionLabel.text = TranslationServer.translate("Transform lists") %Input/Label.text = TranslationServer.translate("Input") %Misc/Label.text = TranslationServer.translate("Miscellaneous") @@ -67,7 +65,6 @@ func setup_setting_labels() -> void: TranslationServer.translate("Any changes will apply immediately.") %ContentContainer/Theme/ThemeSettings/Warning.text =\ TranslationServer.translate("Any changes will apply immediately.") - tabs.get_node(^"FormattingTab").text = TranslationServer.translate("Formatting") tabs.get_node(^"PalettesTab").text = TranslationServer.translate("Palettes") @@ -115,16 +112,19 @@ func setup_setting_labels() -> void: "Angle precision digits") %XMLVBox/AddTrailingNewline.label.text = TranslationServer.translate( "Add trailing newline") - %XMLVBox/UseShorthandTagSyntax.label.text = TranslationServer.translate( + %XMLVBox/ShorthandTags.label.text = TranslationServer.translate( "Use shorthand tag syntax") - %NumberVBox/NumberEnable.label.text = TranslationServer.translate( - "Enable autoformatting") + %XMLVBox/PrettyFormatting.label.text = TranslationServer.translate( + "Use pretty formatting") + + for checkbox in [%NumberVBox/NumberEnable, %ColorVBox/ColorEnable, + %PathdataVBox/PathdataEnable, %TransformListVBox/TransformListEnable]: + checkbox.label.text = TranslationServer.translate("Enable autoformatting") + %NumberVBox/RemoveZeroPadding.label.text = TranslationServer.translate( "Remove zero padding") %NumberVBox/RemoveLeadingZero.label.text = TranslationServer.translate( "Remove leading zero") - %ColorVBox/ColorEnable.label.text = TranslationServer.translate( - "Enable autoformatting") %ColorVBox/ConvertRGBToHex.label.text = TranslationServer.translate( "Convert rgb format to hex") %ColorVBox/ConvertNamedToHex.label.text = TranslationServer.translate( @@ -133,69 +133,65 @@ func setup_setting_labels() -> void: "Use shorthand hex code") %ColorVBox/UseNamedColors.label.text = TranslationServer.translate( "Use short named colors") - %PathVBox/CompressNumbers.label.text = TranslationServer.translate( + %PathdataVBox/CompressNumbers.label.text = TranslationServer.translate( "Compress numbers") - %PathVBox/MinimizeSpacing.label.text = TranslationServer.translate( + %PathdataVBox/MinimizeSpacing.label.text = TranslationServer.translate( "Minimize spacing") - %PathVBox/RemoveSpacingAfterFlags.label.text = TranslationServer.translate( + %PathdataVBox/RemoveSpacingAfterFlags.label.text = TranslationServer.translate( "Remove spacing after flags") - %PathVBox/RemoveConsecutiveCommands.label.text = TranslationServer.translate( + %PathdataVBox/RemoveConsecutiveCommands.label.text = TranslationServer.translate( "Remove consecutive commands") - %TransformVBox/CompressNumbers.label.text = TranslationServer.translate( + %TransformListVBox/CompressNumbers.label.text = TranslationServer.translate( "Compress numbers") - %TransformVBox/MinimizeSpacing.label.text = TranslationServer.translate( + %TransformListVBox/MinimizeSpacing.label.text = TranslationServer.translate( "Minimize spacing") - %TransformVBox/RemoveUnnecessaryParams.label.text = TranslationServer.translate( + %TransformListVBox/RemoveUnnecessaryParams.label.text = TranslationServer.translate( "Remove unnecessary parameters") - %HighlighterVBox/SymbolColor.label.text = TranslationServer.translate( - "Symbol color") - %HighlighterVBox/TagColor.label.text = TranslationServer.translate( - "Tag color") + %HighlighterVBox/SymbolColor.label.text = TranslationServer.translate("Symbol color") + %HighlighterVBox/TagColor.label.text = TranslationServer.translate("Tag color") %HighlighterVBox/AttributeColor.label.text = TranslationServer.translate( "Attribute color") - %HighlighterVBox/StringColor.label.text = TranslationServer.translate( - "String color") - %HighlighterVBox/CommentColor.label.text = TranslationServer.translate( - "Comment color") - %HighlighterVBox/TextColor.label.text = TranslationServer.translate( - "Text color") - %HighlighterVBox/CDATAColor.label.text = TranslationServer.translate( - "CDATA color") - %HighlighterVBox/ErrorColor.label.text = TranslationServer.translate( - "Error color") - %HandleColors/InsideColor.label.text = TranslationServer.translate( - "Inside color") - %HandleColors/NormalColor.label.text = TranslationServer.translate( - "Normal color") - %HandleColors/HoveredColor.label.text = TranslationServer.translate( - "Hovered color") - %HandleColors/SelectedColor.label.text = TranslationServer.translate( - "Selected color") + %HighlighterVBox/StringColor.label.text = TranslationServer.translate("String color") + %HighlighterVBox/CommentColor.label.text = TranslationServer.translate("Comment color") + %HighlighterVBox/TextColor.label.text = TranslationServer.translate("Text color") + %HighlighterVBox/CDATAColor.label.text = TranslationServer.translate("CDATA color") + %HighlighterVBox/ErrorColor.label.text = TranslationServer.translate("Error color") + %HandleColors/InsideColor.label.text = TranslationServer.translate("Inside color") + %HandleColors/NormalColor.label.text = TranslationServer.translate("Normal color") + %HandleColors/HoveredColor.label.text = TranslationServer.translate("Hovered color") + %HandleColors/SelectedColor.label.text = TranslationServer.translate("Selected color") %HandleColors/HoveredSelectedColor.label.text = TranslationServer.translate( "Hovered selected color") %BasicColorsVBox/BackgroundColor.label.text = TranslationServer.translate( "Background color") - %BasicColorsVBox/ValidColor.label.text = TranslationServer.translate( - "Valid color") - %BasicColorsVBox/ErrorColor.label.text = TranslationServer.translate( - "Error color") - %BasicColorsVBox/WarningColor.label.text = TranslationServer.translate( - "Warning color") + %BasicColorsVBox/ValidColor.label.text = TranslationServer.translate("Valid color") + %BasicColorsVBox/ErrorColor.label.text = TranslationServer.translate("Error color") + %BasicColorsVBox/WarningColor.label.text = TranslationServer.translate("Warning color") func _on_language_pressed() -> void: var btn_arr: Array[Button] = [] for lang in TranslationServer.get_loaded_locales(): - btn_arr.append(ContextPopup.create_button( - TranslationServer.get_locale_name(lang) + " (" + lang + ")", - _on_language_chosen.bind(lang), lang == TranslationServer.get_locale())) + var new_btn := ContextPopup.create_button( + TranslationServer.get_locale_name(lang) + " (" + lang.to_upper() + ")", + _on_language_chosen.bind(lang), lang == TranslationServer.get_locale()) + # TODO turn this into dim text on the right like shortcuts, instead of a tooltip. + if lang != "en": + var translation_obj := TranslationServer.get_translation_object(lang) + var translated_count := 0 + for msg in translation_obj.get_translated_message_list(): + if not msg.is_empty(): + translated_count += 1 + new_btn.tooltip_text = String.num(translated_count * 100.0 /\ + translation_obj.get_message_count(), 1) + "%" + btn_arr.append(new_btn) var lang_popup := ContextPopup.new() lang_popup.setup(btn_arr, true, lang_button.size.x) HandlerGUI.popup_under_rect(lang_popup, lang_button.get_global_rect(), get_viewport()) func _on_language_chosen(locale: String) -> void: GlobalSettings.language = locale - custom_notify(Utils.CustomNotification.LANGUAGE_CHANGED) + Utils.custom_notify(Utils.CustomNotification.LANGUAGE_CHANGED) update_language_button() func update_language_button() -> void: @@ -239,12 +235,12 @@ func rebuild_color_palettes() -> void: @onready var xml_vbox: VBoxContainer = %XMLVBox @onready var number_vbox: VBoxContainer = %NumberVBox @onready var color_vbox: VBoxContainer = %ColorVBox -@onready var path_vbox: VBoxContainer = %PathVBox -@onready var transform_vbox: VBoxContainer = %TransformVBox +@onready var pathdata_vbox: VBoxContainer = %PathdataVBox +@onready var transform_list_vbox: VBoxContainer = %TransformListVBox func setup_format_tab() -> void: disable_format_checkboxes() - for vbox in [xml_vbox, number_vbox, color_vbox, path_vbox, transform_vbox]: + for vbox in [xml_vbox, number_vbox, color_vbox, pathdata_vbox, transform_list_vbox]: for child in vbox.get_children(): if child is SettingCheckBox: child.pressed.connect(_on_format_settings_changed) @@ -252,11 +248,11 @@ func setup_format_tab() -> void: %GeneralVBox/AnglePrecision.value_changed.connect(SVG.refresh) func _on_format_settings_changed() -> void: - SVG.refresh() + SVG.sync() disable_format_checkboxes() func _on_number_precision_changed() -> void: - SVG.refresh() + SVG.sync() # Update snap to fit the new precision. var snapping_on := GlobalSettings.save_data.snap > 0 var quanta := 0.1 ** GlobalSettings.general_number_precision @@ -265,7 +261,7 @@ func _on_number_precision_changed() -> void: GlobalSettings.save_data.snap = quanta if not snapping_on: GlobalSettings.save_data.snap *= -1 - custom_notify(Utils.CustomNotification.NUMBER_PRECISION_CHANGED) + Utils.custom_notify(Utils.CustomNotification.NUMBER_PRECISION_CHANGED) func disable_format_checkboxes() -> void: var is_autoformatting_numbers := GlobalSettings.number_enable_autoformatting @@ -321,23 +317,20 @@ func show_keybinds(category: String): func setup_theming_tab() -> void: for child in %HighlighterVBox.get_children(): if child is SettingColor: - child.value_changed.connect(custom_notify.bind( + child.value_changed.connect(Utils.custom_notify.bind( Utils.CustomNotification.HIGHLIGHT_COLORS_CHANGED)) for child in %HandleColors.get_children(): if child is SettingColor: - child.value_changed.connect(custom_notify.bind( + child.value_changed.connect(Utils.custom_notify.bind( Utils.CustomNotification.HANDLE_VISUALS_CHANGED)) for child in %BasicColorsVBox.get_children(): if child is SettingColor: - child.value_changed.connect(custom_notify.bind( + child.value_changed.connect(Utils.custom_notify.bind( Utils.CustomNotification.BASIC_COLORS_CHANGED)) func _on_theme_settings_changed() -> void: ThemeGenerator.generate_theme() -func custom_notify(notif: Utils.CustomNotification) -> void: - get_tree().get_root().propagate_notification(notif) - # Optimize by only generating content on demand. @@ -372,11 +365,11 @@ func _on_theme_tab_toggled(toggled_on: bool) -> void: func _on_other_tab_toggled(toggled_on: bool) -> void: if toggled_on and not generated_content.other: %ContentContainer/Other/OtherSettings/Misc/HandleSize.value_changed.connect( - custom_notify.bind(Utils.CustomNotification.HANDLE_VISUALS_CHANGED)) + Utils.custom_notify.bind(Utils.CustomNotification.HANDLE_VISUALS_CHANGED)) %ContentContainer/Other/OtherSettings/Misc/UIScale.value_changed.connect( - custom_notify.bind(Utils.CustomNotification.UI_SCALE_CHANGED)) + Utils.custom_notify.bind(Utils.CustomNotification.UI_SCALE_CHANGED)) %ContentContainer/Other/OtherSettings/Misc/AutoUIScale.pressed.connect( - custom_notify.bind(Utils.CustomNotification.UI_SCALE_CHANGED)) + Utils.custom_notify.bind(Utils.CustomNotification.UI_SCALE_CHANGED)) # Disable mouse wrap if not available. if not DisplayServer.has_feature(DisplayServer.FEATURE_MOUSE_WARP): wrap_mouse.checkbox.set_pressed_no_signal(false) diff --git a/src/ui_parts/settings_menu.tscn b/src/ui_parts/settings_menu.tscn index 75233898..e1cf243c 100644 --- a/src/ui_parts/settings_menu.tscn +++ b/src/ui_parts/settings_menu.tscn @@ -145,14 +145,14 @@ text = "General" [node name="NumberPrecision" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/GeneralVBox" instance=ExtResource("5_dx7vp")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "general_number_precision" values = Array[String](["2", "3", "4"]) type = 2 [node name="AnglePrecision" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/GeneralVBox" instance=ExtResource("5_dx7vp")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "general_angle_precision" values = Array[String](["1", "2"]) type = 2 @@ -168,14 +168,19 @@ text = "XML" [node name="AddTrailingNewline" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/XMLVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "xml_add_trailing_newline" -[node name="UseShorthandTagSyntax" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/XMLVBox" instance=ExtResource("4_2qeh2")] +[node name="ShorthandTags" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/XMLVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "xml_shorthand_tags" +[node name="PrettyFormatting" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/XMLVBox" instance=ExtResource("4_2qeh2")] +layout_mode = 2 +section_name = "formatting" +setting_name = "xml_pretty_formatting" + [node name="NumberVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] unique_name_in_owner = true layout_mode = 2 @@ -186,17 +191,17 @@ layout_mode = 2 [node name="NumberEnable" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/NumberVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "number_enable_autoformatting" [node name="RemoveZeroPadding" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/NumberVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "number_remove_zero_padding" [node name="RemoveLeadingZero" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/NumberVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "number_remove_leading_zero" [node name="ColorVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] @@ -209,89 +214,89 @@ layout_mode = 2 [node name="ColorEnable" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/ColorVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "color_enable_autoformatting" [node name="ConvertRGBToHex" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/ColorVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "color_convert_rgb_to_hex" [node name="ConvertNamedToHex" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/ColorVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "color_convert_named_to_hex" [node name="UseShorthandHex" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/ColorVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "color_use_shorthand_hex_code" [node name="UseNamedColors" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/ColorVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" +section_name = "formatting" setting_name = "color_use_short_named_colors" -[node name="PathVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] +[node name="PathdataVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 3 -[node name="SectionLabel" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox"] +[node name="SectionLabel" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox"] layout_mode = 2 -[node name="Note" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox"] +[node name="PathdataEnable" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -theme_override_colors/font_color = Color(0.501961, 0.501961, 0.501961, 1) -theme_override_font_sizes/font_size = 12 +section_name = "formatting" +setting_name = "pathdata_enable_autoformatting" -[node name="CompressNumbers" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox" instance=ExtResource("4_2qeh2")] +[node name="CompressNumbers" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "path_compress_numbers" +section_name = "formatting" +setting_name = "pathdata_compress_numbers" -[node name="MinimizeSpacing" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox" instance=ExtResource("4_2qeh2")] +[node name="MinimizeSpacing" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "path_minimize_spacing" +section_name = "formatting" +setting_name = "pathdata_minimize_spacing" -[node name="RemoveSpacingAfterFlags" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox" instance=ExtResource("4_2qeh2")] +[node name="RemoveSpacingAfterFlags" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "path_remove_spacing_after_flags" +section_name = "formatting" +setting_name = "pathdata_remove_spacing_after_flags" -[node name="RemoveConsecutiveCommands" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathVBox" instance=ExtResource("4_2qeh2")] +[node name="RemoveConsecutiveCommands" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/PathdataVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "path_remove_consecutive_commands" +section_name = "formatting" +setting_name = "pathdata_remove_consecutive_commands" -[node name="TransformVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] +[node name="TransformListVBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox"] unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 3 -[node name="SectionLabel" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformVBox"] +[node name="SectionLabel" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformListVBox"] layout_mode = 2 -[node name="Note" type="Label" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformVBox"] +[node name="TransformListEnable" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformListVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -theme_override_colors/font_color = Color(0.501961, 0.501961, 0.501961, 1) -theme_override_font_sizes/font_size = 12 +section_name = "formatting" +setting_name = "transform_list_enable_autoformatting" -[node name="CompressNumbers" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformVBox" instance=ExtResource("4_2qeh2")] +[node name="CompressNumbers" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformListVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "transform_compress_numbers" +section_name = "formatting" +setting_name = "transform_list_compress_numbers" -[node name="MinimizeSpacing" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformVBox" instance=ExtResource("4_2qeh2")] +[node name="MinimizeSpacing" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformListVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "transform_minimize_spacing" +section_name = "formatting" +setting_name = "transform_list_minimize_spacing" -[node name="RemoveUnnecessaryParams" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformVBox" instance=ExtResource("4_2qeh2")] +[node name="RemoveUnnecessaryParams" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer/Autoformatting/AutoformattingVBox/TransformListVBox" instance=ExtResource("4_2qeh2")] layout_mode = 2 -section_name = "autoformat" -setting_name = "transform_remove_unnecessary_params" +section_name = "formatting" +setting_name = "transform_list_remove_unnecessary_params" [node name="Palettes" type="ScrollContainer" parent="VBoxContainer/HBoxContainer/MainPanel/ContentContainer"] visible = false diff --git a/src/ui_parts/tag_container.gd b/src/ui_parts/tag_container.gd index 73174e2d..f0e29d8c 100644 --- a/src/ui_parts/tag_container.gd +++ b/src/ui_parts/tag_container.gd @@ -12,7 +12,7 @@ func _ready(): Indications.requested_scroll_to_tag_editor.connect(scroll_to_view_tag_editor) func _process(delta: float) -> void: - if Indications.proposed_drop_tid.is_empty(): + if Indications.proposed_drop_xid.is_empty(): return # Autoscroll when the dragged object is near the edge of the screen. @@ -29,57 +29,57 @@ func _process(delta: float) -> void: var old_scroll_vertical := scroll_container.scroll_vertical scroll_container.scroll_vertical += scroll_value if scroll_container.scroll_vertical != old_scroll_vertical: - update_proposed_tid() + update_proposed_xid() -func update_proposed_tid() -> void: +func update_proposed_xid() -> void: var y_pos := get_local_mouse_position().y + scroll_container.scroll_vertical var in_top_buffer := false var in_bottom_buffer := false # Keep track of the last tag editor whose position is before y_pos. - var prev_tid := PackedInt32Array([-1]) + var prev_xid := PackedInt32Array([-1]) var prev_y := -INF # Keep track of the first tag editor whose end is after y_pos. - var next_tid := PackedInt32Array([SVG.root_tag.get_child_count()]) + var next_xid := PackedInt32Array([SVG.root_tag.get_child_count()]) var next_y := INF - for tid in SVG.root_tag.get_all_tids(): - var tag_rect := get_tag_editor_rect(tid) + for tag in SVG.root_tag.get_all_tags(): + var tag_rect := get_tag_editor_rect(tag.xid) var buffer := minf(tag_rect.size.y / 3, 26) var tag_end := tag_rect.end.y var tag_start := tag_rect.position.y if y_pos < tag_end and tag_end < next_y: next_y = tag_end - next_tid = tid + next_xid = tag.xid if y_pos > tag_end - buffer: in_bottom_buffer = true if y_pos > tag_start and tag_start > prev_y: prev_y = tag_start - prev_tid = tid + prev_xid = tag.xid if y_pos < tag_start + buffer: in_top_buffer = true - # Set the proposed drop TID based on what the previous and next tag editors are. + # Set the proposed drop XID based on what the previous and next tag editors are. if in_top_buffer: - Indications.set_proposed_drop_tid(prev_tid) + Indications.set_proposed_drop_xid(prev_xid) elif in_bottom_buffer: - Indications.set_proposed_drop_tid(Utils.get_parent_tid(next_tid) +\ - PackedInt32Array([next_tid[-1] + 1])) - elif next_tid[0] >= SVG.root_tag.get_child_count(): - Indications.set_proposed_drop_tid(next_tid) - elif Utils.is_tid_parent_or_self(prev_tid, next_tid): - for i in range(prev_tid.size(), next_tid.size()): - if next_tid[i] != 0: + Indications.set_proposed_drop_xid(Utils.get_parent_xid(next_xid) +\ + PackedInt32Array([next_xid[-1] + 1])) + elif next_xid[0] >= SVG.root_tag.get_child_count(): + Indications.set_proposed_drop_xid(next_xid) + elif Utils.is_xid_parent_or_self(prev_xid, next_xid): + for i in range(prev_xid.size(), next_xid.size()): + if next_xid[i] != 0: return - Indications.set_proposed_drop_tid(prev_tid + PackedInt32Array([0])) + Indications.set_proposed_drop_xid(prev_xid + PackedInt32Array([0])) func _notification(what: int) -> void: if is_inside_tree() and not get_tree().paused: if what == NOTIFICATION_DRAG_BEGIN: covering_rect.show() - update_proposed_tid() + update_proposed_xid() elif what == NOTIFICATION_DRAG_END: covering_rect.hide() - Indications.clear_proposed_drop_tid() + Indications.clear_proposed_drop_xid() func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: @@ -95,40 +95,43 @@ func _gui_input(event: InputEvent) -> void: location += 1 # Create the context popup. var btn_array: Array[Button] = [] - for tag_name in ["path", "circle", "ellipse", "rect", "line"]: + for tag_name in ["path", "circle", "ellipse", "rect", "line", "g"]: var btn := ContextPopup.create_button(tag_name, add_tag.bind(tag_name, location), false, DB.get_tag_icon(tag_name)) btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf")) btn_array.append(btn) + if location == 0: + for tag_name in ["linearGradient", "radialGradient"]: + var btn := ContextPopup.create_button(tag_name, + add_tag.bind(tag_name, location), false, DB.get_tag_icon(tag_name)) + btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf")) + btn_array.append(btn) + + var separation_indices: Array[int] = [1, 5] + var add_popup := ContextPopup.new() - add_popup.setup_with_title(btn_array, TranslationServer.translate("New tag"), true) + add_popup.setup_with_title(btn_array, TranslationServer.translate("New tag"), + true, -1, separation_indices) var vp := get_viewport() HandlerGUI.popup_under_pos(add_popup, vp.get_mouse_position(), vp) func add_tag(tag_name: String, tag_location: int) -> void: - var tag: Tag - match tag_name: - "path": tag = TagPath.new() - "circle": tag = TagCircle.new() - "ellipse": tag = TagEllipse.new() - "rect": tag = TagRect.new() - "line": tag = TagLine.new() - tag.user_setup() - SVG.root_tag.add_tag(tag, PackedInt32Array([tag_location])) + SVG.root_tag.add_tag(DB.tag(tag_name), PackedInt32Array([tag_location])) -# This function assumes there exists a tag editor for the corresponding TID. -func get_tag_editor_rect(tid: PackedInt32Array) -> Rect2: - if tid.is_empty(): +# This function assumes there exists a tag editor for the corresponding XID. +func get_tag_editor_rect(xid: PackedInt32Array) -> Rect2: + if xid.is_empty(): return Rect2() - var tag_editor: Control = tags.get_child(tid[0]) - for i in range(1, tid.size()): - tag_editor = tag_editor.child_tags_container.get_child(tid[i]) + var tag_editor: Control = tags.get_child(xid[0]) + for i in range(1, xid.size()): + tag_editor = tag_editor.child_tags_container.get_child(xid[i]) # Position relative to the tag container. return Rect2(tag_editor.global_position - scroll_container.global_position +\ Vector2(0, scroll_container.scroll_vertical), tag_editor.size) -# This function assumes there exists a tag editor for the corresponding TID -func scroll_to_view_tag_editor(tid: PackedInt32Array) -> void: - scroll_container.get_v_scroll_bar().value = get_tag_editor_rect(tid).position.y - scroll_container.size.y / 5 +# This function assumes there exists a tag editor for the corresponding XID. +func scroll_to_view_tag_editor(xid: PackedInt32Array) -> void: + scroll_container.get_v_scroll_bar().value = get_tag_editor_rect(xid).position.y -\ + scroll_container.size.y / 5 diff --git a/src/ui_parts/tag_frame.gd b/src/ui_parts/tag_frame.gd index e400fc15..aaa7a4ba 100644 --- a/src/ui_parts/tag_frame.gd +++ b/src/ui_parts/tag_frame.gd @@ -1,28 +1,41 @@ extends VBoxContainer const code_font = preload("res://visual/fonts/FontMono.ttf") +const warning_icon = preload("res://visual/icons/TagWarning.svg") +# FIXME this seems like a not us issue. static var TagFrame: PackedScene: get: if !is_instance_valid(TagFrame): TagFrame = load("res://src/ui_parts/tag_frame.tscn") return TagFrame -const UnknownField = preload("res://src/ui_elements/unknown_field.tscn") -const TagContentUnknown = preload("res://src/ui_elements/tag_content_unknown.tscn") -const TagContentPath = preload("res://src/ui_elements/tag_content_path.tscn") -const TagContentCircle = preload("res://src/ui_elements/tag_content_circle.tscn") -const TagContentEllipse = preload("res://src/ui_elements/tag_content_ellipse.tscn") -const TagContentRect = preload("res://src/ui_elements/tag_content_rect.tscn") -const TagContentLine = preload("res://src/ui_elements/tag_content_line.tscn") -const TagContentStop = preload("res://src/ui_elements/tag_content_stop.tscn") +const UnrecognizedField = preload("res://src/ui_elements/unrecognized_field.tscn") +const ColorField = preload("res://src/ui_elements/color_field.tscn") +const NumberField = preload("res://src/ui_elements/number_field.tscn") +const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") +const IDField = preload("res://src/ui_elements/id_field.tscn") +const EnumField = preload("res://src/ui_elements/enum_field.tscn") +const TransformField = preload("res://src/ui_elements/transform_field.tscn") + +const tag_content_types = { + "path": preload("res://src/ui_elements/tag_content_path.tscn"), + "circle": preload("res://src/ui_elements/tag_content_basic_shape.tscn"), + "ellipse": preload("res://src/ui_elements/tag_content_basic_shape.tscn"), + "rect": preload("res://src/ui_elements/tag_content_basic_shape.tscn"), + "line": preload("res://src/ui_elements/tag_content_basic_shape.tscn"), + "stop": preload("res://src/ui_elements/tag_content_stop.tscn"), + "g": preload("res://src/ui_elements/tag_content_g.tscn"), + "linearGradient": preload("res://src/ui_elements/tag_content_linear_gradient.tscn"), + "radialGradient": preload("res://src/ui_elements/tag_content_radial_gradient.tscn"), +} +const TagContentUnrecognized = preload("res://src/ui_elements/tag_content_unrecognized.tscn") @onready var main_container: VBoxContainer = $Content/MainContainer @onready var title_bar: Panel = $TitleBar var child_tags_container: VBoxContainer # Only created if there are child tags. @onready var content: PanelContainer = $Content -var tid: PackedInt32Array var tag: Tag var surface := RenderingServer.canvas_item_create() # Used for the drop indicator. @@ -37,33 +50,49 @@ func _ready() -> void: determine_selection_highlight() title_bar.queue_redraw() - # If there are unknown attributes, they would always be on top. - if not tag.unknown_attributes.is_empty(): - var unknown_container := HFlowContainer.new() - main_container.add_child(unknown_container) - main_container.move_child(unknown_container, 0) - for attribute in tag.unknown_attributes: - var input_field := UnknownField.instantiate() - input_field.attribute = attribute + # If there are unrecognized attributes, they would always be on top. + var has_unrecognized_attributes := false + var unknown_container: HFlowContainer + for attribute in tag.attributes.values(): + if not DB.is_attribute_recognized(tag.name, attribute.name): + if not has_unrecognized_attributes: + has_unrecognized_attributes = true + unknown_container = HFlowContainer.new() + main_container.add_child(unknown_container) + main_container.move_child(unknown_container, 0) + + var input_field: Control + match DB.get_attribute_type(attribute.name): + DB.AttributeType.COLOR: input_field = ColorField.instantiate() + DB.AttributeType.ENUM: input_field = EnumField.instantiate() + DB.AttributeType.TRANSFORM_LIST: input_field = TransformField.instantiate() + DB.AttributeType.ID: input_field = IDField.instantiate() + DB.AttributeType.NUMERIC: + var min_value: float = DB.attribute_numeric_bounds[attribute.name].x + var max_value: float = DB.attribute_numeric_bounds[attribute.name].y + if is_inf(max_value): + input_field = NumberField.instantiate() + if not is_inf(min_value): + input_field.allow_lower = false + input_field.min_value = min_value + else: + input_field = NumberSlider.instantiate() + input_field.allow_lower = false + input_field.allow_higher = false + input_field.min_value = min_value + input_field.max_value = max_value + input_field.slider_step = 0.01 + _: input_field = UnrecognizedField.instantiate() + input_field.tag = tag + input_field.attribute_name = attribute.name unknown_container.add_child(input_field) var tag_content: Control - if tag is TagPath: - tag_content = TagContentPath.instantiate() - elif tag is TagCircle: - tag_content = TagContentCircle.instantiate() - elif tag is TagEllipse: - tag_content = TagContentEllipse.instantiate() - elif tag is TagRect: - tag_content = TagContentRect.instantiate() - elif tag is TagLine: - tag_content = TagContentLine.instantiate() - elif tag is TagStop: - tag_content = TagContentStop.instantiate() + if tag.name in tag_content_types: + tag_content = tag_content_types[tag.name].instantiate() else: - tag_content = TagContentUnknown.instantiate() + tag_content = TagContentUnrecognized.instantiate() tag_content.tag = tag - tag_content.tid = tid main_container.add_child(tag_content) if not tag.is_standalone(): @@ -74,9 +103,6 @@ func _ready() -> void: var child_tag := tag.child_tags[tag_idx] var tag_editor := TagFrame.instantiate() tag_editor.tag = child_tag - var new_tid := tid.duplicate() - new_tid.append(tag_idx) - tag_editor.tid = new_tid child_tags_container.add_child(tag_editor) func _exit_tree() -> void: @@ -84,15 +110,14 @@ func _exit_tree() -> void: # Logic for dragging. func _get_drag_data(_at_position: Vector2) -> Variant: - var data: Array[PackedInt32Array] = Utils.filter_descendant_tids( - Indications.selected_tids.duplicate(true)) + var data: Array[PackedInt32Array] = Utils.filter_descendant_xids( + Indications.selected_xids.duplicate(true)) # Set up a preview. var tags_container := VBoxContainer.new() for data_idx in range(data.size() - 1, -1, -1): - var drag_tid := data[data_idx] + var drag_xid := data[data_idx] var preview := TagFrame.instantiate() - preview.tag = SVG.root_tag.get_tag(drag_tid) - preview.tid = drag_tid + preview.tag = SVG.root_tag.get_tag(drag_xid) preview.custom_minimum_size.x = size.x preview.z_index = 2 tags_container.add_child(preview) @@ -109,44 +134,45 @@ func _notification(what: int) -> void: func _on_title_button_pressed() -> void: # Update the selection immediately, since if this tag editor is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(tid) + Indications.normal_select(tag.xid) var viewport := get_viewport() var rect := title_bar.get_global_rect() HandlerGUI.popup_under_rect_center(Indications.get_selection_context( HandlerGUI.popup_under_rect_center.bind(rect, viewport), - Indications.SelectionContext.TAG_EDITOR), rect, viewport) + Indications.Context.TAG_EDITOR), rect, viewport) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: - if Indications.semi_hovered_tid != tid and\ - not Utils.is_tid_parent(tid, Indications.hovered_tid): - Indications.set_hovered(tid) + if Indications.semi_hovered_xid != tag.xid and\ + not Utils.is_xid_parent(tag.xid, Indications.hovered_xid): + Indications.set_hovered(tag.xid) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.is_pressed(): if event.shift_pressed: - Indications.shift_select(tid) + Indications.shift_select(tag.xid) elif event.is_command_or_control_pressed(): - Indications.ctrl_select(tid) - elif not tid in Indications.selected_tids: - Indications.normal_select(tid) + Indications.ctrl_select(tag.xid) + elif not tag.xid in Indications.selected_xids: + Indications.normal_select(tag.xid) elif event.is_released() and not event.shift_pressed and\ not event.is_command_or_control_pressed() and\ - Indications.selected_tids.size() > 1 and tid in Indications.selected_tids: - Indications.normal_select(tid) + Indications.selected_xids.size() > 1 and tag.xid in Indications.selected_xids: + Indications.normal_select(tag.xid) accept_event() elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - if not tid in Indications.selected_tids: - Indications.normal_select(tid) + if not tag.xid in Indications.selected_xids: + Indications.normal_select(tag.xid) var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), Indications.SelectionContext.TAG_EDITOR), popup_pos, viewport) + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), + Indications.Context.TAG_EDITOR), popup_pos, viewport) accept_event() func _on_mouse_entered() -> void: - var tag_icon_size: Vector2 = tag.icon.get_size() + var tag_icon_size := DB.get_tag_icon(tag.name).get_size() var half_bar_width := title_bar.size.x / 2 var title_width := code_font.get_string_size(tag.name, HORIZONTAL_ALIGNMENT_LEFT, 180, 12).x @@ -163,9 +189,19 @@ func _on_mouse_entered() -> void: title_button.gui_input.connect(_on_title_button_gui_input.bind(title_button)) title_button.pressed.connect(_on_title_button_pressed) mouse_exited.connect(title_button.queue_free) + # Add warning button. + var tag_warnings := tag.get_config_warnings() + if not tag_warnings.is_empty(): + var warning_sign := TextureRect.new() + warning_sign.tooltip_text = "\n".join(tag_warnings) + warning_sign.texture = warning_icon + warning_sign.size = Vector2(warning_icon.get_size()) + warning_sign.position = title_bar.position + Vector2(title_bar.size.x - 23, 4) + title_bar.add_child(warning_sign) + mouse_exited.connect(warning_sign.queue_free) func _on_mouse_exited() -> void: - Indications.remove_hovered(tid) + Indications.remove_hovered(tag.xid) determine_selection_highlight() @@ -187,8 +223,8 @@ func determine_selection_highlight() -> void: content_sb.content_margin_bottom = 7 content_sb.content_margin_right = 7 - var is_selected := tid in Indications.selected_tids - var is_hovered := Indications.hovered_tid == tid + var is_selected := tag.xid in Indications.selected_xids + var is_hovered := Indications.hovered_xid == tag.xid if is_selected: if is_hovered: @@ -210,7 +246,7 @@ func determine_selection_highlight() -> void: content_sb.border_color = Color.from_hsv(0.6, 0.5, 0.35) title_sb.border_color = Color.from_hsv(0.6, 0.5, 0.35) - var depth := tid.size() - 1 + var depth := tag.xid.size() - 1 var depth_tint := depth * 0.12 if depth > 0: content_sb.bg_color = Color.from_hsv(content_sb.bg_color.h + depth_tint, @@ -228,26 +264,26 @@ func _draw() -> void: RenderingServer.canvas_item_clear(surface) # There's only stuff to draw if there are drag-and-drop actions. - if Indications.proposed_drop_tid.is_empty(): + if Indications.proposed_drop_xid.is_empty(): return - for selected_tid in Indications.selected_tids: - if Utils.is_tid_parent_or_self(selected_tid, tid): + for selected_xid in Indications.selected_xids: + if Utils.is_xid_parent_or_self(selected_xid, tag.xid): return - var parent_tid := Utils.get_parent_tid(tid) + var parent_xid := Utils.get_parent_xid(tag.xid) # Draw the yellow indicator of drag and drop actions. var drop_sb := StyleBoxFlat.new() - var proposed_drop_tid := Indications.proposed_drop_tid - drop_sb.border_color = Color.YELLOW - if proposed_drop_tid == parent_tid + PackedInt32Array([tid[-1]]): + var proposed_drop_xid := Indications.proposed_drop_xid + drop_sb.border_color = Color.GREEN + if proposed_drop_xid == parent_xid + PackedInt32Array([tag.xid[-1]]): drop_sb.border_width_top = 2 - elif proposed_drop_tid == parent_tid + PackedInt32Array([tid[-1] + 1]): + elif proposed_drop_xid == parent_xid + PackedInt32Array([tag.xid[-1] + 1]): drop_sb.border_width_bottom = 2 - elif proposed_drop_tid == tid + PackedInt32Array([0]): + elif proposed_drop_xid == tag.xid + PackedInt32Array([0]): drop_sb.set_border_width_all(2) if is_instance_valid(child_tags_container): - drop_sb.border_color = Color(Color.YELLOW, 0.4) + drop_sb.border_color = Color(Color.GREEN, 0.4) else: return @@ -256,15 +292,21 @@ func _draw() -> void: drop_sb.draw(surface, Rect2(Vector2.ZERO, get_size())) func _on_title_bar_draw() -> void: - var tag_icon_size: Vector2 = tag.icon.get_size() + var tag_icon := DB.get_tag_icon(tag.name) + var tag_icon_size: Vector2 = tag_icon.get_size() var half_bar_width := title_bar.size.x / 2 var title_width := code_font.get_string_size(tag.name, HORIZONTAL_ALIGNMENT_LEFT, 180, 12).x code_font.draw_string(title_bar_ci, Vector2(half_bar_width - title_width / 2 +\ tag_icon_size.x / 2, 18), tag.name, HORIZONTAL_ALIGNMENT_LEFT, 180, 12) - tag.icon.draw_rect(title_bar_ci, Rect2(Vector2(half_bar_width - title_width / 2 -\ + tag_icon.draw_rect(title_bar_ci, Rect2(Vector2(half_bar_width - title_width / 2 -\ tag_icon_size.x + 6, 4).round(), tag_icon_size), false) + + var tag_warnings := tag.get_config_warnings() + if not tag_warnings.is_empty(): + warning_icon.draw_rect(title_bar_ci, Rect2(Vector2(title_bar.size.x - 23, 4), + warning_icon.get_size()), false) # Block dragging from starting when pressing the title button. func _on_title_button_gui_input(event: InputEvent, title_button: Button) -> void: diff --git a/src/ui_parts/viewport.gd b/src/ui_parts/viewport.gd index e367da2d..d4b6ae5c 100644 --- a/src/ui_parts/viewport.gd +++ b/src/ui_parts/viewport.gd @@ -18,7 +18,7 @@ var _zoom_to: Vector2 func _ready() -> void: zoom_menu.zoom_changed.connect(view.update.unbind(2)) - SVG.root_tag.resized.connect(resize) + SVG.resized.connect(resize) Indications.viewport_size_changed.connect(adjust_view) resize() await get_tree().process_frame @@ -149,10 +149,8 @@ func adjust_view(offset := Vector2(0.5, 0.5)) -> void: var old_size := last_size_adjusted last_size_adjusted = size / Indications.zoom - var svg_w := 16384.0 if SVG.root_tag.attributes.width.get_value().is_empty()\ - else SVG.root_tag.width - var svg_h := 16384.0 if SVG.root_tag.attributes.height.get_value().is_empty()\ - else SVG.root_tag.height + var svg_w := SVG.root_tag.width if SVG.root_tag.attributes.has("width") else 16384.0 + var svg_h := SVG.root_tag.height if SVG.root_tag.attributes.has("height") else 16384.0 var zoomed_size := buffer_view_space * size / Indications.zoom view.limit_left = int(-zoomed_size.x) diff --git a/translations/GodSVG.pot b/translations/GodSVG.pot index 18a32020..8121f79c 100644 --- a/translations/GodSVG.pot +++ b/translations/GodSVG.pot @@ -93,8 +93,8 @@ msgstr "" msgid "Paste XML" msgstr "" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "" @@ -127,14 +127,6 @@ msgstr "" msgid "Add shortcut" msgstr "" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "" @@ -176,11 +168,19 @@ msgid "Translators" msgstr "" #: src/ui_parts/about_menu.gd -msgid "Authors" +msgid "Donors" msgstr "" #: src/ui_parts/about_menu.gd -msgid "Donors" +msgid "Golden donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +msgid "Diamond donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +msgid "Authors" msgstr "" #: src/ui_parts/about_menu.gd @@ -199,7 +199,7 @@ msgstr "" msgid "Alert!" msgstr "" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "" @@ -249,18 +249,6 @@ msgstr "" msgid "Overlay reference" msgstr "" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "" @@ -293,14 +281,6 @@ msgstr "" msgid "Check for updates" msgstr "" -#: src/ui_parts/display.gd -msgid "Check for updates?" -msgstr "" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "" - #: src/ui_parts/display.gd msgid "Enable snapping" msgstr "" @@ -411,8 +391,8 @@ msgid "" "contents!" msgstr "" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd -msgid "New tag" +#: src/ui_parts/handles_manager.gd +msgid "New shape" msgstr "" #: src/ui_parts/import_warning_dialog.gd @@ -420,11 +400,11 @@ msgid "Import Problems" msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown tag" +msgid "Unrecognized tag" msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown attribute" +msgid "Unrecognized attribute" msgstr "" #: src/ui_parts/import_warning_dialog.gd @@ -527,6 +507,26 @@ msgstr "" msgid "Language" msgstr "" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "" @@ -691,6 +691,10 @@ msgstr "" msgid "Warning color" msgstr "" +#: src/ui_parts/tag_container.gd +msgid "New tag" +msgstr "" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "" @@ -756,6 +760,14 @@ msgstr "" msgid "Quit" msgstr "" +#: src/HandlerGUI.gd +msgid "Check for updates?" +msgstr "" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "" + #: src/Indications.gd msgid "View In List" msgstr "" @@ -772,10 +784,6 @@ msgstr "" msgid "Import" msgstr "" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "" diff --git a/translations/bg.po b/translations/bg.po index f9dc54f3..c35c6bfd 100644 --- a/translations/bg.po +++ b/translations/bg.po @@ -93,8 +93,8 @@ msgstr "Копирай като XML" msgid "Paste XML" msgstr "Постави XML" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "Премахни" @@ -127,14 +127,6 @@ msgstr "Неизползван" msgid "Add shortcut" msgstr "Добави бърз клавиш" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "Промени" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "Премахни" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "Натисни клавиши…" @@ -175,14 +167,22 @@ msgstr "Програмисти" msgid "Translators" msgstr "Преводачи" -#: src/ui_parts/about_menu.gd -msgid "Authors" -msgstr "Автори" - #: src/ui_parts/about_menu.gd msgid "Donors" msgstr "Дарители" +#: src/ui_parts/about_menu.gd +msgid "Golden donors" +msgstr "Златни дарители" + +#: src/ui_parts/about_menu.gd +msgid "Diamond donors" +msgstr "Диамантени дарители" + +#: src/ui_parts/about_menu.gd +msgid "Authors" +msgstr "Автори" + #: src/ui_parts/about_menu.gd msgid "License" msgstr "Лиценз" @@ -199,7 +199,7 @@ msgstr "Лицензи от трети партии" msgid "Alert!" msgstr "Предупреждение!" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "Добре" @@ -249,18 +249,6 @@ msgstr "Покажни справочното изображение" msgid "Overlay reference" msgstr "Насложи справочното изображение" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "Покажи решетката" @@ -293,14 +281,6 @@ msgstr "Уебсайта на GodSVG" msgid "Check for updates" msgstr "Провери за ъпдейти" -#: src/ui_parts/display.gd -msgid "Check for updates?" -msgstr "Провери за ъпдейти?" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "Това изисква GodSVG да се свърже с интерната." - #: src/ui_parts/display.gd msgid "Enable snapping" msgstr "Включи захващането" @@ -410,23 +390,23 @@ msgid "" "A file named \"{file_name}\" already exists. Replacing will overwrite its " "contents!" msgstr "" -"Файл кръстен \"{file_name}\" вече съществува. Заместването ще пренапише " +"Файл с името \"{file_name}\" вече съществува. Заместването ще пренапише " "неговото съдържание!" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd -msgid "New tag" -msgstr "Нов таг" +#: src/ui_parts/handles_manager.gd +msgid "New shape" +msgstr "Нова фигура" #: src/ui_parts/import_warning_dialog.gd msgid "Import Problems" msgstr "Проблеми в импортирането" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown tag" +msgid "Unrecognized tag" msgstr "Непознат таг" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown attribute" +msgid "Unrecognized attribute" msgstr "Непознат атрибут" #: src/ui_parts/import_warning_dialog.gd @@ -529,6 +509,26 @@ msgstr "Автоматичен мащаб" msgid "Language" msgstr "Език" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "Файл" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "Промени" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "Гледка" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "Инструмент" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "Помощ" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "Разменя увеличението и намалянето с колелцето на мишката." @@ -699,6 +699,10 @@ msgstr "Цвят на валидният текст" msgid "Warning color" msgstr "Цвят на предупрежденията" +#: src/ui_parts/tag_container.gd +msgid "New tag" +msgstr "Нов таг" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "Включи предварителните издания" @@ -769,6 +773,14 @@ msgstr "Наистина ли искаш да затвориш GodSVG?" msgid "Quit" msgstr "Затвори" +#: src/HandlerGUI.gd +msgid "Check for updates?" +msgstr "Провери за ъпдейти?" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "Това изисква GodSVG да се свърже с интерната." + #: src/Indications.gd msgid "View In List" msgstr "Виж в Списъка" @@ -785,10 +797,6 @@ msgstr "Превърни в" msgid "Import" msgstr "Импортирай" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "Оптимизирай" @@ -912,15 +920,3 @@ msgstr "Кубична крива на Безие до" #: src/TranslationUtils.gd msgid "Shorthand Cubic Bezier to" msgstr "Кратка кубична крива на Безие до" - -#~ msgid "File" -#~ msgstr "Файл" - -#~ msgid "View" -#~ msgstr "Гледка" - -#~ msgid "Tool" -#~ msgstr "Инструмент" - -#~ msgid "Help" -#~ msgstr "Помощ" diff --git a/translations/de.po b/translations/de.po index 4c859102..498b85f8 100644 --- a/translations/de.po +++ b/translations/de.po @@ -93,8 +93,8 @@ msgstr "Kopiere als XML" msgid "Paste XML" msgstr "XML Einfügen" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "Löschen" @@ -127,14 +127,6 @@ msgstr "Unbenutzt" msgid "Add shortcut" msgstr "Tastenkombination hinzufügen" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "Bearbeiten" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "Entfernen" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "Drücke Tasten…" @@ -178,13 +170,21 @@ msgid "Translators" msgstr "Transformationen" #: src/ui_parts/about_menu.gd -msgid "Authors" -msgstr "Autoren" +msgid "Donors" +msgstr "" #: src/ui_parts/about_menu.gd -msgid "Donors" +msgid "Golden donors" msgstr "" +#: src/ui_parts/about_menu.gd +msgid "Diamond donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +msgid "Authors" +msgstr "Autoren" + #: src/ui_parts/about_menu.gd msgid "License" msgstr "Lizenz" @@ -202,7 +202,7 @@ msgstr "Drittanbieter-Lizenzen" msgid "Alert!" msgstr "Warnung!" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "OK" @@ -256,18 +256,6 @@ msgstr "" msgid "Overlay reference" msgstr "" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "Raster anzeigen" @@ -302,14 +290,6 @@ msgstr "GodSVG Repository" msgid "Check for updates" msgstr "" -#: src/ui_parts/display.gd -msgid "Check for updates?" -msgstr "" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "" - #: src/ui_parts/display.gd #, fuzzy msgid "Enable snapping" @@ -428,9 +408,9 @@ msgid "" "contents!" msgstr "" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd +#: src/ui_parts/handles_manager.gd #, fuzzy -msgid "New tag" +msgid "New shape" msgstr "Neues Element hinzufügen" #: src/ui_parts/import_warning_dialog.gd @@ -438,13 +418,12 @@ msgid "Import Problems" msgstr "Probleme beim Importieren" #: src/ui_parts/import_warning_dialog.gd -#, fuzzy -msgid "Unknown tag" -msgstr "Unbekanntes Element" +msgid "Unrecognized tag" +msgstr "" #: src/ui_parts/import_warning_dialog.gd #, fuzzy -msgid "Unknown attribute" +msgid "Unrecognized attribute" msgstr "Unbekannte Attribute" #: src/ui_parts/import_warning_dialog.gd @@ -553,6 +532,26 @@ msgstr "Skalieren" msgid "Language" msgstr "Sprache" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "Bearbeiten" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "Vertauscht Hinein- und Herauszoomen mit dem Mausrad." @@ -726,6 +725,11 @@ msgstr "Gültige Farbe" msgid "Warning color" msgstr "Warnfarbe" +#: src/ui_parts/tag_container.gd +#, fuzzy +msgid "New tag" +msgstr "Neues Element hinzufügen" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "" @@ -796,6 +800,14 @@ msgstr "" msgid "Quit" msgstr "" +#: src/HandlerGUI.gd +msgid "Check for updates?" +msgstr "" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "" + #: src/Indications.gd msgid "View In List" msgstr "" @@ -812,10 +824,6 @@ msgstr "Konvertieren zu" msgid "Import" msgstr "Importieren" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "Optimieren" @@ -949,6 +957,13 @@ msgstr "" msgid "Shorthand Cubic Bezier to" msgstr "" +#~ msgid "Remove" +#~ msgstr "Entfernen" + +#, fuzzy +#~ msgid "Unknown tag" +#~ msgstr "Unbekanntes Element" + #~ msgid "Documentation…" #~ msgstr "Dokumentation…" diff --git a/translations/en.po b/translations/en.po index 468a3242..c6aec227 100644 --- a/translations/en.po +++ b/translations/en.po @@ -93,8 +93,8 @@ msgstr "" msgid "Paste XML" msgstr "" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "" @@ -127,14 +127,6 @@ msgstr "" msgid "Add shortcut" msgstr "" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "" @@ -176,11 +168,19 @@ msgid "Translators" msgstr "" #: src/ui_parts/about_menu.gd -msgid "Authors" +msgid "Donors" msgstr "" #: src/ui_parts/about_menu.gd -msgid "Donors" +msgid "Golden donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +msgid "Diamond donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +msgid "Authors" msgstr "" #: src/ui_parts/about_menu.gd @@ -199,7 +199,7 @@ msgstr "" msgid "Alert!" msgstr "" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "" @@ -249,18 +249,6 @@ msgstr "" msgid "Overlay reference" msgstr "" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "" @@ -293,14 +281,6 @@ msgstr "" msgid "Check for updates" msgstr "" -#: src/ui_parts/display.gd -msgid "Check for updates?" -msgstr "" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "" - #: src/ui_parts/display.gd msgid "Enable snapping" msgstr "" @@ -411,8 +391,8 @@ msgid "" "contents!" msgstr "" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd -msgid "New tag" +#: src/ui_parts/handles_manager.gd +msgid "New shape" msgstr "" #: src/ui_parts/import_warning_dialog.gd @@ -420,11 +400,11 @@ msgid "Import Problems" msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown tag" +msgid "Unrecognized tag" msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown attribute" +msgid "Unrecognized attribute" msgstr "" #: src/ui_parts/import_warning_dialog.gd @@ -527,6 +507,26 @@ msgstr "" msgid "Language" msgstr "" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "" @@ -691,6 +691,10 @@ msgstr "" msgid "Warning color" msgstr "" +#: src/ui_parts/tag_container.gd +msgid "New tag" +msgstr "" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "" @@ -756,6 +760,14 @@ msgstr "" msgid "Quit" msgstr "" +#: src/HandlerGUI.gd +msgid "Check for updates?" +msgstr "" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "" + #: src/Indications.gd msgid "View In List" msgstr "" @@ -772,10 +784,6 @@ msgstr "" msgid "Import" msgstr "" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "" diff --git a/translations/ru.po b/translations/ru.po index 3a7b6d1c..c695b10f 100644 --- a/translations/ru.po +++ b/translations/ru.po @@ -13,7 +13,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "translation-credits" -msgstr "volkov , Gallifreyan " +msgstr "" +"volkov , Gallifreyan " #: src/parsers/SVGParser.gd msgid "Doesn’t describe an SVG." @@ -93,8 +94,8 @@ msgstr "Скопировать как XML" msgid "Paste XML" msgstr "Вставить XML" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "Удалить" @@ -127,14 +128,6 @@ msgstr "Не используется" msgid "Add shortcut" msgstr "Добавить сочетание клавиш" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "Редактировать" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "Удалить" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "Нажмите клавиши…" @@ -176,13 +169,22 @@ msgid "Translators" msgstr "Переводчики" #: src/ui_parts/about_menu.gd -msgid "Authors" -msgstr "Авторы" +msgid "Donors" +msgstr "Благотворители" #: src/ui_parts/about_menu.gd -msgid "Donors" +msgid "Golden donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +#, fuzzy +msgid "Diamond donors" msgstr "Благотворители" +#: src/ui_parts/about_menu.gd +msgid "Authors" +msgstr "Авторы" + #: src/ui_parts/about_menu.gd msgid "License" msgstr "Лицензия" @@ -199,7 +201,7 @@ msgstr "Лицензии третьих лиц" msgid "Alert!" msgstr "Внимание!" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "Хорошо" @@ -249,18 +251,6 @@ msgstr "Показать референс-изображение" msgid "Overlay reference" msgstr "Наложить референс-изображение" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "Импортировать референс-изображение" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "Показать референс-изображение" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "Наложить референс-изображение" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "Показать сетку" @@ -293,14 +283,6 @@ msgstr "Веб-сайт GodSVG" msgid "Check for updates" msgstr "Проверить обновления" -#: src/ui_parts/display.gd -msgid "Check for updates?" -msgstr "Проверить обновления?" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "Для этого необходимо подключение к Интернету" - #: src/ui_parts/display.gd msgid "Enable snapping" msgstr "Включить привязку" @@ -413,8 +395,9 @@ msgstr "" "Файл с названием \"{file_name}\" уже существует. Его замена перезапишет его " "содержимое!" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd -msgid "New tag" +#: src/ui_parts/handles_manager.gd +#, fuzzy +msgid "New shape" msgstr "Новый тег" #: src/ui_parts/import_warning_dialog.gd @@ -422,11 +405,12 @@ msgid "Import Problems" msgstr "Проблемы с импортированием" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown tag" -msgstr "Неизвестный тег" +msgid "Unrecognized tag" +msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown attribute" +#, fuzzy +msgid "Unrecognized attribute" msgstr "Неизвестный атрибут" #: src/ui_parts/import_warning_dialog.gd @@ -529,6 +513,26 @@ msgstr "Автоматический маштаб интерфейса поль msgid "Language" msgstr "Язык" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "Файл" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "Редактировать" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "Просмотр" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "Инструмент" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "Помощь" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "Поменяет местами увеличение и уменьшение масштаба через колесико мыши." @@ -700,6 +704,10 @@ msgstr "Цвет валидности" msgid "Warning color" msgstr "Цвет предупреждения" +#: src/ui_parts/tag_container.gd +msgid "New tag" +msgstr "Новый тег" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "Включить пре-релизы" @@ -745,8 +753,8 @@ msgid "" "\"{passed_extension}\" is a unsupported file extension. Only \"svg\" files " "are supported." msgstr "" -"Расширение \"{passed_extension}\" поддерживается. " -"Поддерживается только «svg»." +"Расширение \"{passed_extension}\" поддерживается. Поддерживается только " +"«svg»." #: src/FileUtils.gd msgid "" @@ -770,6 +778,14 @@ msgstr "Вы хотите выйти из GodSVG?" msgid "Quit" msgstr "Выйти" +#: src/HandlerGUI.gd +msgid "Check for updates?" +msgstr "Проверить обновления?" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "Для этого необходимо подключение к Интернету" + #: src/Indications.gd msgid "View In List" msgstr "Просмотреть в списке" @@ -786,10 +802,6 @@ msgstr "Конвертировать в" msgid "Import" msgstr "Импортировать" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "Загрузить референс-изображение" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "Оптимизировать" @@ -914,17 +926,23 @@ msgstr "Кубическая Безье до" msgid "Shorthand Cubic Bezier to" msgstr "Сокращенная кубическая Безье до" -#~ msgid "File" -#~ msgstr "Файл" +#~ msgid "Remove" +#~ msgstr "Удалить" + +#~ msgid "Import Reference Image" +#~ msgstr "Импортировать референс-изображение" -#~ msgid "View" -#~ msgstr "Просмотр" +#~ msgid "Show Reference Image" +#~ msgstr "Показать референс-изображение" -#~ msgid "Tool" -#~ msgstr "Инструмент" +#~ msgid "Overlay Reference Image" +#~ msgstr "Наложить референс-изображение" -#~ msgid "Help" -#~ msgstr "Помощь" +#~ msgid "Unknown tag" +#~ msgstr "Неизвестный тег" + +#~ msgid "Load Reference Image" +#~ msgstr "Загрузить референс-изображение" #~ msgid "Documentation…" #~ msgstr "Документация…" @@ -947,4 +965,3 @@ msgstr "Сокращенная кубическая Безье до" #~ msgid "Import SVG" #~ msgstr "Импортировать SVG" - diff --git a/translations/uk.po b/translations/uk.po index 56ca2d34..efb7327f 100644 --- a/translations/uk.po +++ b/translations/uk.po @@ -93,8 +93,8 @@ msgstr "Скопіювати як XML" msgid "Paste XML" msgstr "Вставити XML" -#: src/ui_elements/palette_config.gd src/ui_elements/transform_popup.gd -#: src/Indications.gd +#: src/ui_elements/palette_config.gd src/ui_elements/setting_shortcut.gd +#: src/ui_elements/transform_popup.gd src/Indications.gd msgid "Delete" msgstr "Видалити" @@ -127,14 +127,6 @@ msgstr "Не використовується" msgid "Add shortcut" msgstr "Додати клавіатурне скорочення" -#: src/ui_elements/setting_shortcut.gd -msgid "Edit" -msgstr "Редагування" - -#: src/ui_elements/setting_shortcut.gd -msgid "Remove" -msgstr "Видалити" - #: src/ui_elements/setting_shortcut.gd msgid "Press keys…" msgstr "Натисніть кнопки…" @@ -176,13 +168,22 @@ msgid "Translators" msgstr "Перекладачі" #: src/ui_parts/about_menu.gd -msgid "Authors" -msgstr "Автори" +msgid "Donors" +msgstr "Благодійники" #: src/ui_parts/about_menu.gd -msgid "Donors" +msgid "Golden donors" +msgstr "" + +#: src/ui_parts/about_menu.gd +#, fuzzy +msgid "Diamond donors" msgstr "Благодійники" +#: src/ui_parts/about_menu.gd +msgid "Authors" +msgstr "Автори" + #: src/ui_parts/about_menu.gd msgid "License" msgstr "Ліцензія" @@ -199,7 +200,7 @@ msgstr "Ліцензії третіх осіб" msgid "Alert!" msgstr "Увага!" -#: src/ui_parts/alert_dialog.gd src/ui_parts/display.gd +#: src/ui_parts/alert_dialog.gd src/HandlerGUI.gd msgid "OK" msgstr "Добре" @@ -249,18 +250,6 @@ msgstr "" msgid "Overlay reference" msgstr "" -#: src/ui_parts/display.gd -msgid "Import Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Show Reference Image" -msgstr "" - -#: src/ui_parts/display.gd -msgid "Overlay Reference Image" -msgstr "" - #: src/ui_parts/display.gd msgid "Show Grid" msgstr "Показати ґратку" @@ -293,15 +282,6 @@ msgstr "Веб-сайт GodSVG" msgid "Check for updates" msgstr "Перевірити оновлення" -#: src/ui_parts/display.gd -#, fuzzy -msgid "Check for updates?" -msgstr "Перевірити оновлення" - -#: src/ui_parts/display.gd -msgid "This requires GodSVG to connect to the internet." -msgstr "" - #: src/ui_parts/display.gd msgid "Enable snapping" msgstr "Активувати прилипання" @@ -415,8 +395,9 @@ msgstr "" "Файл з назваою \"{file_name}\" вже існує. Його заміна призведе до перезапису " "його вмісту!" -#: src/ui_parts/handles_manager.gd src/ui_parts/tag_container.gd -msgid "New tag" +#: src/ui_parts/handles_manager.gd +#, fuzzy +msgid "New shape" msgstr "Новий тег" #: src/ui_parts/import_warning_dialog.gd @@ -424,11 +405,12 @@ msgid "Import Problems" msgstr "Проблема з імпортуванням" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown tag" -msgstr "Невідомий тег" +msgid "Unrecognized tag" +msgstr "" #: src/ui_parts/import_warning_dialog.gd -msgid "Unknown attribute" +#, fuzzy +msgid "Unrecognized attribute" msgstr "Невідомий атрибут" #: src/ui_parts/import_warning_dialog.gd @@ -531,6 +513,26 @@ msgstr "Автоматичний масштаб інтерфейсу" msgid "Language" msgstr "Мова" +#: src/ui_parts/settings_menu.gd +msgid "File" +msgstr "Файл" + +#: src/ui_parts/settings_menu.gd +msgid "Edit" +msgstr "Редагування" + +#: src/ui_parts/settings_menu.gd +msgid "View" +msgstr "Перегляд" + +#: src/ui_parts/settings_menu.gd +msgid "Tool" +msgstr "Інструмент" + +#: src/ui_parts/settings_menu.gd +msgid "Help" +msgstr "Допомога" + #: src/ui_parts/settings_menu.gd msgid "Swaps zoom in and zoom out with the mouse wheel." msgstr "" @@ -704,6 +706,10 @@ msgstr "Колір валідності" msgid "Warning color" msgstr "Колір попередження" +#: src/ui_parts/tag_container.gd +msgid "New tag" +msgstr "Новий тег" + #: src/ui_parts/update_menu.gd msgid "Include prereleases" msgstr "" @@ -775,6 +781,15 @@ msgstr "Ви дійсно бажаєте вийти із GodSVG?" msgid "Quit" msgstr "Вийти" +#: src/HandlerGUI.gd +#, fuzzy +msgid "Check for updates?" +msgstr "Перевірити оновлення" + +#: src/HandlerGUI.gd +msgid "This requires GodSVG to connect to the internet." +msgstr "" + #: src/Indications.gd msgid "View In List" msgstr "Переглянути списком" @@ -791,10 +806,6 @@ msgstr "Конвертувати в" msgid "Import" msgstr "Імпортувати" -#: src/TranslationUtils.gd -msgid "Load Reference Image" -msgstr "" - #: src/TranslationUtils.gd msgid "Optimize" msgstr "Оптимізувати" @@ -919,17 +930,11 @@ msgstr "Кубічна Безьє до" msgid "Shorthand Cubic Bezier to" msgstr "Скорочена кубічна Безьє до" -#~ msgid "File" -#~ msgstr "Файл" - -#~ msgid "View" -#~ msgstr "Перегляд" - -#~ msgid "Tool" -#~ msgstr "Інструмент" +#~ msgid "Remove" +#~ msgstr "Видалити" -#~ msgid "Help" -#~ msgstr "Допомога" +#~ msgid "Unknown tag" +#~ msgstr "Невідомий тег" #~ msgid "Documentation…" #~ msgstr "Документація…" diff --git a/visual/icons/ApplyMatrix.svg b/visual/icons/ApplyMatrix.svg index b9608c58..87ba3b4b 100644 --- a/visual/icons/ApplyMatrix.svg +++ b/visual/icons/ApplyMatrix.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Clear.svg b/visual/icons/Clear.svg index d7a94236..8d8b36b6 100644 --- a/visual/icons/Clear.svg +++ b/visual/icons/Clear.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/CodeOptions.svg b/visual/icons/CodeOptions.svg index df9b2c71..4fef705c 100644 --- a/visual/icons/CodeOptions.svg +++ b/visual/icons/CodeOptions.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Compress.svg b/visual/icons/Compress.svg index e67acc24..a6c4f97b 100644 --- a/visual/icons/Compress.svg +++ b/visual/icons/Compress.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/CreateFolder.svg b/visual/icons/CreateFolder.svg index 5de0bb8b..bb7434d1 100644 --- a/visual/icons/CreateFolder.svg +++ b/visual/icons/CreateFolder.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/DirDocuments.svg b/visual/icons/DirDocuments.svg index 539e47c1..e1e368e6 100644 --- a/visual/icons/DirDocuments.svg +++ b/visual/icons/DirDocuments.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/DirMovies.svg b/visual/icons/DirMovies.svg index 0a43fb26..73a82653 100644 --- a/visual/icons/DirMovies.svg +++ b/visual/icons/DirMovies.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/DirMusic.svg b/visual/icons/DirMusic.svg index f5cfcd19..7ba2143d 100644 --- a/visual/icons/DirMusic.svg +++ b/visual/icons/DirMusic.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/DirPictures.svg b/visual/icons/DirPictures.svg index 86153c63..c596bb9f 100644 --- a/visual/icons/DirPictures.svg +++ b/visual/icons/DirPictures.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Edit.svg b/visual/icons/Edit.svg index bc25806e..0c8287f4 100644 --- a/visual/icons/Edit.svg +++ b/visual/icons/Edit.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/FileBroken.svg b/visual/icons/FileBroken.svg index d55bd4e9..4d44802d 100644 --- a/visual/icons/FileBroken.svg +++ b/visual/icons/FileBroken.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/InsertAfter.svg b/visual/icons/InsertAfter.svg index beeb66d3..ca66f42c 100644 --- a/visual/icons/InsertAfter.svg +++ b/visual/icons/InsertAfter.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/InsertBefore.svg b/visual/icons/InsertBefore.svg index 2431abe1..116dd8d8 100644 --- a/visual/icons/InsertBefore.svg +++ b/visual/icons/InsertBefore.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/NoneColor.svg b/visual/icons/NoneColor.svg index 30c292ab..93c98e01 100644 --- a/visual/icons/NoneColor.svg +++ b/visual/icons/NoneColor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/OpenFile.svg b/visual/icons/OpenFile.svg index 96153633..59448014 100644 --- a/visual/icons/OpenFile.svg +++ b/visual/icons/OpenFile.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/OpenFolder.svg b/visual/icons/OpenFolder.svg index 5de0a10f..3e6928e6 100644 --- a/visual/icons/OpenFolder.svg +++ b/visual/icons/OpenFolder.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Redo.svg b/visual/icons/Redo.svg index bc890b8b..f2835dc7 100644 --- a/visual/icons/Redo.svg +++ b/visual/icons/Redo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Reference.svg b/visual/icons/Reference.svg index 1cc8169d..8eebab5e 100644 --- a/visual/icons/Reference.svg +++ b/visual/icons/Reference.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Rotate.svg b/visual/icons/Rotate.svg index 92541691..a099d47f 100644 --- a/visual/icons/Rotate.svg +++ b/visual/icons/Rotate.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Save.svg b/visual/icons/Save.svg index 239388e2..a9959519 100644 --- a/visual/icons/Save.svg +++ b/visual/icons/Save.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/Scale.svg b/visual/icons/Scale.svg index c9fe715c..82a33957 100644 --- a/visual/icons/Scale.svg +++ b/visual/icons/Scale.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/SideSliderArrow.svg b/visual/icons/SideSliderArrow.svg index 7b5670e0..20b64e7d 100644 --- a/visual/icons/SideSliderArrow.svg +++ b/visual/icons/SideSliderArrow.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/SkewX.svg b/visual/icons/SkewX.svg index b3a1cc1b..bcc15991 100644 --- a/visual/icons/SkewX.svg +++ b/visual/icons/SkewX.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/SkewY.svg b/visual/icons/SkewY.svg index 21b70b13..65f1813b 100644 --- a/visual/icons/SkewY.svg +++ b/visual/icons/SkewY.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/SliderArrow.svg b/visual/icons/SliderArrow.svg index 4f8c8193..011e94c6 100644 --- a/visual/icons/SliderArrow.svg +++ b/visual/icons/SliderArrow.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/Snap.svg b/visual/icons/Snap.svg index 49a7f280..c94a9ad1 100644 --- a/visual/icons/Snap.svg +++ b/visual/icons/Snap.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/TagWarning.svg b/visual/icons/TagWarning.svg new file mode 100644 index 00000000..bca7ff90 --- /dev/null +++ b/visual/icons/TagWarning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/visual/icons/TagWarning.svg.import b/visual/icons/TagWarning.svg.import new file mode 100644 index 00000000..4c448ca1 --- /dev/null +++ b/visual/icons/TagWarning.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbyjet4nt246k" +path="res://.godot/imported/TagWarning.svg-229c14c2b5031de4abbee36f927866c2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/TagWarning.svg" +dest_files=["res://.godot/imported/TagWarning.svg-229c14c2b5031de4abbee36f927866c2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/visual/icons/Undo.svg b/visual/icons/Undo.svg index 05a67846..a2e880fe 100644 --- a/visual/icons/Undo.svg +++ b/visual/icons/Undo.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/backgrounds/ColorButtonBG.svg b/visual/icons/backgrounds/ColorButtonBG.svg index 2d154241..04a3fce3 100644 --- a/visual/icons/backgrounds/ColorButtonBG.svg +++ b/visual/icons/backgrounds/ColorButtonBG.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/foreign_logos/KoFiLogo.svg b/visual/icons/foreign_logos/KoFiLogo.svg index f2faf528..4d26af84 100644 --- a/visual/icons/foreign_logos/KoFiLogo.svg +++ b/visual/icons/foreign_logos/KoFiLogo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/tag/g.svg b/visual/icons/tag/g.svg index 02f33f54..9921ebb1 100644 --- a/visual/icons/tag/g.svg +++ b/visual/icons/tag/g.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/visual/icons/tag/linearGradient.svg b/visual/icons/tag/linearGradient.svg index 14ce71f6..10491f38 100644 --- a/visual/icons/tag/linearGradient.svg +++ b/visual/icons/tag/linearGradient.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/tag/radialGradient.svg b/visual/icons/tag/radialGradient.svg index f3e1c8cb..d0be2964 100644 --- a/visual/icons/tag/radialGradient.svg +++ b/visual/icons/tag/radialGradient.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/tag/stop.svg b/visual/icons/tag/stop.svg index 9129b198..f1b9bee9 100644 --- a/visual/icons/tag/stop.svg +++ b/visual/icons/tag/stop.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/visual/icons/tag/unknown.svg b/visual/icons/tag/unrecognized.svg similarity index 100% rename from visual/icons/tag/unknown.svg rename to visual/icons/tag/unrecognized.svg diff --git a/visual/icons/tag/unknown.svg.import b/visual/icons/tag/unrecognized.svg.import similarity index 73% rename from visual/icons/tag/unknown.svg.import rename to visual/icons/tag/unrecognized.svg.import index 36b1fd94..51f0450e 100644 --- a/visual/icons/tag/unknown.svg.import +++ b/visual/icons/tag/unrecognized.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://brua6wag257kc" -path="res://.godot/imported/unknown.svg-5ab2cc84835c40cff0c8541c05d0d795.ctex" +path="res://.godot/imported/unrecognized.svg-39738dc7ca3df867b16222d00eef9375.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://visual/icons/tag/unknown.svg" -dest_files=["res://.godot/imported/unknown.svg-5ab2cc84835c40cff0c8541c05d0d795.ctex"] +source_file="res://visual/icons/tag/unrecognized.svg" +dest_files=["res://.godot/imported/unrecognized.svg-39738dc7ca3df867b16222d00eef9375.ctex"] [params]