diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..a05e3b8e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,10 @@ +--- +name: Bug report +about: Report a bug in GodSVG +title: Title +labels: bug +assignees: '' + +--- + +Give a clear and concise description of what the bug is and what the expected behavior is. For non-trivial issues, provide reproduction steps and screenshots if relevant. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..3191b285 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,10 @@ +--- +name: Feature request +about: Suggest a new feature for GodSVG +title: Title +labels: proposal +assignees: '' + +--- + +Describe the limitation you're facing and how the feature will help resolve it. If you can, give more detail about the implementation. diff --git a/.gitignore b/.gitignore index 63061181..16c13bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # Imported translations (automatically generated from CSV files) *.translation +export_presets.cfg diff --git a/AppInfo.gd b/AppInfo.gd index e86d6a12..01c661aa 100644 --- a/AppInfo.gd +++ b/AppInfo.gd @@ -6,6 +6,11 @@ const version := "1.0.dev" const project_founders: Array[String] = ["MewPurPur"] const project_managers: Array[String] = ["MewPurPur"] + +# The developers who contributed significant patches to the MIT-licensed source code +# of GodSVG are listed here. What counts as "significant" is decided arbitrarily. +# The format is either "Real Name (GitHub username)" or "GitHub username". + const authors: Array[String] = [ "ajreckof", "Kiisu-Master", @@ -13,4 +18,5 @@ const authors: Array[String] = [ "Serem Titus (SeremTitus)", "Swarkin", "thiagola92", + "Tom Blackwell (Volts-s)" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d544b35..ca480c6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,8 +28,9 @@ Do understand that PRs with a large maintenance cost may be under high scrutiny For scripts, only GDScript code is allowed. Follow the [GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). Most of its rules are enforced here. Additionally: - Static typing is predominantly used. -- Comments should be written like sentences with punctuation. -- Class names must use `class_name X extends Y` syntax. -- `@export` should only be used for nodes if the runtime structure is not known. +- Comments are usually written like sentences with punctuation. +- For empty lines in the middle of indented blocks, the scope's indentation is kept. +- Class names use `class_name X extends Y` syntax. +- `@export` for nodes is only used if the runtime structure is not known. -Pull requests may get production tweaks to fix their style before being merged. +Don't make pull requests for code style changes without discussing them first (unless it's for corrections to abide by the ones described here). Pull requests may also get production tweaks to fix their style before being merged. diff --git a/README.md b/README.md index ffe88a6a..5f1b6f1a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # GodSVG -![image](https://github.com/MewPurPur/GodSVG/assets/85438892/2a8a674d-af79-4db8-a817-57342549e670) +![image](https://github.com/MewPurPur/GodSVG/assets/85438892/27720600-3e51-46c5-98e8-80f2469781a8) **GodSVG is an application for creating optimized Scalable Vector Graphics (SVG) files.** It is mainly designed for programmers, allowing them to easily edit individual SVG elements and view the corresponding code in real-time. -GodSVG is inspired by the need for a SVG editor without abstractions that produces optimized SVG files. +GodSVG is inspired by the need for an SVG editor without abstractions that produces optimized SVG files. ## Features -- **Interactive SVG editing:** Modify individual elements of a SVG file using a user-friendly interface. +- **Interactive SVG editing:** Modify individual elements of an SVG file using a user-friendly interface. - **Real-time code:** As you manipulate elements in the UI, code is instantly generated and can be edited. - **Optimized SVG output:** Generate clean and efficient SVG files. _(Planned: Ways to minify the output)_ - **Accessible on mobile:** GodSVG aims to be usable on mobile devices. @@ -28,23 +28,21 @@ GodSVG is inspired by the need for a SVG editor without abstractions that produc | rect | Supported | | | stop | Planned soon | | -Support for all other elements is currently not planned. GodSVG still aims to allow interfacing with other tags, but not natively. +Support for all other elements is currently not planned. GodSVG can still work with them, though it won't recognize them. ## Installation Currently, there are no pre-built binaries available for GodSVG. However, you can still run it by following these steps: 1. Clone the repository: `git clone https://github.com/MewPurPur/GodSVG.git` -2. Open the project in Godot Engine. (4.2.dev5 minimum needed) +2. Open the project in Godot Engine. (4.2.beta6 minimum needed) 3. Build and run the project within the Godot Engine editor. ## Community and contributing -Contributions are very welcome! GodSVG is built in Godot. For code contributions, read the [Contributing Guidelines](CONTRIBUTING.md). +Contributions are very welcome! GodSVG is built in Godot. For code contributions, read the [Contributing Guidelines](CONTRIBUTING.md). Before starting work on features, first propose them by using the issue form and wait for approval. -To report bugs, use Github's issue form. Please first propose new features by using the issue form and wait for approval before starting to work on them. - -For more casual discussion around the tool or contributing to it, find me on [GodSVG's Discord](https://discord.gg/K4ThGckGGw). +To report bugs or propose features, use Github's issue form. For more casual discussion around the tool or contributing to it, find me on [GodSVG's Discord](https://discord.gg/K4ThGckGGw). ## License @@ -53,3 +51,5 @@ GodSVG is licensed under the MIT License: - You are free to use GodSVG for any purpose. GodSVG's license terms and copyright do not apply to the content created with it. - You can study how GodSVG works and change it. - You may distribute modified versions of GodSVG. Derivative products may use a different license, but they must still document that they derive from the MIT-licensed GodSVG. + +The above explanation reflects my understanding of my own license terms and does not constitute legal advice. diff --git a/project.godot b/project.godot index 94eb8f6d..fd6a96ba 100644 --- a/project.godot +++ b/project.godot @@ -15,8 +15,15 @@ config/tags=PackedStringArray("project") run/main_scene="res://src/ui_parts/main_scene.tscn" config/features=PackedStringArray("4.2", "Forward Plus") run/low_processor_mode=true +boot_splash/bg_color=Color(0.1065, 0.1181, 0.15, 1) +boot_splash/image="res://visual/splash.png" +boot_splash/fullsize=false config/icon="res://visual/icon.png" +[audio] + +driver/driver="Dummy" + [autoload] SVG="*res://src/SVG.gd" @@ -28,8 +35,14 @@ Indications="*res://src/Indications.gd" window/size/viewport_width=1024 window/size/viewport_height=640 window/size/mode=2 +window/energy_saving/keep_screen_on=false mouse_cursor/tooltip_position_offset=Vector2(0, 10) +[filesystem] + +import/blender/enabled=false +import/fbx/enabled=false + [gui] theme/custom="res://visual/main_theme.tres" @@ -68,6 +81,11 @@ duplicate={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) ] } +select_all={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) +] +} move_relative={ "deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":77,"key_label":0,"unicode":109,"echo":false,"script":null) @@ -165,7 +183,7 @@ shorthand_cubic_bezier_relative={ } shorthand_cubic_bezier_absolute={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":67,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":83,"echo":false,"script":null) ] } @@ -177,4 +195,3 @@ locale/translations=PackedStringArray("res://translations/translation_sheet.bg.t renderer/rendering_method="gl_compatibility" environment/defaults/default_clear_color=Color(0.12, 0.132, 0.2, 1) -anti_aliasing/quality/screen_space_aa=1 diff --git a/src/GlobalSettings.gd b/src/GlobalSettings.gd index 1f66dcef..31152bd2 100644 --- a/src/GlobalSettings.gd +++ b/src/GlobalSettings.gd @@ -1,8 +1,11 @@ ## This singleton handles save data and settings. extends Node -const save_path = "user://save.tres" var save_data := SaveData.new() +const save_path = "user://save.tres" + +var _palettes := SavedColorPalettes.new() +const palettes_save_path = "user://palettes.tres" const config_path = "user://config.tres" var config := ConfigFile.new() @@ -24,15 +27,6 @@ var language: StringName: TranslationServer.set_locale(new_value) save_setting("text", "language", language) -var save_window_mode := false: - set(new_value): - save_window_mode = new_value - save_setting("session", "save_window_mode", save_window_mode) - -var save_svg := false: - set(new_value): - save_svg = new_value - save_setting("session", "save_svg", save_svg) var invert_zoom := false: set(new_value): invert_zoom = new_value @@ -42,23 +36,44 @@ func save_setting(section: String, setting: String, saved_value: Variant) -> voi config.set_value(section, setting, saved_value) config.save(config_path) +func modify_save_data(property: StringName, new_value: Variant) -> void: + save_data.set(property, new_value) + ResourceSaver.save(save_data, save_path) + func save_user_data() -> void: ResourceSaver.save(save_data, save_path) + ResourceSaver.save(_palettes, palettes_save_path) func load_user_data() -> void: if FileAccess.file_exists(save_path): save_data = ResourceLoader.load(save_path) + + if FileAccess.file_exists(palettes_save_path): + _palettes = ResourceLoader.load(palettes_save_path) + else: + var default_palette_pure := ColorPalette.new("Pure", [ + NamedColor.new("fff", "White"), + NamedColor.new("000", "Black"), + NamedColor.new("f00", "Red"), + NamedColor.new("0f0", "Green"), + NamedColor.new("00f", "Blue"), + NamedColor.new("ff0", "Yellow"), + NamedColor.new("f0f", "Magenta"), + NamedColor.new("0ff", "Cyan"), + ]) + get_palettes().append(default_palette_pure) + ResourceSaver.save(_palettes, palettes_save_path) func _exit_tree() -> void: save_data.window_mode = DisplayServer.window_get_mode() - save_data.svg = SVG.string + save_data.svg_text = SVG.text save_user_data() func _enter_tree() -> void: load_settings() load_user_data() - if save_window_mode: - DisplayServer.window_set_mode(save_data.window_mode) + DisplayServer.window_set_mode(save_data.window_mode) + get_window().wrap_controls = true # Prevents the main window from getting too small. func load_settings() -> void: @@ -75,3 +90,6 @@ func reset_settings() -> void: for section in default_config.keys(): for setting in default_config[section].keys(): set(setting, default_config[section][setting]) + +func get_palettes() -> Array[ColorPalette]: + return _palettes.palettes diff --git a/src/Indications.gd b/src/Indications.gd index 58fb644d..09cb849a 100644 --- a/src/Indications.gd +++ b/src/Indications.gd @@ -23,6 +23,7 @@ signal selection_changed # PackedInt32Array() means it's invalid. var hovered_tid := PackedInt32Array() var selected_tids: Array[PackedInt32Array] = [] +var selection_pivot_tid := PackedInt32Array() # Semi-hovered means the tag has inner selections, but it is not selected itself. # For example, individual path commands. @@ -32,61 +33,154 @@ var semi_selected_tid := 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 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.connect(_on_tags_moved) + SVG.root_tag.tags_moved_in_parent.connect(_on_tags_moved_in_parent) + #SVG.root_tag.tags_moved_to.connect(_on_tags_moved_to) # TODO SVG.root_tag.changed_unknown.connect(clear_selection) - SVG.root_tag.child_attribute_changed.connect(clear_inner_selection) + SVG.root_tag.child_attribute_changed.connect(clear_inner_selection.unbind(1)) + + +## 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(): + return + + if inner_idx == -1: + var old_selected_tids := selected_tids.duplicate() + if not semi_selected_tid.is_empty(): + semi_selected_tid.clear() + inner_selections.clear() + if selected_tids.size() == 1 and selected_tids[0] == tid: + return + selection_pivot_tid = tid.duplicate() + selected_tids = [tid.duplicate()] + if old_selected_tids != selected_tids: + selection_changed.emit() + else: + selected_tids.clear() + var old_inner_selections := inner_selections.duplicate() + if semi_selected_tid == tid and\ + inner_selections.size() == 1 and inner_selections[0] == inner_idx: + return + semi_selected_tid = tid.duplicate() + inner_selection_pivot = inner_idx + inner_selections = [inner_idx] + if inner_selections != old_inner_selections: + selection_changed.emit() ## If the tag was selected, unselect it. If it was unselected, select it. -func toggle_selection(tid: PackedInt32Array) -> void: +## If inner_idx is given, this will be an inner selection. +func ctrl_select(tid: PackedInt32Array, inner_idx := -1) -> void: if tid.is_empty(): return - var tid_idx := selected_tids.find(tid) - if tid_idx == -1: - selected_tids.append(tid.duplicate()) + 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()) + else: + selected_tids.remove_at(tid_idx) + if selected_tids.is_empty(): + selection_pivot_tid = PackedInt32Array() else: - selected_tids.remove_at(tid_idx) - inner_selections.clear() + if semi_selected_tid != tid: + normal_select(tid, inner_idx) + else: + selected_tids.clear() + var idx_idx := inner_selections.find(inner_idx) + if idx_idx == -1: + inner_selection_pivot = inner_idx + inner_selections.append(inner_idx) + else: + inner_selections.remove_at(idx_idx) + if inner_selections.is_empty(): + inner_selection_pivot = -1 + selection_changed.emit() -## If the inner tag was selected, unselect it. If it was unselected, select it. -func toggle_inner_selection(tid: PackedInt32Array, inner_idx: int) -> 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(): return - var idx_idx := inner_selections.find(inner_idx) - if idx_idx == -1: - inner_selections.append(inner_idx) + if inner_idx == -1: + if selection_pivot_tid.is_empty(): + if selected_tids.is_empty(): + normal_select(tid, inner_idx) + return + + if tid == selection_pivot_tid: + return + + var old_selected_tids := selected_tids.duplicate() + + if tid.size() != selection_pivot_tid.size(): + if not tid in selected_tids: + selected_tids.append(tid) + selection_changed.emit() + return + + var parent_tag := tid.duplicate() + parent_tag.resize(parent_tag.size() - 1) + var tid_idx := tid[-1] + var selection_pivot_tid_idx := selection_pivot_tid[-1] + + var first_idx := mini(tid_idx, selection_pivot_tid_idx) + var last_idx := maxi(tid_idx, selection_pivot_tid_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) + + if selected_tids == old_selected_tids: + return + else: - inner_selections.remove_at(idx_idx) - selected_tids.clear() + if inner_selection_pivot == -1: + if inner_selections.is_empty(): + normal_select(tid, inner_idx) + return + + var old_inner_selections := inner_selections.duplicate() + var first_idx := mini(inner_selection_pivot, inner_idx) + var last_idx := maxi(inner_selection_pivot, inner_idx) + for i in range(first_idx, last_idx + 1): + if not i in inner_selections: + inner_selections.append(i) + + if inner_selections == old_inner_selections: + return + selection_changed.emit() -## Override the selected tags with a single new selected tag. -func set_selection(tid: PackedInt32Array) -> void: - if selected_tids.size() != 1 or selected_tids[0] != tid: - if not semi_selected_tid.is_empty(): - semi_selected_tid.clear() - inner_selections.clear() - selected_tids = [tid.duplicate()] - selection_changed.emit() - -## Override the inner selections with a single new inner selection. -func set_inner_selection(tid: PackedInt32Array, inner_idx: int) -> void: - semi_selected_tid = tid.duplicate() - inner_selections = [inner_idx] - selected_tids.clear() +## Select all tags. +func select_all() -> void: + clear_inner_selection() + var tid_list := SVG.root_tag.get_all_tids() + if selected_tids == tid_list: + return + + for tid in SVG.root_tag.get_all_tids(): + if not tid in selected_tids: + selected_tids.append(tid) selection_changed.emit() + ## Clear the selected tags. func clear_selection() -> void: if not selected_tids.is_empty(): selected_tids.clear() + selection_pivot_tid.clear() selection_changed.emit() ## Clear the inner selection. @@ -94,6 +188,7 @@ func clear_inner_selection() -> void: if not inner_selections.is_empty() or not semi_selected_tid.is_empty(): inner_selections.clear() semi_selected_tid.clear() + inner_selection_pivot = -1 selection_changed.emit() ## Clear the selected tags or the inner selection. @@ -165,48 +260,67 @@ func _on_tags_deleted(tids: Array[PackedInt32Array]) -> void: if old_selected_tids != selected_tids: selection_changed.emit() -# TODO -func _on_tags_moved(parent_tid: PackedInt32Array, old_idx: int, new_idx: int) -> void: - var parent_tag := SVG.root_tag.get_by_tid(parent_tid) - #for i in changed_tids_count: - #for j in selected_tids.size(): - #var idx := selected_tids[i][-1] - #if (idx < old_idx and idx < new_idx) or (idx > old_idx and idx > new_idx): - #continue - #elif idx > old_idx and idx < new_idx: - #selected_tids[j][-1] += 1 - #elif idx <= old_idx and idx > new_idx: - #selected_tids[j][-1] -= 1 - #elif idx == old_idx: - #selected_tids[j][-1] = new_idx +# If selected tags were moved, 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] = [] + + 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]) + + # If the TID or a child of it is found, append it. + for tid in selected_tids: + if Utils.is_tid_parent(old_moved_tid, tid) or 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 old_selected_tids != selected_tids: + selection_changed.emit() + +# TODO implement this. +#func _on_tags_moved_to(tid: PackedInt32Array, old_tids: Array[PackedInt32Array]) -> void: + #return func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed(&"delete"): if not selected_tids.is_empty(): SVG.root_tag.delete_tags(selected_tids) - elif !inner_selections.is_empty() and not semi_selected_tid.is_empty(): + elif not inner_selections.is_empty() and not semi_selected_tid.is_empty(): inner_selections.sort() inner_selections.reverse() var tag_ref := SVG.root_tag.get_by_tid(semi_selected_tid) match tag_ref.name: "path": - for cmd_idx in inner_selections: - tag_ref.attributes.d.delete_command(cmd_idx) + tag_ref.attributes.d.delete_commands(inner_selections) + clear_inner_selection() elif event.is_action_pressed(&"move_up"): - SVG.root_tag.move_tags(selected_tids, false) + SVG.root_tag.move_tags_in_parent(selected_tids, false) elif event.is_action_pressed(&"move_down"): - SVG.root_tag.move_tags(selected_tids, true) + SVG.root_tag.move_tags_in_parent(selected_tids, true) elif event.is_action_pressed(&"duplicate"): SVG.root_tag.duplicate_tags(selected_tids) - else: + elif event.is_action_pressed(&"select_all"): + select_all() + elif event is InputEventKey: # Path commands using keys. - if inner_selections.is_empty(): + if inner_selections.is_empty() or event.is_command_or_control_pressed(): return + var max_inner_selection = inner_selections.max() for action_name in path_actions_dict.keys(): if event.is_action_pressed(action_name): - var last_inner_selection = inner_selections.max() var real_tag := SVG.root_tag.get_by_tid(semi_selected_tid) real_tag.attributes.d.insert_command( - last_inner_selection + 1, path_actions_dict[action_name]) + max_inner_selection + 1, path_actions_dict[action_name]) break diff --git a/src/SVG.gd b/src/SVG.gd index c5e9665f..8203d8a4 100644 --- a/src/SVG.gd +++ b/src/SVG.gd @@ -2,33 +2,53 @@ ## The SVG text, and the native [TagSVG] representation. extends Node -var string := "" +var text := "" var root_tag := TagSVG.new() -signal parsing_finished(error_id: StringName) +var saved_text := "" +var UR := UndoRedo.new() + +signal parsing_finished(error_id: StringName) func _ready() -> void: - SVG.root_tag.changed_unknown.connect(update_string) - SVG.root_tag.attribute_changed.connect(update_string) - SVG.root_tag.child_attribute_changed.connect(update_string) - SVG.root_tag.tags_added.connect(update_string.unbind(1)) - SVG.root_tag.tags_deleted.connect(update_string.unbind(1)) - SVG.root_tag.tags_moved.connect(update_string.unbind(2)) + SVG.root_tag.changed_unknown.connect(update_text.bind(false)) + SVG.root_tag.attribute_changed.connect(update_text) + SVG.root_tag.child_attribute_changed.connect(update_text) + SVG.root_tag.tag_layout_changed.connect(update_text) - if GlobalSettings.save_svg: - string = GlobalSettings.save_data.svg - sync_data() + if GlobalSettings.save_data.svg_text.is_empty(): + root_tag.attributes.width.set_value(16.0, Attribute.SyncMode.SILENT) + root_tag.attributes.height.set_value(16.0, Attribute.SyncMode.SILENT) + update_text(false) else: - update_string() + text = GlobalSettings.save_data.svg_text + saved_text = GlobalSettings.save_data.svg_text + update_tags() + UR.clear_history() + -func sync_data() -> void: - var err_id := SVGParser.get_svg_syntax_error(string) +func update_tags() -> void: + var err_id := SVGParser.get_svg_syntax_error(text) parsing_finished.emit(err_id) if err_id == &"": - update_tags() + root_tag.replace_self(SVGParser.text_to_svg(text)) -func update_string() -> void: - string = SVGParser.svg_to_text(root_tag) +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_undo_property(self, &"text", saved_text) + UR.commit_action() + saved_text = text + else: + text = SVGParser.svg_to_text(root_tag) -func update_tags() -> void: - root_tag.replace_self(SVGParser.text_to_svg(string)) + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed(&"redo"): + if UR.has_redo(): + UR.redo() + update_tags() + elif event.is_action_pressed(&"undo") and UR.has_undo(): + UR.undo() + update_tags() diff --git a/src/Utils.gd b/src/Utils.gd index d0c797d0..1046602f 100644 --- a/src/Utils.gd +++ b/src/Utils.gd @@ -1,5 +1,155 @@ class_name Utils extends RefCounted +const named_colors := { # Dictionary{String: Color} + "aliceblue": Color("#f0f8ff"), + "antiquewhite": Color("#faebd7"), + "aqua": Color("#00ffff"), + "aquamarine": Color("#7fffd4"), + "azure": Color("#f0ffff"), + "beige": Color("#f5f5dc"), + "bisque": Color("#ffe4c4"), + "black": Color("#000000"), + "blanchedalmond": Color("#ffebcd"), + "blue": Color("#0000ff"), + "blueviolet": Color("#8a2be2"), + "brown": Color("#a52a2a"), + "burlywood": Color("#deb887"), + "cadetblue": Color("#5f9ea0"), + "chartreuse": Color("#7fff00"), + "chocolate": Color("#d2691e"), + "coral": Color("#ff7f50"), + "cornflowerblue": Color("#6495ed"), + "cornsilk": Color("#fff8dc"), + "crimson": Color("#dc143c"), + "cyan": Color("#00ffff"), + "darkblue": Color("#00008b"), + "darkcyan": Color("#008b8b"), + "darkgoldenrod": Color("#b8860b"), + "darkgray": Color("#a9a9a9"), + "darkgreen": Color("#006400"), + "darkgrey": Color("#a9a9a9"), + "darkkhaki": Color("#bdb76b"), + "darkmagenta": Color("#8b008b"), + "darkolivegreen": Color("#556b2f"), + "darkorange": Color("#ff8c00"), + "darkorchid": Color("#9932cc"), + "darkred": Color("#8b0000"), + "darksalmon": Color("#e9967a"), + "darkseagreen": Color("#8fbc8f"), + "darkslateblue": Color("#483d8b"), + "darkslategray": Color("#2f4f4f"), + "darkslategrey": Color("#2f4f4f"), + "darkturquoise": Color("#00ced1"), + "darkviolet": Color("#9400d3"), + "deeppink": Color("#ff1493"), + "deepskyblue": Color("#00bfff"), + "dimgray": Color("#696969"), + "dimgrey": Color("#696969"), + "dodgerblue": Color("#1e90ff"), + "firebrick": Color("#b22222"), + "floralwhite": Color("#fffaf0"), + "forestgreen": Color("#228b22"), + "fuchsia": Color("#ff00ff"), + "gainsboro": Color("#dcdcdc"), + "ghostwhite": Color("#f8f8ff"), + "gold": Color("#ffd700"), + "goldenrod": Color("#daa520"), + "gray": Color("#808080"), + "green": Color("#008000"), + "greenyellow": Color("#adff2f"), + "grey": Color("#808080"), + "honeydew": Color("#f0fff0"), + "hotpink": Color("#ff69b4"), + "indianred": Color("#cd5c5c"), + "indigo": Color("#4b0082"), + "ivory": Color("#fffff0"), + "khaki": Color("#f0e68c"), + "lavender": Color("#e6e6fa"), + "lavenderblush": Color("#fff0f5"), + "lawngreen": Color("#7cfc00"), + "lemonchiffon": Color("#fffacd"), + "lightblue": Color("#add8e6"), + "lightcoral": Color("#f08080"), + "lightcyan": Color("#e0ffff"), + "lightgoldenrodyellow": Color("#fafad2"), + "lightgray": Color("#d3d3d3"), + "lightgreen": Color("#90ee90"), + "lightgrey": Color("#d3d3d3"), + "lightpink": Color("#ffb6c1"), + "lightsalmon": Color("#ffa07a"), + "lightseagreen": Color("#20b2aa"), + "lightskyblue": Color("#87cefa"), + "lightslategray": Color("#778899"), + "lightslategrey": Color("#778899"), + "lightsteelblue": Color("#b0c4de"), + "lightyellow": Color("#ffffe0"), + "lime": Color("#00ff00"), + "limegreen": Color("#32cd32"), + "linen": Color("#faf0e6"), + "magenta": Color("#ff00ff"), + "maroon": Color("#800000"), + "mediumaquamarine": Color("#66cdaa"), + "mediumblue": Color("#0000cd"), + "mediumorchid": Color("#ba55d3"), + "mediumpurple": Color("#9370db"), + "mediumseagreen": Color("#3cb371"), + "mediumslateblue": Color("#7b68ee"), + "mediumspringgreen": Color("#00fa9a"), + "mediumturquoise": Color("#48d1cc"), + "mediumvioletred": Color("#c71585"), + "midnightblue": Color("#191970"), + "mintcream": Color("#f5fffa"), + "mistyrose": Color("#ffe4e1"), + "moccasin": Color("#ffe4b5"), + "navajowhite": Color("#ffdead"), + "navy": Color("#000080"), + "oldlace": Color("#fdf5e6"), + "olive": Color("#808000"), + "olivedrab": Color("#6b8e23"), + "orange": Color("#ffa500"), + "orangered": Color("#ff4500"), + "orchid": Color("#da70d6"), + "palegoldenrod": Color("#eee8aa"), + "palegreen": Color("#98fb98"), + "paleturquoise": Color("#afeeee"), + "palevioletred": Color("#db7093"), + "papayawhip": Color("#ffefd5"), + "peachpuff": Color("#ffdab9"), + "peru": Color("#cd853f"), + "pink": Color("#ffc0cb"), + "plum": Color("#dda0dd"), + "powderblue": Color("#b0e0e6"), + "purple": Color("#800080"), + "red": Color("#ff0000"), + "rosybrown": Color("#bc8f8f"), + "royalblue": Color("#4169e1"), + "saddlebrown": Color("#8b4513"), + "salmon": Color("#fa8072"), + "sandybrown": Color("#f4a460"), + "seagreen": Color("#2e8b57"), + "seashell": Color("#fff5ee"), + "sienna": Color("#a0522d"), + "silver": Color("#c0c0c0"), + "skyblue": Color("#87ceeb"), + "slateblue": Color("#6a5acd"), + "slategray": Color("#708090"), + "slategrey": Color("#708090"), + "snow": Color("#fffafa"), + "springgreen": Color("#00ff7f"), + "steelblue": Color("#4682b4"), + "tan": Color("#d2b48c"), + "teal": Color("#008080"), + "thistle": Color("#d8bfd8"), + "tomato": Color("#ff6347"), + "turquoise": Color("#40e0d0"), + "violet": Color("#ee82ee"), + "wheat": Color("#f5deb3"), + "white": Color("#ffffff"), + "whitesmoke": Color("#f5f5f5"), + "yellow": Color("#ffff00"), + "yellowgreen": Color("#9acd32") +} + static func is_string_upper(string: String) -> bool: return string.to_upper() == string @@ -11,23 +161,39 @@ static func defocus_control_on_outside_click(control: Control, event: InputEvent not control.get_global_rect().has_point(event.position)): control.release_focus() -static func calculate_popup_rect(button_global_pos: Vector2, -button_size: Vector2, popup_size: Vector2, align_center := false) -> Rect2: - var screen_h: int =\ - ProjectSettings.get_setting("display/window/size/viewport_height", 640) +static func popup_under_control(popup: Popup, control: Control) -> void: + var screen_h := control.get_viewport_rect().size.y var popup_pos := Vector2.ZERO + var true_global_pos = control.global_position # Popup below if there's enough space or we're in the bottom half of the screen. - if button_global_pos.y + button_size.y + popup_size.y < screen_h or\ - button_global_pos.y + button_size.y / 2 <= screen_h / 2.0: - popup_pos.y = button_global_pos.y + button_size.y + if true_global_pos.y + control.size.y + popup.size.y < screen_h or\ + true_global_pos.y + control.size.y / 2 <= screen_h / 2.0: + popup_pos.y = true_global_pos.y + control.size.y else: - popup_pos.y = button_global_pos.y - popup_size.y - # Align horizontally. - if align_center: - popup_pos.x = button_global_pos.x - popup_size.x / 2 + button_size.x / 2 + popup_pos.y = true_global_pos.y - popup.size.y + # Horizontal alignment and other things. + popup_pos.x = true_global_pos.x + popup_pos += control.get_viewport().get_screen_transform().get_origin() + popup.popup(Rect2(popup_pos, popup.size)) + +static func popup_under_control_centered(popup: Popup, control: Control) -> void: + var screen_h := control.get_viewport_rect().size.y + var popup_pos := Vector2.ZERO + var true_global_pos = control.global_position + # Popup below if there's enough space or we're in the bottom half of the screen. + if true_global_pos.y + control.size.y + popup.size.y < screen_h or\ + true_global_pos.y + control.size.y / 2 <= screen_h / 2.0: + popup_pos.y = true_global_pos.y + control.size.y else: - popup_pos.x = button_global_pos.x - return Rect2(popup_pos, popup_size) + popup_pos.y = true_global_pos.y - popup.size.y + # Align horizontally and other things. + popup_pos.x = true_global_pos.x - popup.size.x / 2.0 + control.size.x / 2 + popup_pos += control.get_viewport().get_screen_transform().get_origin() + popup.popup(Rect2(popup_pos, popup.size)) + +static func popup_under_mouse(popup: Popup, mouse_pos: Vector2) -> void: + popup.popup(Rect2(mouse_pos, popup.size)) + static func get_cubic_bezier_points(cp1: Vector2, cp2: Vector2, cp3: Vector2, cp4: Vector2) -> PackedVector2Array: @@ -88,8 +254,10 @@ static func compare_tids(tid1: PackedInt32Array, tid2: PackedInt32Array) -> bool static func compare_tids_r(tid1: PackedInt32Array, tid2: PackedInt32Array) -> bool: return not compare_tids(tid1, tid2) -# Indirect parent, i.e. ancestor. +# Indirect parent, i.e. ancestor. Passing the root tag as parent will return false. static func is_tid_parent(parent: PackedInt32Array, child: PackedInt32Array) -> bool: + if parent.is_empty(): + return false var parent_size := parent.size() if parent_size >= child.size(): return false @@ -103,3 +271,18 @@ 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_viewbox_zoom(viewbox: Rect2, width: float, height: float) -> float: + return minf(width / viewbox.size.x, height / viewbox.size.y) + + +static func is_event_drag(event: InputEvent) -> bool: + return event is InputEventMouseMotion and event.button_mask == MOUSE_BUTTON_LEFT + +static func is_event_drag_start(event: InputEvent) -> bool: + return event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and\ + event.is_pressed() + +static func is_event_drag_end(event: InputEvent) -> bool: + return event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and\ + event.is_released() diff --git a/src/data_classes/Attribute.gd b/src/data_classes/Attribute.gd index 5a6b7fc0..5482ec0d 100644 --- a/src/data_classes/Attribute.gd +++ b/src/data_classes/Attribute.gd @@ -2,25 +2,41 @@ class_name Attribute extends RefCounted signal value_changed(new_value: Variant) -signal propagate_value_changed() - -enum Type {UNKNOWN, INT, FLOAT, UFLOAT, NFLOAT, COLOR, PATHDATA, ENUM, RECT} +signal propagate_value_changed(undo_redo: bool) +enum Type {UNKNOWN, INT, FLOAT, UFLOAT, NFLOAT, COLOR, PATHDATA, ENUM, VIEWBOX} var type: Type -var default: Variant +var default: Variant var _value: Variant -# Sometimes, value changes will be "quiet" and won't propagate - for example, if -# the attributes corresponding to 2D position get updated at the same time, -# we would want both changes to be noted by their input fields, -# but we also want only the second change to update the whole SVG and the code. -func set_value(new_value: Variant, propagate := true) -> void: - if new_value != _value: +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: Variant, sync_mode := SyncMode.LOUD) -> void: + if new_value != _value or sync_mode == SyncMode.FINAL: _value = new_value - value_changed.emit(new_value) - if propagate: - propagate_value_changed.emit() + if sync_mode != SyncMode.SILENT: + value_changed.emit(new_value) + if sync_mode != SyncMode.NO_PROPAGATION: + propagate_value_changed.emit(sync_mode != SyncMode.INTERMEDIATE) func get_value() -> Variant: return _value @@ -29,4 +45,4 @@ func get_value() -> Variant: func _init(new_type: Type, new_default: Variant = null, new_init: Variant = null) -> void: type = new_type default = new_default - set_value(new_init if new_init != null else new_default, false) + set_value(new_init if new_init != null else new_default, SyncMode.SILENT) diff --git a/src/data_classes/AttributeEnum.gd b/src/data_classes/AttributeEnum.gd index 55eb1efc..44c70852 100644 --- a/src/data_classes/AttributeEnum.gd +++ b/src/data_classes/AttributeEnum.gd @@ -7,4 +7,4 @@ func _init(new_possible_values: Array[String], new_default_idx := 0) -> void: type = Type.ENUM possible_values = new_possible_values default = possible_values[new_default_idx] - set_value(default, false) + set_value(default, SyncMode.SILENT) diff --git a/src/data_classes/AttributePath.gd b/src/data_classes/AttributePath.gd index 3e7982ae..997650d4 100644 --- a/src/data_classes/AttributePath.gd +++ b/src/data_classes/AttributePath.gd @@ -1,15 +1,18 @@ ## The "d" attribute of [TagPath]. class_name AttributePath extends Attribute -signal command_changed +signal command_changed(sync_mode: SyncMode) var commands: Array[PathCommand] func _init() -> void: type = Type.PATHDATA default = "" - set_value(default, false) + set_value(default, SyncMode.SILENT) + command_changed.connect(sync_value) +func sync_value(sync_mode := SyncMode.LOUD) -> void: + set_value(PathDataParser.path_commands_to_value(commands), sync_mode) func locate_start_points() -> void: # Start points are absolute. @@ -46,38 +49,62 @@ func get_command(idx: int) -> PathCommand: func set_command_property(idx: int, property: StringName, new_value: float, -emit_command_changed := true) -> void: - if commands[idx].get(property) != new_value: +sync_mode := SyncMode.LOUD) -> void: + if commands[idx].get(property) != new_value or sync_mode == SyncMode.FINAL: commands[idx].set(property, new_value) locate_start_points() - if emit_command_changed: - command_changed.emit() - -func add_command(command_char: String) -> void: - commands.append(PathCommand.translation_dict[command_char.to_upper()].new()) - if Utils.is_string_lower(command_char): - commands.back().toggle_relative() - locate_start_points() - command_changed.emit() + command_changed.emit(sync_mode) func insert_command(idx: int, command_char: String) -> void: commands.insert(idx, PathCommand.translation_dict[command_char.to_upper()].new()) if Utils.is_string_lower(command_char): commands[idx].toggle_relative() locate_start_points() - command_changed.emit() + command_changed.emit(SyncMode.LOUD) -func delete_command(idx: int) -> void: +func convert_command(idx: int, command_char: String) -> void: + var old_command: PathCommand = commands[idx] + if old_command.command_char == command_char: + return + + var new_command: PathCommand =\ + PathCommand.translation_dict[command_char.to_upper()].new() commands.remove_at(idx) + commands.insert(idx, new_command) + for property in [&"x", &"y", &"x1", &"y1", &"x2", &"y2"]: + if property in old_command and property in new_command: + new_command[property] = old_command[property] + + var is_relative := Utils.is_string_lower(command_char) + + if &"x" in new_command and not &"x" in old_command: + new_command.x = 0.0 if is_relative else old_command.start.x + if &"y" in new_command and not &"y" in old_command: + new_command.y = 0.0 if is_relative else old_command.start.y + + if is_relative: + commands[idx].toggle_relative() locate_start_points() - command_changed.emit() + command_changed.emit(SyncMode.LOUD) + +func delete_commands(indices: Array[int]) -> void: + if indices.is_empty(): + return + + indices = indices.duplicate() + indices.sort() + indices.reverse() + for idx in indices: + commands.remove_at(idx) + locate_start_points() + command_changed.emit(SyncMode.LOUD) func toggle_relative_command(idx: int) -> void: commands[idx].toggle_relative() - command_changed.emit() + command_changed.emit(SyncMode.LOUD) -func set_value(path_string: Variant, _emit_attribute_changed := false) -> void: - # Don't emit changed, as this rebuilds the data. +func set_value(path_string: Variant, sync_mode := SyncMode.LOUD) -> void: commands = PathDataParser.parse_path_data(path_string) locate_start_points() - super(path_string) + # Don't emit changed, as this rebuilds the data. + super(path_string, sync_mode) diff --git a/src/data_classes/AttributeUnknown.gd b/src/data_classes/AttributeUnknown.gd index 506243a5..8f8aef37 100644 --- a/src/data_classes/AttributeUnknown.gd +++ b/src/data_classes/AttributeUnknown.gd @@ -7,4 +7,4 @@ func _init(new_name: String, new_init := "") -> void: type = Type.UNKNOWN default = null name = new_name - set_value(new_init) + set_value(new_init, SyncMode.SILENT) diff --git a/src/data_classes/AttributeViewbox.gd b/src/data_classes/AttributeViewbox.gd new file mode 100644 index 00000000..48b4689d --- /dev/null +++ b/src/data_classes/AttributeViewbox.gd @@ -0,0 +1,35 @@ +## The "viewBox" attribute of [TagSVG]. +class_name AttributeViewbox extends Attribute + +var rect: Rect2 + +func _init() -> void: + type = Type.VIEWBOX + default = null + set_value(null, SyncMode.SILENT) + +func set_value(rect_string: Variant, sync_mode := SyncMode.LOUD) -> void: + if rect_string != null: + rect = ViewboxParser.string_to_rect(rect_string) + super(rect_string, sync_mode) + +func set_rect(new_rect: Rect2, sync_mode := SyncMode.LOUD) -> void: + rect = new_rect + super.set_value(ViewboxParser.rect_to_string(new_rect), sync_mode) + + +func set_rect_x(new_x: float, sync_mode := SyncMode.LOUD) -> void: + rect.position.x = new_x + set_value(ViewboxParser.rect_to_string(rect), sync_mode) + +func set_rect_y(new_y: float, sync_mode := SyncMode.LOUD) -> void: + rect.position.y = new_y + set_value(ViewboxParser.rect_to_string(rect), sync_mode) + +func set_rect_w(new_w: float, sync_mode := SyncMode.LOUD) -> void: + rect.size.x = new_w + set_value(ViewboxParser.rect_to_string(rect), sync_mode) + +func set_rect_h(new_h: float, sync_mode := SyncMode.LOUD) -> void: + rect.size.y = new_h + set_value(ViewboxParser.rect_to_string(rect), sync_mode) diff --git a/src/data_classes/ColorPalette.gd b/src/data_classes/ColorPalette.gd new file mode 100644 index 00000000..e280f549 --- /dev/null +++ b/src/data_classes/ColorPalette.gd @@ -0,0 +1,8 @@ +class_name ColorPalette extends Resource + +@export var name: String # Color palettes must be uniquely named. +@export var named_colors: Array[NamedColor] # Array because consistent order is helpful. + +func _init(new_name := "", new_colors: Array[NamedColor] = []) -> void: + name = new_name + named_colors = new_colors diff --git a/src/data_classes/NamedColor.gd b/src/data_classes/NamedColor.gd new file mode 100644 index 00000000..01dc5de3 --- /dev/null +++ b/src/data_classes/NamedColor.gd @@ -0,0 +1,8 @@ +class_name NamedColor extends Resource + +@export var name: String # Names don't need to be unique. +@export var color: String + +func _init(new_color := "", new_name := "") -> void: + color = new_color + name = new_name diff --git a/src/data_classes/PathDataParser.gd b/src/data_classes/PathDataParser.gd index d4137c44..f5e7e5ef 100644 --- a/src/data_classes/PathDataParser.gd +++ b/src/data_classes/PathDataParser.gd @@ -32,6 +32,9 @@ static func path_data_to_arrays(path_string: String) -> Array[Array]: args_left = translation_dict[curr_command.to_upper()].new().arg_count " ": continue "-", "+", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": + if prev_command.is_empty(): + continue + match prev_command: "Z", "z": return new_commands @@ -144,7 +147,8 @@ static func path_commands_to_value(commands_arr: Array[PathCommand]) -> String: if cmd_char == "A": generated_value += String.num(command.rx, 4) + " " +\ String.num(command.ry, 4) + " " + String.num(command.rot, 2) + " " +\ - str(command.large_arc_flag) + " " + str(command.sweep_flag) + " " + String.num_uint64(command.large_arc_flag) + " " +\ + String.num_uint64(command.sweep_flag) + " " if cmd_char == "Q" or cmd_char == "C": generated_value += String.num(command.x1, 4) + " " +\ String.num(command.y1, 4) + " " @@ -160,12 +164,13 @@ static func path_commands_to_value(commands_arr: Array[PathCommand]) -> String: # DEBUG -#func _init() -> void: +#static func _static_init() -> void: #var tests := { #"Jerky": [], #"M 3s 6 h 6 v 3 z": [], #"M 3 s6 h 6 v 3 z": [], #"M 3 .s6 h 6 v 3 z": [], + #" 0 2": [], #"M 0 0": [["M", 0.0, 0.0]], #"M2 1 L3 4": [["M", 2.0, 1.0], ["L", 3.0, 4.0]], #"m2 0 3 4": [["m", 2.0, 0.0], ["l", 3.0, 4.0]], diff --git a/src/data_classes/SVGHighlighter.gd b/src/data_classes/SVGHighlighter.gd index 8799fec3..5e0037e5 100644 --- a/src/data_classes/SVGHighlighter.gd +++ b/src/data_classes/SVGHighlighter.gd @@ -6,7 +6,7 @@ class_name SVGHighlighter extends SyntaxHighlighter @export var attribute_color := Color("bce0ff") @export var string_color := Color("a1ffe0") @export var comment_color := Color("cdcfd280") -@export var text_color := Color("cdcfd2aa") +@export var text_color := Color("cdcfeaac") @export var error_color := Color("ff866b") var unknown_tag_color := tag_color.darkened(0.3) @@ -17,9 +17,12 @@ func is_attribute_symbol(c: String) -> bool: (c >= "0" and c <= "9") or c == "-" or c == ":" func _get_line_syntax_highlighting(line: int) -> Dictionary: + var svg_text := get_text_edit().get_line(line) + if svg_text.is_empty(): + return {} + var color_map := {} # Dictionary{int: Dictionary{String: Color}} var parser := XMLParser.new() - var svg_text := get_text_edit().get_line(line) parser.open_buffer(svg_text.to_ascii_buffer()) while parser.read() == OK: var offset := parser.get_node_offset() @@ -46,7 +49,10 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: tag_color if SVGDB.is_tag_known(tag_name) else unknown_tag_color} offset += tag_name.length() color_map[offset] = {"color": symbol_color} + # Parsing stuff inside an element. + if offset >= svg_text.length() or svg_text[offset] == ">": + continue offset += 1 # Find where the current tag ends to be safe. var next_end: int diff --git a/src/data_classes/SVGParser.gd b/src/data_classes/SVGParser.gd index d45235af..61163277 100644 --- a/src/data_classes/SVGParser.gd +++ b/src/data_classes/SVGParser.gd @@ -3,10 +3,17 @@ class_name SVGParser extends RefCounted static func svg_to_text(svg_tag: TagSVG) -> String: var w: float = svg_tag.attributes.width.get_value() var h: float = svg_tag.attributes.height.get_value() - var viewbox: Rect2 = svg_tag.attributes.viewBox.get_value() + var viewbox: Variant = svg_tag.attributes.viewBox.get_value() + + var text := ' String: text += value.to_int() Attribute.Type.FLOAT, Attribute.Type.UFLOAT, Attribute.Type.NFLOAT: text += String.num(value, 4) - Attribute.Type.COLOR, Attribute.Type.PATHDATA, Attribute.Type.ENUM: + Attribute.Type.COLOR, Attribute.Type.PATHDATA, Attribute.Type.ENUM,\ + Attribute.Type.VIEWBOX: text += value - Attribute.Type.RECT: - text += AttributeRect.rect_to_string(value) text += '"' for attribute in tag.unknown_attributes: @@ -65,28 +71,26 @@ static func text_to_svg(text: String) -> TagSVG: # SVG tag requires width and height without defaults, so do the logic early. if node_name == "svg": - var new_w: float = attrib_dict["width"].to_float() if\ - attrib_dict.has("width") else 0.0 - var new_h: float = attrib_dict["height"].to_float() if\ - attrib_dict.has("height") else 0.0 - var new_viewbox := AttributeRect.string_to_rect(attrib_dict["viewBox"])\ - if attrib_dict.has("viewBox") else Rect2(0, 0, new_w, new_h) - if new_w == 0.0 and new_h == 0.0 and new_viewbox != Rect2(0, 0, 0, 0): - new_w = new_viewbox.size.x - new_h = new_viewbox.size.y - - svg_tag.attributes.width.set_value(new_w, false) - svg_tag.attributes.height.set_value(new_h, false) - svg_tag.attributes.viewBox.set_value(new_viewbox, false) + if attrib_dict.has("width"): + svg_tag.attributes.width.set_value(attrib_dict["width"].to_float(), + Attribute.SyncMode.SILENT) + if attrib_dict.has("height"): + svg_tag.attributes.height.set_value(attrib_dict["height"].to_float(), + Attribute.SyncMode.SILENT) + if attrib_dict.has("viewBox"): + svg_tag.attributes.viewBox.set_value(attrib_dict["viewBox"], + Attribute.SyncMode.SILENT) var unknown: Array[AttributeUnknown] = [] for element in attrib_dict: if svg_tag.attributes.has(element): var attribute: Attribute = svg_tag.attributes[element] if typeof(attribute.get_value()) == Variant.Type.TYPE_STRING: - attribute.set_value(attrib_dict[element], false) + attribute.set_value(attrib_dict[element], + Attribute.SyncMode.SILENT) elif typeof(attribute.get_value()) == Variant.Type.TYPE_FLOAT: - attribute.set_value(attrib_dict[element].to_float(), false) + attribute.set_value(attrib_dict[element].to_float(), + Attribute.SyncMode.SILENT) else: unknown.append(AttributeUnknown.new(element, attrib_dict[element])) svg_tag.set_unknown_attributes(unknown) @@ -106,9 +110,11 @@ static func text_to_svg(text: String) -> TagSVG: if tag.attributes.has(element): var attribute: Attribute = tag.attributes[element] if typeof(attribute.get_value()) == Variant.Type.TYPE_STRING: - attribute.set_value(attrib_dict[element], false) + attribute.set_value(attrib_dict[element], + Attribute.SyncMode.SILENT) elif typeof(attribute.get_value()) == Variant.Type.TYPE_FLOAT: - attribute.set_value(attrib_dict[element].to_float(), false) + attribute.set_value(attrib_dict[element].to_float(), + Attribute.SyncMode.SILENT) else: unknown.append(AttributeUnknown.new(element, attrib_dict[element])) tag.set_unknown_attributes(unknown) diff --git a/src/data_classes/SaveData.gd b/src/data_classes/SaveData.gd index 7e784feb..6db221f1 100644 --- a/src/data_classes/SaveData.gd +++ b/src/data_classes/SaveData.gd @@ -1,5 +1,12 @@ ## Stores data that needs to be retained between sessions. class_name SaveData extends Resource +const GoodColorPicker = preload("res://src/ui_elements/good_color_picker.gd") + @export var window_mode := DisplayServer.WINDOW_MODE_MAXIMIZED -@export var svg := "" +@export var svg_text := "" +@export var viewbox_coupling := true +@export var snap := -0.5 # Negative when disabled. +@export var color_picker_slider_mode := GoodColorPicker.SliderMode.RGB +@export var path_command_relative := false +@export var last_used_dir := "" diff --git a/src/data_classes/SavedColorPalettes.gd b/src/data_classes/SavedColorPalettes.gd new file mode 100644 index 00000000..9a090179 --- /dev/null +++ b/src/data_classes/SavedColorPalettes.gd @@ -0,0 +1,3 @@ +class_name SavedColorPalettes extends Resource + +@export var palettes: Array[ColorPalette] diff --git a/src/data_classes/Tag.gd b/src/data_classes/Tag.gd index a8df5653..59b8cc5e 100644 --- a/src/data_classes/Tag.gd +++ b/src/data_classes/Tag.gd @@ -3,7 +3,7 @@ class_name Tag extends RefCounted var child_tags: Array[Tag] -signal attribute_changed +signal attribute_changed(undo_redo: bool) var name: String var attributes: Dictionary # Dictionary{String: Attribute} @@ -23,8 +23,8 @@ func set_unknown_attributes(attribs: Array[AttributeUnknown]) -> void: for attribute in unknown_attributes: attribute.propagate_value_changed.connect(emit_attribute_changed) -func emit_attribute_changed(): - attribute_changed.emit() +func emit_attribute_changed(undo_redo: bool): + attribute_changed.emit(undo_redo) func get_child_count() -> int: return child_tags.size() diff --git a/src/data_classes/TagSVG.gd b/src/data_classes/TagSVG.gd index b3af9f4c..5bc4054d 100644 --- a/src/data_classes/TagSVG.gd +++ b/src/data_classes/TagSVG.gd @@ -1,24 +1,57 @@ ## A tag. class_name TagSVG extends Tag -signal child_attribute_changed +# 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(parent_tid: PackedInt32Array, new_indices: Array[int]) -signal changed_unknown +signal tags_moved_in_parent(parent_tid: PackedInt32Array, old_indices: Array[int]) +signal tags_moved_to(tid: PackedInt32Array, old_tids: Array[PackedInt32Array]) +signal tag_layout_changed # Emitted together with any of the above 4. +# This list is currently only used by the highlighter, so xmlns is here. const known_attributes = ["width", "height", "viewBox", "xmlns"] func _init() -> void: name = "svg" attributes = { - "height": Attribute.new(Attribute.Type.UFLOAT, null, 16.0), - "width": Attribute.new(Attribute.Type.UFLOAT, null, 16.0), - "viewBox": AttributeRect.new(null, Rect2(0, 0, 16, 16)), + "height": Attribute.new(Attribute.Type.UFLOAT, NAN), + "width": Attribute.new(Attribute.Type.UFLOAT, NAN), + "viewBox": AttributeViewbox.new(), } unknown_attributes.append(AttributeUnknown.new("xmlns", "http://www.w3.org/2000/svg")) super() +# Functions for getting the dimensions. +func get_width() -> float: + if !is_nan(attributes.width.get_value()): + return attributes.width.get_value() + else: + return attributes.viewBox.rect.size.x + +func get_height() -> float: + if !is_nan(attributes.height.get_value()): + return attributes.height.get_value() + else: + return attributes.viewBox.rect.size.y + +func get_size() -> Vector2: + return Vector2(get_width(), get_height()) + +func get_viewbox() -> Rect2: + if attributes.viewBox.get_value() != null: + return attributes.viewBox.rect + else: + if is_nan(attributes.width.get_value()) or is_nan(attributes.height.get_value()): + return Rect2(0, 0, 0, 0) + else: + return Rect2(0, 0, attributes.width.get_value(), attributes.height.get_value()) + func get_all_tids() -> Array[PackedInt32Array]: var tids: Array[PackedInt32Array] = [] @@ -51,10 +84,13 @@ func add_tag(new_tag: Tag, new_tid: PackedInt32Array) -> void: 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(), false) + attributes[attrib].set_value(new_tag.attributes[attrib].get_value(), + Attribute.SyncMode.SILENT) unknown_attributes.clear() for attrib in new_tag.unknown_attributes: @@ -66,7 +102,10 @@ func replace_self(new_tag: Tag) -> void: for tid in get_all_tids(): get_by_tid(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(): @@ -93,13 +132,14 @@ func delete_tags(tids: Array[PackedInt32Array]) -> void: if tag_idx < parent_tag.get_child_count(): parent_tag.child_tags.remove_at(tag_idx) tags_deleted.emit(tids) + tag_layout_changed.emit() -# TODO # Moves tags up or down, not to an arbitrary position. -func move_tags(tids: Array[PackedInt32Array], down: bool) -> void: +func move_tags_in_parent(tids: Array[PackedInt32Array], down: bool) -> void: if tids.is_empty(): return + tids = tids.duplicate() tids.sort_custom(Utils.compare_tids_r) # Linear scan to get the minimal set of TIDs to move. var last_accepted := tids[0] @@ -119,38 +159,45 @@ func move_tags(tids: Array[PackedInt32Array], down: bool) -> void: if tid.size() != depth or Utils.get_parent_tid(tid) != parent_tid: return - # The set of tags on the bottom shouldn't be moved. + var tid_indices: Array[int] = [] # The last indices of the TIDs. + for tid in tids: + tid_indices.append(tid[-1]) + var parent_tag := get_by_tid(parent_tid) var parent_child_count := parent_tag.get_child_count() + var old_indices: Array[int] = [] + for k in parent_child_count: + old_indices.append(k) + # Do the moving. if down: - var unaffected := parent_child_count - 1 - tids.reverse() - for tid_idx in tids.size(): - if tids[tid_idx][-1] == unaffected: - unaffected -= 1 - tids.remove_at(tid_idx) - else: - break + 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 unaffected := 0 - for tid_idx in tids.size(): - if tids[tid_idx][-1] == unaffected: - unaffected += 1 - tids.remove_at(tid_idx) - else: - break - - var new_indices := range(parent_tag.get_child_count()) - # Do the moving. - for tid in tids: - var new_tid := tid.duplicate() - new_tid[-1] += (1 if down else -1) - var new_tag: Tag = parent_tag.child_tags.pop_at(tid[-1]) - parent_tag.child_tags.insert(new_tid[-1], new_tag) - tags_moved.emit(parent_tid, new_indices) + 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 + tags_moved_in_parent.emit(parent_tid, old_indices) + tag_layout_changed.emit() -func move_tags_to(tids: Array[PackedInt32Array], pos: PackedInt32Array) -> void: - pass # TODO implement this. +#func move_tags_to(tids: Array[PackedInt32Array], pos: PackedInt32Array) -> void: + #pass # TODO implement this. func duplicate_tags(tids: Array[PackedInt32Array]) -> void: if tids.is_empty(): @@ -192,7 +239,12 @@ func duplicate_tags(tids: Array[PackedInt32Array]) -> void: 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 emit_child_attribute_changed(undo_redo: bool) -> void: + child_attribute_changed.emit(undo_redo) -func emit_child_attribute_changed() -> void: - child_attribute_changed.emit() +func emit_attribute_changed(undo_redo: bool) -> void: + super(undo_redo) + resized.emit() diff --git a/src/data_classes/AttributeRect.gd b/src/data_classes/ViewboxParser.gd similarity index 82% rename from src/data_classes/AttributeRect.gd rename to src/data_classes/ViewboxParser.gd index 9694f296..5392eb97 100644 --- a/src/data_classes/AttributeRect.gd +++ b/src/data_classes/ViewboxParser.gd @@ -1,11 +1,7 @@ -## An attribute representing a rectangle. -class_name AttributeRect extends Attribute - -func _init(new_default: Variant = null, new_init: Variant = null) -> void: - type = Type.RECT - default = new_default - set_value(new_init if new_init != null else new_default, false) +## A parser for the viewBox attribute of [TagSVG]. +class_name ViewboxParser extends RefCounted +# TODO Turn this into a ListParser (handling any amount of numbers) static func string_to_rect(string: String) -> Rect2: var nums_parsed: Array[float] = [] diff --git a/src/shaders/color_wheel.gdshader b/src/shaders/color_wheel.gdshader new file mode 100644 index 00000000..8dfccc5f --- /dev/null +++ b/src/shaders/color_wheel.gdshader @@ -0,0 +1,22 @@ +shader_type canvas_item; + +uniform float v = 1.0; + +void fragment() { + float x = UV.x - 0.5; + float y = UV.y - 0.5; + float a = atan(y, x); + x += 0.001; + y += 0.001; + float b = float(sqrt(x * x + y * y) < 0.5); + x -= 0.002; + float b2 = float(sqrt(x * x + y * y) < 0.5); + y -= 0.002; + float b3 = float(sqrt(x * x + y * y) < 0.5); + x += 0.002; + float b4 = float(sqrt(x * x + y * y) < 0.5); + + COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), + ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.0); +} \ No newline at end of file diff --git a/src/shaders/slider_visuals.gdshader b/src/shaders/slider_visuals.gdshader new file mode 100644 index 00000000..5c698c7f --- /dev/null +++ b/src/shaders/slider_visuals.gdshader @@ -0,0 +1,45 @@ +shader_type canvas_item; + +uniform vec3 base_color = vec3(1.0, 1.0, 1.0); +// 0 = Red, 1 = Green, 2 = Blue, 3 = Hue, 4 = Saturation, 5 = Value +uniform int interpolation = 0; +uniform bool horizontal = true; +uniform bool inverted = false; + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void fragment() { + float offset = (horizontal ? UV.x : UV.y); + offset = (inverted ? 1.0 - offset : offset); + + if (interpolation == 0) { + COLOR = vec4(offset, base_color.g, base_color.b, 1.0); + } else if (interpolation == 1) { + COLOR = vec4(base_color.r, offset, base_color.b, 1.0); + } else if (interpolation == 2) { + COLOR = vec4(base_color.r, base_color.g, offset, 1.0); + } else { + vec3 hsv = rgb2hsv(base_color.rgb); + if (interpolation == 3) { + hsv.x = offset; + } else if (interpolation == 4) { + hsv.y = offset; + } else if (interpolation == 5) { + hsv.z = offset; + } + COLOR = vec4(hsv2rgb(hsv), 1.0); + } +} \ No newline at end of file diff --git a/src/ui_parts/zoom_shader.gdshader b/src/shaders/zoom_shader.gdshader similarity index 100% rename from src/ui_parts/zoom_shader.gdshader rename to src/shaders/zoom_shader.gdshader diff --git a/src/ui_elements/AttributeEditor.gd b/src/ui_elements/AttributeEditor.gd index 5ebd3573..8692a7b0 100644 --- a/src/ui_elements/AttributeEditor.gd +++ b/src/ui_elements/AttributeEditor.gd @@ -6,3 +6,10 @@ class_name AttributeEditor extends Control var attribute: Attribute var attribute_name: String + +# Values to be used for set_value(). +# REGULAR means that value_changed will emit if the new value is different. +# NO_SIGNAL means value_changed won't emit. +# INTERMEDIATE and FINAL cause the attribute update to have the corresponding sync mode. +# Note that FINAL causes the equivalence check to be skipped. +enum UpdateType {REGULAR, NO_SIGNAL, INTERMEDIATE, FINAL} diff --git a/src/ui_elements/BetterLineEdit.gd b/src/ui_elements/BetterLineEdit.gd index 84bf088f..d81f6281 100644 --- a/src/ui_elements/BetterLineEdit.gd +++ b/src/ui_elements/BetterLineEdit.gd @@ -2,32 +2,38 @@ class_name BetterLineEdit extends LineEdit const code_font = preload("res://visual/fonts/FontMono.ttf") +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") var hovered := false -@export var hover_stylebox: StyleBox -@export var focus_stylebox: StyleBox -@export var code_font_tooltip := false +@export var hover_stylebox: StyleBox ## Overlayed on top when you hover the LineEdit. +@export var focus_stylebox: StyleBox ## Overlayed on top when the LineEdit is focused. +@export var code_font_tooltip := false ## Use the mono font for the tooltip. func _ready() -> void: focus_entered.connect(_on_focus_entered) focus_exited.connect(_on_focus_exited) mouse_entered.connect(_on_mouse_entered) mouse_exited.connect(_on_mouse_exited) - text_submitted.connect(_on_text_submitted) + text_submitted.connect(release_focus.unbind(1)) + gui_input.connect(_on_gui_input) func _input(event: InputEvent) -> void: if has_focus() and event is InputEventMouseButton and\ not get_global_rect().has_point(event.position): release_focus() +var tree_was_paused_before := false func _on_focus_entered() -> void: process_mode = PROCESS_MODE_ALWAYS - get_tree().paused = true + tree_was_paused_before = get_tree().paused + if not tree_was_paused_before: + get_tree().paused = true func _on_focus_exited() -> void: - get_tree().paused = false process_mode = PROCESS_MODE_INHERIT + if not tree_was_paused_before: + get_tree().paused = false func _on_mouse_entered() -> void: hovered = true @@ -37,9 +43,6 @@ func _on_mouse_exited() -> void: hovered = false queue_redraw() -func _on_text_submitted(_new_text: String) -> void: - release_focus() - func _draw() -> void: if editable: if has_focus() and focus_stylebox != null: @@ -51,8 +54,50 @@ func _make_custom_tooltip(for_text: String) -> Object: if code_font_tooltip: var label := Label.new() label.add_theme_font_override(&"font", code_font) - label.add_theme_font_size_override(&"font_size", 12) + label.add_theme_font_size_override(&"font_size", 13) label.text = for_text return label else: return null + + +func _on_gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_RIGHT: + var context_popup := ContextPopup.instantiate() + var btn_arr: Array[Button] = [] + + var undo_button := Button.new() + undo_button.text = tr(&"#undo") + undo_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + undo_button.pressed.connect(menu_option.bind(LineEdit.MENU_UNDO)) + btn_arr.append(undo_button) + + var redo_button := Button.new() + redo_button.text = tr(&"#redo") + redo_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + redo_button.pressed.connect(menu_option.bind(LineEdit.MENU_REDO)) + btn_arr.append(redo_button) + + var copy_button := Button.new() + copy_button.text = tr(&"#copy") + copy_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + copy_button.pressed.connect(menu_option.bind(LineEdit.MENU_COPY)) + btn_arr.append(copy_button) + + var paste_button := Button.new() + paste_button.text = tr(&"#paste") + paste_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + paste_button.pressed.connect(menu_option.bind(LineEdit.MENU_PASTE)) + btn_arr.append(paste_button) + + var cut_button := Button.new() + cut_button.text = tr(&"#cut") + cut_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + cut_button.pressed.connect(menu_option.bind(LineEdit.MENU_CUT)) + btn_arr.append(cut_button) + + add_child(context_popup) + context_popup.set_min_width(72.0) + context_popup.set_btn_array(btn_arr) + Utils.popup_under_mouse(context_popup, get_global_mouse_position()) diff --git a/src/ui_elements/BetterTabContainer.gd b/src/ui_elements/BetterTabContainer.gd index 9aa60dc0..f21fb11f 100644 --- a/src/ui_elements/BetterTabContainer.gd +++ b/src/ui_elements/BetterTabContainer.gd @@ -1,17 +1,20 @@ -## A TabContainer with translated tab titles +## A TabContainer that automatically localizes tab titles. class_name BetterTabContainer extends TabContainer -var tab_keys: Array[String] +var tab_keys: Array[StringName] +# Localize tab titles. func _ready() -> void: for i in get_tab_count(): - tab_keys.append(get_tab_title(i)) + tab_keys.append(StringName(get_tab_title(i))) translate_titles() +func _notification(what: int) -> void: + if what == NOTIFICATION_TRANSLATION_CHANGED: + translate_titles() + + func translate_titles() -> void: for i in tab_keys.size(): set_tab_title(i, tr(tab_keys[i])) -func _notification(what: int) -> void: - if what == NOTIFICATION_TRANSLATION_CHANGED: - translate_titles() diff --git a/src/ui_elements/BetterTextEdit.gd b/src/ui_elements/BetterTextEdit.gd new file mode 100644 index 00000000..776c49bf --- /dev/null +++ b/src/ui_elements/BetterTextEdit.gd @@ -0,0 +1,106 @@ +## A TextEdit that doesn't fully redraw on caret blink and has a custom context menu. +class_name BetterTextEdit extends TextEdit + +const code_font = preload("res://visual/fonts/FontMono.ttf") +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") +const caret_color = Color("defd") + +var surface := RenderingServer.canvas_item_create() +var timer := Timer.new() + +func _ready() -> void: + RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) + add_child(timer) + timer.timeout.connect(blink) + get_v_scroll_bar().value_changed.connect(redraw_caret.unbind(1)) + get_h_scroll_bar().value_changed.connect(redraw_caret.unbind(1)) + gui_input.connect(_on_gui_input) + + +func redraw_caret() -> void: + await get_tree().process_frame # Buggy with backspace otherwise, likely a Godot bug. + blonk = false + blink() + timer.start(0.6) + RenderingServer.canvas_item_clear(surface) + if has_focus(): + var char_size := code_font.get_char_size(69, + get_theme_font_size(&"TextEdit", &"font_size")) + for caret in get_caret_count(): + # FIXME There's a bug(?) causing the draw pos to sometimes not update + # when outside of the screen. + var caret_draw_pos := get_caret_draw_pos(caret) + if is_overtype_mode_enabled(): + RenderingServer.canvas_item_add_line(surface, caret_draw_pos - Vector2(1, 0), + caret_draw_pos + Vector2(char_size.x - 2, 0), caret_color, 1) + else: + RenderingServer.canvas_item_add_line(surface, caret_draw_pos - Vector2(0, 1), + caret_draw_pos - Vector2(0, char_size.y - 2), caret_color, 1) + +var blonk := true +func blink() -> void: + blonk = not blonk + RenderingServer.canvas_item_set_visible(surface, blonk) + +func _on_focus_entered() -> void: + timer.start(0.6) + +func _on_focus_exited() -> void: + timer.stop() + RenderingServer.canvas_item_clear(surface) + + +func _on_gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_RIGHT: + var context_popup := ContextPopup.instantiate() + var btn_arr: Array[Button] = [] + + var undo_button := Button.new() + undo_button.text = tr(&"#undo") + if not has_undo(): + undo_button.disabled = true + undo_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + undo_button.pressed.connect(undo) + btn_arr.append(undo_button) + + var redo_button := Button.new() + redo_button.text = tr(&"#redo") + if not has_redo(): + redo_button.disabled = true + redo_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + redo_button.pressed.connect(redo) + btn_arr.append(redo_button) + + var copy_button := Button.new() + copy_button.text = tr(&"#copy") + copy_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + copy_button.pressed.connect(copy) + btn_arr.append(copy_button) + + var paste_button := Button.new() + paste_button.text = tr(&"#paste") + paste_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + paste_button.pressed.connect(paste) + btn_arr.append(paste_button) + + var cut_button := Button.new() + cut_button.text = tr(&"#cut") + cut_button.alignment = HORIZONTAL_ALIGNMENT_LEFT + cut_button.pressed.connect(cut) + btn_arr.append(cut_button) + + add_child(context_popup) + context_popup.set_min_width(72.0) + context_popup.set_btn_array(btn_arr) + Utils.popup_under_mouse(context_popup, get_global_mouse_position()) + else: + # Set these inputs as handled, so the default UndoRedo doesn't eat them. + if event.is_action_pressed(&"redo"): + if has_redo(): + redo() + accept_event() + elif event.is_action_pressed(&"undo"): + if has_undo(): + undo() + accept_event() diff --git a/src/ui_elements/BetterToggleButton.gd b/src/ui_elements/BetterToggleButton.gd new file mode 100644 index 00000000..5842394c --- /dev/null +++ b/src/ui_elements/BetterToggleButton.gd @@ -0,0 +1,30 @@ +## A regular Button that overlays a stylebox when hovered while pressed. +class_name BetterToggleButton extends Button + +var hovered := false + +# Overlayed on top when the Button is hovered while pressed. +@export var hover_pressed_stylebox: StyleBox +@export var hover_pressed_font_color: Color + +func _ready() -> void: + mouse_entered.connect(_on_mouse_entered) + mouse_exited.connect(_on_mouse_exited) + add_theme_color_override(&"font_hover_color", get_theme_color( + &"font_hover_color", &"Button").blend(hover_pressed_font_color)) + +func _on_mouse_entered() -> void: + hovered = true + if not disabled and hover_pressed_font_color != Color.BLACK: + add_theme_color_override(&"font_pressed_color", get_theme_color( + &"font_pressed_color", &"Button").blend(hover_pressed_font_color)) + queue_redraw() + +func _on_mouse_exited() -> void: + hovered = false + remove_theme_color_override(&"font_pressed_color") + queue_redraw() + +func _draw() -> void: + if not disabled and button_pressed and hovered and hover_pressed_stylebox != null: + draw_style_box(hover_pressed_stylebox, Rect2(Vector2.ZERO, size)) diff --git a/src/ui_elements/CustomSpacedHBoxContainer.gd b/src/ui_elements/CustomSpacedHBoxContainer.gd new file mode 100644 index 00000000..798deecf --- /dev/null +++ b/src/ui_elements/CustomSpacedHBoxContainer.gd @@ -0,0 +1,24 @@ +## Allows passing an array of custom horizontal spacings. +class_name CustomSpacedHBoxContainer extends Container + +# Array for the spacing between elements. +var _spacing_array: Array # Array[int] + +func _ready() -> void: + sort_children.connect(_sort_children) + +func set_spacing_array(new_arr: Array) -> void: + _spacing_array = new_arr + queue_sort() + +func _sort_children(): + var current_x: float = 0 + var spacing_arr_size := _spacing_array.size() + + for i in get_child_count(): + var child: Control = get_child(i) + child.position.x = current_x + + current_x += child.size.x + if i < spacing_arr_size: + current_x += _spacing_array[i] diff --git a/src/ui_elements/color_edit.gd b/src/ui_elements/color_edit.gd new file mode 100644 index 00000000..2fa8e770 --- /dev/null +++ b/src/ui_elements/color_edit.gd @@ -0,0 +1,88 @@ +## A color editor, not tied to any attribute. +extends HBoxContainer + +const ColorPopup = preload("res://src/ui_elements/color_popup.tscn") +const ColorPickerPopup = preload("res://src/ui_elements/color_picker_popup.tscn") +const checkerboard = preload("res://visual/ColorButtonBG.svg") + +@onready var color_button: Button = $Button +@onready var color_edit: LineEdit = $LineEdit +@onready var color_picker: Popup + +@export var enable_palettes := true + +signal value_changed(new_value: String) +var current_value: String: + set(new_value): + current_value = validate(new_value) + value_changed.emit(current_value) + + +func _ready() -> void: + color_edit.text = current_value + +func validate(new_value: String) -> String: + if is_color_valid_non_hex(new_value) or new_value.is_valid_html_color(): + return new_value.trim_prefix("#") + return "000" + +func _on_value_changed(new_value: String) -> void: + color_edit.remove_theme_color_override(&"font_color") + color_edit.text = new_value.trim_prefix("#") + queue_redraw() + +func _on_button_pressed() -> void: + if enable_palettes: + color_picker = ColorPopup.instantiate() + else: + color_picker = ColorPickerPopup.instantiate() + color_picker.current_value = current_value + add_child(color_picker) + color_picker.color_picked.connect(_on_color_picked) + Utils.popup_under_control(color_picker, color_edit) + +func _draw() -> void: + var button_size := color_button.get_size() + var line_edit_size := color_edit.get_size() + draw_set_transform(Vector2(line_edit_size.x, 1)) + var stylebox := StyleBoxFlat.new() + stylebox.corner_radius_top_right = 5 + stylebox.corner_radius_bottom_right = 5 + if Utils.named_colors.has(current_value): + stylebox.bg_color = Utils.named_colors[current_value] + else: + stylebox.bg_color = Color.from_string(current_value, Color.TRANSPARENT) + draw_texture(checkerboard, Vector2.ZERO) + draw_style_box(stylebox, Rect2(Vector2.ZERO, button_size - Vector2(1, 2))) + + +func _on_focus_exited() -> void: + current_value = color_edit.text + +func _on_text_submitted(new_text: String) -> void: + current_value = new_text + + +func _on_color_picked(new_color: String, close_picker: bool) -> void: + current_value = new_color + if close_picker: + color_picker.queue_free() + +func is_color_valid_non_hex(color: String) -> bool: + return color == "none" or Utils.named_colors.has(color) or\ + (color.begins_with("url(#") and color.ends_with(")")) + +func is_color_valid(color: String) -> bool: + return color.is_valid_html_color() or is_color_valid_non_hex(color) + + +func _on_button_resized() -> void: + # Not sure why this is needed, but the button doesn't have a correct size at first + # which screws with the drawing logic. + queue_redraw() + +func _on_line_edit_text_changed(new_text: String) -> void: + if is_color_valid(new_text): + color_edit.add_theme_color_override(&"font_color", Color(0.6, 1.0, 0.6)) + else: + color_edit.add_theme_color_override(&"font_color", Color(1.0, 0.6, 0.6)) diff --git a/src/ui_elements/color_edit.tscn b/src/ui_elements/color_edit.tscn new file mode 100644 index 00000000..017a2e14 --- /dev/null +++ b/src/ui_elements/color_edit.tscn @@ -0,0 +1,59 @@ +[gd_scene load_steps=5 format=3 uid="uid://5f8uxavn1or1"] + +[ext_resource type="Script" path="res://src/ui_elements/color_edit.gd" id="1_1uexr"] +[ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="1_efrfl"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q6tej"] +draw_center = false +border_width_left = 2 +border_width_top = 2 +border_width_right = 1 +border_width_bottom = 2 +border_color = Color(1, 1, 1, 0.0666667) +corner_radius_top_left = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_edu3w"] +draw_center = false +border_width_left = 2 +border_width_top = 2 +border_width_right = 1 +border_width_bottom = 2 +border_color = Color(0.501961, 0.74902, 1, 0.2) +corner_radius_top_left = 5 +corner_radius_bottom_left = 5 + +[node name="ColorEdit" type="HBoxContainer"] +custom_minimum_size = Vector2(0, 22) +offset_right = 50.0 +offset_bottom = 21.0 +theme_override_constants/separation = 0 +script = ExtResource("1_1uexr") + +[node name="LineEdit" type="LineEdit" parent="."] +custom_minimum_size = Vector2(54, 0) +layout_mode = 2 +focus_mode = 1 +theme_type_variation = &"RightConnectedLineEdit" +max_length = 20 +context_menu_enabled = false +select_all_on_focus = true +caret_blink = true +script = ExtResource("1_efrfl") +hover_stylebox = SubResource("StyleBoxFlat_q6tej") +focus_stylebox = SubResource("StyleBoxFlat_edu3w") +code_font_tooltip = true + +[node name="Button" type="Button" parent="."] +custom_minimum_size = Vector2(13, 0) +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"LeftConnectedButtonTransparent" + +[connection signal="value_changed" from="." to="." method="_on_value_changed"] +[connection signal="focus_exited" from="LineEdit" to="." method="_on_focus_exited"] +[connection signal="text_changed" from="LineEdit" to="." method="_on_line_edit_text_changed"] +[connection signal="text_submitted" from="LineEdit" to="." method="_on_text_submitted"] +[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] +[connection signal="resized" from="Button" to="." method="_on_button_resized"] diff --git a/src/ui_elements/color_field.gd b/src/ui_elements/color_field.gd index 0dcf00e1..db4d04bd 100644 --- a/src/ui_elements/color_field.gd +++ b/src/ui_elements/color_field.gd @@ -1,171 +1,26 @@ ## An editor to be tied to a color attribute. extends AttributeEditor -const named_colors := { # Dictionary{String: Color} - "aliceblue": Color("#f0f8ff"), - "antiquewhite": Color("#faebd7"), - "aqua": Color("#00ffff"), - "aquamarine": Color("#7fffd4"), - "azure": Color("#f0ffff"), - "beige": Color("#f5f5dc"), - "bisque": Color("#ffe4c4"), - "black": Color("#000000"), - "blanchedalmond": Color("#ffebcd"), - "blue": Color("#0000ff"), - "blueviolet": Color("#8a2be2"), - "brown": Color("#a52a2a"), - "burlywood": Color("#deb887"), - "cadetblue": Color("#5f9ea0"), - "chartreuse": Color("#7fff00"), - "chocolate": Color("#d2691e"), - "coral": Color("#ff7f50"), - "cornflowerblue": Color("#6495ed"), - "cornsilk": Color("#fff8dc"), - "crimson": Color("#dc143c"), - "cyan": Color("#00ffff"), - "darkblue": Color("#00008b"), - "darkcyan": Color("#008b8b"), - "darkgoldenrod": Color("#b8860b"), - "darkgray": Color("#a9a9a9"), - "darkgreen": Color("#006400"), - "darkgrey": Color("#a9a9a9"), - "darkkhaki": Color("#bdb76b"), - "darkmagenta": Color("#8b008b"), - "darkolivegreen": Color("#556b2f"), - "darkorange": Color("#ff8c00"), - "darkorchid": Color("#9932cc"), - "darkred": Color("#8b0000"), - "darksalmon": Color("#e9967a"), - "darkseagreen": Color("#8fbc8f"), - "darkslateblue": Color("#483d8b"), - "darkslategray": Color("#2f4f4f"), - "darkslategrey": Color("#2f4f4f"), - "darkturquoise": Color("#00ced1"), - "darkviolet": Color("#9400d3"), - "deeppink": Color("#ff1493"), - "deepskyblue": Color("#00bfff"), - "dimgray": Color("#696969"), - "dimgrey": Color("#696969"), - "dodgerblue": Color("#1e90ff"), - "firebrick": Color("#b22222"), - "floralwhite": Color("#fffaf0"), - "forestgreen": Color("#228b22"), - "fuchsia": Color("#ff00ff"), - "gainsboro": Color("#dcdcdc"), - "ghostwhite": Color("#f8f8ff"), - "gold": Color("#ffd700"), - "goldenrod": Color("#daa520"), - "gray": Color("#808080"), - "green": Color("#008000"), - "greenyellow": Color("#adff2f"), - "grey": Color("#808080"), - "honeydew": Color("#f0fff0"), - "hotpink": Color("#ff69b4"), - "indianred": Color("#cd5c5c"), - "indigo": Color("#4b0082"), - "ivory": Color("#fffff0"), - "khaki": Color("#f0e68c"), - "lavender": Color("#e6e6fa"), - "lavenderblush": Color("#fff0f5"), - "lawngreen": Color("#7cfc00"), - "lemonchiffon": Color("#fffacd"), - "lightblue": Color("#add8e6"), - "lightcoral": Color("#f08080"), - "lightcyan": Color("#e0ffff"), - "lightgoldenrodyellow": Color("#fafad2"), - "lightgray": Color("#d3d3d3"), - "lightgreen": Color("#90ee90"), - "lightgrey": Color("#d3d3d3"), - "lightpink": Color("#ffb6c1"), - "lightsalmon": Color("#ffa07a"), - "lightseagreen": Color("#20b2aa"), - "lightskyblue": Color("#87cefa"), - "lightslategray": Color("#778899"), - "lightslategrey": Color("#778899"), - "lightsteelblue": Color("#b0c4de"), - "lightyellow": Color("#ffffe0"), - "lime": Color("#00ff00"), - "limegreen": Color("#32cd32"), - "linen": Color("#faf0e6"), - "magenta": Color("#ff00ff"), - "maroon": Color("#800000"), - "mediumaquamarine": Color("#66cdaa"), - "mediumblue": Color("#0000cd"), - "mediumorchid": Color("#ba55d3"), - "mediumpurple": Color("#9370db"), - "mediumseagreen": Color("#3cb371"), - "mediumslateblue": Color("#7b68ee"), - "mediumspringgreen": Color("#00fa9a"), - "mediumturquoise": Color("#48d1cc"), - "mediumvioletred": Color("#c71585"), - "midnightblue": Color("#191970"), - "mintcream": Color("#f5fffa"), - "mistyrose": Color("#ffe4e1"), - "moccasin": Color("#ffe4b5"), - "navajowhite": Color("#ffdead"), - "navy": Color("#000080"), - "oldlace": Color("#fdf5e6"), - "olive": Color("#808000"), - "olivedrab": Color("#6b8e23"), - "orange": Color("#ffa500"), - "orangered": Color("#ff4500"), - "orchid": Color("#da70d6"), - "palegoldenrod": Color("#eee8aa"), - "palegreen": Color("#98fb98"), - "paleturquoise": Color("#afeeee"), - "palevioletred": Color("#db7093"), - "papayawhip": Color("#ffefd5"), - "peachpuff": Color("#ffdab9"), - "peru": Color("#cd853f"), - "pink": Color("#ffc0cb"), - "plum": Color("#dda0dd"), - "powderblue": Color("#b0e0e6"), - "purple": Color("#800080"), - "red": Color("#ff0000"), - "rosybrown": Color("#bc8f8f"), - "royalblue": Color("#4169e1"), - "saddlebrown": Color("#8b4513"), - "salmon": Color("#fa8072"), - "sandybrown": Color("#f4a460"), - "seagreen": Color("#2e8b57"), - "seashell": Color("#fff5ee"), - "sienna": Color("#a0522d"), - "silver": Color("#c0c0c0"), - "skyblue": Color("#87ceeb"), - "slateblue": Color("#6a5acd"), - "slategray": Color("#708090"), - "slategrey": Color("#708090"), - "snow": Color("#fffafa"), - "springgreen": Color("#00ff7f"), - "steelblue": Color("#4682b4"), - "tan": Color("#d2b48c"), - "teal": Color("#008080"), - "thistle": Color("#d8bfd8"), - "tomato": Color("#ff6347"), - "turquoise": Color("#40e0d0"), - "violet": Color("#ee82ee"), - "wheat": Color("#f5deb3"), - "white": Color("#ffffff"), - "whitesmoke": Color("#f5f5f5"), - "yellow": Color("#ffff00"), - "yellowgreen": Color("#9acd32") -} +const ColorPopup = preload("res://src/ui_elements/color_popup.tscn") +const checkerboard = preload("res://visual/ColorButtonBG.svg") @onready var color_button: Button = $Button @onready var color_edit: LineEdit = $LineEdit -@onready var color_picker: Popup = $ColorPopup +@onready var color_popup: Popup -@export var checkerboard: Texture2D - -signal value_changed(new_value: String) +signal value_changed(new_value: String, update_type: UpdateType) var _value: String # Must not be updated directly. -func set_value(new_value: String, emit_value_changed := true): +func set_value(new_value: String, update_type := UpdateType.REGULAR): var old_value := _value _value = validate(new_value) set_text_tint() - if _value != old_value and emit_value_changed: - value_changed.emit(_value if (is_color_valid_non_hex(_value)) else "#" + _value) + if update_type != UpdateType.NO_SIGNAL and\ + (_value != old_value or update_type == UpdateType.FINAL): + var emitted_value = _value if (is_color_valid_non_hex(_value)) else "#" + _value + value_changed.emit(emitted_value, update_type) + elif color_edit != null: + update_after_change() func get_value() -> String: return _value @@ -173,8 +28,7 @@ func get_value() -> String: func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) + set_value(attribute.get_value()) color_edit.text = get_value() color_edit.tooltip_text = attribute_name @@ -183,15 +37,22 @@ func validate(new_value: String) -> String: return new_value.trim_prefix("#") return "000" -func _on_value_changed(new_value: String) -> void: - color_edit.text = new_value.trim_prefix("#") - queue_redraw() - if attribute != null: - attribute.set_value(new_value) +func _on_value_changed(new_value: String, update_type: UpdateType) -> void: + update_after_change() + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) func _on_button_pressed() -> void: - color_picker.popup(Utils.calculate_popup_rect( - color_edit.global_position, color_edit.size, color_picker.size)) + color_popup = ColorPopup.instantiate() + color_popup.current_value = get_value() + add_child(color_popup) + color_popup.color_picked.connect(_on_color_picked) + Utils.popup_under_control(color_popup, color_edit) func _draw() -> void: var button_size := color_button.get_size() @@ -200,8 +61,8 @@ func _draw() -> void: var stylebox := StyleBoxFlat.new() stylebox.corner_radius_top_right = 5 stylebox.corner_radius_bottom_right = 5 - if named_colors.has(get_value()): - stylebox.bg_color = named_colors[get_value()] + if Utils.named_colors.has(get_value()): + stylebox.bg_color = Utils.named_colors[get_value()] else: stylebox.bg_color = Color.from_string(get_value(), Color(0, 0, 0, 0)) draw_texture(checkerboard, Vector2.ZERO) @@ -215,11 +76,15 @@ func _on_text_submitted(new_text: String) -> void: set_value(new_text) -func _on_color_picked(new_color: String) -> void: - set_value(new_color) +func _on_color_picked(new_color: String, close_picker: bool) -> void: + if close_picker: + color_popup.queue_free() + set_value(new_color, UpdateType.FINAL) + else: + set_value(new_color, UpdateType.INTERMEDIATE) func is_color_valid_non_hex(color: String) -> bool: - return color == "none" or named_colors.has(color) or\ + return color == "none" or Utils.named_colors.has(color) or\ (color.begins_with("url(#") and color.ends_with(")")) func is_color_valid(color: String) -> bool: @@ -244,3 +109,7 @@ func _on_line_edit_text_changed(new_text: String) -> void: color_edit.add_theme_color_override(&"font_color", Color(0.6, 1.0, 0.6)) else: color_edit.add_theme_color_override(&"font_color", Color(1.0, 0.6, 0.6)) + +func update_after_change() -> void: + color_edit.text = get_value().trim_prefix("#") + queue_redraw() diff --git a/src/ui_elements/color_field.tscn b/src/ui_elements/color_field.tscn index 08077fe1..87da54ad 100644 --- a/src/ui_elements/color_field.tscn +++ b/src/ui_elements/color_field.tscn @@ -1,10 +1,7 @@ -[gd_scene load_steps=8 format=3 uid="uid://carf2o1y7wvmc"] +[gd_scene load_steps=5 format=3 uid="uid://carf2o1y7wvmc"] [ext_resource type="Script" path="res://src/ui_elements/color_field.gd" id="1_2pe1j"] -[ext_resource type="Texture2D" uid="uid://y0l74x73w0co" path="res://visual/CheckerboardMini.svg" id="2_t1t6b"] [ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="3_u777p"] -[ext_resource type="Script" path="res://src/ui_elements/color_popup.gd" id="4_4syqq"] -[ext_resource type="PackedScene" uid="uid://cpvtf3kaa2ltr" path="res://src/ui_elements/color_swatch.tscn" id="5_g5jha"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q6tej"] draw_center = false @@ -32,7 +29,6 @@ offset_right = 50.0 offset_bottom = 21.0 theme_override_constants/separation = 0 script = ExtResource("1_2pe1j") -checkerboard = ExtResource("2_t1t6b") [node name="LineEdit" type="LineEdit" parent="."] custom_minimum_size = Vector2(54, 0) @@ -55,189 +51,8 @@ focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"LeftConnectedButtonTransparent" -[node name="ColorPopup" type="Popup" parent="."] -transparent_bg = true -size = Vector2i(150, 220) -script = ExtResource("4_4syqq") - -[node name="PanelContainer" type="PanelContainer" parent="ColorPopup"] -offset_right = 150.0 -offset_bottom = 142.0 - -[node name="MarginContainer" type="MarginContainer" parent="ColorPopup/PanelContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 8 -theme_override_constants/margin_top = 0 -theme_override_constants/margin_right = 8 -theme_override_constants/margin_bottom = 8 - -[node name="MainContainer" type="VBoxContainer" parent="ColorPopup/PanelContainer/MarginContainer"] -layout_mode = 2 -theme_override_constants/separation = 8 - -[node name="Pure" type="VBoxContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="Label" type="Label" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure"] -layout_mode = 2 -text = "Pure" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="PureSwatches" type="HFlowContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/h_separation = 2 - -[node name="White" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure white" -color_hex = "ffffff" - -[node name="Black" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure black" -color_hex = "000000" - -[node name="Red" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure red" -color_hex = "ff0000" - -[node name="Green" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure green" -color_hex = "00ff00" - -[node name="Blue" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure blue" -color_hex = "0000ff" - -[node name="Blue2" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Pure/PureSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Pure blue" -color_hex = "none" - -[node name="Common" type="VBoxContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="Label" type="Label" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common"] -layout_mode = 2 -text = "Common" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="CommonSwatches" type="HFlowContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/h_separation = 2 - -[node name="Icon" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Icon color" -color_hex = "e0e0e0" - -[node name="IconDisabled" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Icon disabled color" -color_hex = "919191" - -[node name="Node2D" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Node2D color" -color_hex = "8da5f3" - -[node name="Control" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Control color" -color_hex = "8eef97" - -[node name="Node3D" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Node3D color" -color_hex = "fc7f7f" - -[node name="Animation" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Animation color" -color_hex = "c38ef1" - -[node name="Mesh" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Mesh color" -color_hex = "ffca5f" - -[node name="Shape" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Shape color" -color_hex = "2998ff" - -[node name="ShapeLight" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Shape light color" -color_hex = "a2d2ff" - -[node name="Input" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Common/CommonSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Input color" -color_hex = "69c4d4" - -[node name="Rainbow" type="VBoxContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="Label" type="Label" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow"] -layout_mode = 2 -text = "Rainbow" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="RainbowSwatches" type="HFlowContainer" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/h_separation = 2 - -[node name="Red" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow red" -color_hex = "ff4545" - -[node name="Yellow" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow yellow" -color_hex = "ffe345" - -[node name="Green" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow green" -color_hex = "80ff45" - -[node name="Aqua" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow aqua" -color_hex = "45ffa2" - -[node name="Blue" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow blue" -color_hex = "45d7ff" - -[node name="Purple" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow purple" -color_hex = "8045ff" - -[node name="Pink" parent="ColorPopup/PanelContainer/MarginContainer/MainContainer/Rainbow/RainbowSwatches" instance=ExtResource("5_g5jha")] -layout_mode = 2 -tooltip_text = "Rainbow pink" -color_hex = "ff4596" - [connection signal="focus_exited" from="LineEdit" to="." method="_on_focus_exited"] [connection signal="text_changed" from="LineEdit" to="." method="_on_line_edit_text_changed"] [connection signal="text_submitted" from="LineEdit" to="." method="_on_text_submitted"] [connection signal="pressed" from="Button" to="." method="_on_button_pressed"] [connection signal="resized" from="Button" to="." method="_on_button_resized"] -[connection signal="color_picked" from="ColorPopup" to="." method="_on_color_picked"] diff --git a/src/ui_elements/color_picker_popup.gd b/src/ui_elements/color_picker_popup.gd new file mode 100644 index 00000000..e1eae5d8 --- /dev/null +++ b/src/ui_elements/color_picker_popup.gd @@ -0,0 +1,18 @@ +extends Popup + +const GoodColorPickerType = preload("res://src/ui_elements/good_color_picker.gd") + +@onready var picker: GoodColorPickerType = $PanelContainer/MarginContainer/ColorPicker + +signal color_picked(new_color: String, final: bool) +var current_value: String + +func _ready() -> void: + picker.setup_color(current_value) + +func pick_color(color: String) -> void: + color_picked.emit(color, false) + + +func _on_popup_hide() -> void: + queue_free() diff --git a/src/ui_elements/color_picker_popup.tscn b/src/ui_elements/color_picker_popup.tscn new file mode 100644 index 00000000..f21191ba --- /dev/null +++ b/src/ui_elements/color_picker_popup.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://bs68u5annwepo"] + +[ext_resource type="Script" path="res://src/ui_elements/color_picker_popup.gd" id="1_wmbff"] +[ext_resource type="PackedScene" uid="uid://b1eig44cov474" path="res://src/ui_elements/good_color_picker.tscn" id="2_jafm6"] + +[node name="ColorPickerPopup" type="Popup"] +transparent_bg = true +size = Vector2i(218, 322) +visible = true +script = ExtResource("1_wmbff") + +[node name="PanelContainer" type="PanelContainer" parent="."] +offset_right = 4.0 +offset_bottom = 4.0 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 8 + +[node name="ColorPicker" parent="PanelContainer/MarginContainer" instance=ExtResource("2_jafm6")] +layout_mode = 2 + +[connection signal="popup_hide" from="." to="." method="_on_popup_hide"] +[connection signal="color_changed" from="PanelContainer/MarginContainer/ColorPicker" to="." method="pick_color"] diff --git a/src/ui_elements/color_popup.gd b/src/ui_elements/color_popup.gd index bcb99801..025e51b5 100644 --- a/src/ui_elements/color_popup.gd +++ b/src/ui_elements/color_popup.gd @@ -1,21 +1,88 @@ ## A popup for picking a color. extends Popup -signal color_picked(new_color: String) +const GoodColorPickerType = preload("res://src/ui_elements/good_color_picker.gd") +const ColorSwatchType = preload("res://src/ui_elements/color_swatch.gd") -@onready var swatch_containers: Array[Node] =\ - [%PureSwatches, %CommonSwatches, %RainbowSwatches] +const ColorSwatch = preload("res://src/ui_elements/color_swatch.tscn") + +signal color_picked(new_color: String, final: bool) +var current_value: String + +var palette_mode := true + +@onready var palettes_content: ScrollContainer = %Content/Palettes +@onready var palettes_content_container: VBoxContainer = %Content/Palettes/VBox +@onready var color_picker_content: VBoxContainer = %Content/ColorPicker +@onready var color_picker: GoodColorPickerType = %Content/ColorPicker +@onready var switch_mode_button: Button = $PanelContainer/MainContainer/SwitchMode +@onready var panel_container: PanelContainer = $PanelContainer + +var swatches_list: Array[ColorSwatchType] = [] # Updated manually. func _ready() -> void: - for swatch_container in swatch_containers: - for swatch in swatch_container.get_children(): - swatch.gui_input.connect(_on_gui_input) - -func _on_gui_input(event: InputEvent) -> void: - if event is InputEventMouseButton and event.is_pressed() and\ - event.button_mask == MOUSE_BUTTON_LEFT: - for swatch_container in swatch_containers: - for swatch in swatch_container.get_children(): - if swatch.get_global_rect().has_point(get_mouse_position()): - color_picked.emit(swatch.color_hex) - hide() + update_palettes() + update_color_picker() + +func update_palettes() -> void: + var reserved_color_palette := ColorPalette.new("", [NamedColor.new("none")]) + # TODO Gradients should be added here. + var displayed_palettes: Array[ColorPalette] = [reserved_color_palette] + displayed_palettes += GlobalSettings.get_palettes() + for palette in displayed_palettes: + if palette.named_colors.is_empty(): + continue + + var palette_container := VBoxContainer.new() + # Only the reserved palette should have an empty name. + if not palette.name.is_empty(): + var palette_label := Label.new() + palette_label.text = palette.name + palette_label.add_theme_font_size_override(&"font_size", 16) + palette_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + palette_container.add_child(palette_label) + + var swatch_container := HFlowContainer.new() + swatch_container.add_theme_constant_override(&"h_separation", 3) + for named_color in palette.named_colors: + var swatch := ColorSwatch.instantiate() + swatch.named_color = named_color + swatch.pressed.connect(pick_palette_color.bind(named_color.color)) + swatch_container.add_child(swatch) + swatches_list.append(swatch) + palette_container.add_child(swatch_container) + palettes_content_container.add_child(palette_container) + disable_swatches() + +func disable_swatches() -> void: + for swatch in swatches_list: + if swatch.named_color.color == current_value: + swatch.disabled = true + swatch.mouse_default_cursor_shape = Control.CURSOR_ARROW + else: + swatch.disabled = false + swatch.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + +func update_color_picker() -> void: + color_picker.setup_color(current_value) + +func pick_palette_color(color: String) -> void: + color_picked.emit(color, true) + +func pick_color(color: String) -> void: + current_value = color + disable_swatches() + color_picked.emit(color, false) + + +# Switching between palette mode and color picker mode. +func _switch_mode() -> void: + palette_mode = not palette_mode + switch_mode_button.text = tr(&"#palettes" if palette_mode else &"#color_picker") + color_picker_content.visible = not palette_mode + palettes_content.visible = palette_mode + + +func _on_popup_hide() -> void: + color_picked.emit(current_value, true) + queue_free() diff --git a/src/ui_elements/color_popup.tscn b/src/ui_elements/color_popup.tscn new file mode 100644 index 00000000..9422a7dc --- /dev/null +++ b/src/ui_elements/color_popup.tscn @@ -0,0 +1,87 @@ +[gd_scene load_steps=6 format=3 uid="uid://f5cljfdpe85v"] + +[ext_resource type="Script" path="res://src/ui_elements/color_popup.gd" id="1_t1mgf"] +[ext_resource type="PackedScene" uid="uid://b1eig44cov474" path="res://src/ui_elements/good_color_picker.tscn" id="2_jv3ea"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_prjsf"] +content_margin_left = 6.0 +content_margin_top = 2.0 +content_margin_right = 6.0 +content_margin_bottom = 3.0 +bg_color = Color(0.866667, 0.933333, 1, 0.0470588) +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5ydiu"] +content_margin_left = 6.0 +content_margin_top = 2.0 +content_margin_right = 6.0 +content_margin_bottom = 3.0 +bg_color = Color(0.866667, 0.933333, 1, 0.0705882) +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kiodn"] +content_margin_left = 6.0 +content_margin_top = 3.0 +content_margin_right = 6.0 +content_margin_bottom = 4.0 +bg_color = Color(0.866667, 0.933333, 1, 0.145098) +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[node name="ColorPopup" type="Popup"] +transparent_bg = true +size = Vector2i(218, 344) +visible = true +script = ExtResource("1_t1mgf") + +[node name="PanelContainer" type="PanelContainer" parent="."] +custom_minimum_size = Vector2(160, 0) +offset_right = 218.0 +offset_bottom = 43.0 + +[node name="MainContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="Content" type="MarginContainer" parent="PanelContainer/MainContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(214, 0) +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 2 + +[node name="Palettes" type="ScrollContainer" parent="PanelContainer/MainContainer/Content"] +custom_minimum_size = Vector2(0, 303) +layout_mode = 2 +horizontal_scroll_mode = 0 + +[node name="VBox" type="VBoxContainer" parent="PanelContainer/MainContainer/Content/Palettes"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 7 + +[node name="ColorPicker" parent="PanelContainer/MainContainer/Content" instance=ExtResource("2_jv3ea")] +visible = false +layout_mode = 2 + +[node name="SwitchMode" type="Button" parent="PanelContainer/MainContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_override_colors/font_disabled_color = Color(0.866667, 0.933333, 1, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_prjsf") +theme_override_styles/hover = SubResource("StyleBoxFlat_5ydiu") +theme_override_styles/pressed = SubResource("StyleBoxFlat_kiodn") +text = "#palettes" + +[node name="Label" type="Label" parent="."] +offset_right = 40.0 +offset_bottom = 22.0 + +[connection signal="popup_hide" from="." to="." method="_on_popup_hide"] +[connection signal="color_changed" from="PanelContainer/MainContainer/Content/ColorPicker" to="." method="pick_color"] +[connection signal="pressed" from="PanelContainer/MainContainer/SwitchMode" to="." method="_switch_mode"] diff --git a/src/ui_elements/color_swatch.gd b/src/ui_elements/color_swatch.gd index 19183e2f..e853e3cf 100644 --- a/src/ui_elements/color_swatch.gd +++ b/src/ui_elements/color_swatch.gd @@ -1,10 +1,62 @@ -extends PanelContainer +extends Button -@export var color_hex := "" +const code_font = preload("res://visual/fonts/FontMono.ttf") +const checkerboard = preload("res://visual/Checkerboard.svg") +const plus_icon = preload("res://visual/icons/Plus.svg") +const gear_icon = preload("res://visual/icons/GearOutlined.svg") -@onready var color_rect: ColorRect = $ColorRect +enum Type {CHOOSE_COLOR, CONFIGURE_COLOR, ADD_COLOR} +var type := Type.CHOOSE_COLOR + +var named_color: NamedColor func _ready() -> void: - if color_hex == "none": - color_rect.queue_free() - color_rect.color = Color.from_string(color_hex, Color(0, 0, 0)) + if type == Type.ADD_COLOR: + tooltip_text = tr(&"#add_color") + +func _draw() -> void: + if type == Type.ADD_COLOR: + plus_icon.draw(get_canvas_item(), (size - plus_icon.get_size()) / 2) + return + + var color := Color.from_string(named_color.color, Color(0, 0, 0)) + var bounds := Vector2(2, 2) + if color.a != 1 or named_color.color == "none": + draw_texture_rect(checkerboard, Rect2(bounds, size - bounds * 2), false) + if named_color.color != "none": + draw_rect(Rect2(bounds, size - bounds * 2), color) + if type == Type.CONFIGURE_COLOR and is_hovered(): + gear_icon.draw(get_canvas_item(), (size - gear_icon.get_size()) / 2) + +func _make_custom_tooltip(_for_text: String) -> Object: + if type == Type.ADD_COLOR: + return null + elif type == Type.CHOOSE_COLOR or type == Type.CONFIGURE_COLOR: + var rtl := RichTextLabel.new() + rtl.autowrap_mode = TextServer.AUTOWRAP_OFF + rtl.fit_content = true + rtl.bbcode_enabled = true + rtl.add_theme_font_override(&"mono_font", code_font) + # Set up the text. + if not named_color.name.is_empty(): + rtl.add_text(named_color.name) + rtl.newline() + rtl.push_mono() + if named_color.color == "none": + rtl.add_text("none") + else: + rtl.add_text("#" + named_color.color) + return rtl + + return null + + +# For configuration swatches. +func change_color_name(new_name: String) -> void: + named_color.name = new_name + GlobalSettings.save_user_data() + +func change_color(new_color: String) -> void: + named_color.color = new_color + GlobalSettings.save_user_data() + queue_redraw() diff --git a/src/ui_elements/color_swatch.tscn b/src/ui_elements/color_swatch.tscn index 31a3a2ec..f89e97da 100644 --- a/src/ui_elements/color_swatch.tscn +++ b/src/ui_elements/color_swatch.tscn @@ -1,36 +1,37 @@ -[gd_scene load_steps=4 format=3 uid="uid://cpvtf3kaa2ltr"] +[gd_scene load_steps=5 format=3 uid="uid://cpvtf3kaa2ltr"] [ext_resource type="Script" path="res://src/ui_elements/color_swatch.gd" id="1_2b2yq"] -[ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://visual/Checkerboard.svg" id="2_f4i7i"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i5pvy"] -bg_color = Color(0, 0, 0, 0) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.866667, 0.933333, 1, 1) -corner_radius_top_left = 1 -corner_radius_top_right = 1 -corner_radius_bottom_right = 1 -corner_radius_bottom_left = 1 -corner_detail = 2 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m3oh3"] +bg_color = Color(0.164706, 0.188235, 0.301961, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[node name="ColorSwatch" type="PanelContainer"] -mouse_default_cursor_shape = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_i5pvy") -script = ExtResource("1_2b2yq") -color_hex = null +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kojaf"] +bg_color = Color(0.247059, 0.313726, 0.45098, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[node name="TextureRect" type="TextureRect" parent="."] -custom_minimum_size = Vector2(16, 16) -layout_mode = 2 -mouse_filter = 2 -texture = ExtResource("2_f4i7i") -expand_mode = 1 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ym6fi"] +bg_color = Color(0.34902, 0.52549, 0.701961, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[node name="ColorRect" type="ColorRect" parent="."] -custom_minimum_size = Vector2(16, 16) -layout_mode = 2 -size_flags_horizontal = 0 -mouse_filter = 2 +[node name="ColorSwatch" type="Button"] +custom_minimum_size = Vector2(22, 22) +offset_right = 20.0 +offset_bottom = 20.0 +tooltip_text = "_" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_override_styles/normal = SubResource("StyleBoxFlat_m3oh3") +theme_override_styles/hover = SubResource("StyleBoxFlat_kojaf") +theme_override_styles/pressed = SubResource("StyleBoxFlat_ym6fi") +theme_override_styles/disabled = SubResource("StyleBoxFlat_ym6fi") +script = ExtResource("1_2b2yq") diff --git a/src/ui_elements/context_popup.gd b/src/ui_elements/context_popup.gd index f9728ce1..8895940f 100644 --- a/src/ui_elements/context_popup.gd +++ b/src/ui_elements/context_popup.gd @@ -1,31 +1,33 @@ ## The standard context menu popup. extends Popup +@onready var panel: PanelContainer = $PanelContainer @onready var main_container: VBoxContainer = $PanelContainer/MainContainer -func add_button(butt: Button, top_corners := false, bottom_corners := false,\ -should_reset_size := true) -> void: +func add_button(butt: Button, should_reset_size := true) -> void: if not butt is CheckBox: var normal_stylebox := StyleBoxEmpty.new() normal_stylebox.set_content_margin_all(3) butt.add_theme_stylebox_override(&"normal", normal_stylebox) var hover_stylebox := StyleBoxFlat.new() hover_stylebox.bg_color = Color("#def1") + hover_stylebox.set_content_margin_all(3) + hover_stylebox.set_corner_radius_all(4) var pressed_stylebox := StyleBoxFlat.new() pressed_stylebox.bg_color = Color("#def2") + pressed_stylebox.set_content_margin_all(3) + pressed_stylebox.set_corner_radius_all(4) var disabled_stylebox := StyleBoxFlat.new() disabled_stylebox.bg_color = Color("#05060766") - for stylebox: StyleBoxFlat in [hover_stylebox, pressed_stylebox, disabled_stylebox]: - stylebox.set_content_margin_all(3) - if top_corners: - stylebox.corner_radius_top_left = 5 - stylebox.corner_radius_top_right = 5 - if bottom_corners: - stylebox.corner_radius_bottom_left = 5 - stylebox.corner_radius_bottom_right = 5 + disabled_stylebox.set_content_margin_all(3) + disabled_stylebox.set_corner_radius_all(4) butt.add_theme_stylebox_override(&"hover", hover_stylebox) butt.add_theme_stylebox_override(&"disabled", disabled_stylebox) butt.add_theme_stylebox_override(&"pressed", pressed_stylebox) + butt.pressed.connect(queue_free) + if not butt.disabled: + butt.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + butt.focus_mode = Control.FOCUS_NONE main_container.add_child(butt) if should_reset_size: reset_size() @@ -35,15 +37,20 @@ func set_btn_array(buttons: Array[Button]) -> void: button.free() if buttons.is_empty(): return - elif buttons.size() == 1: - add_button(buttons[0], true, true) - return else: - add_button(buttons.pop_front(), true, false, false) - for i in buttons.size() - 1: - add_button(buttons.pop_front(), false, false, false) - add_button(buttons[0], false, true) + var last_button_idx := buttons.size() - 1 + for i in last_button_idx: + add_button(buttons[i], false) + add_button(buttons[last_button_idx]) + +func set_min_width(w: float) -> void: + min_size.x = ceili(w) + panel.custom_minimum_size.x = w func get_button_count() -> int: return main_container.get_child_count() + + +func _on_popup_hide() -> void: + queue_free() diff --git a/src/ui_elements/context_popup.tscn b/src/ui_elements/context_popup.tscn index c3bb29df..26d475a3 100644 --- a/src/ui_elements/context_popup.tscn +++ b/src/ui_elements/context_popup.tscn @@ -5,17 +5,18 @@ [node name="ContextPopup" type="Popup"] disable_3d = true transparent_bg = true -size = Vector2i(100, 27) +size = Vector2i(4, 4) visible = true script = ExtResource("1_d45ly") [node name="PanelContainer" type="PanelContainer" parent="."] -custom_minimum_size = Vector2(96, 0) -offset_right = 96.0 -offset_bottom = 27.0 +offset_right = 4.0 +offset_bottom = 4.0 [node name="MainContainer" type="VBoxContainer" parent="PanelContainer"] unique_name_in_owner = true clip_contents = true layout_mode = 2 theme_override_constants/separation = 0 + +[connection signal="popup_hide" from="." to="." method="_on_popup_hide"] diff --git a/src/ui_elements/dropdown.gd b/src/ui_elements/dropdown.gd index d2ad1ad9..e8782685 100644 --- a/src/ui_elements/dropdown.gd +++ b/src/ui_elements/dropdown.gd @@ -1,9 +1,10 @@ ## A dropdown with multiple options, not tied to any attribute. extends HBoxContainer +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") + signal value_changed(new_value: String) -@onready var value_picker: Popup = $ContextPopup @onready var line_edit: BetterLineEdit = $LineEdit @export var values: Array[String] @@ -21,16 +22,6 @@ func _ready() -> void: if not values.is_empty(): current_value = values[0] - var btn_arr: Array[Button] = [] - for value in values: - var button := Button.new() - button.text = value - button.pressed.connect(_on_value_chosen.bind(value)) - button.mouse_default_cursor_shape = CURSOR_POINTING_HAND - button.alignment = HORIZONTAL_ALIGNMENT_CENTER - btn_arr.append(button) - value_picker.set_btn_array(btn_arr) - var max_length := 0 for value in values: max_length = maxi(value.length(), max_length) @@ -41,11 +32,23 @@ func _ready() -> void: line_edit.size.x = 0 func _on_button_pressed() -> void: - value_picker.popup(Utils.calculate_popup_rect( - line_edit.global_position, line_edit.size, value_picker.size)) + var btn_arr: Array[Button] = [] + for value in values: + var button := Button.new() + button.text = value + button.pressed.connect(_on_value_chosen.bind(value)) + button.alignment = HORIZONTAL_ALIGNMENT_CENTER + if value == current_value: + button.disabled = true + btn_arr.append(button) + + var value_picker := ContextPopup.instantiate() + add_child(value_picker) + value_picker.set_btn_array(btn_arr) + value_picker.set_min_width(50) + Utils.popup_under_control(value_picker, line_edit) func _on_value_chosen(new_value: String) -> void: - value_picker.hide() current_value = new_value diff --git a/src/ui_elements/dropdown.tscn b/src/ui_elements/dropdown.tscn index a1a89442..99e9db70 100644 --- a/src/ui_elements/dropdown.tscn +++ b/src/ui_elements/dropdown.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=7 format=3 uid="uid://dbu1lvajypafb"] +[gd_scene load_steps=6 format=3 uid="uid://dbu1lvajypafb"] [ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="1_0ifbb"] [ext_resource type="Script" path="res://src/ui_elements/dropdown.gd" id="1_133xu"] [ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://visual/icons/Arrow.svg" id="2_4oygd"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="3_l1jr8"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q1iin"] draw_center = false @@ -54,16 +53,6 @@ theme_type_variation = &"LeftConnectedButton" icon = ExtResource("2_4oygd") expand_icon = true -[node name="ContextPopup" parent="." instance=ExtResource("3_l1jr8")] -size = Vector2i(60, 27) -visible = false - -[node name="PanelContainer" parent="ContextPopup" index="0"] -custom_minimum_size = Vector2(52, 0) -offset_right = 52.0 - [connection signal="text_changed" from="LineEdit" to="." method="_on_text_changed"] [connection signal="text_submitted" from="LineEdit" to="." method="_on_text_submitted"] [connection signal="pressed" from="Button" to="." method="_on_button_pressed"] - -[editable path="ContextPopup"] diff --git a/src/ui_elements/enum_field.gd b/src/ui_elements/enum_field.gd index a2861824..66baf542 100644 --- a/src/ui_elements/enum_field.gd +++ b/src/ui_elements/enum_field.gd @@ -1,19 +1,19 @@ ## An editor to be tied to an AttributeEnum. extends AttributeEditor +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") const bold_font = preload("res://visual/fonts/FontBold.ttf") -@onready var value_picker: Popup = $ContextPopup @onready var indicator: LineEdit = $LineEdit -signal value_changed(new_value: String) +signal value_changed(new_value: String, update_type: UpdateType) var _value: String # Must not be updated directly. -func set_value(new_value: String, emit_value_changed := true): - if _value != new_value: +func set_value(new_value: String, update_type := UpdateType.REGULAR): + if _value != new_value or update_type == UpdateType.FINAL: _value = new_value - if emit_value_changed: - value_changed.emit(new_value) + if update_type != UpdateType.NO_SIGNAL: + value_changed.emit(new_value, update_type) func get_value() -> String: return _value @@ -21,37 +21,40 @@ func get_value() -> String: func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) - indicator.text = str(get_value()) + set_value(attribute.get_value()) indicator.tooltip_text = attribute_name + indicator.text = get_value() func _on_button_pressed() -> void: + var value_picker := ContextPopup.instantiate() var buttons_arr: Array[Button] = [] for enum_constant in attribute.possible_values: var btn := Button.new() - btn.text = str(enum_constant) + btn.text = enum_constant btn.pressed.connect(_on_option_pressed.bind(enum_constant)) if enum_constant == get_value(): btn.disabled = true - else: - btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if attribute != null and enum_constant == attribute.default: btn.add_theme_font_override(&"font", bold_font) buttons_arr.append(btn) + add_child(value_picker) value_picker.set_btn_array(buttons_arr) - value_picker.popup(Utils.calculate_popup_rect( - indicator.global_position, indicator.size, value_picker.size)) + value_picker.set_min_width(74) + Utils.popup_under_control(value_picker, indicator) func _on_option_pressed(option: String) -> void: - value_picker.hide() set_value(option) -func _on_value_changed(new_value: String) -> void: +func _on_value_changed(new_value: String, update_type: UpdateType) -> void: indicator.text = new_value - if attribute != null: - attribute.set_value(new_value) - set_text_tint() + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) + set_text_tint() func _on_text_submitted(new_text: String) -> void: diff --git a/src/ui_elements/enum_field.tscn b/src/ui_elements/enum_field.tscn index e5de8270..82a101b0 100644 --- a/src/ui_elements/enum_field.tscn +++ b/src/ui_elements/enum_field.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=7 format=3 uid="uid://d2da0thyq5rq8"] +[gd_scene load_steps=6 format=3 uid="uid://d2da0thyq5rq8"] [ext_resource type="Script" path="res://src/ui_elements/enum_field.gd" id="1_1jqoy"] [ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="2_4bajq"] [ext_resource type="Texture2D" uid="uid://coda6chhcatal" path="res://visual/icons/Arrow.svg" id="3_vhd8v"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="4_4cpb7"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y4kmw"] draw_center = false @@ -54,7 +53,4 @@ theme_type_variation = &"LeftConnectedButton" icon = ExtResource("3_vhd8v") expand_icon = true -[node name="ContextPopup" parent="." instance=ExtResource("4_4cpb7")] -visible = false - [connection signal="pressed" from="Button" to="." method="_on_button_pressed"] diff --git a/src/ui_elements/flag_field.gd b/src/ui_elements/flag_field.gd index 6a978a70..27418a6c 100644 --- a/src/ui_elements/flag_field.gd +++ b/src/ui_elements/flag_field.gd @@ -22,11 +22,11 @@ func _on_toggled(is_state_pressed: bool) -> void: func _ready() -> void: value_changed.connect(_on_value_changed) button_pressed = (get_value() == 1) - text = str(get_value()) + text = String.num_uint64(get_value()) func _on_value_changed(new_value: int) -> void: - button_pressed = new_value == 1 - text = str(new_value) + button_pressed = (new_value == 1) + text = String.num_uint64(new_value) func _on_mouse_entered() -> void: diff --git a/src/ui_elements/good_color_picker.gd b/src/ui_elements/good_color_picker.gd new file mode 100644 index 00000000..f5450937 --- /dev/null +++ b/src/ui_elements/good_color_picker.gd @@ -0,0 +1,264 @@ +extends VBoxContainer + +const handle_texture = preload("res://visual/icons/HandleBig.svg") +const slider_arrow = preload("res://visual/icons/SliderArrow.svg") +const side_slider_arrow = preload("res://visual/icons/SideSliderArrow.svg") + +var UR := UndoRedo.new() + +enum SliderMode {RGB, HSV} +var slider_mode: SliderMode: + set(new_mode): + slider_mode = new_mode + var disabled_button := hsv_button if new_mode == SliderMode.HSV else rgb_button + for btn in [hsv_button, rgb_button]: + btn.disabled = (btn == disabled_button) + btn.mouse_default_cursor_shape = Control.CURSOR_ARROW if\ + btn == disabled_button else Control.CURSOR_POINTING_HAND + if slider_mode == SliderMode.RGB: + slider1_track.material.set_shader_parameter(&"interpolation", 0) + slider2_track.material.set_shader_parameter(&"interpolation", 1) + slider3_track.material.set_shader_parameter(&"interpolation", 2) + elif slider_mode == SliderMode.HSV: + slider1_track.material.set_shader_parameter(&"interpolation", 3) + slider2_track.material.set_shader_parameter(&"interpolation", 4) + slider3_track.material.set_shader_parameter(&"interpolation", 5) + # Clamping like this doesn't change the hex representation, but + # it helps avoid locking certain sliders (e.g. hue slider when saturation is 0). + var new_color := color + new_color.h = clampf(new_color.h, 0.0, 0.9999) + new_color.s = clampf(new_color.s, 0.0001, 1.0) + new_color.v = clampf(new_color.v, 0.0001, 1.0) + set_color(new_color) + + slider1.queue_redraw() + slider2.queue_redraw() + slider3.queue_redraw() + +@onready var side_slider: MarginContainer = $ShapeContainer/SideSlider +@onready var side_slider_drawn: ColorRect = $ShapeContainer/SideSlider/SideSliderDraw +@onready var color_wheel: MarginContainer = $ShapeContainer/ColorWheel +@onready var color_wheel_drawn: ColorRect = $ShapeContainer/ColorWheel/ColorWheelDraw +@onready var rgb_button: Button = $SliderContainer/ColorSpaceContainer/RGB +@onready var hsv_button: Button = $SliderContainer/ColorSpaceContainer/HSV +@onready var start_color_rect: ColorRect = %ColorsDisplay/StartColorRect +@onready var color_rect: ColorRect = %ColorsDisplay/ColorRect +@onready var slider1: MarginContainer = %Slider1 +@onready var slider2: MarginContainer = %Slider2 +@onready var slider3: MarginContainer = %Slider3 +@onready var slider1_track: ColorRect = %Slider1/ColorTrack +@onready var slider2_track: ColorRect = %Slider2/ColorTrack +@onready var slider3_track: ColorRect = %Slider3/ColorTrack +@onready var none_button: Button = $ColorContainer/None +@onready var reset_color_button: Button = %ColorsDisplay/ColorRect/ResetColorButton +@onready var center: Vector2 = color_wheel_drawn.get_rect().get_center() + +var color_wheel_surface := RenderingServer.canvas_item_create() +var is_dragging_slider1 := false +var is_dragging_slider2 := false +var is_dragging_slider3 := false +var is_dragging_side_slider := false + +var starting_color: String +signal color_changed(new_color: String) +var is_none := false +var color := Color(0, 0, 0) + +# To be called right after the color picker is added. +func setup_color(new_color: String) -> void: + starting_color = new_color + is_none = (new_color == "none") + setup_none_button() + color = Color.from_string(new_color, Color(0, 0, 0)) + slider_mode = GlobalSettings.save_data.color_picker_slider_mode + if not is_node_ready(): + await ready + update() + +func set_color(new_color: Color) -> void: + if is_none: + toggle_none() + if color != new_color: + color = new_color + update() + color_changed.emit(new_color.to_html(false)) + +func update() -> void: + color_wheel_drawn.material.set_shader_parameter(&"v", color.v) + side_slider_drawn.material.set_shader_parameter(&"base_color", + Color.from_hsv(color.h, color.s, 1.0)) + slider1_track.material.set_shader_parameter(&"base_color", color) + slider2_track.material.set_shader_parameter(&"base_color", color) + slider3_track.material.set_shader_parameter(&"base_color", color) + color_rect.color = color + if starting_color == "none": + start_color_rect.color = Color.TRANSPARENT + elif !starting_color.is_empty(): + start_color_rect.color = Color(starting_color) + queue_redraw() + side_slider.queue_redraw() + color_wheel_drawn.queue_redraw() + slider1.queue_redraw() + slider2.queue_redraw() + slider3.queue_redraw() + + +func _ready() -> void: + RenderingServer.canvas_item_set_parent(color_wheel_surface, + color_wheel_drawn.get_canvas_item()) + + +func _on_side_slider_gui_input(event: InputEvent) -> void: + if Utils.is_event_drag(event) or Utils.is_event_drag_start(event): + var new_color := color + new_color.v = clampf(1 - event.position.y / side_slider.size.y, 0.0001, 1.0) + set_color(new_color) + if Utils.is_event_drag_start(event) or Utils.is_event_drag_end(event): + is_dragging_side_slider = event.is_pressed() + side_slider.queue_redraw() + +func _on_color_wheel_gui_input(event: InputEvent) -> void: + if Utils.is_event_drag(event) or Utils.is_event_drag_start(event): + var new_color := color + var event_pos_on_wheel: Vector2 = event.position + color_wheel.position -\ + color_wheel_drawn.position + new_color.h = fposmod(center.angle_to_point(event_pos_on_wheel), TAU) / TAU + new_color.s = minf(event_pos_on_wheel.distance_to(center) * 2 /\ + color_wheel_drawn.size.x, 1.0) + set_color(new_color) + +func _on_slider1_gui_input(event: InputEvent) -> void: + if Utils.is_event_drag(event) or Utils.is_event_drag_start(event): + var new_color := color + if slider_mode == SliderMode.RGB: + new_color.r = clampf(event.position.x / slider1.size.x, 0.0, 1.0) + elif slider_mode == SliderMode.HSV: + new_color.h = clampf(event.position.x / slider1.size.x, 0.0, 0.9999) + set_color(new_color) + if Utils.is_event_drag_start(event) or Utils.is_event_drag_end(event): + is_dragging_slider1 = event.is_pressed() + slider1.queue_redraw() + +func _on_slider2_gui_input(event: InputEvent) -> void: + if Utils.is_event_drag(event) or Utils.is_event_drag_start(event): + var new_color := color + if slider_mode == SliderMode.RGB: + new_color.g = clampf(event.position.x / slider2.size.x, 0.0, 1.0) + elif slider_mode == SliderMode.HSV: + new_color.s = clampf(event.position.x / slider2.size.x, 0.0001, 1.0) + set_color(new_color) + if Utils.is_event_drag_start(event) or Utils.is_event_drag_end(event): + is_dragging_slider2 = event.is_pressed() + slider2.queue_redraw() + +func _on_slider3_gui_input(event: InputEvent) -> void: + if Utils.is_event_drag(event) or Utils.is_event_drag_start(event): + var new_color := color + if slider_mode == SliderMode.RGB: + new_color.b = clampf(event.position.x / slider3.size.x, 0.0, 1.0) + elif slider_mode == SliderMode.HSV: + new_color.v = clampf(event.position.x / slider3.size.x, 0.0001, 1.0) + set_color(new_color) + if Utils.is_event_drag_start(event) or Utils.is_event_drag_end(event): + is_dragging_slider3 = event.is_pressed() + slider3.queue_redraw() + + +func _on_rgb_pressed() -> void: + slider_mode = SliderMode.RGB + GlobalSettings.modify_save_data(&"color_picker_slider_mode", SliderMode.RGB) + +func _on_hsv_pressed() -> void: + slider_mode = SliderMode.HSV + GlobalSettings.modify_save_data(&"color_picker_slider_mode", SliderMode.HSV) + + +# Draw inside the side slider to give it a little arrow to the side. +func _on_side_slider_draw() -> void: + var arrow_modulate := Color(1, 1, 1) if is_dragging_side_slider\ + else Color(0.8, 0.8, 0.8) + side_slider.draw_texture(side_slider_arrow, Vector2(0, side_slider.size.y *\ + (1 - color.v) - side_slider_arrow.get_height() / 2.0), arrow_modulate) + +func _draw() -> void: + RenderingServer.canvas_item_clear(color_wheel_surface) + # Draw color wheel handle. + var point_pos := center + Vector2(center.x * cos(color.h * TAU), + center.y * sin(color.h * TAU)) * color.s + RenderingServer.canvas_item_add_texture_rect(color_wheel_surface, Rect2(point_pos -\ + handle_texture.get_size() / 2, handle_texture.get_size()), handle_texture) + +func _on_slider1_draw() -> void: + var offset := color.r if slider_mode == SliderMode.RGB else color.h + var arrow_modulate := Color(1, 1, 1) if is_dragging_slider1 else Color(0.8, 0.8, 0.8) + slider1.draw_texture(slider_arrow, Vector2(slider1.size.x * offset -\ + slider_arrow.get_width() / 2.0, slider1_track.size.y), arrow_modulate) + var chr := "R" if slider_mode == SliderMode.RGB else "H" + slider1.draw_string(ThemeDB.get_project_theme().default_font, Vector2(-14, 11), chr, + HORIZONTAL_ALIGNMENT_LEFT, -1, 14) + +func _on_slider2_draw() -> void: + var offset := color.g if slider_mode == SliderMode.RGB else color.s + var arrow_modulate := Color(1, 1, 1) if is_dragging_slider2 else Color(0.8, 0.8, 0.8) + slider2.draw_texture(slider_arrow, Vector2(slider2.size.x * offset -\ + slider_arrow.get_width() / 2.0, slider2_track.size.y), arrow_modulate) + var chr := "G" if slider_mode == SliderMode.RGB else "S" + slider2.draw_string(ThemeDB.get_project_theme().default_font, Vector2(-14, 11), chr, + HORIZONTAL_ALIGNMENT_LEFT, -1, 14) + +func _on_slider3_draw() -> void: + var offset := color.b if slider_mode == SliderMode.RGB else color.v + var arrow_modulate := Color(1, 1, 1) if is_dragging_slider3 else Color(0.8, 0.8, 0.8) + slider3.draw_texture(slider_arrow, Vector2(slider3.size.x * offset -\ + slider_arrow.get_width() / 2.0, slider3_track.size.y), arrow_modulate) + var chr := "B" if slider_mode == SliderMode.RGB else "V" + slider3.draw_string(ThemeDB.get_project_theme().default_font, Vector2(-14, 11), chr, + HORIZONTAL_ALIGNMENT_LEFT, -1, 14) + + +# This sets the color to "none", usually when the button is pressed. +func toggle_none() -> void: + is_none = not is_none + if is_none: + color_changed.emit("none") + else: + color_changed.emit(color.to_html(false)) + setup_none_button() + +func setup_none_button() -> void: + none_button.button_pressed = is_none + none_button.tooltip_text = tr(&"#enable_color") if is_none else tr(&"#disable_color") + + +func _on_reset_color_button_gui_input(event: InputEvent) -> void: + if event is InputEventMouseMotion and event.button_mask != MOUSE_BUTTON_MASK_LEFT: + if starting_color == "none" or color.to_html(false) == starting_color: + reset_color_button.disabled = true + return + reset_color_button.disabled = false + if color.get_luminance() < 0.455: + reset_color_button.add_theme_color_override(&"icon_hover_color", Color.WHITE) + reset_color_button.add_theme_color_override(&"icon_pressed_color", + Color(0.5, 1, 1)) + else: + reset_color_button.add_theme_color_override(&"icon_hover_color", Color.BLACK) + reset_color_button.add_theme_color_override(&"icon_pressed_color", + Color(0, 0.5, 0.5)) + +func _on_reset_color_button_pressed() -> void: + reset_color_button.disabled = true + if starting_color != "none": + set_color(starting_color) + + +func _input(event: InputEvent) -> void: + if not visible: + return + + if event.is_action_pressed(&"redo"): + if UR.has_redo(): + UR.redo() + accept_event() + elif event.is_action_pressed(&"undo") and UR.has_undo(): + UR.undo() + accept_event() diff --git a/src/ui_elements/good_color_picker.tscn b/src/ui_elements/good_color_picker.tscn new file mode 100644 index 00000000..3b9f41a8 --- /dev/null +++ b/src/ui_elements/good_color_picker.tscn @@ -0,0 +1,233 @@ +[gd_scene load_steps=15 format=3 uid="uid://b1eig44cov474"] + +[ext_resource type="Script" path="res://src/ui_elements/good_color_picker.gd" id="1_0pc78"] +[ext_resource type="Shader" path="res://src/shaders/color_wheel.gdshader" id="2_nf1uk"] +[ext_resource type="Shader" path="res://src/shaders/slider_visuals.gdshader" id="5_acxpg"] +[ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://visual/icons/Reload.svg" id="5_rh0xc"] +[ext_resource type="Texture2D" uid="uid://d36qn2f7a0nok" path="res://visual/icons/NoneColor.svg" id="5_rnr60"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_wl372"] +shader = ExtResource("2_nf1uk") +shader_parameter/v = 1.0 + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_gihhg"] +shader = ExtResource("5_acxpg") +shader_parameter/base_color = Vector3(0, 0, 0) +shader_parameter/interpolation = 5 +shader_parameter/horizontal = false +shader_parameter/inverted = true + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mqx76"] +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color(0.866667, 0.933333, 1, 0.133333) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cle8x"] +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color(0.866667, 0.933333, 1, 0.2) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sndty"] +bg_color = Color(0.866667, 0.933333, 1, 0.333333) +border_width_top = 2 + +[sub_resource type="ButtonGroup" id="ButtonGroup_w0iiw"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_fvg6q"] +shader = ExtResource("5_acxpg") +shader_parameter/base_color = Vector3(1, 1, 1) +shader_parameter/interpolation = 0 +shader_parameter/horizontal = true +shader_parameter/inverted = false + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_dlmj5"] +shader = ExtResource("5_acxpg") +shader_parameter/base_color = Vector3(1, 1, 1) +shader_parameter/interpolation = 0 +shader_parameter/horizontal = true +shader_parameter/inverted = false + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_p3pbp"] +shader = ExtResource("5_acxpg") +shader_parameter/base_color = Vector3(1, 1, 1) +shader_parameter/interpolation = 0 +shader_parameter/horizontal = true +shader_parameter/inverted = false + +[node name="GoodColorPicker" type="VBoxContainer"] +offset_right = 190.0 +offset_bottom = 266.0 +theme_override_constants/separation = 6 +script = ExtResource("1_0pc78") + +[node name="ShapeContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_constants/separation = 0 +alignment = 1 + +[node name="ColorWheel" type="MarginContainer" parent="ShapeContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 6 +theme_override_constants/margin_top = 6 +theme_override_constants/margin_right = 6 +theme_override_constants/margin_bottom = 6 + +[node name="ColorWheelDraw" type="ColorRect" parent="ShapeContainer/ColorWheel"] +material = SubResource("ShaderMaterial_wl372") +custom_minimum_size = Vector2(160, 160) +layout_mode = 2 +mouse_filter = 1 + +[node name="SideSlider" type="MarginContainer" parent="ShapeContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 8 + +[node name="SideSliderDraw" type="ColorRect" parent="ShapeContainer/SideSlider"] +material = SubResource("ShaderMaterial_gihhg") +custom_minimum_size = Vector2(18, 0) +layout_mode = 2 +mouse_filter = 1 + +[node name="ColorContainer" type="HBoxContainer" parent="."] +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 + +[node name="None" type="Button" parent="ColorContainer"] +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +theme_override_font_sizes/font_size = 10 +toggle_mode = true +icon = ExtResource("5_rnr60") + +[node name="ColorsDisplay" type="HBoxContainer" parent="ColorContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 18) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +theme_override_constants/separation = 0 + +[node name="StartColorRect" type="ColorRect" parent="ColorContainer/ColorsDisplay"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ColorRect" type="ColorRect" parent="ColorContainer/ColorsDisplay"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ResetColorButton" type="Button" parent="ColorContainer/ColorsDisplay/ColorRect"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +focus_mode = 0 +theme_type_variation = &"TextButton" +theme_override_colors/icon_normal_color = Color(0, 0, 0, 0) +theme_override_colors/icon_disabled_color = Color(0, 0, 0, 0) +icon = ExtResource("5_rh0xc") +icon_alignment = 1 + +[node name="SliderContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="ColorSpaceContainer" type="HBoxContainer" parent="SliderContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="RGB" type="Button" parent="SliderContainer/ColorSpaceContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_override_colors/font_disabled_color = Color(0.866667, 0.933333, 1, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_mqx76") +theme_override_styles/hover = SubResource("StyleBoxFlat_cle8x") +theme_override_styles/pressed = SubResource("StyleBoxFlat_sndty") +theme_override_styles/disabled = SubResource("StyleBoxFlat_sndty") +toggle_mode = true +button_group = SubResource("ButtonGroup_w0iiw") +text = "RGB" + +[node name="HSV" type="Button" parent="SliderContainer/ColorSpaceContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_override_colors/font_disabled_color = Color(0.866667, 0.933333, 1, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_mqx76") +theme_override_styles/hover = SubResource("StyleBoxFlat_cle8x") +theme_override_styles/pressed = SubResource("StyleBoxFlat_sndty") +theme_override_styles/disabled = SubResource("StyleBoxFlat_sndty") +toggle_mode = true +button_group = SubResource("ButtonGroup_w0iiw") +text = "HSV" + +[node name="HBoxContainer" type="HBoxContainer" parent="SliderContainer"] +layout_mode = 2 + +[node name="Spacer" type="Control" parent="SliderContainer/HBoxContainer"] +custom_minimum_size = Vector2(10, 0) +layout_mode = 2 + +[node name="TracksContainer" type="VBoxContainer" parent="SliderContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +alignment = 2 + +[node name="Slider1" type="MarginContainer" parent="SliderContainer/HBoxContainer/TracksContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_bottom = 8 + +[node name="ColorTrack" type="ColorRect" parent="SliderContainer/HBoxContainer/TracksContainer/Slider1"] +material = SubResource("ShaderMaterial_fvg6q") +custom_minimum_size = Vector2(0, 12) +layout_mode = 2 +mouse_filter = 1 + +[node name="Slider2" type="MarginContainer" parent="SliderContainer/HBoxContainer/TracksContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_bottom = 8 + +[node name="ColorTrack" type="ColorRect" parent="SliderContainer/HBoxContainer/TracksContainer/Slider2"] +material = SubResource("ShaderMaterial_dlmj5") +custom_minimum_size = Vector2(0, 12) +layout_mode = 2 +mouse_filter = 1 + +[node name="Slider3" type="MarginContainer" parent="SliderContainer/HBoxContainer/TracksContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_bottom = 8 + +[node name="ColorTrack" type="ColorRect" parent="SliderContainer/HBoxContainer/TracksContainer/Slider3"] +material = SubResource("ShaderMaterial_p3pbp") +custom_minimum_size = Vector2(0, 12) +layout_mode = 2 +mouse_filter = 1 + +[connection signal="gui_input" from="." to="." method="_on_gui_input"] +[connection signal="gui_input" from="ShapeContainer/ColorWheel" to="." method="_on_color_wheel_gui_input"] +[connection signal="draw" from="ShapeContainer/SideSlider" to="." method="_on_side_slider_draw"] +[connection signal="gui_input" from="ShapeContainer/SideSlider" to="." method="_on_side_slider_gui_input"] +[connection signal="pressed" from="ColorContainer/None" to="." method="toggle_none"] +[connection signal="gui_input" from="ColorContainer/ColorsDisplay/ColorRect/ResetColorButton" to="." method="_on_reset_color_button_gui_input"] +[connection signal="pressed" from="ColorContainer/ColorsDisplay/ColorRect/ResetColorButton" to="." method="_on_reset_color_button_pressed"] +[connection signal="pressed" from="SliderContainer/ColorSpaceContainer/RGB" to="." method="_on_rgb_pressed"] +[connection signal="pressed" from="SliderContainer/ColorSpaceContainer/HSV" to="." method="_on_hsv_pressed"] +[connection signal="draw" from="SliderContainer/HBoxContainer/TracksContainer/Slider1" to="." method="_on_slider1_draw"] +[connection signal="gui_input" from="SliderContainer/HBoxContainer/TracksContainer/Slider1" to="." method="_on_slider1_gui_input"] +[connection signal="draw" from="SliderContainer/HBoxContainer/TracksContainer/Slider2" to="." method="_on_slider2_draw"] +[connection signal="gui_input" from="SliderContainer/HBoxContainer/TracksContainer/Slider2" to="." method="_on_slider2_gui_input"] +[connection signal="draw" from="SliderContainer/HBoxContainer/TracksContainer/Slider3" to="." method="_on_slider3_draw"] +[connection signal="gui_input" from="SliderContainer/HBoxContainer/TracksContainer/Slider3" to="." method="_on_slider3_gui_input"] diff --git a/src/ui_elements/mini_number_field.gd b/src/ui_elements/mini_number_field.gd index f6ec93cc..a94b1b01 100644 --- a/src/ui_elements/mini_number_field.gd +++ b/src/ui_elements/mini_number_field.gd @@ -9,12 +9,13 @@ var mode := Mode.DEFAULT signal value_changed(new_value: float) var _value: float # Must not be updated directly. -func set_value(new_value: float, emit_value_changed := true): +func set_value(new_value: float): if is_nan(new_value): + text = String.num(_value, 4) return var old_value := _value _value = validate(new_value) - if _value != old_value and emit_value_changed: + if _value != old_value: value_changed.emit(_value) text = String.num(_value, 4) @@ -26,7 +27,7 @@ func get_value() -> float: func _ready() -> void: super() value_changed.connect(_on_value_changed) - text = str(get_value()) + text = String.num(get_value(), 4) func validate(new_value: float) -> float: match mode: @@ -44,4 +45,3 @@ func _on_focus_exited() -> void: func _on_text_submitted(submitted_text: String) -> void: set_value(Utils.evaluate_numeric_expression(submitted_text)) - super(submitted_text) diff --git a/src/ui_elements/mini_number_field.tscn b/src/ui_elements/mini_number_field.tscn index 5477cf69..5a8a6f19 100644 --- a/src/ui_elements/mini_number_field.tscn +++ b/src/ui_elements/mini_number_field.tscn @@ -6,11 +6,13 @@ draw_center = false border_width_bottom = 2 border_color = Color(1, 1, 1, 0.133333) +corner_detail = 1 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ul27o"] draw_center = false border_width_bottom = 2 border_color = Color(0.501961, 1, 1, 0.4) +corner_detail = 1 [node name="MiniNumberField" type="LineEdit"] custom_minimum_size = Vector2(44, 19) diff --git a/src/ui_elements/number_edit.gd b/src/ui_elements/number_edit.gd index 423766b2..3c8a5f57 100644 --- a/src/ui_elements/number_edit.gd +++ b/src/ui_elements/number_edit.gd @@ -1,27 +1,35 @@ ## A number editor, not tied to any attribute. extends BetterLineEdit -@export var initial_value: float @export var min_value := 0.0 @export var max_value := 1.0 +@export var initial_value := 0.5 @export var allow_lower := true @export var allow_higher := true @export var is_float := true signal value_changed(new_value: float) -var current_value: float: - set(new_value): - if is_nan(new_value): - return - elif current_value != new_value: - current_value = validate(new_value) - text = String.num(current_value, 4) - value_changed.emit(new_value) +var _value := NAN + +func set_value(new_value: float, emit_changed := true) -> void: + if is_nan(new_value): + text = String.num(_value, 4) + return + elif _value != new_value: + _value = validate(new_value) + text = String.num(_value, 4) + if emit_changed: + value_changed.emit(_value) + +func get_value() -> float: + return _value func _ready() -> void: - value_changed.connect(_on_value_changed) - current_value = initial_value + super() + # Done like this so a signal isn't emitted. + _value = initial_value + text = String.num(_value, 4) func validate(new_value: float) -> float: if allow_lower: @@ -40,8 +48,8 @@ func _on_value_changed(new_value: float) -> void: func _on_focus_exited() -> void: - current_value = Utils.evaluate_numeric_expression(text) + set_value(Utils.evaluate_numeric_expression(text)) + super() func _on_text_submitted(submitted_text: String) -> void: - release_focus() - current_value = Utils.evaluate_numeric_expression(submitted_text) + set_value(Utils.evaluate_numeric_expression(submitted_text)) diff --git a/src/ui_elements/number_edit.tscn b/src/ui_elements/number_edit.tscn index a2fa264f..a126cc77 100644 --- a/src/ui_elements/number_edit.tscn +++ b/src/ui_elements/number_edit.tscn @@ -38,5 +38,5 @@ script = ExtResource("1_dywrg") hover_stylebox = SubResource("StyleBoxFlat_oa7o2") focus_stylebox = SubResource("StyleBoxFlat_ucydw") -[connection signal="focus_exited" from="." to="." method="_on_focus_exited"] [connection signal="text_submitted" from="." to="." method="_on_text_submitted"] +[connection signal="value_changed" from="." to="." method="_on_value_changed"] diff --git a/src/ui_elements/number_field.gd b/src/ui_elements/number_field.gd index 958548c5..b719b0fa 100644 --- a/src/ui_elements/number_field.gd +++ b/src/ui_elements/number_field.gd @@ -10,19 +10,20 @@ var allow_higher := true var is_float := true -signal value_changed(new_value: float) +signal value_changed(new_value: float, update_type: UpdateType) var _value: float # Must not be updated directly. -func set_value(new_value: float, emit_value_changed := true) -> void: +func set_value(new_value: float, update_type := UpdateType.REGULAR) -> void: if is_nan(new_value): + num_edit.text = String.num(_value, 4) return var old_value := _value _value = validate(new_value) - if _value != old_value and emit_value_changed: - value_changed.emit(_value) + if update_type != UpdateType.NO_SIGNAL and\ + (_value != old_value or update_type == UpdateType.FINAL): + value_changed.emit(_value, update_type) elif num_edit != null: - num_edit.text = String.num(_value, 4) - set_text_tint() + update_after_change() func get_value() -> float: return _value @@ -30,12 +31,10 @@ func get_value() -> float: func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) - attribute.value_changed.connect(set_value) - set_text_tint() - num_edit.tooltip_text = attribute_name - num_edit.text = str(get_value()) + set_value(attribute.get_value()) + attribute.value_changed.connect(set_value) + num_edit.tooltip_text = attribute_name + num_edit.text = String.num(get_value(), 4) func validate(new_value: float) -> float: if allow_lower: @@ -49,10 +48,15 @@ func validate(new_value: float) -> float: else: return clampf(new_value, min_value, max_value) -func _on_value_changed(new_value: float) -> void: - num_edit.text = String.num(new_value, 4) - if attribute != null: - attribute.set_value(new_value) +func _on_value_changed(new_value: float, update_type: UpdateType) -> void: + update_after_change() + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) func _on_focus_exited() -> void: @@ -73,3 +77,8 @@ func set_text_tint() -> void: num_edit.add_theme_color_override(&"font_color", Color(0.64, 0.64, 0.64)) else: num_edit.remove_theme_color_override(&"font_color") + +func update_after_change() -> void: + if num_edit != null: + num_edit.text = String.num(get_value(), 4) + set_text_tint() diff --git a/src/ui_elements/number_field_with_slider.gd b/src/ui_elements/number_field_with_slider.gd index 99aea800..3a227381 100644 --- a/src/ui_elements/number_field_with_slider.gd +++ b/src/ui_elements/number_field_with_slider.gd @@ -13,20 +13,20 @@ var allow_higher := true var is_float := true -signal value_changed(new_value: float) +signal value_changed(new_value: float, update_type: UpdateType) var _value: float # Must not be updated directly. -func set_value(new_value: float, emit_value_changed := true) -> void: +func set_value(new_value: float, update_type := UpdateType.REGULAR) -> void: if is_nan(new_value): + update_after_change() return var old_value := _value _value = validate(new_value) - if _value != old_value and emit_value_changed: - value_changed.emit(_value) + if update_type != UpdateType.NO_SIGNAL and\ + (_value != old_value or update_type == UpdateType.FINAL): + value_changed.emit(_value, update_type) elif num_edit != null: - num_edit.text = String.num(_value, 4) - set_text_tint() - queue_redraw() + update_after_change() func get_value() -> float: return _value @@ -34,12 +34,11 @@ func get_value() -> float: func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) - attribute.value_changed.connect(set_value) - set_text_tint() - num_edit.tooltip_text = attribute_name - num_edit.text = str(get_value()) + set_value(attribute.get_value()) + attribute.value_changed.connect(set_value) + set_text_tint() + num_edit.tooltip_text = attribute_name + num_edit.text = String.num(get_value(), 4) func validate(new_value: float) -> float: if allow_lower: @@ -53,10 +52,15 @@ func validate(new_value: float) -> float: else: return clampf(new_value, min_value, max_value) -func _on_value_changed(new_value: float) -> void: - num_edit.text = String.num(new_value, 4) - if attribute != null: - attribute.set_value(new_value) +func _on_value_changed(new_value: float, update_type: UpdateType) -> void: + update_after_change() + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) # Hacks to make LineEdit bearable. @@ -80,8 +84,15 @@ func set_text_tint() -> void: else: num_edit.remove_theme_color_override(&"font_color") +func update_after_change() -> void: + if num_edit != null: + num_edit.text = String.num(get_value(), 4) + set_text_tint() + queue_redraw() + # Slider +var initial_slider_value: float var slider_dragged := false: set(new_value): if slider_dragged != new_value: @@ -103,28 +114,40 @@ func _draw() -> void: var stylebox := StyleBoxFlat.new() stylebox.corner_radius_top_right = 5 stylebox.corner_radius_bottom_right = 5 - stylebox.bg_color = Color("#121233") + stylebox.bg_color = num_edit.get_theme_stylebox(&"normal", &"LineEdit").bg_color draw_style_box(stylebox, Rect2(Vector2.ZERO, slider_size - Vector2(1, 2))) var fill_height := (slider_size.y - 4) * (get_value() - min_value) / max_value - if slider_hovered or slider_dragged: + if slider_dragged: draw_rect(Rect2(0, 1 + slider_size.y - 4 - fill_height, slider_size.x - 2, fill_height), Color("#def")) + elif slider_hovered: + draw_rect(Rect2(0, 1 + slider_size.y - 4 - fill_height, + slider_size.x - 2, fill_height), Color("#defb")) else: draw_rect(Rect2(0, 1 + slider_size.y - 4 - fill_height, - slider_size.x - 2, fill_height), Color("#defa")) + slider_size.x - 2, fill_height), Color("#def8")) func _on_slider_resized() -> void: queue_redraw() # Whyyyyy are their sizes wrong at first... func _on_slider_gui_input(event: InputEvent) -> void: - var slider_h := slider.get_size().y - 4 - if event is InputEventMouseButton or event is InputEventMouseMotion: - if event.button_mask == MOUSE_BUTTON_LEFT: + if not slider_dragged: + if Utils.is_event_drag_start(event): slider_dragged = true - set_value(snappedf(lerpf(max_value, min_value, - (event.position.y - 4) / slider_h), slider_step)) - return - slider_dragged = false + initial_slider_value = get_value() + set_value(get_slider_value_at_y(event.position.y), UpdateType.INTERMEDIATE) + else: + if Utils.is_event_drag(event): + set_value(get_slider_value_at_y(event.position.y), UpdateType.INTERMEDIATE) + 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_value(final_slider_value, UpdateType.FINAL) + +func get_slider_value_at_y(y_coord: float) -> float: + return snappedf(lerpf(max_value, min_value, + (y_coord - 4) / (slider.get_size().y - 4)), slider_step) func _on_slider_mouse_exited() -> void: slider_hovered = false diff --git a/src/ui_elements/number_field_with_slider.tscn b/src/ui_elements/number_field_with_slider.tscn index 84bb1e9e..8fdc24af 100644 --- a/src/ui_elements/number_field_with_slider.tscn +++ b/src/ui_elements/number_field_with_slider.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=6 format=3 uid="uid://bp2vpf7g8w8aj"] +[gd_scene load_steps=7 format=3 uid="uid://bp2vpf7g8w8aj"] [ext_resource type="Script" path="res://src/ui_elements/number_field_with_slider.gd" id="1_ymm02"] [ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="2_ytia1"] @@ -23,7 +23,7 @@ border_color = Color(0.501961, 0.752941, 1, 0.2) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_smesa"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwc4n"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -33,10 +33,23 @@ border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fd68s"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +draw_center = false +border_width_left = 1 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.152941, 0.152941, 0.2, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [node name="NumberFieldWithSlider" type="HBoxContainer"] custom_minimum_size = Vector2(0, 22) @@ -66,7 +79,8 @@ layout_mode = 2 focus_mode = 0 mouse_default_cursor_shape = 2 theme_type_variation = &"LeftConnectedButtonTransparent" -theme_override_styles/pressed = SubResource("StyleBoxFlat_smesa") +theme_override_styles/hover = SubResource("StyleBoxFlat_wwc4n") +theme_override_styles/pressed = SubResource("StyleBoxFlat_fd68s") [connection signal="focus_exited" from="LineEdit" to="." method="_on_focus_exited"] [connection signal="text_submitted" from="LineEdit" to="." method="_on_text_submitted"] diff --git a/src/ui_elements/path_command_button.gd b/src/ui_elements/path_command_button.gd index d452b135..44af7bb5 100644 --- a/src/ui_elements/path_command_button.gd +++ b/src/ui_elements/path_command_button.gd @@ -1,6 +1,8 @@ ## A button for a path command picker. extends Button +signal pressed_custom(cmd_char: String) + @onready var rtl: RichTextLabel = $RichTextLabel @export var command_char := "" @@ -9,6 +11,10 @@ extends Button func _ready() -> void: text = "" update_text() + pressed.connect(emit_pressed_custom) + +func emit_pressed_custom() -> void: + pressed_custom.emit(command_char) func update_text() -> void: rtl.text = "" diff --git a/src/ui_elements/path_command_editor.gd b/src/ui_elements/path_command_editor.gd index d56ab8c0..5d86bc0c 100644 --- a/src/ui_elements/path_command_editor.gd +++ b/src/ui_elements/path_command_editor.gd @@ -1,49 +1,51 @@ ## An editor for a single path command. extends PanelContainer +const spacing_dict = { + "A": [3, 4, 4, 4, 4, 3], + "C": [3, 4, 3, 4, 3], + "Q": [3, 4, 3], + "S": [3, 4, 3], + "M": [3], + "L": [3], + "T": [3], +} + signal cmd_update_value(idx: int, new_value: float, property: StringName) signal cmd_delete(idx: int) signal cmd_toggle_relative(idx: int) signal cmd_insert_after(idx: int, cmd_char: String) +signal cmd_convert_to(idx: int, cmd_char: String) +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") const MiniNumberField = preload("mini_number_field.tscn") const FlagField = preload("flag_field.tscn") +const PathCommandPopup = preload("path_popup.tscn") var tid := PackedInt32Array() var cmd_char := "" var cmd_idx := -1 var path_command: PathCommand -# This is needed for the hover detection hack. -@onready var first_ancestor_scroll_container := find_first_ancestor_scroll_container() - -func find_first_ancestor_scroll_container() -> ScrollContainer: - var ancestor := get_parent() - while not ancestor is ScrollContainer: - if not ancestor is Control: - return null - ancestor = ancestor.get_parent() - return ancestor - @onready var relative_button: Button = $HBox/RelativeButton @onready var more_button: Button = $HBox/MoreButton -@onready var fields_container: HBoxContainer = $HBox/Fields -@onready var action_popup: Popup = $ActionsPopup -@onready var command_picker: Popup = $PathPopup +@onready var fields_container: CustomSpacedHBoxContainer = $HBox/Fields -var fields_added_before_ready: Array[Control] = [] var fields: Array[Control] = [] func update_type() -> void: cmd_char = path_command.command_char - var command_type := cmd_char.to_upper() + var cmd_type := cmd_char.to_upper() fields.clear() + setup_relative_button() + + var spacing: Array = spacing_dict[cmd_type] if cmd_type in spacing_dict else [] + fields_container.set_spacing_array(spacing) # Instantiate the input fields. - if command_type == "A": - var fields_rx_ry: Array[BetterLineEdit] = add_number_field_pair() - var field_rx := fields_rx_ry[0] - var field_ry := fields_rx_ry[1] + if cmd_type == "A": + var field_rx: BetterLineEdit = add_number_field() + var field_ry: BetterLineEdit = add_number_field() var field_rot: BetterLineEdit = add_number_field() var field_large_arc_flag: Button = add_flag_field() var field_sweep_flag: Button = add_flag_field() @@ -71,10 +73,9 @@ func update_type() -> void: fields.append(field_rot) fields.append(field_large_arc_flag) fields.append(field_sweep_flag) - if command_type == "Q" or command_type == "C": - var fields_x1_y1: Array[BetterLineEdit] = add_number_field_pair() - var field_x1 := fields_x1_y1[0] - var field_y1 := fields_x1_y1[1] + if cmd_type == "Q" or cmd_type == "C": + var field_x1: BetterLineEdit = add_number_field() + var field_y1: BetterLineEdit = add_number_field() field_x1.set_value(path_command.x1) field_y1.set_value(path_command.y1) field_x1.tooltip_text = "x1" @@ -83,10 +84,9 @@ func update_type() -> void: field_y1.value_changed.connect(update_value.bind(&"y1")) fields.append(field_x1) fields.append(field_y1) - if command_type == "C" or command_type == "S": - var fields_x2_y2: Array[BetterLineEdit] = add_number_field_pair() - var field_x2 := fields_x2_y2[0] - var field_y2 := fields_x2_y2[1] + if cmd_type == "C" or cmd_type == "S": + var field_x2: BetterLineEdit = add_number_field() + var field_y2: BetterLineEdit = add_number_field() field_x2.set_value(path_command.x2) field_y2.set_value(path_command.y2) field_x2.tooltip_text = "x2" @@ -95,27 +95,26 @@ func update_type() -> void: field_y2.value_changed.connect(update_value.bind(&"y2")) fields.append(field_x2) fields.append(field_y2) - if command_type != "Z": - if command_type == "H": + if cmd_type != "Z": + if cmd_type == "H": var field_x: BetterLineEdit = add_number_field() field_x.set_value(path_command.x) field_x.tooltip_text = "x" field_x.value_changed.connect(update_value.bind(&"x")) fields.append(field_x) - elif command_type == "V": + elif cmd_type == "V": var field_y: BetterLineEdit = add_number_field() field_y.set_value(path_command.y) field_y.tooltip_text ="y" field_y.value_changed.connect(update_value.bind(&"y")) fields.append(field_y) else: - var fields_x_y: Array[BetterLineEdit] = add_number_field_pair() - var field_x := fields_x_y[0] - var field_y := fields_x_y[1] + var field_x: BetterLineEdit = add_number_field() + var field_y: BetterLineEdit = add_number_field() field_x.set_value(path_command.x) field_x.tooltip_text = "x" field_y.set_value(path_command.y) - field_y.tooltip_text ="y" + field_y.tooltip_text = "y" field_x.value_changed.connect(update_value.bind(&"x")) field_y.value_changed.connect(update_value.bind(&"y")) fields.append(field_x) @@ -124,7 +123,7 @@ func update_type() -> void: # Alternative to fully rebuilding the path command editor, if the layout is unchanged. func sync_values(cmd: PathCommand) -> void: # Instantiate the input fields. - match cmd_char: + match cmd_char.to_upper(): "A": fields[0].set_value(cmd.rx) fields[1].set_value(cmd.ry) @@ -161,29 +160,43 @@ func sync_values(cmd: PathCommand) -> void: func update_value(value: float, property: StringName) -> void: - Indications.set_inner_selection(tid, cmd_idx) + Indications.normal_select(tid, cmd_idx) cmd_update_value.emit(cmd_idx, value, property) func delete() -> void: - action_popup.hide() cmd_delete.emit(cmd_idx) func toggle_relative() -> void: cmd_toggle_relative.emit(cmd_idx) func insert_after() -> void: - action_popup.hide() - command_picker.popup(Utils.calculate_popup_rect( - more_button.global_position, more_button.size, command_picker.size)) - -func open_actions() -> void: - Indications.set_inner_selection(tid, cmd_idx) + var command_picker := PathCommandPopup.instantiate() + add_child(command_picker) + match cmd_char.to_upper(): + "M": command_picker.disable_invalid(["M", "Z", "T"]) + "Z": command_picker.disable_invalid(["Z"]) + "L", "H", "V", "A": command_picker.disable_invalid(["S", "T"]) + "C", "S": command_picker.disable_invalid(["T"]) + "Q", "T": command_picker.disable_invalid(["S"]) + command_picker.path_command_picked.connect(_on_insert_path_command_picked) + Utils.popup_under_control_centered(command_picker, more_button) + +func convert_to() -> void: + var command_picker := PathCommandPopup.instantiate() + add_child(command_picker) + command_picker.force_relativity(Utils.is_string_lower(cmd_char)) + command_picker.disable_invalid([cmd_char.to_upper()]) + command_picker.path_command_picked.connect(_on_convert_path_command_picked) + Utils.popup_under_control_centered(command_picker, more_button) + +func open_actions(popup_from_mouse := false) -> void: + Indications.normal_select(tid, cmd_idx) + var action_popup := ContextPopup.instantiate() var buttons_arr: Array[Button] = [] var delete_btn := Button.new() delete_btn.text = tr(&"#delete") delete_btn.icon = load("res://visual/icons/Delete.svg") - delete_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND delete_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT delete_btn.pressed.connect(delete) buttons_arr.append(delete_btn) @@ -191,25 +204,30 @@ func open_actions() -> void: var insert_after_btn := Button.new() insert_after_btn.text = tr(&"#insert_after") insert_after_btn.icon = load("res://visual/icons/Plus.svg") - insert_after_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND insert_after_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT insert_after_btn.pressed.connect(insert_after) buttons_arr.append(insert_after_btn) + if cmd_idx != 0: + var convert_btn := Button.new() + convert_btn.text = tr(&"#convert_to") + convert_btn.icon = load("res://visual/icons/Reload.svg") + convert_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT + convert_btn.pressed.connect(convert_to) + buttons_arr.append(convert_btn) + + add_child(action_popup) action_popup.set_btn_array(buttons_arr) - action_popup.popup(Utils.calculate_popup_rect(more_button.global_position, - more_button.size, action_popup.size, true)) + if popup_from_mouse: + Utils.popup_under_mouse(action_popup, get_global_mouse_position()) + else: + Utils.popup_under_control_centered(action_popup, more_button) func _ready() -> void: Indications.selection_changed.connect(determine_selection_highlight) Indications.hover_changed.connect(determine_selection_highlight) determine_selection_highlight() - setup_relative_button() - setup_command_picker() - more_button.pressed.connect(open_actions) - while not fields_added_before_ready.is_empty(): - fields_container.add_child(fields_added_before_ready.pop_front()) # Helpers @@ -244,62 +262,48 @@ func setup_relative_button() -> void: relative_button.add_theme_stylebox_override(&"pressed", create_stylebox( Color.from_hsv(0.74, 0.6, 1.0), Color.from_hsv(0.7, 0.4, 1.0))) -func setup_command_picker() -> void: - command_picker.disable_invalid(cmd_char) - func add_number_field() -> BetterLineEdit: var new_field := MiniNumberField.instantiate() - safely_add_field(new_field) + fields_container.add_child(new_field) return new_field func add_flag_field() -> Button: var new_field := FlagField.instantiate() - safely_add_field(new_field) + fields_container.add_child(new_field) return new_field -func add_number_field_pair() -> Array[BetterLineEdit]: - var hbox := HBoxContainer.new() - hbox.add_theme_constant_override(&"separation", 3) - var new_fields: Array[BetterLineEdit] =\ - [MiniNumberField.instantiate(), MiniNumberField.instantiate()] - hbox.add_child(new_fields[0]) - hbox.add_child(new_fields[1]) - safely_add_field(hbox) - return new_fields - -func safely_add_field(field: Control) -> void: - if fields_container == null: - fields_added_before_ready.append(field) - else: - fields_container.add_child(field) func _on_relative_button_pressed() -> void: cmd_char = cmd_char.to_upper() if Utils.is_string_lower(cmd_char)\ else cmd_char.to_lower() setup_relative_button() -func _on_path_command_picked(new_command: String) -> void: +func _on_insert_path_command_picked(new_command: String) -> void: cmd_insert_after.emit(cmd_idx + 1, new_command) +func _on_convert_path_command_picked(new_command: String) -> void: + cmd_convert_to.emit(cmd_idx, new_command) + func _on_gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.is_pressed(): if event.button_index == MOUSE_BUTTON_LEFT: if event.ctrl_pressed: - Indications.toggle_inner_selection(tid, cmd_idx) + Indications.ctrl_select(tid, cmd_idx) + elif event.shift_pressed: + Indications.shift_select(tid, cmd_idx) else: - Indications.set_inner_selection(tid, cmd_idx) + Indications.normal_select(tid, cmd_idx) elif event.button_index == MOUSE_BUTTON_RIGHT: - Indications.set_inner_selection(tid, cmd_idx) - open_actions() + Indications.normal_select(tid, cmd_idx) + open_actions(true) func determine_selection_highlight() -> void: var stylebox: StyleBox if Indications.semi_selected_tid == tid and cmd_idx in Indications.inner_selections: stylebox = StyleBoxFlat.new() stylebox.set_corner_radius_all(3) - if Indications.semi_hovered_tid == tid and\ - Indications.inner_hovered == cmd_idx: + if Indications.semi_hovered_tid == tid and Indications.inner_hovered == cmd_idx: stylebox.bg_color = Color(0.7, 0.7, 1.0, 0.18) else: stylebox.bg_color = Color(0.6, 0.6, 1.0, 0.16) @@ -316,17 +320,8 @@ func determine_selection_highlight() -> void: add_theme_stylebox_override(&"panel", stylebox) -var mouse_inside := false: - set(new_value): - if mouse_inside != new_value: - mouse_inside = new_value - if mouse_inside: - Indications.set_inner_hovered(tid, cmd_idx) - else: - Indications.remove_inner_hovered(tid, cmd_idx) +func _on_mouse_entered(): + Indications.set_inner_hovered(tid, cmd_idx) -func _input(event: InputEvent) -> void: - if event is InputEventMouseMotion and event.button_mask == 0: - mouse_inside = get_global_rect().has_point(get_global_mouse_position()) and\ - first_ancestor_scroll_container.get_global_rect().has_point( - get_global_mouse_position()) +func _on_mouse_exited(): + Indications.remove_inner_hovered(tid, cmd_idx) diff --git a/src/ui_elements/path_command_editor.tscn b/src/ui_elements/path_command_editor.tscn index a2f80f79..58aca175 100644 --- a/src/ui_elements/path_command_editor.tscn +++ b/src/ui_elements/path_command_editor.tscn @@ -1,36 +1,12 @@ -[gd_scene load_steps=11 format=3 uid="uid://dcdrc3r60bgg3"] +[gd_scene load_steps=6 format=3 uid="uid://dcdrc3r60bgg3"] [ext_resource type="Script" path="res://src/ui_elements/path_command_editor.gd" id="1_om2fk"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="2_o5eem"] [ext_resource type="Texture2D" uid="uid://cmepkbqde0jh0" path="res://visual/icons/SmallMore.svg" id="3_a76tm"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="4_xqjqs"] -[ext_resource type="Script" path="res://src/ui_elements/path_popup.gd" id="5_3dynt"] -[ext_resource type="PackedScene" uid="uid://co2btefrqrm0e" path="res://src/ui_elements/path_command_button.tscn" id="6_oj0oy"] +[ext_resource type="Script" path="res://src/ui_elements/CustomSpacedHBoxContainer.gd" id="3_p38uu"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_sewij"] -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dsai0"] -content_margin_left = 2.0 -content_margin_right = 2.0 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ky4q6"] -content_margin_left = 2.0 -content_margin_right = 2.0 -bg_color = Color(1, 1, 1, 0.0666667) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jayv"] -content_margin_left = 2.0 -content_margin_right = 2.0 -bg_color = Color(1, 1, 1, 0.2) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - [node name="PathCommandEditor" type="PanelContainer"] offset_right = 38.0 offset_bottom = 14.0 @@ -52,113 +28,20 @@ theme_override_colors/font_color = Color(0.866667, 0.933333, 1, 1) theme_override_fonts/font = ExtResource("2_o5eem") theme_override_font_sizes/font_size = 13 -[node name="Fields" type="HBoxContainer" parent="HBox"] +[node name="Fields" type="Container" parent="HBox"] layout_mode = 2 -theme_override_constants/separation = 5 +script = ExtResource("3_p38uu") [node name="MoreButton" type="Button" parent="HBox"] layout_mode = 2 size_flags_horizontal = 10 focus_mode = 0 mouse_default_cursor_shape = 2 -theme_override_colors/icon_normal_color = Color(0.666667, 0.666667, 0.666667, 1) -theme_override_colors/icon_hover_color = Color(0.866667, 0.866667, 0.866667, 1) -theme_override_styles/normal = SubResource("StyleBoxEmpty_dsai0") -theme_override_styles/hover = SubResource("StyleBoxFlat_ky4q6") -theme_override_styles/pressed = SubResource("StyleBoxFlat_5jayv") +theme_type_variation = &"FlatButton" icon = ExtResource("3_a76tm") -[node name="ActionsPopup" parent="." instance=ExtResource("4_xqjqs")] -visible = false - -[node name="PathPopup" type="Popup" parent="."] -transparent_bg = true -size = Vector2i(240, 285) -script = ExtResource("5_3dynt") - -[node name="PanelContainer" type="PanelContainer" parent="PathPopup"] -custom_minimum_size = Vector2(240, 0) -offset_right = 106.0 -offset_bottom = 97.0 - -[node name="VBoxContainer" type="VBoxContainer" parent="PathPopup/PanelContainer"] -layout_mode = 2 -theme_override_constants/separation = 2 - -[node name="MarginContainer" type="MarginContainer" parent="PathPopup/PanelContainer/VBoxContainer"] -layout_mode = 2 -theme_override_constants/margin_top = 2 -theme_override_constants/margin_right = 4 - -[node name="MainContainer" type="VBoxContainer" parent="PathPopup/PanelContainer/VBoxContainer/MarginContainer"] -layout_mode = 2 -theme_override_constants/separation = 2 - -[node name="RelativeToggle" type="CheckButton" parent="PathPopup/PanelContainer/VBoxContainer/MarginContainer/MainContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 8 -focus_mode = 0 -mouse_default_cursor_shape = 2 -text = "Relative" -flat = true -alignment = 2 - -[node name="CommandContainer" type="VBoxContainer" parent="PathPopup/PanelContainer/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="M" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "M" -command_text = "Move to" - -[node name="L" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "L" -command_text = "Line to" - -[node name="H" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "H" -command_text = "Horizontal Line to" - -[node name="V" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "V" -command_text = "Vertical Line to" - -[node name="Z" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "Z" -command_text = "Close Path" - -[node name="A" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "A" -command_text = "Elliptical Arc to" - -[node name="Q" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "Q" -command_text = "Quadratic Bezier to" - -[node name="T" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "T" -command_text = "Shorthand Quadratic Bezier to" - -[node name="C" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "C" -command_text = "Cubic Bezier to" - -[node name="S" parent="PathPopup/PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("6_oj0oy")] -layout_mode = 2 -command_char = "S" -command_text = "Shorthand Cubic Bezier to" - [connection signal="gui_input" from="." to="." method="_on_gui_input"] +[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"] +[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"] [connection signal="pressed" from="HBox/RelativeButton" to="HBox" method="_on_relative_button_pressed"] -[connection signal="path_command_picked" from="PathPopup" to="." method="_on_path_command_picked"] +[connection signal="pressed" from="HBox/MoreButton" to="." method="open_actions"] diff --git a/src/ui_elements/path_field.gd b/src/ui_elements/path_field.gd index ef09f03d..8c8c3055 100644 --- a/src/ui_elements/path_field.gd +++ b/src/ui_elements/path_field.gd @@ -3,39 +3,38 @@ extends AttributeEditor const CommandEditor = preload("path_command_editor.tscn") -@onready var line_edit: LineEdit = $MainLine/LineEdit -@onready var commands_container: VBoxContainer = $Commands +@onready var line_edit: LineEdit = $LineEdit +@onready var commands_container: VBoxContainer = $HBox/Commands @onready var add_move: Button = $AddMove -signal value_changed(new_value: String) +signal value_changed(new_value: String, update_type: UpdateType) var _value: String # Must not be updated directly. -func set_value(new_value: String, emit_value_changed := true): - if _value != new_value: +func set_value(new_value: String, update_type := UpdateType.REGULAR): + if _value != new_value or update_type == UpdateType.FINAL: _value = new_value - if emit_value_changed: - value_changed.emit(new_value) + if update_type != UpdateType.NO_SIGNAL: + value_changed.emit(new_value, update_type) func get_value() -> String: return _value -func sync_value() -> void: - set_value(PathDataParser.path_commands_to_value(attribute.commands)) - func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - attribute.command_changed.connect(sync_value) - set_value(attribute.get_value()) + set_value(attribute.get_value()) + attribute.value_changed.connect(set_value) -func _on_value_changed(new_value: String) -> void: +func _on_value_changed(new_value: String, update_type: UpdateType) -> void: line_edit.text = new_value - if attribute != null: - attribute.set_value(new_value) - if attribute.commands.is_empty(): - add_move.show() - else: - add_move.hide() + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) + # A plus button for adding a move command if empty. + add_move.visible = attribute.commands.is_empty() rebuild_commands() @@ -59,14 +58,15 @@ func rebuild_commands() -> void: var command_editor := CommandEditor.instantiate() command_editor.path_command = attribute.get_command(command_idx) # TODO Fix this mess, it's needed for individual path commands selection. - command_editor.tid = get_node(^"../../../../../../..").tid + command_editor.tid = get_node(^"../../../../..").tid command_editor.cmd_idx = command_idx - command_editor.update_type() command_editor.cmd_update_value.connect(_update_command_value) command_editor.cmd_delete.connect(_delete) command_editor.cmd_toggle_relative.connect(_toggle_relative) command_editor.cmd_insert_after.connect(_insert_after) + command_editor.cmd_convert_to.connect(_convert_to) commands_container.add_child(command_editor) + command_editor.update_type() command_idx += 1 @@ -74,7 +74,8 @@ func _update_command_value(idx: int, new_value: float, property: StringName) -> attribute.set_command_property(idx, property, new_value) func _delete(idx: int) -> void: - attribute.delete_command(idx) + var arr: Array[int] = [idx] + attribute.delete_commands(arr) func _toggle_relative(idx: int) -> void: attribute.toggle_relative_command(idx) @@ -82,9 +83,12 @@ func _toggle_relative(idx: int) -> void: func _insert_after(idx: int, cmd_type: String) -> void: attribute.insert_command(idx, cmd_type) +func _convert_to(idx: int, new_type: String) -> void: + attribute.convert_command(idx, new_type) + func _on_line_edit_text_submitted(new_text: String) -> void: set_value(new_text) func _on_add_move_pressed() -> void: - attribute.add_command("M") + attribute.insert_command(0, "M") diff --git a/src/ui_elements/path_field.tscn b/src/ui_elements/path_field.tscn index 0bcc0829..9647181b 100644 --- a/src/ui_elements/path_field.tscn +++ b/src/ui_elements/path_field.tscn @@ -57,20 +57,16 @@ corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 [node name="PathField" type="VBoxContainer"] -offset_right = 332.0 -offset_bottom = 26.0 +offset_right = 336.0 +offset_bottom = 45.0 size_flags_horizontal = 3 theme_override_constants/separation = 2 script = ExtResource("1_22rk2") -[node name="MainLine" type="HBoxContainer" parent="."] -custom_minimum_size = Vector2(0, 22) -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="LineEdit" type="LineEdit" parent="MainLine"] -custom_minimum_size = Vector2(300, 0) +[node name="LineEdit" type="LineEdit" parent="."] +custom_minimum_size = Vector2(344, 0) layout_mode = 2 +size_flags_horizontal = 0 tooltip_text = "d" focus_mode = 1 context_menu_enabled = false @@ -81,13 +77,6 @@ hover_stylebox = SubResource("StyleBoxFlat_4rmxx") focus_stylebox = SubResource("StyleBoxFlat_wqlhg") code_font_tooltip = true -[node name="Commands" type="VBoxContainer" parent="."] -custom_minimum_size = Vector2(336, 0) -layout_mode = 2 -size_flags_horizontal = 0 -mouse_filter = 0 -theme_override_constants/separation = 0 - [node name="AddMove" type="Button" parent="."] layout_mode = 2 size_flags_horizontal = 0 @@ -99,5 +88,19 @@ theme_override_styles/hover = SubResource("StyleBoxFlat_4yine") theme_override_styles/pressed = SubResource("StyleBoxFlat_kmpxk") icon = ExtResource("3_at2g1") -[connection signal="text_submitted" from="MainLine/LineEdit" to="." method="_on_line_edit_text_submitted"] +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Spacer" type="Control" parent="HBox"] +custom_minimum_size = Vector2(8, 0) +layout_mode = 2 + +[node name="Commands" type="VBoxContainer" parent="HBox"] +custom_minimum_size = Vector2(336, 0) +layout_mode = 2 +size_flags_horizontal = 0 +mouse_filter = 0 +theme_override_constants/separation = 0 + +[connection signal="text_submitted" from="LineEdit" to="." method="_on_line_edit_text_submitted"] [connection signal="pressed" from="AddMove" to="." method="_on_add_move_pressed"] diff --git a/src/ui_elements/path_popup.gd b/src/ui_elements/path_popup.gd index 1cd80db2..eeedbaa1 100644 --- a/src/ui_elements/path_popup.gd +++ b/src/ui_elements/path_popup.gd @@ -5,17 +5,21 @@ signal path_command_picked(new_command: String) @onready var command_container: VBoxContainer = %CommandContainer @onready var relative_toggle: CheckButton = %RelativeToggle +@onready var vbox: VBoxContainer = $PanelContainer/VBoxContainer +@onready var top_margin: MarginContainer = $PanelContainer/VBoxContainer/MarginContainer func _ready() -> void: relative_toggle.toggled.connect(_on_relative_toggle_toggled) + relative_toggle.button_pressed = GlobalSettings.save_data.path_command_relative for command_button in command_container.get_children(): - command_button.pressed.connect(emit_picked.bind(command_button.command_char)) + command_button.pressed_custom.connect(emit_picked) func emit_picked(cmd_char: String) -> void: path_command_picked.emit(cmd_char) hide() func _on_relative_toggle_toggled(toggled_on: bool) -> void: + GlobalSettings.modify_save_data("path_command_relative", toggled_on) for command_button in command_container.get_children(): if toggled_on: command_button.command_char = command_button.command_char.to_lower() @@ -24,14 +28,20 @@ func _on_relative_toggle_toggled(toggled_on: bool) -> void: command_button.command_char = command_button.command_char.to_upper() command_button.update_text() -func disable_invalid(cmd_char: String) -> void: - var cmd_char_upper := cmd_char.to_upper() - if cmd_char_upper == "M": - command_container.get_node(^"Z").set_invalid() - command_container.get_node(^"M").set_invalid() - if cmd_char_upper == "Z": - command_container.get_node(^"Z").set_invalid() - if cmd_char_upper != "C" and cmd_char_upper != "S": - command_container.get_node(^"S").set_invalid() - if cmd_char_upper != "Q" and cmd_char_upper != "T": - command_container.get_node(^"T").set_invalid() +func disable_invalid(cmd_chars: Array) -> void: + for cmd_char in cmd_chars: + var cmd_char_upper: String = cmd_char.to_upper() + command_container.get_node(cmd_char_upper).set_invalid() + +func force_relativity(relative: bool) -> void: + relative_toggle.hide() + vbox.add_theme_constant_override(&"separation", 0) + top_margin.add_theme_constant_override(&"margin_top", 0) + for command_button in command_container.get_children(): + if relative: + command_button.command_char = command_button.command_char.to_lower() + command_button.update_text() + else: + command_button.command_char = command_button.command_char.to_upper() + command_button.update_text() + reset_size() diff --git a/src/ui_elements/path_popup.tscn b/src/ui_elements/path_popup.tscn new file mode 100644 index 00000000..df8d26c1 --- /dev/null +++ b/src/ui_elements/path_popup.tscn @@ -0,0 +1,103 @@ +[gd_scene load_steps=3 format=3 uid="uid://bvnheiqqay5ke"] + +[ext_resource type="Script" path="res://src/ui_elements/path_popup.gd" id="1_j10aq"] +[ext_resource type="PackedScene" uid="uid://co2btefrqrm0e" path="res://src/ui_elements/path_command_button.tscn" id="2_1jd8y"] + +[node name="PathCommandPopup" type="Popup"] +transparent_bg = true +size = Vector2i(236, 282) +visible = true +script = ExtResource("1_j10aq") + +[node name="PanelContainer" type="PanelContainer" parent="."] +custom_minimum_size = Vector2(236, 0) +offset_right = 106.0 +offset_bottom = 97.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 4 + +[node name="MainContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="RelativeToggle" type="CheckButton" parent="PanelContainer/VBoxContainer/MarginContainer/MainContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 8 +focus_mode = 0 +mouse_default_cursor_shape = 2 +text = "Relative" +flat = true +alignment = 2 + +[node name="CommandContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="M" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "M" +command_text = "Move to" + +[node name="L" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "L" +command_text = "Line to" + +[node name="H" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "H" +command_text = "Horizontal Line to" + +[node name="V" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "V" +command_text = "Vertical Line to" + +[node name="Z" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "Z" +command_text = "Close Path" + +[node name="A" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "A" +command_text = "Elliptical Arc to" + +[node name="Q" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "Q" +command_text = "Quadratic Bezier to" + +[node name="T" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "T" +command_text = "Shorthand Quadratic Bezier to" + +[node name="C" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "C" +command_text = "Cubic Bezier to" + +[node name="S" parent="PanelContainer/VBoxContainer/CommandContainer" instance=ExtResource("2_1jd8y")] +layout_mode = 2 +focus_mode = 0 +command_char = "S" +command_text = "Shorthand Cubic Bezier to" diff --git a/src/ui_elements/rect_field.gd b/src/ui_elements/rect_field.gd deleted file mode 100644 index 1129111b..00000000 --- a/src/ui_elements/rect_field.gd +++ /dev/null @@ -1,55 +0,0 @@ -## An editor to be tied to an AttributeRect. -extends AttributeEditor - -@onready var x_field: Control = $XField -@onready var y_field: Control = $YField -@onready var w_field: Control = $WField -@onready var h_field: Control = $HField - -signal value_changed(new_value: Rect2) -var _value: Rect2 # Must not be updated directly. - -func set_value(new_value: Rect2, emit_value_changed := true): - if _value != new_value: - _value = new_value - if emit_value_changed: - value_changed.emit(new_value) - -func get_value() -> Rect2: - return _value - - -func _ready() -> void: - w_field.allow_lower = false - h_field.allow_lower = false - x_field.min_value = -1024.0 - y_field.min_value = -1024.0 - value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) - attribute.value_changed.connect(set_value) - - -func _on_x_field_value_changed(new_value: float) -> void: - set_value(Rect2( - new_value, y_field.get_value(), w_field.get_value(), h_field.get_value())) - -func _on_y_field_value_changed(new_value: float) -> void: - set_value(Rect2( - x_field.get_value(), new_value, w_field.get_value(), h_field.get_value())) - -func _on_w_field_value_changed(new_value: float) -> void: - set_value(Rect2( - x_field.get_value(), y_field.get_value(), new_value, h_field.get_value())) - -func _on_h_field_value_changed(new_value: float) -> void: - set_value(Rect2( - x_field.get_value(), y_field.get_value(), w_field.get_value(), new_value)) - -func _on_value_changed(new_value: Rect2) -> void: - x_field.set_value(new_value.position.x, false) - y_field.set_value(new_value.position.y, false) - w_field.set_value(new_value.size.x, false) - h_field.set_value(new_value.size.y, false) - if attribute != null: - attribute.set_value(new_value) diff --git a/src/ui_elements/rect_field.tscn b/src/ui_elements/rect_field.tscn deleted file mode 100644 index 441e7bb0..00000000 --- a/src/ui_elements/rect_field.tscn +++ /dev/null @@ -1,26 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://dh0dj6qdbfrb0"] - -[ext_resource type="Script" path="res://src/ui_elements/rect_field.gd" id="1_fmk5c"] -[ext_resource type="PackedScene" uid="uid://c6vgjud6wrdu4" path="res://src/ui_elements/number_field.tscn" id="2_hfi4f"] - -[node name="RectField" type="HBoxContainer"] -offset_right = 64.0 -offset_bottom = 22.0 -script = ExtResource("1_fmk5c") - -[node name="XField" parent="." instance=ExtResource("2_hfi4f")] -layout_mode = 2 - -[node name="YField" parent="." instance=ExtResource("2_hfi4f")] -layout_mode = 2 - -[node name="WField" parent="." instance=ExtResource("2_hfi4f")] -layout_mode = 2 - -[node name="HField" parent="." instance=ExtResource("2_hfi4f")] -layout_mode = 2 - -[connection signal="value_changed" from="XField" to="." method="_on_x_field_value_changed"] -[connection signal="value_changed" from="YField" to="." method="_on_y_field_value_changed"] -[connection signal="value_changed" from="WField" to="." method="_on_w_field_value_changed"] -[connection signal="value_changed" from="HField" to="." method="_on_h_field_value_changed"] diff --git a/src/ui_elements/unknown_field.gd b/src/ui_elements/unknown_field.gd index 211d4429..191f97d7 100644 --- a/src/ui_elements/unknown_field.gd +++ b/src/ui_elements/unknown_field.gd @@ -4,14 +4,14 @@ extends AttributeEditor @onready var line_edit: BetterLineEdit = $LineEdit -signal value_changed(new_value: String) +signal value_changed(new_value: String, update_type: UpdateType) var _value: String # Must not be updated directly. -func set_value(new_value: String, emit_value_changed := true): - if _value != new_value: +func set_value(new_value: String, update_type := UpdateType.REGULAR): + if _value != new_value or update_type == UpdateType.FINAL: _value = new_value - if emit_value_changed: - value_changed.emit(new_value) + if update_type != UpdateType.NO_SIGNAL: + value_changed.emit(new_value, update_type) func get_value() -> String: return _value @@ -19,15 +19,19 @@ func get_value() -> String: func _ready() -> void: value_changed.connect(_on_value_changed) - if attribute != null: - set_value(attribute.get_value()) + set_value(attribute.get_value()) line_edit.text = get_value() line_edit.tooltip_text = attribute_name + "\n(" + tr(&"#unknown_tooltip") + ")" -func _on_value_changed(new_value: String) -> void: +func _on_value_changed(new_value: String, update_type: UpdateType) -> void: line_edit.text = new_value - if attribute != null: - attribute.set_value(new_value) + match update_type: + UpdateType.INTERMEDIATE: + attribute.set_value(new_value, Attribute.SyncMode.INTERMEDIATE) + UpdateType.FINAL: + attribute.set_value(new_value, Attribute.SyncMode.FINAL) + _: + attribute.set_value(new_value) func _on_text_submitted(new_text: String) -> void: diff --git a/src/ui_parts/Handle.gd b/src/ui_parts/Handle.gd index 5b792c3b..2cd8ceee 100644 --- a/src/ui_parts/Handle.gd +++ b/src/ui_parts/Handle.gd @@ -1,8 +1,6 @@ ## Base class for handles. class_name Handle extends RefCounted -signal moved(new_value: Vector2) - enum DisplayMode {BIG, SMALL} var display_mode := DisplayMode.BIG @@ -10,11 +8,13 @@ var tag: Tag var tid := PackedInt32Array() var pos: Vector2 +var initial_pos: Vector2 # The position of a handle when it started being dragged. + func _init() -> void: pass -func set_pos(new_pos: Vector2) -> void: - moved.emit(new_pos) - func sync() -> void: pass + +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 bda9166b..e1b686d1 100644 --- a/src/ui_parts/PathHandle.gd +++ b/src/ui_parts/PathHandle.gd @@ -15,25 +15,34 @@ x_name := &"x", y_name := &"y") -> void: y_param = y_name sync() -func set_pos(new_pos: Vector2) -> void: +func set_pos(new_pos: Vector2, undo_redo := false) -> void: var command := path_attribute.get_command(command_index) - if x_param in command: - # Don't emit command_changed for the X change if there'll be a Y change too. - path_attribute.set_command_property(command_index, x_param, - new_pos.x - command.start.x if command.relative else new_pos.x, - not y_param in command) - pos.x = new_pos.x + 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: - pos.x = command.start.x - if y_param in command: - path_attribute.set_command_property(command_index, y_param, - new_pos.y - command.start.y if command.relative else new_pos.y) - pos.y = new_pos.y - else: - pos.y = command.start.y - path_attribute.set_value( - PathDataParser.path_commands_to_value(path_attribute.commands)) - super(new_pos) + if x_param in command: + # Don't emit command_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_pos.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_pos.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 + path_attribute.set_value( + PathDataParser.path_commands_to_value(path_attribute.commands)) func sync() -> void: diff --git a/src/ui_parts/XYHandle.gd b/src/ui_parts/XYHandle.gd index 37503faa..5b0bc7e5 100644 --- a/src/ui_parts/XYHandle.gd +++ b/src/ui_parts/XYHandle.gd @@ -10,13 +10,18 @@ func _init(id: PackedInt32Array, x_ref: Attribute, y_ref: Attribute) -> void: y_attribute = y_ref sync() -func set_pos(new_pos: Vector2) -> void: - if new_pos.x != pos.x: - x_attribute.set_value(new_pos.x, new_pos.y == pos.y) - if new_pos.y != pos.y: - y_attribute.set_value(new_pos.y) +func set_pos(new_pos: Vector2, undo_redo := false) -> void: + if undo_redo: + if initial_pos != new_pos: + x_attribute.set_value(new_pos.x, Attribute.SyncMode.NO_PROPAGATION) + y_attribute.set_value(new_pos.y, Attribute.SyncMode.FINAL) + else: + if new_pos.x != pos.x: + x_attribute.set_value(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_value(new_pos.y, Attribute.SyncMode.INTERMEDIATE) pos = new_pos - super(new_pos) func sync() -> void: pos = Vector2(x_attribute.get_value() if x_attribute != null else 0.0, diff --git a/src/ui_parts/about_menu.gd b/src/ui_parts/about_menu.gd index 426ae564..1866178f 100644 --- a/src/ui_parts/about_menu.gd +++ b/src/ui_parts/about_menu.gd @@ -24,8 +24,9 @@ func add_section(section_title: String, authors: Array[String]): authors_label.pop() authors_label.newline() -func _on_godot_license_pressed() -> void: - OS.shell_open("https://godotengine.org/license/") +func _on_components_pressed() -> void: + OS.shell_open("https://github.com/godotengine/godot/blob/master/COPYRIGHT.txt") + func _on_close_pressed() -> void: queue_free() diff --git a/src/ui_parts/about_menu.tscn b/src/ui_parts/about_menu.tscn index 60bdea9d..eaced4b8 100644 --- a/src/ui_parts/about_menu.tscn +++ b/src/ui_parts/about_menu.tscn @@ -1,11 +1,11 @@ [gd_scene load_steps=8 format=3 uid="uid://mhfp37lr7q4f"] [ext_resource type="Script" path="res://src/ui_parts/about_menu.gd" id="1_xxltt"] -[ext_resource type="Texture2D" uid="uid://v7hej5yexkvl" path="res://visual/icon.png" id="2_rgruj"] +[ext_resource type="Texture2D" uid="uid://barsurula6j8n" path="res://visual/icon.svg" id="2_y5kl0"] [ext_resource type="Script" path="res://src/ui_elements/BetterTabContainer.gd" id="3_1xgiv"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="3_e8i1t"] [ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://visual/fonts/FontBold.ttf" id="4_n6gp0"] -[ext_resource type="Texture2D" uid="uid://cgxpm1e3v0i3v" path="res://visual/icons/Link.svg" id="4_s5gee"] +[ext_resource type="Texture2D" uid="uid://cgxpm1e3v0i3v" path="res://visual/icons/Link.svg" id="6_hbk78"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_75xfp"] content_margin_left = 6.0 @@ -59,7 +59,7 @@ theme_override_constants/separation = 8 [node name="TextureRect" type="TextureRect" parent="PanelContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 -texture = ExtResource("2_rgruj") +texture = ExtResource("2_y5kl0") expand_mode = 2 [node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/HBoxContainer"] @@ -129,44 +129,61 @@ theme_override_constants/margin_top = 8 theme_override_constants/margin_right = 8 theme_override_constants/margin_bottom = 8 -[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab"] +[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab"] layout_mode = 2 -theme_override_constants/separation = 14 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 20 alignment = 1 -[node name="Godot" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer"] +[node name="ThirdParties" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Godot" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties"] layout_mode = 2 -[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/Godot"] +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/Godot"] layout_mode = 2 theme_override_fonts/font = ExtResource("4_n6gp0") text = "Godot Engine" horizontal_alignment = 1 -[node name="GodotLicense" type="Button" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/Godot"] +[node name="License" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/Godot"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_e8i1t") +theme_override_font_sizes/font_size = 12 +text = "License: Expat" +horizontal_alignment = 1 + +[node name="Components" type="Button" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/Godot"] layout_mode = 2 size_flags_horizontal = 4 +focus_mode = 0 mouse_default_cursor_shape = 2 -text = "Open Godot's License" -icon = ExtResource("4_s5gee") +theme_override_font_sizes/font_size = 12 +text = "#godot_third_party" +icon = ExtResource("6_hbk78") -[node name="NotoSans" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer"] +[node name="NotoSans" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties"] layout_mode = 2 theme_override_constants/separation = 3 -[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/NotoSans"] +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/NotoSans"] layout_mode = 2 theme_override_fonts/font = ExtResource("4_n6gp0") text = "Noto Sans font" horizontal_alignment = 1 -[node name="Copy" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/NotoSans"] +[node name="Copy" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/NotoSans"] layout_mode = 2 theme_override_font_sizes/font_size = 12 text = "© 2012, Google Inc." horizontal_alignment = 1 -[node name="Files" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/NotoSans"] +[node name="Files" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/NotoSans"] layout_mode = 2 theme_override_fonts/font = ExtResource("3_e8i1t") theme_override_font_sizes/font_size = 12 @@ -174,29 +191,130 @@ text = "res://visual/fonts/Font.ttf res://visual/fonts/FontBold.ttf" horizontal_alignment = 1 -[node name="JetbrainsMono" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer"] +[node name="License" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/NotoSans"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_e8i1t") +theme_override_font_sizes/font_size = 12 +text = "License: OFL-1.1" +horizontal_alignment = 1 + +[node name="JetbrainsMono" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties"] layout_mode = 2 theme_override_constants/separation = 3 -[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/JetbrainsMono"] +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/JetbrainsMono"] layout_mode = 2 theme_override_fonts/font = ExtResource("4_n6gp0") text = "JetBrains Mono font" horizontal_alignment = 1 -[node name="Copy" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/JetbrainsMono"] +[node name="Copy" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/JetbrainsMono"] layout_mode = 2 theme_override_font_sizes/font_size = 12 text = "© 2020, JetBrains s.r.o" horizontal_alignment = 1 -[node name="File" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/JetbrainsMono"] +[node name="File" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/JetbrainsMono"] layout_mode = 2 theme_override_fonts/font = ExtResource("3_e8i1t") theme_override_font_sizes/font_size = 12 text = "res://visual/fonts/FontMono.ttf" horizontal_alignment = 1 +[node name="License" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/JetbrainsMono"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_e8i1t") +theme_override_font_sizes/font_size = 12 +text = "License: OFL-1.1" +horizontal_alignment = 1 + +[node name="LicenseTexts" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Expat" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="Title" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts/Expat"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("4_n6gp0") +text = "Expat license text" +horizontal_alignment = 1 + +[node name="Text" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts/Expat"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_e8i1t") +theme_override_font_sizes/font_size = 10 +text = "Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the \"Software\"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE." + +[node name="OFL" type="VBoxContainer" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="Title" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts/OFL"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("4_n6gp0") +text = "OFL-1.1 license text" +horizontal_alignment = 1 + +[node name="Text" type="Label" parent="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/LicenseTexts/OFL"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_e8i1t") +theme_override_font_sizes/font_size = 10 +text = "PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +\"Font Software\" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s). + +\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +\"Modified Version\" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + + 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE." +autowrap_mode = 3 + [node name="#authors_tab" type="MarginContainer" parent="PanelContainer/VBoxContainer/TabContainer"] visible = false layout_mode = 2 @@ -225,5 +343,5 @@ size_flags_vertical = 8 mouse_default_cursor_shape = 2 text = "Close" -[connection signal="pressed" from="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/VBoxContainer/Godot/GodotLicense" to="." method="_on_godot_license_pressed"] +[connection signal="pressed" from="PanelContainer/VBoxContainer/TabContainer/#third-party-licenses_tab/ScrollContainer/VBoxContainer/ThirdParties/Godot/Components" to="." method="_on_components_pressed"] [connection signal="pressed" from="PanelContainer/Close" to="." method="_on_close_pressed"] diff --git a/src/ui_parts/alert_dialog.gd b/src/ui_parts/alert_dialog.gd new file mode 100644 index 00000000..c3d7f956 --- /dev/null +++ b/src/ui_parts/alert_dialog.gd @@ -0,0 +1,16 @@ +extends ColorRect + +@onready var title: Label = %MainContainer/TextContainer/Title +@onready var label: RichTextLabel = %MainContainer/TextContainer/Label +@onready var ok_button: Button = $PanelContainer/MarginContainer/MainContainer/OKButton + +func _ready() -> void: + ok_button.grab_focus() + +func setup(message: String, title_text := "#alert", min_width := 180.0) -> void: + label.text = tr(message) + title.text = tr(title_text) + label.custom_minimum_size.x = min_width + +func _on_ok_button_pressed() -> void: + queue_free() diff --git a/src/ui_parts/alert_dialog.tscn b/src/ui_parts/alert_dialog.tscn new file mode 100644 index 00000000..e9bdd1c2 --- /dev/null +++ b/src/ui_parts/alert_dialog.tscn @@ -0,0 +1,63 @@ +[gd_scene load_steps=3 format=3 uid="uid://c0x44loihhyyo"] + +[ext_resource type="FontFile" uid="uid://dc0w4sx0h0fui" path="res://visual/fonts/FontBold.ttf" id="1_3yrpq"] +[ext_resource type="Script" path="res://src/ui_parts/alert_dialog.gd" id="1_qntyo"] + +[node name="AlertDialog" type="ColorRect"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.4) +script = ExtResource("1_qntyo") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -2.0 +offset_top = -2.0 +offset_right = 2.0 +offset_bottom = 2.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 6 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 8 + +[node name="MainContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="TextContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer/MainContainer"] +layout_mode = 2 + +[node name="Title" type="Label" parent="PanelContainer/MarginContainer/MainContainer/TextContainer"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("1_3yrpq") +theme_override_font_sizes/font_size = 16 +text = "Alert!" +horizontal_alignment = 1 + +[node name="Label" type="RichTextLabel" parent="PanelContainer/MarginContainer/MainContainer/TextContainer"] +custom_minimum_size = Vector2(180, 0) +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 12 +fit_content = true + +[node name="OKButton" type="Button" parent="PanelContainer/MarginContainer/MainContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +mouse_default_cursor_shape = 2 +text = "#ok" + +[connection signal="pressed" from="PanelContainer/MarginContainer/MainContainer/OKButton" to="." method="_on_ok_button_pressed"] diff --git a/src/ui_parts/code_editor.gd b/src/ui_parts/code_editor.gd index 884d94b6..44d3ff25 100644 --- a/src/ui_parts/code_editor.gd +++ b/src/ui_parts/code_editor.gd @@ -1,12 +1,13 @@ extends VBoxContainer -const SVGFileDialog := preload("svg_file_dialog.tscn") -const ImportWarningDialog := preload("import_warning_dialog.tscn") -const ExportDialog := preload("export_dialog.tscn") +const SVGFileDialog = preload("svg_file_dialog.tscn") +const ImportWarningDialog = preload("import_warning_dialog.tscn") +const AlertDialog = preload("alert_dialog.tscn") +const ExportDialog = preload("export_dialog.tscn") @onready var code_edit: TextEdit = $ScriptEditor/SVGCodeEdit @onready var error_bar: PanelContainer = $ScriptEditor/ErrorBar -@onready var error_label: RichTextLabel = $ScriptEditor/ErrorBar/Padding/Label +@onready var error_label: RichTextLabel = $ScriptEditor/ErrorBar/Label @onready var size_label: Label = %SizeLabel func _ready() -> void: @@ -14,17 +15,16 @@ func _ready() -> void: auto_update_text() update_size_label() code_edit.clear_undo_history() - SVG.root_tag.attribute_changed.connect(auto_update_text) - SVG.root_tag.child_attribute_changed.connect(auto_update_text) - SVG.root_tag.tags_added.connect(auto_update_text.unbind(1)) - SVG.root_tag.tags_deleted.connect(auto_update_text.unbind(1)) - SVG.root_tag.tags_moved.connect(auto_update_text.unbind(2)) + 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) func auto_update_text() -> void: if not code_edit.has_focus(): - code_edit.text = SVG.string - update_size_label() + code_edit.text = SVG.text + code_edit.clear_undo_history() + update_size_label() func update_error(err_id: StringName) -> void: if err_id == &"": @@ -40,18 +40,13 @@ func update_error(err_id: StringName) -> void: if not error_bar.visible: error_bar.show() error_label.text = tr(err_id) - var stylebox := ThemeDB.get_project_theme().\ - get_stylebox(&"normal", &"TextEdit").duplicate() - stylebox.corner_radius_bottom_right = 0 - stylebox.corner_radius_bottom_left = 0 - stylebox.border_width_bottom = 1 - code_edit.add_theme_stylebox_override(&"normal", stylebox) - var stylebox2 := ThemeDB.get_project_theme().\ - get_stylebox(&"focus", &"CodeEdit").duplicate() - stylebox2.corner_radius_bottom_right = 0 - stylebox2.corner_radius_bottom_left = 0 - stylebox2.border_width_bottom = 1 - code_edit.add_theme_stylebox_override(&"focus", stylebox2) + for theming in [&"normal", &"focus"]: + var stylebox := ThemeDB.get_project_theme().\ + get_stylebox(theming, &"TextEdit").duplicate() + stylebox.corner_radius_bottom_right = 0 + stylebox.corner_radius_bottom_left = 0 + stylebox.border_width_bottom = 1 + code_edit.add_theme_stylebox_override(theming, stylebox) var error_bar_real_height := error_bar.size.y - 2 code_edit.custom_minimum_size.y -= error_bar_real_height code_edit.size.y -= error_bar_real_height @@ -64,11 +59,18 @@ func _on_copy_button_pressed() -> void: func native_file_import(has_selected: bool, files: PackedStringArray, _filter_idx: int): if has_selected: apply_svg_from_path(files[0]) + GlobalSettings.modify_save_data("last_used_dir", files[0].get_base_dir()) func _on_import_button_pressed() -> void: + var default_path: String + if GlobalSettings.save_data.last_used_dir.is_empty()\ + or not DirAccess.dir_exists_absolute(GlobalSettings.save_data.last_used_dir): + default_path = OS.get_system_dir(OS.SYSTEM_DIR_PICTURES) + else: + default_path = GlobalSettings.save_data.last_used_dir if DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG): DisplayServer.file_dialog_show( - "Import a .svg file", "", "", false, + "Import a .svg file", default_path, "", false, DisplayServer.FILE_DIALOG_MODE_OPEN_FILE, ["*.svg"], native_file_import) else: var svg_import_dialog := SVGFileDialog.instantiate() @@ -80,7 +82,14 @@ func _on_export_button_pressed() -> void: get_tree().get_root().add_child(export_panel) func apply_svg_from_path(path: String) -> void: - var svg_text := FileAccess.open(path, FileAccess.READ).get_as_text() + var svg_file := FileAccess.open(path, FileAccess.READ) + if svg_file == null: + var alert_dialog := AlertDialog.instantiate() + get_tree().get_root().add_child(alert_dialog) + alert_dialog.setup("#file_open_fail_message", "#alert", 280.0) + return + + var svg_text := svg_file.get_as_text() var warning_panel := ImportWarningDialog.instantiate() warning_panel.imported.connect(set_new_text) warning_panel.set_svg(svg_text) @@ -92,9 +101,8 @@ func set_new_text(svg_text: String) -> void: func _on_code_edit_text_changed() -> void: - SVG.string = code_edit.text - SVG.sync_data() - update_size_label() + SVG.text = code_edit.text + SVG.update_tags() func _input(event: InputEvent) -> void: @@ -105,3 +113,8 @@ func _input(event: InputEvent) -> void: func update_size_label() -> void: size_label.text = String.humanize_size(code_edit.text.length()) + +func _on_svg_code_edit_focus_exited() -> void: + code_edit.text = SVG.text + if SVG.saved_text != code_edit.text: + SVG.update_text(true) diff --git a/src/ui_parts/code_editor.tscn b/src/ui_parts/code_editor.tscn new file mode 100644 index 00000000..6ee34857 --- /dev/null +++ b/src/ui_parts/code_editor.tscn @@ -0,0 +1,170 @@ +[gd_scene load_steps=14 format=3 uid="uid://cr1fdlmbknnko"] + +[ext_resource type="Script" path="res://src/ui_parts/code_editor.gd" id="1_nffk0"] +[ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="2_p4nol"] +[ext_resource type="Texture2D" uid="uid://ccvjkdd0s7rb4" path="res://visual/icons/Copy.svg" id="3_6x47p"] +[ext_resource type="Texture2D" uid="uid://6ymbl3jqersp" path="res://visual/icons/Import.svg" id="4_cuhac"] +[ext_resource type="Texture2D" uid="uid://d0uvwj0t44n6v" path="res://visual/icons/Export.svg" id="5_pgurh"] +[ext_resource type="Script" path="res://src/data_classes/SVGHighlighter.gd" id="6_hpsqx"] +[ext_resource type="Script" path="res://src/ui_parts/svg_code_edit.gd" id="7_hafdu"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56qh"] +content_margin_left = 8.0 +content_margin_top = 3.0 +content_margin_right = 6.0 +content_margin_bottom = 2.0 +bg_color = Color(0.0975, 0.0975, 0.15, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_color = Color(0.152941, 0.152941, 0.2, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rppt3"] +content_margin_left = 5.0 +content_margin_top = 2.0 +content_margin_right = 5.0 +content_margin_bottom = 2.0 +bg_color = Color(1, 1, 1, 0.0666667) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bm3bl"] +content_margin_left = 5.0 +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) +border_width_left = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.152941, 0.152941, 0.2, 1) +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k6btx"] +draw_center = false +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.301961, 0.45098, 0.6, 1) +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_njere"] +script = ExtResource("6_hpsqx") +symbol_color = Color(0.670588, 0.788235, 1, 1) +tag_color = Color(1, 0.54902, 0.8, 1) +attribute_color = Color(0.737255, 0.878431, 1, 1) +string_color = Color(0.631373, 1, 0.878431, 1) +comment_color = Color(0.803922, 0.811765, 0.823529, 0.501961) +text_color = Color(0.803922, 0.811765, 0.917647, 0.67451) +error_color = Color(1, 0.52549, 0.419608, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k0een"] +content_margin_left = 10.0 +content_margin_right = 8.0 +bg_color = Color(0.0975, 0.0975, 0.15, 1) +border_width_left = 2 +border_width_top = 1 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.1875, 0.1875, 0.25, 1) +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[node name="CodeEditor" type="VBoxContainer"] +size_flags_vertical = 3 +theme_override_constants/separation = 0 +script = ExtResource("1_nffk0") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_q56qh") + +[node name="CodeButtons" type="HBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="PanelContainer/CodeButtons"] +layout_mode = 2 +size_flags_vertical = 4 +theme_override_styles/panel = SubResource("StyleBoxFlat_rppt3") + +[node name="SizeLabel" type="Label" parent="PanelContainer/CodeButtons/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_fonts/font = ExtResource("2_p4nol") +theme_override_font_sizes/font_size = 12 + +[node name="MetaActions" type="HBoxContainer" parent="PanelContainer/CodeButtons"] +layout_mode = 2 +size_flags_horizontal = 10 + +[node name="CopyButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"] +layout_mode = 2 +tooltip_text = "#copy_button_tooltip" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("3_6x47p") + +[node name="ImportButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"] +layout_mode = 2 +tooltip_text = "#import_button_tooltip" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("4_cuhac") + +[node name="ExportButton" type="Button" parent="PanelContainer/CodeButtons/MetaActions"] +layout_mode = 2 +tooltip_text = "#export_button_tooltip" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("5_pgurh") + +[node name="ScriptEditor" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = -2 + +[node name="SVGCodeEdit" type="TextEdit" parent="ScriptEditor"] +custom_minimum_size = Vector2(0, 96) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/normal = SubResource("StyleBoxFlat_bm3bl") +theme_override_styles/focus = SubResource("StyleBoxFlat_k6btx") +context_menu_enabled = false +wrap_mode = 1 +scroll_smooth = true +scroll_v_scroll_speed = 30.0 +caret_multiple = false +syntax_highlighter = SubResource("SyntaxHighlighter_njere") +highlight_all_occurrences = true +script = ExtResource("7_hafdu") + +[node name="ErrorBar" type="PanelContainer" parent="ScriptEditor"] +visible = false +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_k0een") + +[node name="Label" type="RichTextLabel" parent="ScriptEditor/ErrorBar"] +custom_minimum_size = Vector2(0, 20) +layout_mode = 2 +theme_override_colors/default_color = Color(1, 0.4, 0.4, 1) +theme_override_fonts/normal_font = ExtResource("2_p4nol") +theme_override_font_sizes/normal_font_size = 14 +fit_content = true + +[connection signal="pressed" from="PanelContainer/CodeButtons/MetaActions/CopyButton" to="." method="_on_copy_button_pressed"] +[connection signal="pressed" from="PanelContainer/CodeButtons/MetaActions/ImportButton" to="." method="_on_import_button_pressed"] +[connection signal="pressed" from="PanelContainer/CodeButtons/MetaActions/ExportButton" to="." method="_on_export_button_pressed"] +[connection signal="caret_changed" from="ScriptEditor/SVGCodeEdit" to="ScriptEditor/SVGCodeEdit" method="redraw_caret"] +[connection signal="focus_entered" from="ScriptEditor/SVGCodeEdit" to="ScriptEditor/SVGCodeEdit" method="_on_focus_entered"] +[connection signal="focus_exited" from="ScriptEditor/SVGCodeEdit" to="." method="_on_svg_code_edit_focus_exited"] +[connection signal="focus_exited" from="ScriptEditor/SVGCodeEdit" to="ScriptEditor/SVGCodeEdit" method="_on_focus_exited"] +[connection signal="text_changed" from="ScriptEditor/SVGCodeEdit" to="." method="_on_code_edit_text_changed"] diff --git a/src/ui_parts/configure_color_popup.gd b/src/ui_parts/configure_color_popup.gd new file mode 100644 index 00000000..c80e23e9 --- /dev/null +++ b/src/ui_parts/configure_color_popup.gd @@ -0,0 +1,55 @@ +extends Popup + +signal color_deletion_requested + +@onready var color_label: Label = %ConfigureContainer/TopContainer/ColorLabel +@onready var color_name_edit: BetterLineEdit = %ConfigureContainer/TopContainer/NameEdit +@onready var color_name_edit_button: Button = %ConfigureContainer/TopContainer/EditButton +@onready var color_edit: HBoxContainer = %ConfigureContainer/BottomContainer/ColorEdit +@onready var delete_button: Button = %ConfigureContainer/BottomContainer/DeleteButton + +var named_color: NamedColor + +func _ready() -> void: + set_label_text(named_color.name) + color_edit.current_value = named_color.color + + +func _on_edit_button_pressed() -> void: + color_name_edit.text = named_color.name + color_name_edit.show() + color_name_edit.grab_focus() + color_name_edit.caret_column = color_name_edit.text.length() + color_label.hide() + color_name_edit_button.hide() + +func _on_name_edit_text_submitted(new_text: String) -> void: + var new_name := new_text.strip_edges() + set_label_text(new_name) + hide_name_edit() + + +func hide_name_edit() -> void: + color_name_edit.hide() + color_name_edit_button.show() + color_label.show() + +func set_label_text(new_text: String) -> void: + if new_text.is_empty(): + color_label.text = tr(&"#unnamed") + color_label.add_theme_color_override(&"font_color", Color(0.5, 0.5, 0.5)) + else: + color_label.text = new_text + color_label.remove_theme_color_override(&"font_color") + + +func _on_name_edit_focus_exited() -> void: + hide_name_edit() + +func _on_delete_button_pressed() -> void: + color_deletion_requested.emit() + queue_free() + + +func _on_popup_hide() -> void: + queue_free() diff --git a/src/ui_parts/configure_color_popup.tscn b/src/ui_parts/configure_color_popup.tscn new file mode 100644 index 00000000..a57a299d --- /dev/null +++ b/src/ui_parts/configure_color_popup.tscn @@ -0,0 +1,95 @@ +[gd_scene load_steps=7 format=3 uid="uid://b7wobq0ndm35"] + +[ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="1_i4wi2"] +[ext_resource type="Script" path="res://src/ui_parts/configure_color_popup.gd" id="1_x6cll"] +[ext_resource type="Texture2D" uid="uid://dr2erka82g6j4" path="res://visual/icons/Edit.svg" id="2_0dind"] +[ext_resource type="PackedScene" uid="uid://5f8uxavn1or1" path="res://src/ui_elements/color_edit.tscn" id="3_dq5ly"] +[ext_resource type="Texture2D" uid="uid://cj5x2ti8150ja" path="res://visual/icons/Delete.svg" id="5_5n8tt"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jm6ej"] +content_margin_left = 5.0 +bg_color = Color(0.062, 0.062, 0.1, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.211765, 0.258824, 0.345098, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[node name="ConfigurePopup" type="Popup"] +transparent_bg = true +size = Vector2i(128, 70) +visible = true +script = ExtResource("1_x6cll") + +[node name="PanelContainer" type="PanelContainer" parent="."] +custom_minimum_size = Vector2(128, 40) +offset_right = 4.0 +offset_bottom = 4.0 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 6 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 6 +theme_override_constants/margin_bottom = 6 + +[node name="ConfigureContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="TopContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/ConfigureContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_constants/separation = 6 + +[node name="ColorLabel" type="Label" parent="PanelContainer/MarginContainer/ConfigureContainer/TopContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 13 +text = "Yeet" +horizontal_alignment = 1 + +[node name="NameEdit" type="LineEdit" parent="PanelContainer/MarginContainer/ConfigureContainer/TopContainer"] +visible = false +custom_minimum_size = Vector2(80, 0) +layout_mode = 2 +theme_override_styles/normal = SubResource("StyleBoxFlat_jm6ej") +max_length = 30 +script = ExtResource("1_i4wi2") + +[node name="EditButton" type="Button" parent="PanelContainer/MarginContainer/ConfigureContainer/TopContainer"] +layout_mode = 2 +tooltip_text = "#edit_color_name" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"FlatButton" +icon = ExtResource("2_0dind") + +[node name="BottomContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/ConfigureContainer"] +layout_mode = 2 +theme_override_constants/separation = 6 +alignment = 1 + +[node name="ColorEdit" parent="PanelContainer/MarginContainer/ConfigureContainer/BottomContainer" instance=ExtResource("3_dq5ly")] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +enable_palettes = false + +[node name="DeleteButton" type="Button" parent="PanelContainer/MarginContainer/ConfigureContainer/BottomContainer"] +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("5_5n8tt") + +[connection signal="popup_hide" from="." to="." method="_on_popup_hide"] +[connection signal="focus_exited" from="PanelContainer/MarginContainer/ConfigureContainer/TopContainer/NameEdit" to="." method="_on_name_edit_focus_exited"] +[connection signal="text_submitted" from="PanelContainer/MarginContainer/ConfigureContainer/TopContainer/NameEdit" to="." method="_on_name_edit_text_submitted"] +[connection signal="pressed" from="PanelContainer/MarginContainer/ConfigureContainer/TopContainer/EditButton" to="." method="_on_edit_button_pressed"] +[connection signal="pressed" from="PanelContainer/MarginContainer/ConfigureContainer/BottomContainer/DeleteButton" to="." method="_on_delete_button_pressed"] diff --git a/src/ui_parts/display.gd b/src/ui_parts/display.gd index e391e649..f5b80b66 100644 --- a/src/ui_parts/display.gd +++ b/src/ui_parts/display.gd @@ -4,101 +4,105 @@ const settings_menu = preload("settings_menu.tscn") const about_menu = preload("about_menu.tscn") const docs = preload("docs.tscn") +const NumberEditType = preload("res://src/ui_elements/number_edit.gd") +const BetterToggleButtonType = preload("res://src/ui_elements/BetterToggleButton.gd") + +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") const NumberField = preload("res://src/ui_elements/number_field.tscn") -@onready var viewport: SubViewport = $ViewportContainer/Viewport +@onready var viewport: SubViewport = $ViewportPanel/ViewportContainer/Viewport @onready var controls: Control = %Checkerboard/Controls -@onready var grid_visuals: Camera2D = $ViewportContainer/Viewport/ViewCamera +@onready var grid_visuals: Camera2D = $ViewportPanel/ViewportContainer/Viewport/ViewCamera @onready var visuals_button: Button = %LeftMenu/Visuals -@onready var visuals_popup: Popup = %LeftMenu/VisualsPopup @onready var more_button: Button = %LeftMenu/MoreOptions -@onready var more_popup: Popup = %LeftMenu/MorePopup -@onready var snapper: BetterLineEdit = %LeftMenu/Snapping/NumberEdit +@onready var snapper: NumberEditType = %LeftMenu/Snapping/NumberEdit +@onready var snap_button: BetterToggleButtonType = %LeftMenu/Snapping/SnapButton + + +func _ready() -> void: + update_snap_config() +func update_snap_config() -> void: + var snap_config := GlobalSettings.save_data.snap + var snap_enabled := snap_config > 0.0 + snap_button.button_pressed = snap_enabled + snapper.editable = snap_enabled + snapper.set_value(absf(snap_config)) func _on_settings_pressed() -> void: - more_popup.hide() var settings_menu_instance := settings_menu.instantiate() get_tree().get_root().add_child(settings_menu_instance) func _on_visuals_button_pressed() -> void: + var btn_arr: Array[Button] = [] var show_visuals_btn := CheckBox.new() show_visuals_btn.text = tr(&"#show_grid") - show_visuals_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND show_visuals_btn.button_pressed = grid_visuals.visible show_visuals_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT show_visuals_btn.pressed.connect(toggle_grid_visuals) var show_handles_btn := CheckBox.new() show_handles_btn.text = tr(&"#show_handles") - show_handles_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND show_handles_btn.button_pressed = controls.visible show_handles_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT show_handles_btn.pressed.connect(toggle_handles_visuals) var rasterize_btn := CheckBox.new() rasterize_btn.text = tr(&"#rasterize_svg") - rasterize_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND rasterize_btn.button_pressed = viewport.display_texture.rasterized rasterize_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT rasterize_btn.pressed.connect(toggle_rasterization) - visuals_popup.set_btn_array([show_visuals_btn, show_handles_btn, rasterize_btn]\ - as Array[Button]) - visuals_popup.popup(Utils.calculate_popup_rect( - visuals_button.global_position, visuals_button.size, visuals_popup.size, true)) + btn_arr = [show_visuals_btn, show_handles_btn, rasterize_btn] + var visuals_popup := ContextPopup.instantiate() + add_child(visuals_popup) + visuals_popup.set_btn_array(btn_arr) + Utils.popup_under_control_centered(visuals_popup, visuals_button) func _on_more_options_pressed() -> void: var open_repo_btn := Button.new() open_repo_btn.text = tr(&"#repo_button_text") open_repo_btn.icon = load("res://visual/icons/Link.svg") - open_repo_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND open_repo_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT open_repo_btn.pressed.connect(open_godsvg_repo) var about_btn := Button.new() about_btn.text = tr(&"#about_button_text") - about_btn.icon = load("res://visual/icon.png") + about_btn.icon = load("res://visual/icon.svg") about_btn.expand_icon = true - about_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND about_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT about_btn.pressed.connect(open_about) var docs_btn := Button.new() docs_btn.text = tr(&"#docs_button_text") docs_btn.icon = load("res://visual/icons/Docs.svg") - docs_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND docs_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT docs_btn.pressed.connect(open_docs) var donate_btn := Button.new() donate_btn.text = tr(&"#donate_button_text") donate_btn.icon = load("res://visual/icons/Heart.svg") - donate_btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND donate_btn.alignment = HORIZONTAL_ALIGNMENT_LEFT donate_btn.pressed.connect(open_sponsor) var buttons_arr: Array[Button] = [open_repo_btn, about_btn, docs_btn, donate_btn] + var more_popup := ContextPopup.instantiate() + add_child(more_popup) more_popup.set_btn_array(buttons_arr) - more_popup.popup(Utils.calculate_popup_rect( - more_button.global_position, more_button.size, more_popup.size, true)) + Utils.popup_under_control_centered(more_popup, more_button) func open_godsvg_repo() -> void: - more_popup.hide() OS.shell_open("https://github.com/MewPurPur/GodSVG") func open_about() -> void: - more_popup.hide() var about_menu_instance := about_menu.instantiate() get_tree().get_root().add_child(about_menu_instance) func open_docs() -> void: - more_popup.hide() var docs_instance := docs.instantiate() get_tree().get_root().add_child(docs_instance) func open_sponsor() -> void: - more_popup.hide() OS.shell_open("https://ko-fi.com/mewpurpur") func toggle_grid_visuals() -> void: @@ -112,4 +116,11 @@ func toggle_rasterization() -> void: func _on_snap_button_toggled(toggled_on: bool) -> void: - snapper.editable = toggled_on + GlobalSettings.modify_save_data(&"snap", + absf(GlobalSettings.save_data.snap) * (1 if toggled_on else -1)) + update_snap_config() + +func _on_number_edit_value_changed(new_value: float) -> void: + GlobalSettings.modify_save_data(&"snap", + new_value * signf(GlobalSettings.save_data.snap)) + update_snap_config() diff --git a/src/ui_parts/display.tscn b/src/ui_parts/display.tscn new file mode 100644 index 00000000..0be03aec --- /dev/null +++ b/src/ui_parts/display.tscn @@ -0,0 +1,211 @@ +[gd_scene load_steps=21 format=3 uid="uid://bvrncl7e6yn5b"] + +[ext_resource type="Script" path="res://src/ui_parts/display.gd" id="1_oib5g"] +[ext_resource type="Texture2D" uid="uid://ccbta5q43jobk" path="res://visual/icons/More.svg" id="2_3wliq"] +[ext_resource type="Texture2D" uid="uid://ckkkgof1hcbld" path="res://visual/icons/Gear.svg" id="3_0w618"] +[ext_resource type="Texture2D" uid="uid://kkxyv1gyrjgj" path="res://visual/icons/Visuals.svg" id="4_n3qjt"] +[ext_resource type="Texture2D" uid="uid://buire51l0mifg" path="res://visual/icons/Snap.svg" id="5_1k2cq"] +[ext_resource type="Script" path="res://src/ui_elements/BetterToggleButton.gd" id="6_3v3ve"] +[ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_elements/number_edit.tscn" id="7_wrrfr"] +[ext_resource type="PackedScene" uid="uid://oltvrf01xrxl" path="res://src/ui_parts/zoom_menu.tscn" id="8_xtdmn"] +[ext_resource type="Script" path="res://src/ui_parts/viewport.gd" id="9_4xrk7"] +[ext_resource type="Shader" path="res://src/shaders/zoom_shader.gdshader" id="10_x7ybk"] +[ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://visual/Checkerboard.svg" id="11_1bm1s"] +[ext_resource type="Script" path="res://src/ui_parts/display_texture.gd" id="12_qi23s"] +[ext_resource type="Script" path="res://src/ui_parts/handles_manager.gd" id="13_lwhwy"] +[ext_resource type="Script" path="res://src/ui_parts/view_camera.gd" id="14_yjb74"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nxvgo"] +content_margin_left = 7.0 +content_margin_top = 6.0 +content_margin_right = 7.0 +content_margin_bottom = 6.0 +bg_color = Color(0.0235294, 0.0235294, 0.0784314, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eujxa"] +bg_color = Color(0.866667, 0.933333, 1, 0.133333) +corner_radius_top_left = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xe141"] +draw_center = false +border_width_left = 1 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(1, 1, 1, 0.0666667) +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kqvye"] +draw_center = false +border_width_left = 1 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.501961, 0.752941, 1, 0.2) +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jk4ec"] +content_margin_left = 2.0 +content_margin_top = 2.0 +draw_center = false +border_width_left = 2 +border_width_top = 2 +border_color = Color(0.329412, 0.403922, 0.54902, 1) +corner_detail = 1 + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_kqplg"] +shader = ExtResource("10_x7ybk") +shader_parameter/uv_scale = 1.0 + +[node name="Display" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 0 +script = ExtResource("1_oib5g") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_nxvgo") + +[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer"] +layout_mode = 2 +alignment = 2 + +[node name="LeftMenu" type="HBoxContainer" parent="PanelContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 2 +theme_override_constants/separation = 5 + +[node name="MoreOptions" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +layout_mode = 2 +size_flags_horizontal = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("2_3wliq") +icon_alignment = 1 + +[node name="Settings" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +layout_mode = 2 +size_flags_horizontal = 2 +tooltip_text = "#settings" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("3_0w618") +icon_alignment = 1 + +[node name="Visuals" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu"] +layout_mode = 2 +size_flags_horizontal = 2 +tooltip_text = "#visuals" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("4_n3qjt") +icon_alignment = 1 + +[node name="Snapping" type="HBoxContainer" parent="PanelContainer/HBoxContainer/LeftMenu"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="SnapButton" type="Button" parent="PanelContainer/HBoxContainer/LeftMenu/Snapping"] +layout_mode = 2 +tooltip_text = "#enable_snap" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"RightConnectedButton" +theme_override_colors/icon_pressed_color = Color(1, 1, 1, 1) +theme_override_colors/icon_hover_color = Color(0.85, 0.85, 0.85, 1) +toggle_mode = true +icon = ExtResource("5_1k2cq") +script = ExtResource("6_3v3ve") +hover_pressed_stylebox = SubResource("StyleBoxFlat_eujxa") + +[node name="NumberEdit" parent="PanelContainer/HBoxContainer/LeftMenu/Snapping" instance=ExtResource("7_wrrfr")] +custom_minimum_size = Vector2(46, 22) +layout_mode = 2 +tooltip_text = "#snap_size" +theme_type_variation = &"LeftConnectedLineEdit" +max_length = 20 +editable = false +min_value = 0.001 +allow_lower = false +hover_stylebox = SubResource("StyleBoxFlat_xe141") +focus_stylebox = SubResource("StyleBoxFlat_kqvye") + +[node name="ZoomMenu" parent="PanelContainer/HBoxContainer" instance=ExtResource("8_xtdmn")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="ViewportPanel" type="PanelContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_jk4ec") + +[node name="ViewportContainer" type="SubViewportContainer" parent="ViewportPanel"] +process_mode = 3 +custom_minimum_size = Vector2(450, 0) +layout_mode = 2 +size_flags_vertical = 3 +stretch = true + +[node name="Viewport" type="SubViewport" parent="ViewportPanel/ViewportContainer"] +disable_3d = true +handle_input_locally = false +gui_snap_controls_to_pixels = false +size = Vector2i(1022, 602) +size_2d_override_stretch = true +render_target_update_mode = 4 +script = ExtResource("9_4xrk7") + +[node name="Checkerboard" type="TextureRect" parent="ViewportPanel/ViewportContainer/Viewport"] +unique_name_in_owner = true +texture_filter = 1 +material = SubResource("ShaderMaterial_kqplg") +texture = ExtResource("11_1bm1s") +expand_mode = 1 +stretch_mode = 1 + +[node name="DisplayTexture" type="TextureRect" parent="ViewportPanel/ViewportContainer/Viewport/Checkerboard"] +clip_contents = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +expand_mode = 1 +script = ExtResource("12_qi23s") + +[node name="Controls" type="Control" parent="ViewportPanel/ViewportContainer/Viewport/Checkerboard"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 1 +script = ExtResource("13_lwhwy") + +[node name="ViewCamera" type="Camera2D" parent="ViewportPanel/ViewportContainer/Viewport"] +anchor_mode = 0 +editor_draw_screen = false +script = ExtResource("14_yjb74") + +[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/MoreOptions" to="." method="_on_more_options_pressed"] +[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/Settings" to="." method="_on_settings_pressed"] +[connection signal="pressed" from="PanelContainer/HBoxContainer/LeftMenu/Visuals" to="." method="_on_visuals_button_pressed"] +[connection signal="toggled" from="PanelContainer/HBoxContainer/LeftMenu/Snapping/SnapButton" to="." method="_on_snap_button_toggled"] +[connection signal="value_changed" from="PanelContainer/HBoxContainer/LeftMenu/Snapping/NumberEdit" to="." method="_on_number_edit_value_changed"] +[connection signal="zoom_changed" from="PanelContainer/HBoxContainer/ZoomMenu" to="ViewportPanel/ViewportContainer/Viewport" method="_on_zoom_changed"] +[connection signal="zoom_reset_pressed" from="PanelContainer/HBoxContainer/ZoomMenu" to="ViewportPanel/ViewportContainer/Viewport" method="center_frame"] +[connection signal="size_changed" from="ViewportPanel/ViewportContainer/Viewport" to="ViewportPanel/ViewportContainer/Viewport" method="_on_size_changed"] diff --git a/src/ui_parts/display_texture.gd b/src/ui_parts/display_texture.gd index 952eb8e1..88572eb8 100644 --- a/src/ui_parts/display_texture.gd +++ b/src/ui_parts/display_texture.gd @@ -1,5 +1,7 @@ extends TextureRect +const strip_count = 8 + var rasterized := false: set(new_value): if new_value != rasterized: @@ -16,13 +18,16 @@ var image_zoom := 0.0 var update_pending := false var svg_change_pending := false +# TODO a bug in ThorVG locks this. The TextureRect should be a Control. +# I had to draw the SVG with the rendering server, I got some white textures otherwise. +#var surface := RenderingServer.canvas_item_create() + func _ready() -> void: - SVG.root_tag.attribute_changed.connect(queue_update) - SVG.root_tag.child_attribute_changed.connect(queue_update) - SVG.root_tag.tags_added.connect(queue_update.unbind(1)) - SVG.root_tag.tags_deleted.connect(queue_update.unbind(1)) - SVG.root_tag.tags_moved.connect(queue_update.unbind(2)) + #RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) + 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)) queue_update() func queue_update(svg_changed := true) -> void: @@ -36,16 +41,57 @@ func _process(_delta: float) -> void: update_pending = false svg_change_pending = false + + func svg_update(svg_changed := true) -> void: - var bigger_side := maxf(SVG.root_tag.attributes.width.get_value(), - SVG.root_tag.attributes.height.get_value()) - var img := Image.new() - var new_image_zoom := 1.0 if rasterized else minf(zoom * 3.0, 16384 / bigger_side) + var bigger_side := maxf(SVG.root_tag.get_width(), SVG.root_tag.get_height()) + # TODO Change 4096 to 16384 when performance concerns are addressed. + # It might actually not be needed in the future, if only the visible part is drawn. + var new_image_zoom := 1.0 if rasterized else minf(zoom, 4096 / bigger_side) if not svg_changed and not rasterized and new_image_zoom <= image_zoom: return # Don't waste time resizing if the new image won't be bigger. else: image_zoom = new_image_zoom - img.load_svg_from_string(SVG.string, image_zoom) - if not img.is_empty(): - texture = ImageTexture.create_from_image(img) + # TODO delete this in favor of the multithreaded solution. See godot issue 85465. + # The multithreaded solution uses RenderingServer. + # If there are bugs with it, an HBoxContainer with 8 TextureRect strips will also do. + var img := Image.new() + img.load_svg_from_string(SVG.text, image_zoom) + img.fix_alpha_edges() # See godot issue 82579. + texture = ImageTexture.create_from_image(img) + + #var task_id := WorkerThreadPool.add_group_task(generate_strip, strip_count) + #WorkerThreadPool.wait_for_group_task_completion(task_id) + #queue_redraw() + +# Strips of the final image. +#var svg_strips: Array[Texture2D] = [null, null, null, null, null, null, null, null] + +# TODO this is locked by a bug in ThorVG. +# 4 strips to be handled by WorkerThreadPool for faster image loading. +#func generate_strip(index: int) -> void: + #var svg_tag := SVG.root_tag.create_duplicate() + #svg_tag.attributes.width.set_value(svg_tag.get_width() / strip_count) + #var viewbox_attrib_value: Rect2 = svg_tag.get_viewbox() + #var strip_width := viewbox_attrib_value.size.x / strip_count + #var offset := index * strip_width + #svg_tag.attributes.viewBox.set_value(Rect2(viewbox_attrib_value.position +\ + #Vector2(offset, 0), Vector2(strip_width, viewbox_attrib_value.size.y))) + #var svg_strip_string := SVGParser.svg_to_text(svg_tag) + #var img := Image.new() + #img.load_svg_from_string(svg_strip_string, image_zoom) + #svg_strips[index] = ImageTexture.create_from_image(img) +# +#func _draw() -> void: + #RenderingServer.canvas_item_clear(surface) + #RenderingServer.canvas_item_set_default_texture_filter(surface, + #RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_NEAREST if rasterized else\ + #RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_LINEAR) + #for strip_idx in svg_strips.size(): + #var strip := svg_strips[strip_idx] + #if strip != null: + #var rect := get_rect() + #rect.size.x /= strip_count + #rect.position.x += strip_idx * rect.size.x + #strip.draw_rect(surface, rect, false) diff --git a/src/ui_parts/export_dialog.gd b/src/ui_parts/export_dialog.gd index bbb38f94..e2dc3193 100644 --- a/src/ui_parts/export_dialog.gd +++ b/src/ui_parts/export_dialog.gd @@ -1,6 +1,7 @@ extends Dialog -const SVGFileDialog := preload("svg_file_dialog.tscn") +const NumberEditType = preload("res://src/ui_elements/number_edit.gd") +const SVGFileDialog = preload("svg_file_dialog.tscn") var upscale_amount := -1.0 var extension := "" @@ -10,7 +11,7 @@ var dimensions := Vector2.ZERO @onready var texture_preview: TextureRect = %TexturePreview @onready var dropdown: HBoxContainer = %Dropdown @onready var final_dimensions_label: Label = %FinalDimensions -@onready var scale_edit: BetterLineEdit = %Scale +@onready var scale_edit: NumberEditType = %Scale @onready var scale_container: VBoxContainer = %ScaleContainer func _ready() -> void: @@ -18,14 +19,16 @@ func _ready() -> void: dropdown.value_changed.connect(_on_dropdown_value_changed) extension = dropdown.current_value update_extension_configuration() - dimensions.x = SVG.root_tag.attributes.width.get_value() - dimensions.y = SVG.root_tag.attributes.height.get_value() + dimensions = SVG.root_tag.get_size() + scale_edit.min_value = 1/minf(dimensions.x, dimensions.y) + scale_edit.max_value = 16384/maxf(dimensions.x, dimensions.y) update_dimensions_label() update_final_scale() - var scaling_factor := 512.0 / maxf(dimensions.x, dimensions.y) + var scaling_factor := texture_preview.size.x * 2.0 / maxf(dimensions.x, dimensions.y) var img := Image.new() - img.load_svg_from_string(SVG.string, scaling_factor) + img.load_svg_from_string(SVG.text, scaling_factor) if not img.is_empty(): + img.fix_alpha_edges() texture_preview.texture = ImageTexture.create_from_image(img) @@ -41,12 +44,19 @@ func _on_dropdown_value_changed(new_value: String) -> void: func native_file_export(has_selected: bool, files: PackedStringArray, _filter_idx: int): if has_selected: export(files[0]) + GlobalSettings.modify_save_data("last_used_dir", files[0].get_base_dir()) func _on_ok_button_pressed() -> void: + var default_path: String + if GlobalSettings.save_data.last_used_dir.is_empty()\ + or not DirAccess.dir_exists_absolute(GlobalSettings.save_data.last_used_dir): + default_path = OS.get_system_dir(OS.SYSTEM_DIR_PICTURES) + else: + default_path = GlobalSettings.save_data.last_used_dir if DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG): DisplayServer.file_dialog_show( "Export a ." + extension + " file", - OS.get_system_dir(OS.SYSTEM_DIR_PICTURES), "", false, + default_path, "", false, DisplayServer.FILE_DIALOG_MODE_SAVE_FILE, ["*." + extension], native_file_export) else: @@ -59,14 +69,18 @@ func export(path: String) -> void: var FA := FileAccess.open(path, FileAccess.WRITE) match extension: "png": - var img := texture_preview.texture.get_image() - var exported_size := dimensions * upscale_amount - # It's a single SVG, so just use the most expensive interpolation. - img.resize(int(exported_size.x), int(exported_size.y), Image.INTERPOLATE_LANCZOS) + var export_svg := SVG.root_tag.create_duplicate() + export_svg.attributes.width.set_value( + export_svg.attributes.width.get_value() * upscale_amount) + export_svg.attributes.height.set_value( + export_svg.attributes.height.get_value() * upscale_amount) + var img := Image.new() + img.load_svg_from_string(SVGParser.svg_to_text(export_svg)) + img.fix_alpha_edges() # See godot issue 82579. img.save_png(path) _: # SVG / fallback. - FA.store_string(SVG.string) + FA.store_string(SVG.text) queue_free() func _on_cancel_button_pressed() -> void: @@ -77,7 +91,7 @@ func _on_scale_value_changed(_new_value: float) -> void: update_final_scale() func update_final_scale() -> void: - upscale_amount = scale_edit.current_value + upscale_amount = scale_edit.get_value() var exported_size: Vector2i = dimensions * upscale_amount final_dimensions_label.text = tr(&"#final_size") +\ ": %d×%d" % [exported_size.x, exported_size.y] diff --git a/src/ui_parts/export_dialog.tscn b/src/ui_parts/export_dialog.tscn index de8b67e3..5d984a9e 100644 --- a/src/ui_parts/export_dialog.tscn +++ b/src/ui_parts/export_dialog.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=8 format=3 uid="uid://c13dadqbljqlu"] [ext_resource type="Script" path="res://src/ui_parts/export_dialog.gd" id="1_objnb"] -[ext_resource type="Shader" path="res://src/ui_parts/zoom_shader.gdshader" id="2_6bte3"] +[ext_resource type="Shader" path="res://src/shaders/zoom_shader.gdshader" id="2_6bte3"] [ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://visual/Checkerboard.svg" id="3_q8p2p"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="4_4t2iq"] [ext_resource type="PackedScene" uid="uid://dbu1lvajypafb" path="res://src/ui_elements/dropdown.tscn" id="5_y6ex0"] @@ -54,14 +54,14 @@ horizontal_alignment = 1 layout_mode = 2 theme_override_constants/separation = 12 -[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] +[node name="Checkerboard" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] material = SubResource("ShaderMaterial_y7eee") custom_minimum_size = Vector2(128, 128) layout_mode = 2 texture = ExtResource("3_q8p2p") stretch_mode = 1 -[node name="TexturePreview" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/TextureRect"] +[node name="TexturePreview" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/Checkerboard"] unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 @@ -69,7 +69,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -expand_mode = 3 +expand_mode = 2 stretch_mode = 5 [node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] @@ -123,6 +123,7 @@ custom_minimum_size = Vector2(46, 22) layout_mode = 2 initial_value = 8.0 allow_lower = false +allow_higher = false [node name="FinalDimensions" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/VBoxContainer/ScaleContainer"] unique_name_in_owner = true @@ -138,11 +139,13 @@ alignment = 1 [node name="CancelButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/ButtonContainer"] layout_mode = 2 +focus_mode = 0 mouse_default_cursor_shape = 2 text = "#cancel" [node name="OKButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/ButtonContainer"] layout_mode = 2 +focus_mode = 0 mouse_default_cursor_shape = 2 text = "#export" diff --git a/src/ui_parts/handles_manager.gd b/src/ui_parts/handles_manager.gd index 9320f9b4..964ee512 100644 --- a/src/ui_parts/handles_manager.gd +++ b/src/ui_parts/handles_manager.gd @@ -1,11 +1,6 @@ ## Contours drawing and [Handle]s are managed here. extends Control -const handle_sizes = { - Handle.DisplayMode.BIG: Vector2(8, 8), - Handle.DisplayMode.SMALL: Vector2(6, 6), -} - const normal_handle_textures = { Handle.DisplayMode.BIG: preload("res://visual/icons/HandleBig.svg"), Handle.DisplayMode.SMALL: preload("res://visual/icons/HandleSmall.svg"), @@ -40,11 +35,10 @@ enum InteractionType {NONE = 0, HOVERED = 1, SELECTED = 2, HOVERED_SELECTED = 3} var zoom := 1.0: set(new_value): zoom = new_value + RenderingServer.canvas_item_set_transform(surface, + Transform2D(0.0, Vector2(1/zoom, 1/zoom), 0.0, Vector2(0, 0))) queue_redraw() -var snap_enabled := false -var snap_size := Vector2(0.5, 0.5) - var width: float var height: float var viewbox: Rect2 @@ -54,24 +48,26 @@ var update_pending := false var handles: Array[Handle] +var surface := RenderingServer.canvas_item_create() + func _ready() -> void: - SVG.root_tag.attribute_changed.connect(update_dimensions) - SVG.root_tag.child_attribute_changed.connect(queue_redraw) - SVG.root_tag.child_attribute_changed.connect(sync_handles) - SVG.root_tag.tags_added.connect(queue_update.unbind(1)) - SVG.root_tag.tags_deleted.connect(queue_update.unbind(1)) - SVG.root_tag.tags_moved.connect(queue_update.unbind(2)) + RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) + SVG.root_tag.resized.connect(update_dimensions) + 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.root_tag.changed_unknown.connect(update_dimensions) Indications.selection_changed.connect(queue_redraw) Indications.hover_changed.connect(queue_redraw) update_dimensions() func update_dimensions() -> void: - width = SVG.root_tag.attributes.width.get_value() - height = SVG.root_tag.attributes.height.get_value() - viewbox = SVG.root_tag.attributes.viewBox.get_value() - viewbox_zoom = minf(width / viewbox.size.x, height / viewbox.size.y) + width = SVG.root_tag.get_width() + height = SVG.root_tag.get_height() + viewbox = SVG.root_tag.get_viewbox() + viewbox_zoom = Utils.get_viewbox_zoom(viewbox, width, height) queue_update() @@ -86,35 +82,22 @@ func _process(_delta: float) -> void: func update_handles() -> void: handles.clear() - for tag_idx in SVG.root_tag.get_child_count(): - setup_handles_for_tag(PackedInt32Array([tag_idx])) + for tid in SVG.root_tag.get_all_tids(): + var tag := SVG.root_tag.get_by_tid(tid) + match tag.name: + "circle": + handles.append(generate_xy_handle(tid, tag, "cx", "cy")) + "ellipse": + handles.append(generate_xy_handle(tid, tag, "cx", "cy")) + "rect": + handles.append(generate_xy_handle(tid, tag, "x", "y")) + "line": + handles.append(generate_xy_handle(tid, tag, "x1", "y1")) + handles.append(generate_xy_handle(tid, tag, "x2", "y2")) + "path": + handles += generate_path_handles(tid, tag.attributes.d) queue_redraw() -func setup_handles_for_tag(tid: PackedInt32Array): - var tag := SVG.root_tag.get_by_tid(tid) - var new_handles: Array[Handle] = [] - match tag.name: - "circle": - new_handles.append(XYHandle.new(tid, tag.attributes.cx, tag.attributes.cy)) - "ellipse": - new_handles.append(XYHandle.new(tid, tag.attributes.cx, tag.attributes.cy)) - "rect": - new_handles.append(XYHandle.new(tid, tag.attributes.x, tag.attributes.y)) - "line": - new_handles.append(XYHandle.new(tid, tag.attributes.x1, tag.attributes.y1)) - new_handles.append(XYHandle.new(tid, tag.attributes.x2, tag.attributes.y2)) - "path": - new_handles += generate_path_handles(tid, tag.attributes.d) - for handle in new_handles: - handle.tag = tag - handle.tid = tid - handles += new_handles - - for tag_idx in tag.get_child_count(): - var new_tid := tid.duplicate() - new_tid.append(tag_idx) - setup_handles_for_tag(new_tid) - func sync_handles() -> void: # For XYHandles, sync them. For path handles, sync all but the one being dragged. @@ -150,10 +133,17 @@ path_attribute: AttributePath) -> Array[Handle]: path_handles.append(tangent) return path_handles +# The place where this is used, a tag is already at hand, so no need to find it. +func generate_xy_handle(tid: PackedInt32Array, tag: Tag, x_attrib: String, +y_attrib: String) -> XYHandle: + var new_handle := XYHandle.new(tid, tag.attributes[x_attrib], tag.attributes[y_attrib]) + new_handle.tag = tag + return new_handle + func _draw() -> void: - var thickness := 0.85 / zoom - var tangent_thickness := 0.55 / zoom + var thickness := 1.0 / zoom + var tangent_thickness := 0.6 / zoom var tangent_alpha := 0.8 # Draw the contours of shapes, and also tangents of bezier curves in paths. @@ -172,17 +162,21 @@ func _draw() -> void: var tag := SVG.root_tag.get_by_tid(tid) var attribs := tag.attributes + # Determine if the tag is hovered/selected or has a hovered/selected parent. + var tag_hovered := tid_is_hovered(tid, -1) + var tag_selected := tid_is_selected(tid, -1) + match tag.name: "circle": var c := Vector2(attribs.cx.get_value(), attribs.cy.get_value()) var r: float = attribs.r.get_value() - var points := PackedVector2Array() - for i in range(0, 361, 2): - var d := deg_to_rad(i) - points.append(convert_in(c + Vector2(cos(d) * r, sin(d) * r))) - var tag_hovered := tid == Indications.hovered_tid - var tag_selected := tid in Indications.selected_tids + var points := PackedVector2Array() + points.resize(181) + for i in 180: + var d := i * TAU/180 + points[i] = convert_in(c + Vector2(cos(d) * r, sin(d) * r)) + points[180] = points[0] if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -192,19 +186,18 @@ func _draw() -> void: selected_polylines.append(points) else: normal_polylines.append(points) - + "ellipse": var c := Vector2(attribs.cx.get_value(), attribs.cy.get_value()) var rx: float = attribs.rx.get_value() var ry: float = attribs.ry.get_value() # Squished circle. var points := PackedVector2Array() - for i in range(0, 361, 2): - var d := deg_to_rad(i) - points.append(convert_in(c + Vector2(cos(d) * rx, sin(d) * ry))) - - var tag_hovered := tid == Indications.hovered_tid - var tag_selected := tid in Indications.selected_tids + points.resize(181) + for i in 180: + var d := i * TAU/180 + points[i] = convert_in(c + Vector2(cos(d) * rx, sin(d) * ry)) + points[180] = points[0] if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -214,7 +207,7 @@ func _draw() -> void: selected_polylines.append(points) else: normal_polylines.append(points) - + "rect": var x: float = attribs.x.get_value() var y: float = attribs.y.get_value() @@ -225,11 +218,12 @@ func _draw() -> void: var points := PackedVector2Array() if rx == 0 and ry == 0: # Basic rectangle. - points.append(convert_in(Vector2(x, y))) - points.append(convert_in(Vector2(x + rect_width, y))) - points.append(convert_in(Vector2(x + rect_width, y + rect_height))) - points.append(convert_in(Vector2(x, y + rect_height))) - points.append(convert_in(Vector2(x, y))) + points.resize(5) + points[0] = convert_in(Vector2(x, y)) + points[1] = convert_in(Vector2(x + rect_width, y)) + points[2] = convert_in(Vector2(x + rect_width, y + rect_height)) + points[3] = convert_in(Vector2(x, y + rect_height)) + points[4] = convert_in(Vector2(x, y)) else: if rx == 0: rx = ry @@ -238,30 +232,29 @@ func _draw() -> void: rx = minf(rx, rect_width / 2) ry = minf(ry, rect_height / 2) # Rounded rectangle. - points.append(convert_in(Vector2(x + rx, y))) - points.append(convert_in(Vector2(x + rect_width - rx, y))) - for i in range(-88, 1, 2): - var d := deg_to_rad(i) - points.append(convert_in(Vector2(x + rect_width - rx, y + ry) +\ - Vector2(cos(d) * rx, sin(d) * ry))) - points.append(convert_in(Vector2(x + rect_width, y + rect_height - ry))) - for i in range(2, 92, 2): - var d := deg_to_rad(i) - points.append(convert_in(Vector2(x + rect_width - rx, - y + rect_height - ry) + Vector2(cos(d) * rx, sin(d) * ry))) - points.append(convert_in(Vector2(x + rx, y + rect_height))) - for i in range(92, 181, 2): - var d := deg_to_rad(i) - points.append(convert_in(Vector2(x + rx, y + rect_height - ry) +\ - Vector2(cos(d) * rx, sin(d) * ry))) - points.append(convert_in(Vector2(x, y + ry))) - for i in range(182, 272, 2): - var d := deg_to_rad(i) - points.append(convert_in(Vector2(x + rx, y + ry) +\ - Vector2(cos(d) * rx, sin(d) * ry))) - - var tag_hovered := tid == Indications.hovered_tid - var tag_selected := tid in Indications.selected_tids + points.resize(186) + points[0] = convert_in(Vector2(x + rx, y)) + points[1] = convert_in(Vector2(x + rect_width - rx, y)) + for i in range(135, 180): + var d := i * TAU/180 + points[i - 133] = convert_in(Vector2(x + rect_width - rx, y + ry) +\ + Vector2(cos(d) * rx, sin(d) * ry)) + points[47] = convert_in(Vector2(x + rect_width, y + rect_height - ry)) + for i in range(0, 45): + var d := i * TAU/180 + points[i + 48] = convert_in(Vector2(x + rect_width - rx, + y + rect_height - ry) + Vector2(cos(d) * rx, sin(d) * ry)) + points[93] = convert_in(Vector2(x + rx, y + rect_height)) + for i in range(45, 90): + var d := i * TAU/180 + points[i + 49] = convert_in(Vector2(x + rx, y + rect_height - ry) +\ + Vector2(cos(d) * rx, sin(d) * ry)) + points[139] = convert_in(Vector2(x, y + ry)) + for i in range(90, 135): + var d := i * TAU/180 + points[i + 50] = convert_in(Vector2(x + rx, y + ry) +\ + Vector2(cos(d) * rx, sin(d) * ry)) + points[185] = points[0] if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -271,19 +264,14 @@ func _draw() -> void: selected_polylines.append(points) else: normal_polylines.append(points) - + "line": var x1: float = attribs.x1.get_value() var y1: float = attribs.y1.get_value() var x2: float = attribs.x2.get_value() var y2: float = attribs.y2.get_value() - var points := PackedVector2Array() - points.append(convert_in(Vector2(x1, y1))) - points.append(convert_in(Vector2(x2, y2))) - - var tag_hovered := tid == Indications.hovered_tid - var tag_selected := tid in Indications.selected_tids + var points := PackedVector2Array([Vector2(x1, y1), Vector2(x2, y2)]) if tag_hovered and tag_selected: hovered_selected_polylines.append(points) @@ -293,7 +281,7 @@ func _draw() -> void: selected_polylines.append(points) else: normal_polylines.append(points) - + "path": var pathdata: AttributePath = attribs.d var current_mode := InteractionType.NONE @@ -305,14 +293,10 @@ func _draw() -> void: var relative := cmd.relative current_mode = InteractionType.NONE - if Indications.hovered_tid == tid or\ - (Indications.semi_hovered_tid == tid and\ - Indications.inner_hovered == cmd_idx): + if tid_is_hovered(tid, cmd_idx): @warning_ignore("int_as_enum_without_cast") current_mode += InteractionType.HOVERED - if tid in Indications.selected_tids or\ - (Indications.semi_selected_tid == tid and\ - cmd_idx in Indications.inner_selections): + if tid_is_selected(tid, cmd_idx): @warning_ignore("int_as_enum_without_cast") current_mode += InteractionType.SELECTED @@ -342,12 +326,13 @@ func _draw() -> void: var cp2 := v1 if relative else v1 - cp1 var cp3 := v2 - v - points = Utils.get_cubic_bezier_points(convert_in(cp1), - cp2 * viewbox_zoom, cp3 * viewbox_zoom, convert_in(cp4)) - tangent_points.append(convert_in(cp1)) - tangent_points.append(convert_in(cp1 + v1 if relative else v1)) - tangent_points.append(convert_in(cp1 + v2 if relative else v2)) - tangent_points.append(convert_in(cp4)) + var cp1_transformed := convert_in(cp1) + var cp4_transformed := convert_in(cp4) + points = Utils.get_cubic_bezier_points(cp1_transformed, + cp2 * viewbox_zoom, cp3 * viewbox_zoom, cp4_transformed) + tangent_points.append_array(PackedVector2Array([cp1_transformed, + convert_in(cp1 + v1 if relative else v1), + convert_in(cp1 + v2 if relative else v2), cp4_transformed])) "S": # Shorthand cubic Bezier curve contour. if cmd_idx == 0: @@ -371,12 +356,13 @@ func _draw() -> void: var cp2 := v1 if relative else v1 - cp1 var cp3 := v2 - v - points = Utils.get_cubic_bezier_points(convert_in(cp1), - cp2 * viewbox_zoom, cp3 * viewbox_zoom, convert_in(cp4)) - tangent_points.append(convert_in(cp1)) - tangent_points.append(convert_in(cp1 + v1 if relative else v1)) - tangent_points.append(convert_in(cp1 + v2 if relative else v2)) - tangent_points.append(convert_in(cp4)) + var cp1_transformed := convert_in(cp1) + var cp4_transformed := convert_in(cp4) + points = Utils.get_cubic_bezier_points(cp1_transformed, + cp2 * viewbox_zoom, cp3 * viewbox_zoom, cp4_transformed) + tangent_points.append_array(PackedVector2Array([cp1_transformed, + convert_in(cp1 + v1 if relative else v1), + convert_in(cp1 + v2 if relative else v2), cp4_transformed])) "Q": # Quadratic Bezier curve contour. var v := Vector2(cmd.x, cmd.y) @@ -385,18 +371,19 @@ func _draw() -> void: var cp2 := cp1 + v1 if relative else v1 var cp3 := cp1 + v if relative else v + var cp1_transformed := convert_in(cp1) + var cp2_transformed := convert_in(cp2) + var cp3_transformed := convert_in(cp3) points = Utils.get_quadratic_bezier_points( - convert_in(cp1), convert_in(cp2), convert_in(cp3)) - tangent_points.append(convert_in(cp1)) - tangent_points.append(convert_in(cp2)) - tangent_points.append(convert_in(cp2)) - tangent_points.append(convert_in(cp3)) + cp1_transformed, cp2_transformed, cp3_transformed) + tangent_points.append_array(PackedVector2Array([cp1_transformed, + cp2_transformed, cp2_transformed, cp3_transformed])) "T": # Shorthand quadratic Bezier curve contour. var prevQ_idx := cmd_idx - 1 var prevQ_cmd := pathdata.get_command(prevQ_idx) while prevQ_idx >= 0: - if prevQ_cmd.command_char.to_upper() == "Q": + if prevQ_cmd.command_char.to_upper() != "T": break elif prevQ_cmd.command_char.to_upper() != "T": # Invalid T is drawn as a line. @@ -411,13 +398,20 @@ func _draw() -> void: prevQ_cmd = pathdata.get_command(prevQ_idx) if prevQ_idx == -1: continue - var prevQ_v := Vector2(prevQ_cmd.x, prevQ_cmd.y) - var prevQ_v1 := Vector2(prevQ_cmd.x1, prevQ_cmd.y1) + + var prevQ_x: float = prevQ_cmd.x if &"x" in prevQ_cmd\ + else prevQ_cmd.start.x + var prevQ_y: float = prevQ_cmd.y if &"y" in prevQ_cmd\ + else prevQ_cmd.start.y + var prevQ_v := Vector2(prevQ_x, prevQ_y) + var prevQ_v1 := Vector2(prevQ_cmd.x1, prevQ_cmd.y1) if\ + prevQ_cmd.command_char.to_upper() == "Q" else prevQ_v var prevQ_end := prevQ_cmd.start + prevQ_v\ if prevQ_cmd.relative else prevQ_v var prevQ_control_pt := prevQ_cmd.start + prevQ_v1\ if prevQ_cmd.relative else prevQ_v1 + var v := Vector2(cmd.x, cmd.y) var v1 := prevQ_end * 2 - prevQ_control_pt for T_idx in range(prevQ_idx + 1, cmd_idx): @@ -430,12 +424,13 @@ func _draw() -> void: var cp2 := v1 var cp3 := cp1 + v if relative else v + var cp1_transformed := convert_in(cp1) + var cp2_transformed := convert_in(cp2) + var cp3_transformed := convert_in(cp3) points = Utils.get_quadratic_bezier_points( - convert_in(cp1), convert_in(cp2), convert_in(cp3)) - tangent_points.append(convert_in(cp1)) - tangent_points.append(convert_in(cp2)) - tangent_points.append(convert_in(cp2)) - tangent_points.append(convert_in(cp3)) + cp1_transformed, cp2_transformed, cp3_transformed) + tangent_points.append_array(PackedVector2Array([cp1_transformed, + cp2_transformed, cp2_transformed, cp3_transformed])) "A": # Elliptical arc contour. var start := cmd.start @@ -546,7 +541,7 @@ func _draw() -> void: InteractionType.HOVERED_SELECTED: hovered_selected_polylines.append(points.duplicate()) hovered_selected_tangent_multiline += tangent_points.duplicate() - + for polyline in normal_polylines: draw_polyline(polyline, default_color, thickness, true) for polyline in selected_polylines: @@ -556,41 +551,37 @@ func _draw() -> void: for polyline in hovered_selected_polylines: draw_polyline(polyline, hover_selection_color, thickness, true) + @warning_ignore('integer_division') for i in normal_tangent_multiline.size() / 2: var i2 := i * 2 draw_line(normal_tangent_multiline[i2], normal_tangent_multiline[i2 + 1], Color(default_color, tangent_alpha), tangent_thickness, true) + @warning_ignore('integer_division') for i in selected_tangent_multiline.size() / 2: var i2 := i * 2 draw_line(selected_tangent_multiline[i2], selected_tangent_multiline[i2 + 1], Color(selection_color, tangent_alpha), tangent_thickness, true) + @warning_ignore('integer_division') for i in hovered_tangent_multiline.size() / 2: var i2 := i * 2 draw_line(hovered_tangent_multiline[i2], hovered_tangent_multiline[i2 + 1], Color(hover_color, tangent_alpha), tangent_thickness, true) + @warning_ignore('integer_division') for i in hovered_selected_tangent_multiline.size() / 2: var i2 := i * 2 draw_line(hovered_selected_tangent_multiline[i2], hovered_selected_tangent_multiline[i2 + 1], Color(hover_selection_color, tangent_alpha), tangent_thickness, true) + # First gather all handles in 4 categories, 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 is_hovered := false - var is_selected := false - if handle is XYHandle: - is_hovered = handle.tid == Indications.hovered_tid - is_selected = handle.tid in Indications.selected_tids - elif handle is PathHandle: - is_hovered = handle.tid == Indications.hovered_tid or\ - (handle.tid == Indications.semi_hovered_tid and\ - handle.command_index == Indications.inner_hovered) - is_selected = (handle.tid == Indications.semi_selected_tid and\ - handle.command_index in Indications.inner_selections) or\ - handle.tid in Indications.selected_tids + var cmd_idx: int = handle.command_index if handle is PathHandle else -1 + var is_hovered := tid_is_hovered(handle.tid, cmd_idx) + var is_selected := tid_is_selected(handle.tid, cmd_idx) if is_hovered and is_selected: hovered_selected_handles.append(handle) @@ -601,29 +592,42 @@ func _draw() -> void: else: normal_handles.append(handle) - var handle_texture: Texture2D - var handle_size: Vector2 - var handle_pos: Vector2 + RenderingServer.canvas_item_clear(surface) for handle in normal_handles: - handle_texture = normal_handle_textures[handle.display_mode] - handle_size = handle_sizes[handle.display_mode] / zoom - handle_pos = convert_in(handle.pos) - handle_size / 2 - draw_texture_rect(handle_texture, Rect2(handle_pos, handle_size), false) + var texture: Texture2D = normal_handle_textures[handle.display_mode] + texture.draw(surface, convert_in(handle.pos) * zoom - texture.get_size() / 2) for handle in selected_handles: - handle_texture = selected_handle_textures[handle.display_mode] - handle_size = handle_sizes[handle.display_mode] / zoom - handle_pos = convert_in(handle.pos) - handle_size / 2 - draw_texture_rect(handle_texture, Rect2(handle_pos, handle_size), false) + var texture: Texture2D = selected_handle_textures[handle.display_mode] + texture.draw(surface, convert_in(handle.pos) * zoom - texture.get_size() / 2) for handle in hovered_handles: - handle_texture = hovered_handle_textures[handle.display_mode] - handle_size = handle_sizes[handle.display_mode] / zoom - handle_pos = convert_in(handle.pos) - handle_size / 2 - draw_texture_rect(handle_texture, Rect2(handle_pos, handle_size), false) + var texture: Texture2D = hovered_handle_textures[handle.display_mode] + texture.draw(surface, convert_in(handle.pos) * zoom - texture.get_size() / 2) for handle in hovered_selected_handles: - handle_texture = hovered_selected_handle_textures[handle.display_mode] - handle_size = handle_sizes[handle.display_mode] / zoom - handle_pos = convert_in(handle.pos) - handle_size / 2 - draw_texture_rect(handle_texture, Rect2(handle_pos, handle_size), false) + var texture: Texture2D = hovered_selected_handle_textures[handle.display_mode] + texture.draw(surface, convert_in(handle.pos) * zoom - texture.get_size() / 2) + + +func tid_is_hovered(tid: PackedInt32Array, cmd_idx := -1) -> bool: + if cmd_idx == -1: + return Utils.is_tid_parent(Indications.hovered_tid, tid) or\ + tid == Indications.hovered_tid + else: + return (Utils.is_tid_parent(Indications.hovered_tid, tid) or\ + tid == Indications.hovered_tid) or (Indications.semi_hovered_tid == tid and\ + Indications.inner_hovered == cmd_idx) + +func tid_is_selected(tid: PackedInt32Array, cmd_idx := -1) -> bool: + if cmd_idx == -1: + for selected_tid in Indications.selected_tids: + if Utils.is_tid_parent(selected_tid, tid) or tid == selected_tid: + return true + return false + else: + for selected_tid in Indications.selected_tids: + if Utils.is_tid_parent(selected_tid, tid) or selected_tid == tid: + return true + return Indications.semi_selected_tid == tid and\ + cmd_idx in Indications.inner_selections func convert_in(pos: Vector2) -> Vector2: @@ -647,17 +651,20 @@ var was_handle_moved := false var should_deselect_all = false func _unhandled_input(event: InputEvent) -> void: + var snap_enabled := GlobalSettings.save_data.snap > 0.0 + var snap_size := absf(GlobalSettings.save_data.snap) + var snap_vector := Vector2(snap_size, snap_size) if not visible: return if event is InputEventMouseMotion: should_deselect_all = false - var event_pos: Vector2 = event.position + get_parent().get_parent().view.position + var event_pos: Vector2 = event.position / zoom + get_node(^"../..").view.position if dragged_handle != null: # Move the handle that's being dragged. var new_pos := convert_out(event_pos) if snap_enabled: - new_pos = new_pos.snapped(snap_size) + new_pos = new_pos.snapped(snap_vector) dragged_handle.set_pos(new_pos) was_handle_moved = true accept_event() @@ -675,7 +682,7 @@ func _unhandled_input(event: InputEvent) -> void: Indications.clear_hovered() Indications.clear_inner_hovered() elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: - var event_pos: Vector2 = event.position + get_parent().get_parent().view.position + var event_pos: Vector2 = event.position / zoom + get_node(^"../..").view.position var nearest_handle := find_nearest_handle(event_pos) if nearest_handle != null: hovered_handle = nearest_handle @@ -691,17 +698,24 @@ func _unhandled_input(event: InputEvent) -> void: # React to LMB actions. if hovered_handle != null and event.is_pressed(): dragged_handle = hovered_handle - if hovered_handle is XYHandle: - Indications.set_selection(dragged_handle.tid) - elif hovered_handle is PathHandle: - Indications.set_inner_selection(hovered_handle.tid, - hovered_handle.command_index) + dragged_handle.initial_pos = dragged_handle.pos + var inner_idx = -1 + if hovered_handle is PathHandle: + inner_idx = hovered_handle.command_index + + if event.ctrl_pressed: + Indications.ctrl_select(dragged_handle.tid, inner_idx) + elif event.shift_pressed: + Indications.shift_select(dragged_handle.tid, inner_idx) + else: + Indications.normal_select(dragged_handle.tid, inner_idx) + elif dragged_handle != null and event.is_released(): if was_handle_moved: var new_pos := convert_out(event_pos) if snap_enabled: - new_pos = new_pos.snapped(snap_size) - dragged_handle.set_pos(new_pos) + new_pos = new_pos.snapped(snap_vector) + dragged_handle.set_pos(new_pos, true) was_handle_moved = false dragged_handle = null elif hovered_handle == null and event.is_pressed(): @@ -721,10 +735,3 @@ func find_nearest_handle(event_pos: Vector2) -> Handle: nearest_dist = dist_to_handle nearest_handle = handle return nearest_handle - - -func _on_snapper_value_changed(new_value: float) -> void: - snap_size = Vector2(new_value, new_value) - -func _on_snap_button_toggled(toggled_on: bool) -> void: - snap_enabled = toggled_on diff --git a/src/ui_parts/import_warning_dialog.gd b/src/ui_parts/import_warning_dialog.gd index b0dd90a4..efdbe749 100644 --- a/src/ui_parts/import_warning_dialog.gd +++ b/src/ui_parts/import_warning_dialog.gd @@ -4,18 +4,21 @@ signal imported(text: String) @onready var warnings_label: RichTextLabel = %WarningsLabel @onready var texture_preview: TextureRect = %TexturePreview +@onready var ok_button: Button = %ButtonContainer/OKButton var imported_text := "" func _ready() -> void: + ok_button.grab_focus() # Convert forward and backward to show any artifacts that might occur after parsing. - var import_preview_text := SVGParser.svg_to_text(SVGParser.text_to_svg(imported_text)) - var import_preview_svg := SVGParser.text_to_svg(import_preview_text) - var scaling_factor := 256.0 / maxf(import_preview_svg.attributes.width.get_value(), - import_preview_svg.attributes.height.get_value()) + var preview_text := SVGParser.svg_to_text(SVGParser.text_to_svg(imported_text)) + var preview_svg := SVGParser.text_to_svg(preview_text) + var scaling_factor := texture_preview.size.x * 2.0 /\ + maxf(preview_svg.get_width(), preview_svg.get_height()) var img := Image.new() - img.load_svg_from_string(import_preview_text, scaling_factor) + img.load_svg_from_string(preview_text, scaling_factor) if not img.is_empty(): + img.fix_alpha_edges() texture_preview.texture = ImageTexture.create_from_image(img) var warnings := get_svg_errors(imported_text) if warnings.is_empty(): diff --git a/src/ui_parts/import_warning_dialog.tscn b/src/ui_parts/import_warning_dialog.tscn index 9b653b4c..f9b79bb3 100644 --- a/src/ui_parts/import_warning_dialog.tscn +++ b/src/ui_parts/import_warning_dialog.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=6 format=3 uid="uid://bhskf8yrulqtj"] [ext_resource type="Script" path="res://src/ui_parts/import_warning_dialog.gd" id="1_1rv5w"] -[ext_resource type="Shader" path="res://src/ui_parts/zoom_shader.gdshader" id="2_o24gk"] +[ext_resource type="Shader" path="res://src/shaders/zoom_shader.gdshader" id="2_o24gk"] [ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://visual/Checkerboard.svg" id="3_k3bec"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="4_rpfrk"] @@ -25,10 +25,10 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -2.0 -offset_top = -2.0 -offset_right = 2.0 -offset_bottom = 2.0 +offset_left = -174.0 +offset_top = -111.0 +offset_right = 174.0 +offset_bottom = 111.0 grow_horizontal = 2 grow_vertical = 2 @@ -56,12 +56,13 @@ theme_override_constants/separation = 12 material = SubResource("ShaderMaterial_774g4") custom_minimum_size = Vector2(128, 128) layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 texture = ExtResource("3_k3bec") stretch_mode = 1 [node name="TexturePreview" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/TextureContainer/Checkerboard"] unique_name_in_owner = true -custom_minimum_size = Vector2(128, 128) layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -72,7 +73,7 @@ expand_mode = 2 stretch_mode = 5 [node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/TextureContainer"] -custom_minimum_size = Vector2(192, 0) +custom_minimum_size = Vector2(256, 128) layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/margin_left = 4 @@ -80,15 +81,22 @@ theme_override_constants/margin_top = 6 theme_override_constants/margin_right = 4 theme_override_constants/margin_bottom = 6 -[node name="WarningsLabel" type="RichTextLabel" parent="PanelContainer/MarginContainer/VBoxContainer/TextureContainer/MarginContainer"] +[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/MarginContainer/VBoxContainer/TextureContainer/MarginContainer"] +layout_mode = 2 + +[node name="WarningsLabel" type="RichTextLabel" parent="PanelContainer/MarginContainer/VBoxContainer/TextureContainer/MarginContainer/ScrollContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 +size_flags_vertical = 3 theme_override_colors/default_color = Color(1, 0.4, 0.4, 1) theme_override_fonts/normal_font = ExtResource("4_rpfrk") +fit_content = true +scroll_active = false autowrap_mode = 0 [node name="ButtonContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true layout_mode = 2 theme_override_constants/separation = 12 alignment = 1 diff --git a/src/ui_parts/inspector.gd b/src/ui_parts/inspector.gd index c82f5baa..48d3f383 100644 --- a/src/ui_parts/inspector.gd +++ b/src/ui_parts/inspector.gd @@ -1,37 +1,30 @@ extends VBoxContainer +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") const TagEditor = preload("tag_editor.tscn") -@onready var tags_container: VBoxContainer = %Tags +@onready var tags_container: VBoxContainer = %ScrollContainer/Tags @onready var svg_tag_editor: MarginContainer = $SVGTagEditor -@onready var add_popup: Popup = $AddPopup @onready var add_button: Button = $VBoxContainer/AddButton func _ready() -> void: - populate_add_popup() - SVG.root_tag.attribute_changed.connect(svg_tag_editor.update_svg_attributes) - SVG.root_tag.tags_added.connect(full_rebuild.unbind(1)) - SVG.root_tag.tags_moved.connect(full_rebuild.unbind(2)) - SVG.root_tag.tags_deleted.connect(full_rebuild.unbind(1)) + SVG.root_tag.tag_layout_changed.connect(full_rebuild) SVG.root_tag.changed_unknown.connect(full_rebuild) full_rebuild() func full_rebuild() -> void: - svg_tag_editor.update_svg_attributes() for node in tags_container.get_children(): node.queue_free() svg_tag_editor.tag = SVG.root_tag # Only add the first level of tags, they will automatically add their children. for tag_idx in SVG.root_tag.get_child_count(): - var tag := SVG.root_tag.child_tags[tag_idx] var tag_editor := TagEditor.instantiate() - tag_editor.tag = tag + 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: - add_popup.hide() var new_tid := PackedInt32Array([SVG.root_tag.get_child_count()]) var new_tag: Tag match tag_name: @@ -49,9 +42,9 @@ func _on_tag_container_gui_input(event: InputEvent) -> void: Indications.clear_selection() Indications.clear_inner_selection() -func populate_add_popup() -> void: +func _on_add_button_pressed() -> void: var btn_array: Array[Button] = [] - for tag_name in ["circle", "ellipse", "rect", "path", "line"]: + for tag_name in ["path", "circle", "ellipse", "rect", "line"]: var add_btn := Button.new() add_btn.text = tag_name add_btn.add_theme_font_override(&"font", load("res://visual/fonts/FontMono.ttf")) @@ -61,8 +54,8 @@ func populate_add_popup() -> void: add_btn.pressed.connect(add_tag.bind(tag_name)) btn_array.append(add_btn) + var add_popup := ContextPopup.instantiate() + add_child(add_popup) add_popup.set_btn_array(btn_array) - -func _on_add_button_pressed() -> void: - add_popup.popup(Utils.calculate_popup_rect(add_button.global_position, - add_button.size, add_popup.size)) + add_popup.set_min_width(add_button.size.x) + Utils.popup_under_control(add_popup, add_button) diff --git a/src/ui_parts/inspector.tscn b/src/ui_parts/inspector.tscn index 656b3a4d..1795c2f3 100644 --- a/src/ui_parts/inspector.tscn +++ b/src/ui_parts/inspector.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=6 format=3 uid="uid://ccynisiuyn5qn"] +[gd_scene load_steps=5 format=3 uid="uid://ccynisiuyn5qn"] [ext_resource type="Script" path="res://src/ui_parts/inspector.gd" id="1_16ggy"] [ext_resource type="PackedScene" uid="uid://bktmk76u7dsu0" path="res://src/ui_parts/root_tag_editor.tscn" id="2_jnl50"] [ext_resource type="Texture2D" uid="uid://eif2ioi0mw17" path="res://visual/icons/Plus.svg" id="3_vo6hf"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="4_8yv7j"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4j4hv"] draw_center = false @@ -18,10 +17,14 @@ corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 [node name="Inspector" type="VBoxContainer"] -custom_minimum_size = Vector2(0, 360) +custom_minimum_size = Vector2(392, 0) +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 392.0 +grow_vertical = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_constants/separation = 16 +theme_override_constants/separation = 6 script = ExtResource("1_16ggy") [node name="SVGTagEditor" parent="." instance=ExtResource("2_jnl50")] @@ -42,25 +45,23 @@ icon = ExtResource("3_vo6hf") [node name="TagContainer" type="PanelContainer" parent="VBoxContainer"] clip_contents = true +custom_minimum_size = Vector2(0, 200) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 theme_override_styles/panel = SubResource("StyleBoxFlat_4j4hv") [node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/TagContainer"] +unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 horizontal_scroll_mode = 0 [node name="Tags" type="VBoxContainer" parent="VBoxContainer/TagContainer/ScrollContainer"] -unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 theme_override_constants/separation = 5 -[node name="AddPopup" parent="." instance=ExtResource("4_8yv7j")] -visible = false - [connection signal="pressed" from="VBoxContainer/AddButton" to="." method="_on_add_button_pressed"] [connection signal="gui_input" from="VBoxContainer/TagContainer" to="." method="_on_tag_container_gui_input"] diff --git a/src/ui_parts/main_scene.tscn b/src/ui_parts/main_scene.tscn index 9fc04b64..0122c195 100644 --- a/src/ui_parts/main_scene.tscn +++ b/src/ui_parts/main_scene.tscn @@ -1,94 +1,18 @@ -[gd_scene load_steps=30 format=3 uid="uid://ce6j54x27pom"] +[gd_scene load_steps=6 format=3 uid="uid://ce6j54x27pom"] +[ext_resource type="PackedScene" uid="uid://cr1fdlmbknnko" path="res://src/ui_parts/code_editor.tscn" id="1_0jgh3"] +[ext_resource type="Texture2D" uid="uid://co75w07yqmcro" path="res://visual/icons/theme/SplitGrabber2.svg" id="1_7y812"] [ext_resource type="PackedScene" uid="uid://ccynisiuyn5qn" path="res://src/ui_parts/inspector.tscn" id="1_afxvd"] -[ext_resource type="Texture2D" uid="uid://ccvjkdd0s7rb4" path="res://visual/icons/Copy.svg" id="1_fm0ux"] -[ext_resource type="Script" path="res://src/ui_parts/code_editor.gd" id="1_ry6k8"] -[ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="2_w635r"] -[ext_resource type="Script" path="res://src/ui_parts/display.gd" id="2_ylpv1"] -[ext_resource type="Texture2D" uid="uid://c68og6bsqt0lb" path="res://visual/Checkerboard.svg" id="3_d58qh"] -[ext_resource type="Texture2D" uid="uid://ckkkgof1hcbld" path="res://visual/icons/Gear.svg" id="4_3rshc"] -[ext_resource type="Texture2D" uid="uid://6ymbl3jqersp" path="res://visual/icons/Import.svg" id="4_ehrkr"] -[ext_resource type="Script" path="res://src/ui_parts/handles_manager.gd" id="5_pltvx"] -[ext_resource type="Texture2D" uid="uid://d0uvwj0t44n6v" path="res://visual/icons/Export.svg" id="5_qe6jq"] -[ext_resource type="Script" path="res://src/data_classes/SVGHighlighter.gd" id="6_0jtpx"] -[ext_resource type="Script" path="res://src/ui_parts/viewport.gd" id="6_7hypx"] -[ext_resource type="Texture2D" uid="uid://ccbta5q43jobk" path="res://visual/icons/More.svg" id="7_7iuuq"] -[ext_resource type="Script" path="res://src/ui_parts/display_texture.gd" id="7_cxx0h"] -[ext_resource type="Script" path="res://src/ui_parts/svg_code_edit.gd" id="7_q0ibc"] -[ext_resource type="Texture2D" uid="uid://kkxyv1gyrjgj" path="res://visual/icons/Visuals.svg" id="9_ojv7n"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="10_hraj8"] -[ext_resource type="Texture2D" uid="uid://buire51l0mifg" path="res://visual/icons/Snap.svg" id="11_u7ddj"] -[ext_resource type="Shader" path="res://src/ui_parts/zoom_shader.gdshader" id="12_wewnk"] -[ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_elements/number_edit.tscn" id="13_oxv3i"] -[ext_resource type="Script" path="res://src/ui_parts/view_camera.gd" id="15_v2yj8"] -[ext_resource type="PackedScene" uid="uid://oltvrf01xrxl" path="res://src/ui_parts/zoom_menu.tscn" id="18_e3qve"] +[ext_resource type="PackedScene" uid="uid://bvrncl7e6yn5b" path="res://src/ui_parts/display.tscn" id="3_qbqbs"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_082e3"] content_margin_left = 6.0 -content_margin_top = 8.0 -content_margin_right = 6.0 -content_margin_bottom = 8.0 -bg_color = Color(0.02, 0.02, 0.08, 1) -border_width_right = 2 -border_color = Color(0.4, 0.7, 1, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56qh"] -content_margin_left = 8.0 -content_margin_top = 2.0 -content_margin_right = 6.0 -content_margin_bottom = 2.0 -bg_color = Color(0.0975, 0.0975, 0.15, 1) -border_color = Color(0.1875, 0.1875, 0.25, 1) -corner_radius_top_left = 5 -corner_radius_top_right = 5 -corner_detail = 16 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rppt3"] -content_margin_left = 5.0 -content_margin_top = 2.0 -content_margin_right = 5.0 -content_margin_bottom = 2.0 -bg_color = Color(1, 1, 1, 0.0666667) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_fhint"] -script = ExtResource("6_0jtpx") -symbol_color = Color(0.670588, 0.788235, 1, 1) -tag_color = Color(1, 0.54902, 0.8, 1) -attribute_color = Color(0.737255, 0.878431, 1, 1) -string_color = Color(0.631373, 1, 0.878431, 1) -comment_color = Color(0.803922, 0.811765, 0.823529, 0.501961) -text_color = Color(0.803922, 0.811765, 0.823529, 0.666667) -error_color = Color(1, 0.52549, 0.419608, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k0een"] -content_margin_left = 2.0 -content_margin_right = 2.0 -bg_color = Color(0.0975, 0.0975, 0.15, 1) -border_width_left = 2 -border_width_top = 1 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.1875, 0.1875, 0.25, 1) -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 -corner_detail = 16 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nxvgo"] -content_margin_left = 6.0 -content_margin_top = 5.0 -content_margin_right = 6.0 -content_margin_bottom = 7.0 -bg_color = Color(0.02, 0.02, 0.08, 1) -border_width_bottom = 2 -border_color = Color(0.4, 0.7, 1, 1) - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_kqplg"] -shader = ExtResource("12_wewnk") -shader_parameter/uv_scale = 1.0 +content_margin_top = 6.0 +content_margin_right = 0.0 +content_margin_bottom = 6.0 +bg_color = Color(0.0235294, 0.0235294, 0.0784314, 1) +border_color = Color(0.278431, 0.286275, 0.568627, 1) +expand_margin_right = 6.0 [node name="MainScene" type="HBoxContainer"] anchors_preset = 15 @@ -98,251 +22,31 @@ grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 0 -[node name="PanelContainer" type="PanelContainer" parent="."] +[node name="HSplitContainer" type="HSplitContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 6 +theme_override_icons/grabber = ExtResource("1_7y812") +split_offset = -122 + +[node name="PanelContainer" type="PanelContainer" parent="HSplitContainer"] +custom_minimum_size = Vector2(360, 0) layout_mode = 2 size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxFlat_082e3") -[node name="MainContainer" type="VSplitContainer" parent="PanelContainer"] +[node name="MainContainer" type="VSplitContainer" parent="HSplitContainer/PanelContainer"] layout_mode = 2 theme_override_constants/separation = 8 split_offset = -400 -[node name="CodeEditor" type="VBoxContainer" parent="PanelContainer/MainContainer"] +[node name="CodeEditor" parent="HSplitContainer/PanelContainer/MainContainer" instance=ExtResource("1_0jgh3")] layout_mode = 2 -size_flags_vertical = 3 -theme_override_constants/separation = 0 -script = ExtResource("1_ry6k8") -[node name="PanelContainer" type="PanelContainer" parent="PanelContainer/MainContainer/CodeEditor"] +[node name="Inspector" parent="HSplitContainer/PanelContainer/MainContainer" instance=ExtResource("1_afxvd")] layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_q56qh") -[node name="CodeButtons" type="HBoxContainer" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer"] -layout_mode = 2 - -[node name="PanelContainer" type="PanelContainer" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons"] -layout_mode = 2 -size_flags_vertical = 4 -theme_override_styles/panel = SubResource("StyleBoxFlat_rppt3") - -[node name="SizeLabel" type="Label" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/PanelContainer"] +[node name="Display" parent="HSplitContainer" instance=ExtResource("3_qbqbs")] unique_name_in_owner = true layout_mode = 2 -theme_override_fonts/font = ExtResource("2_w635r") -theme_override_font_sizes/font_size = 12 - -[node name="MetaActions" type="HBoxContainer" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons"] -layout_mode = 2 -size_flags_horizontal = 10 - -[node name="CopyButton" type="Button" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions"] -layout_mode = 2 -tooltip_text = "#copy_button_tooltip" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("1_fm0ux") - -[node name="ImportButton" type="Button" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions"] -layout_mode = 2 -tooltip_text = "#import_button_tooltip" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("4_ehrkr") - -[node name="ExportButton" type="Button" parent="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions"] -layout_mode = 2 -tooltip_text = "#export_button_tooltip" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" -icon = ExtResource("5_qe6jq") - -[node name="ScriptEditor" type="VBoxContainer" parent="PanelContainer/MainContainer/CodeEditor"] -layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/separation = -2 - -[node name="SVGCodeEdit" type="TextEdit" parent="PanelContainer/MainContainer/CodeEditor/ScriptEditor"] -custom_minimum_size = Vector2(0, 96) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -wrap_mode = 1 -scroll_smooth = true -scroll_v_scroll_speed = 30.0 -caret_multiple = false -syntax_highlighter = SubResource("SyntaxHighlighter_fhint") -highlight_all_occurrences = true -script = ExtResource("7_q0ibc") - -[node name="ErrorBar" type="PanelContainer" parent="PanelContainer/MainContainer/CodeEditor/ScriptEditor"] -visible = false -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_k0een") - -[node name="Padding" type="MarginContainer" parent="PanelContainer/MainContainer/CodeEditor/ScriptEditor/ErrorBar"] -layout_mode = 2 -theme_override_constants/margin_left = 8 -theme_override_constants/margin_right = 8 - -[node name="Label" type="RichTextLabel" parent="PanelContainer/MainContainer/CodeEditor/ScriptEditor/ErrorBar/Padding"] -custom_minimum_size = Vector2(0, 20) -layout_mode = 2 -theme_override_colors/default_color = Color(1, 0.4, 0.4, 1) -theme_override_fonts/normal_font = ExtResource("2_w635r") -theme_override_font_sizes/normal_font_size = 14 -fit_content = true - -[node name="Inspector" parent="PanelContainer/MainContainer" instance=ExtResource("1_afxvd")] -layout_mode = 2 - -[node name="Display" type="VBoxContainer" parent="."] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 0 -script = ExtResource("2_ylpv1") - -[node name="PanelContainer" type="PanelContainer" parent="Display"] -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_nxvgo") - -[node name="HBoxContainer" type="HBoxContainer" parent="Display/PanelContainer"] -layout_mode = 2 -alignment = 2 - -[node name="LeftMenu" type="HBoxContainer" parent="Display/PanelContainer/HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 2 -theme_override_constants/separation = 5 - -[node name="MoreOptions" type="Button" parent="Display/PanelContainer/HBoxContainer/LeftMenu"] -custom_minimum_size = Vector2(28, 0) -layout_mode = 2 -size_flags_horizontal = 2 -focus_mode = 0 -mouse_default_cursor_shape = 2 -icon = ExtResource("7_7iuuq") -icon_alignment = 1 - -[node name="Settings" type="Button" parent="Display/PanelContainer/HBoxContainer/LeftMenu"] -custom_minimum_size = Vector2(28, 0) -layout_mode = 2 -size_flags_horizontal = 2 -tooltip_text = "#settings" -focus_mode = 0 -mouse_default_cursor_shape = 2 -icon = ExtResource("4_3rshc") -icon_alignment = 1 - -[node name="Visuals" type="Button" parent="Display/PanelContainer/HBoxContainer/LeftMenu"] -custom_minimum_size = Vector2(28, 0) -layout_mode = 2 -size_flags_horizontal = 2 -tooltip_text = "#visuals" -focus_mode = 0 -mouse_default_cursor_shape = 2 -icon = ExtResource("9_ojv7n") -icon_alignment = 1 - -[node name="Snapping" type="HBoxContainer" parent="Display/PanelContainer/HBoxContainer/LeftMenu"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="SnapButton" type="Button" parent="Display/PanelContainer/HBoxContainer/LeftMenu/Snapping"] -layout_mode = 2 -tooltip_text = "#enable_snap" -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_type_variation = &"RightConnectedButton" -toggle_mode = true -icon = ExtResource("11_u7ddj") - -[node name="NumberEdit" parent="Display/PanelContainer/HBoxContainer/LeftMenu/Snapping" instance=ExtResource("13_oxv3i")] -custom_minimum_size = Vector2(46, 22) -layout_mode = 2 -tooltip_text = "#snap_size" -theme_type_variation = &"LeftConnectedLineEdit" -max_length = 20 -editable = false -initial_value = 0.5 -min_value = 0.001 -allow_lower = false - -[node name="VisualsPopup" parent="Display/PanelContainer/HBoxContainer/LeftMenu" instance=ExtResource("10_hraj8")] -visible = false - -[node name="MorePopup" parent="Display/PanelContainer/HBoxContainer/LeftMenu" instance=ExtResource("10_hraj8")] -visible = false - -[node name="ZoomMenu" parent="Display/PanelContainer/HBoxContainer" instance=ExtResource("18_e3qve")] -unique_name_in_owner = true -layout_mode = 2 - -[node name="ViewportContainer" type="SubViewportContainer" parent="Display"] -process_mode = 3 -layout_mode = 2 -size_flags_horizontal = 10 - -[node name="Viewport" type="SubViewport" parent="Display/ViewportContainer"] -disable_3d = true -handle_input_locally = false -gui_snap_controls_to_pixels = false -size = Vector2i(600, 600) -size_2d_override_stretch = true -render_target_update_mode = 4 -script = ExtResource("6_7hypx") - -[node name="ViewCamera" type="Camera2D" parent="Display/ViewportContainer/Viewport"] -anchor_mode = 0 -editor_draw_screen = false -script = ExtResource("15_v2yj8") - -[node name="Checkerboard" type="TextureRect" parent="Display/ViewportContainer/Viewport"] -unique_name_in_owner = true -texture_filter = 1 -material = SubResource("ShaderMaterial_kqplg") -texture = ExtResource("3_d58qh") -expand_mode = 1 -stretch_mode = 1 - -[node name="DisplayTexture" type="TextureRect" parent="Display/ViewportContainer/Viewport/Checkerboard"] -clip_contents = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -expand_mode = 1 -script = ExtResource("7_cxx0h") - -[node name="Controls" type="Control" parent="Display/ViewportContainer/Viewport/Checkerboard"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 1 -script = ExtResource("5_pltvx") - -[connection signal="pressed" from="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions/CopyButton" to="PanelContainer/MainContainer/CodeEditor" method="_on_copy_button_pressed"] -[connection signal="pressed" from="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions/ImportButton" to="PanelContainer/MainContainer/CodeEditor" method="_on_import_button_pressed"] -[connection signal="pressed" from="PanelContainer/MainContainer/CodeEditor/PanelContainer/CodeButtons/MetaActions/ExportButton" to="PanelContainer/MainContainer/CodeEditor" method="_on_export_button_pressed"] -[connection signal="caret_changed" from="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" to="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" method="redraw_caret"] -[connection signal="focus_entered" from="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" to="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" method="_on_focus_entered"] -[connection signal="focus_exited" from="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" to="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" method="_on_focus_exited"] -[connection signal="text_changed" from="PanelContainer/MainContainer/CodeEditor/ScriptEditor/SVGCodeEdit" to="PanelContainer/MainContainer/CodeEditor" method="_on_code_edit_text_changed"] -[connection signal="pressed" from="Display/PanelContainer/HBoxContainer/LeftMenu/MoreOptions" to="Display" method="_on_more_options_pressed"] -[connection signal="pressed" from="Display/PanelContainer/HBoxContainer/LeftMenu/Settings" to="Display" method="_on_settings_pressed"] -[connection signal="pressed" from="Display/PanelContainer/HBoxContainer/LeftMenu/Visuals" to="Display" method="_on_visuals_button_pressed"] -[connection signal="toggled" from="Display/PanelContainer/HBoxContainer/LeftMenu/Snapping/SnapButton" to="Display" method="_on_snap_button_toggled"] -[connection signal="toggled" from="Display/PanelContainer/HBoxContainer/LeftMenu/Snapping/SnapButton" to="Display/ViewportContainer/Viewport/Checkerboard/Controls" method="_on_snap_button_toggled"] -[connection signal="value_changed" from="Display/PanelContainer/HBoxContainer/LeftMenu/Snapping/NumberEdit" to="Display/ViewportContainer/Viewport/Checkerboard/Controls" method="_on_snapper_value_changed"] -[connection signal="zoom_changed" from="Display/PanelContainer/HBoxContainer/ZoomMenu" to="Display/ViewportContainer/Viewport" method="_on_zoom_changed"] -[connection signal="zoom_reset_pressed" from="Display/PanelContainer/HBoxContainer/ZoomMenu" to="Display/ViewportContainer/Viewport" method="center_frame"] diff --git a/src/ui_parts/palette_config.gd b/src/ui_parts/palette_config.gd new file mode 100644 index 00000000..d9e12449 --- /dev/null +++ b/src/ui_parts/palette_config.gd @@ -0,0 +1,125 @@ +extends PanelContainer + +const ColorSwatch = preload("res://src/ui_elements/color_swatch.tscn") +const ConfigurePopup = preload("res://src/ui_parts/configure_color_popup.tscn") + +signal color_picked(color: String) +signal deleted + +var current_palette: ColorPalette +var currently_edited_color: NamedColor + +@onready var margin_container: MarginContainer = $MarginContainer +@onready var palette_label: Label = %MainContainer/HBoxContainer/PaletteLabel +@onready var name_edit: BetterLineEdit = %MainContainer/HBoxContainer/NameEdit +@onready var name_edit_button: Button = %MainContainer/HBoxContainer/EditButton +@onready var colors_container: HFlowContainer = %MainContainer/ColorsContainer + +# Used to setup a palette for this element. +func assign_palette(palette: ColorPalette) -> void: + current_palette = palette + rebuild_colors() + +# Rebuilds the content of a container. +func rebuild_colors() -> void: + for child in colors_container.get_children(): + child.queue_free() + + set_label_text(current_palette.name) + if current_palette.name.is_empty(): + popup_edit_name() + else: + set_label_text(current_palette.name) + for named_color in current_palette.named_colors: + var swatch := ColorSwatch.instantiate() + swatch.named_color = named_color + swatch.type = swatch.Type.CONFIGURE_COLOR + swatch.pressed.connect(popup_configure_color.bind(swatch)) + colors_container.add_child(swatch) + if named_color == currently_edited_color: + # If you add a color, after the rebuild you should instantly edit the new color. + # TODO figure out how to do without waiting a frame. + await get_tree().process_frame + popup_configure_color(swatch) + # Add the add button. + var fake_swatch := ColorSwatch.instantiate() + fake_swatch.type = fake_swatch.Type.ADD_COLOR + fake_swatch.pressed.connect(popup_add_color) + colors_container.add_child(fake_swatch) + +func popup_configure_color(swatch: Button) -> void: + var configure_popup := ConfigurePopup.instantiate() + configure_popup.named_color = swatch.named_color + add_child(configure_popup) + configure_popup.color_edit.value_changed.connect(swatch.change_color) + configure_popup.color_name_edit.text_submitted.connect(swatch.change_color_name) + configure_popup.color_deletion_requested.connect(delete_color.bind(swatch.named_color)) + Utils.popup_under_control_centered(configure_popup, swatch) + +func popup_edit_name() -> void: + palette_label.hide() + name_edit_button.hide() + margin_container.add_theme_constant_override(&"margin_top", 1) + name_edit.show() + name_edit.text = current_palette.name + name_edit.grab_focus() + name_edit.caret_column = name_edit.text.length() + +func hide_name_edit() -> void: + palette_label.show() + name_edit_button.show() + name_edit.hide() + margin_container.remove_theme_constant_override(&"margin_top") + +# Update text color to red if the name won't work (because it's a duplicate). +func _on_name_edit_text_changed(new_text: String) -> void: + var names: Array[String] = [] + for palette in GlobalSettings.get_palettes(): + names.append(palette.name) + if new_text in names and new_text != current_palette.name: + name_edit.add_theme_color_override(&"font_color", Color(1.0, 0.6, 0.6)) + else: + name_edit.add_theme_color_override(&"font_color", Color(0.6, 1.0, 0.6)) + +func change_name(new_name: String) -> void: + new_name = new_name.strip_edges() + var names: Array[String] = [] + for palette in GlobalSettings.get_palettes(): + names.append(palette.name) + + if not new_name.is_empty() and new_name != current_palette.name and\ + not new_name in names: + current_palette.name = new_name + GlobalSettings.save_user_data() + + set_label_text(current_palette.name) + hide_name_edit() + +func popup_add_color() -> void: + var new_color := NamedColor.new("none", "") + current_palette.named_colors.append(new_color) + currently_edited_color = new_color + rebuild_colors() + +func _on_delete_button_pressed() -> void: + for palette_idx in GlobalSettings.get_palettes().size(): + if GlobalSettings.get_palettes()[palette_idx].name == current_palette.name: + GlobalSettings.get_palettes().remove_at(palette_idx) + GlobalSettings.save_user_data() + break + deleted.emit() + +func _on_name_edit_focus_exited() -> void: + hide_name_edit() + +func set_label_text(new_text: String) -> void: + if new_text.is_empty(): + palette_label.text = tr(&"#unnamed") + palette_label.add_theme_color_override(&"font_color", Color(1.0, 0.5, 0.5)) + else: + palette_label.text = new_text + palette_label.remove_theme_color_override(&"font_color") + +func delete_color(named_color: NamedColor) -> void: + current_palette.named_colors.erase(named_color) # I hope this works. + rebuild_colors() diff --git a/src/ui_parts/palette_config.tscn b/src/ui_parts/palette_config.tscn new file mode 100644 index 00000000..eaca0b12 --- /dev/null +++ b/src/ui_parts/palette_config.tscn @@ -0,0 +1,95 @@ +[gd_scene load_steps=6 format=3 uid="uid://c5cavwa2xdt0b"] + +[ext_resource type="Script" path="res://src/ui_parts/palette_config.gd" id="1_2fy8k"] +[ext_resource type="Texture2D" uid="uid://dr2erka82g6j4" path="res://visual/icons/Edit.svg" id="2_35dgp"] +[ext_resource type="Script" path="res://src/ui_elements/BetterLineEdit.gd" id="2_coqnx"] +[ext_resource type="Texture2D" uid="uid://cj5x2ti8150ja" path="res://visual/icons/Delete.svg" id="4_ms41n"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jm6ej"] +content_margin_left = 5.0 +bg_color = Color(0.062, 0.062, 0.1, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.211765, 0.258824, 0.345098, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[node name="PaletteConfig" type="PanelContainer"] +offset_right = 21.0 +offset_bottom = 46.0 +size_flags_horizontal = 3 +size_flags_vertical = 0 +script = ExtResource("1_2fy8k") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 1 +theme_override_constants/margin_right = 6 +theme_override_constants/margin_bottom = 0 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="MainContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HBoxContainer/MainContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_constants/separation = 6 + +[node name="PaletteLabel" type="Label" parent="MarginContainer/HBoxContainer/MainContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 6 + +[node name="NameEdit" type="LineEdit" parent="MarginContainer/HBoxContainer/MainContainer/HBoxContainer"] +visible = false +custom_minimum_size = Vector2(120, 0) +layout_mode = 2 +theme_override_styles/normal = SubResource("StyleBoxFlat_jm6ej") +max_length = 30 +script = ExtResource("2_coqnx") + +[node name="EditButton" type="Button" parent="MarginContainer/HBoxContainer/MainContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +tooltip_text = "#edit_palette_name" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"FlatButton" +icon = ExtResource("2_35dgp") + +[node name="ColorsContainer" type="HFlowContainer" parent="MarginContainer/HBoxContainer/MainContainer"] +layout_mode = 2 +size_flags_vertical = 4 +theme_override_constants/h_separation = 3 + +[node name="Control" type="Control" parent="MarginContainer/HBoxContainer/MainContainer"] +custom_minimum_size = Vector2(0, 2) +layout_mode = 2 + +[node name="DeleteButton" type="Button" parent="MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 4 +tooltip_text = "#delete_palette" +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" +icon = ExtResource("4_ms41n") + +[connection signal="focus_exited" from="MarginContainer/HBoxContainer/MainContainer/HBoxContainer/NameEdit" to="." method="_on_name_edit_focus_exited"] +[connection signal="text_changed" from="MarginContainer/HBoxContainer/MainContainer/HBoxContainer/NameEdit" to="." method="_on_name_edit_text_changed"] +[connection signal="text_submitted" from="MarginContainer/HBoxContainer/MainContainer/HBoxContainer/NameEdit" to="." method="change_name"] +[connection signal="pressed" from="MarginContainer/HBoxContainer/MainContainer/HBoxContainer/EditButton" to="." method="popup_edit_name"] +[connection signal="pressed" from="MarginContainer/HBoxContainer/DeleteButton" to="." method="_on_delete_button_pressed"] diff --git a/src/ui_parts/root_tag_editor.gd b/src/ui_parts/root_tag_editor.gd index 86f4ed39..2bd265e8 100644 --- a/src/ui_parts/root_tag_editor.gd +++ b/src/ui_parts/root_tag_editor.gd @@ -1,75 +1,173 @@ extends MarginContainer -var coupled_viewbox := true +# So, about this editor. Width and height don't have real default values, so they use NAN +# and are NumberEdits, rather than NumberFields. Viewbox is its own thing and since +# it's made of four numbers, and also since I want a coupling functionality with width +# and height, I didn't make an AttributeEditor for it. It's just 4 NumberEdits. -const NumberField = preload("res://src/ui_elements/number_field.tscn") -const RectField = preload("res://src/ui_elements/rect_field.tscn") +const NumberEditType = preload("res://src/ui_elements/number_edit.gd") const coupled_icon = preload("res://visual/icons/Coupled.svg") const decoupled_icon = preload("res://visual/icons/Decoupled.svg") -signal viewbox_changed(w: float, h: float) +var true_width: float +var true_height: float +var true_viewbox: Rect2 @onready var tag := SVG.root_tag -@onready var width_container: VBoxContainer = $Edits/Size/Width -@onready var height_container: VBoxContainer = $Edits/Size/Height -@onready var viewbox_container: VBoxContainer = $Edits/ViewBox @onready var couple_button: Button = $Edits/CoupleButton +@onready var width_button: Button = $Edits/Size/Width/WidthButton +@onready var height_button: Button = $Edits/Size/Height/HeightButton +@onready var viewbox_button: Button = $Edits/Viewbox/ViewboxButton +@onready var width_edit: NumberEditType = $Edits/Size/Width/WidthEdit +@onready var height_edit: NumberEditType = $Edits/Size/Height/HeightEdit +@onready var viewbox_edit_x: NumberEditType = $Edits/Viewbox/Rect/ViewboxEditX +@onready var viewbox_edit_y: NumberEditType = $Edits/Viewbox/Rect/ViewboxEditY +@onready var viewbox_edit_w: NumberEditType = $Edits/Viewbox/Rect/ViewboxEditW +@onready var viewbox_edit_h: NumberEditType = $Edits/Viewbox/Rect/ViewboxEditH -var width_edit: AttributeEditor -var height_edit: AttributeEditor -var viewbox_edit: AttributeEditor func _ready() -> void: - SVG.root_tag.changed_unknown.connect(determine_coupling) + SVG.root_tag.resized.connect(_on_resized) + SVG.root_tag.changed_unknown.connect(_on_unknown_changed) + update_attributes(true) + + +func _on_resized() -> void: + update_attributes() + +func update_attributes(configure_coupling := false) -> void: + if configure_coupling: + update_coupling_config() + true_width = SVG.root_tag.get_width() + true_height = SVG.root_tag.get_height() + true_viewbox = SVG.root_tag.get_viewbox() + width_edit.set_value(true_width, false) + height_edit.set_value(true_height, false) + viewbox_edit_x.set_value(true_viewbox.position.x, false) + viewbox_edit_y.set_value(true_viewbox.position.y, false) + viewbox_edit_w.set_value(true_viewbox.size.x, false) + viewbox_edit_h.set_value(true_viewbox.size.y, false) + +func _on_unknown_changed() -> void: + if GlobalSettings.save_data.viewbox_coupling and (SVG.root_tag.get_viewbox() !=\ + Rect2(Vector2.ZERO, SVG.root_tag.get_size())): + GlobalSettings.modify_save_data(&"viewbox_coupling", false) + update_attributes(true) + + +func _on_couple_button_toggled(toggled_on: bool) -> void: + GlobalSettings.modify_save_data(&"viewbox_coupling", toggled_on) + update_coupling_config() + +func update_coupling_config() -> void: + update_editable() + if is_nan(SVG.root_tag.attributes.width.get_value()) or\ + is_nan(SVG.root_tag.attributes.height.get_value()) or\ + SVG.root_tag.attributes.viewBox.get_value() == null: + couple_button.disabled = true + couple_button.mouse_default_cursor_shape = Control.CURSOR_ARROW + couple_button.icon = coupled_icon + return + else: + couple_button.disabled = false + couple_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - width_edit = NumberField.instantiate() - width_edit.allow_lower = false - width_edit.attribute = tag.attributes.width - width_edit.attribute_name = "width" - width_container.add_child(width_edit) - width_edit.value_changed.connect(update_svg_attributes.unbind(1)) + var coupling_on := GlobalSettings.save_data.viewbox_coupling + couple_button.button_pressed = coupling_on + couple_button.icon = coupled_icon if coupling_on else decoupled_icon + if coupling_on: + SVG.root_tag.attributes.viewBox.set_rect_x(0.0) + SVG.root_tag.attributes.viewBox.set_rect_y(0.0) + SVG.root_tag.attributes.viewBox.set_rect_w(SVG.root_tag.get_width()) + SVG.root_tag.attributes.viewBox.set_rect_h(SVG.root_tag.get_height()) + + +func update_editable() -> void: + var is_width_valid := !is_nan(SVG.root_tag.attributes.width.get_value()) + var is_height_valid := !is_nan(SVG.root_tag.attributes.height.get_value()) + var is_viewbox_valid := (SVG.root_tag.attributes.viewBox.get_value() != null) + var coupling_on := GlobalSettings.save_data.viewbox_coupling - height_edit = NumberField.instantiate() - height_edit.allow_lower = false - height_edit.attribute = tag.attributes.height - height_edit.attribute_name = "height" - height_container.add_child(height_edit) - height_edit.value_changed.connect(update_svg_attributes.unbind(1)) + width_button.set_pressed_no_signal(is_width_valid) + height_button.set_pressed_no_signal(is_height_valid) + viewbox_button.set_pressed_no_signal(is_viewbox_valid) - viewbox_edit = RectField.instantiate() - viewbox_edit.attribute = tag.attributes.viewBox - viewbox_edit.attribute_name = "viewBox" - viewbox_container.add_child(viewbox_edit) - viewbox_edit.value_changed.connect(update_svg_attributes.unbind(1)) - determine_coupling() - - -func update_svg_attributes() -> void: - var new_width_value: float = SVG.root_tag.attributes.width.get_value() - var new_height_value: float = SVG.root_tag.attributes.height.get_value() - width_edit.set_value(new_width_value, false) - height_edit.set_value(new_height_value, false) - if coupled_viewbox: - viewbox_edit.set_value(Rect2(0, 0, new_width_value, new_height_value)) + width_edit.editable = is_width_valid + height_edit.editable = is_height_valid + viewbox_edit_x.editable = is_viewbox_valid and not coupling_on + viewbox_edit_y.editable = is_viewbox_valid and not coupling_on + viewbox_edit_w.editable = is_viewbox_valid + viewbox_edit_h.editable = is_viewbox_valid + + +func _on_width_edit_value_changed(new_value: float) -> void: + if !is_nan(new_value) and SVG.root_tag.attributes.width.get_value() != new_value: + true_width = new_value + if GlobalSettings.save_data.viewbox_coupling: + SVG.root_tag.attributes.width.set_value(new_value, + Attribute.SyncMode.NO_PROPAGATION) + SVG.root_tag.attributes.viewBox.set_rect_w(new_value) + else: + SVG.root_tag.attributes.width.set_value(new_value) + else: + SVG.root_tag.attributes.width.set_value(SVG.root_tag.get_width(), false) + +func _on_height_edit_value_changed(new_value: float) -> void: + if !is_nan(new_value) and SVG.root_tag.attributes.height.get_value() != new_value: + true_height = new_value + if GlobalSettings.save_data.viewbox_coupling: + SVG.root_tag.attributes.height.set_value(new_value, + Attribute.SyncMode.NO_PROPAGATION) + SVG.root_tag.attributes.viewBox.set_rect_h(new_value) + else: + SVG.root_tag.attributes.height.set_value(new_value) else: - viewbox_edit.set_value(SVG.root_tag.attributes.viewBox.get_value()) + SVG.root_tag.attributes.height.set_value(SVG.root_tag.get_height(), false) +func _on_viewbox_edit_x_value_changed(new_value: float) -> void: + if SVG.root_tag.attributes.viewBox.get_value() != null: + true_viewbox.position.x = new_value + SVG.root_tag.attributes.viewBox.set_rect_x(new_value) -func _on_couple_button_toggled(toggled_on: bool) -> void: - coupled_viewbox = toggled_on - determine_viewbox_edit() - -func determine_viewbox_edit() -> void: - for number_edit in viewbox_edit.get_children(): - number_edit.num_edit.editable = not coupled_viewbox - couple_button.icon = coupled_icon if coupled_viewbox else decoupled_icon - couple_button.button_pressed = coupled_viewbox - update_svg_attributes() - -func determine_coupling() -> void: - var svg_attrib := SVG.root_tag.attributes - if coupled_viewbox and (svg_attrib.viewBox.get_value() !=\ - Rect2(0, 0, svg_attrib.width.get_value(), svg_attrib.height.get_value())): - coupled_viewbox = false - determine_viewbox_edit() +func _on_viewbox_edit_y_value_changed(new_value: float) -> void: + if SVG.root_tag.attributes.viewBox.get_value() != null: + true_viewbox.position.y = new_value + SVG.root_tag.attributes.viewBox.set_rect_y(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.rect.size.x != new_value: + true_viewbox.size.x = new_value + if GlobalSettings.save_data.viewbox_coupling: + SVG.root_tag.attributes.viewBox.set_rect_w(new_value, + Attribute.SyncMode.NO_PROPAGATION) + SVG.root_tag.attributes.width.set_value(new_value) + else: + SVG.root_tag.attributes.viewBox.set_rect_w(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.rect.size.y != new_value: + true_viewbox.size.y = new_value + if GlobalSettings.save_data.viewbox_coupling: + SVG.root_tag.attributes.viewBox.set_rect_h(new_value, + Attribute.SyncMode.NO_PROPAGATION) + SVG.root_tag.attributes.height.set_value(new_value) + else: + SVG.root_tag.attributes.viewBox.set_rect_h(new_value) + +func _on_width_button_toggled(toggled_on: bool) -> void: + SVG.root_tag.attributes.width.set_value(true_width if toggled_on else NAN) + update_coupling_config() + +func _on_height_button_toggled(toggled_on: bool) -> void: + SVG.root_tag.attributes.height.set_value(true_height if toggled_on else NAN) + update_coupling_config() + +func _on_viewbox_button_toggled(toggled_on: bool) -> void: + if toggled_on: + SVG.root_tag.attributes.viewBox.set_rect(true_viewbox) + else: + SVG.root_tag.attributes.viewBox.set_value(null) + update_coupling_config() diff --git a/src/ui_parts/root_tag_editor.tscn b/src/ui_parts/root_tag_editor.tscn index 28339251..51e1a996 100644 --- a/src/ui_parts/root_tag_editor.tscn +++ b/src/ui_parts/root_tag_editor.tscn @@ -1,15 +1,25 @@ -[gd_scene load_steps=4 format=3 uid="uid://bktmk76u7dsu0"] +[gd_scene load_steps=7 format=3 uid="uid://bktmk76u7dsu0"] [ext_resource type="Script" path="res://src/ui_parts/root_tag_editor.gd" id="1_xgyg0"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="2_fm5sa"] +[ext_resource type="PackedScene" uid="uid://dad7fkhmsooc6" path="res://src/ui_elements/number_edit.tscn" id="3_1gu7n"] [ext_resource type="Texture2D" uid="uid://bv4lcvienlyfa" path="res://visual/icons/Coupled.svg" id="3_yhfll"] +[ext_resource type="Script" path="res://src/ui_elements/BetterToggleButton.gd" id="4_7r848"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u8h0i"] +bg_color = Color(0.866667, 0.933333, 1, 0.133333) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 [node name="SVGTagEditor" type="MarginContainer"] offset_right = 100.0 offset_bottom = 35.0 -theme_override_constants/margin_left = 8 -theme_override_constants/margin_top = 8 -theme_override_constants/margin_right = 8 +theme_override_constants/margin_left = 6 +theme_override_constants/margin_top = 6 +theme_override_constants/margin_right = 6 +theme_override_constants/margin_bottom = 6 script = ExtResource("1_xgyg0") [node name="Edits" type="HBoxContainer" parent="."] @@ -24,23 +34,43 @@ theme_override_constants/separation = 8 layout_mode = 2 theme_override_constants/separation = 0 -[node name="Label" type="Label" parent="Edits/Size/Width"] +[node name="WidthButton" type="Button" parent="Edits/Size/Width"] layout_mode = 2 +size_flags_horizontal = 4 +focus_mode = 0 +theme_type_variation = &"TextButton" theme_override_fonts/font = ExtResource("2_fm5sa") theme_override_font_sizes/font_size = 12 +toggle_mode = true text = "width" -horizontal_alignment = 1 +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")] +custom_minimum_size = Vector2(48, 22) +layout_mode = 2 +allow_lower = false [node name="Height" type="VBoxContainer" parent="Edits/Size"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="Label" type="Label" parent="Edits/Size/Height"] +[node name="HeightButton" type="Button" parent="Edits/Size/Height"] layout_mode = 2 +size_flags_horizontal = 4 +focus_mode = 0 +theme_type_variation = &"TextButton" theme_override_fonts/font = ExtResource("2_fm5sa") theme_override_font_sizes/font_size = 12 +toggle_mode = true text = "height" -horizontal_alignment = 1 +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")] +custom_minimum_size = Vector2(48, 22) +layout_mode = 2 +allow_lower = false [node name="CoupleButton" type="Button" parent="Edits"] layout_mode = 2 @@ -52,16 +82,51 @@ mouse_default_cursor_shape = 2 theme_type_variation = &"IconButton" toggle_mode = true icon = ExtResource("3_yhfll") +script = ExtResource("4_7r848") +hover_pressed_stylebox = SubResource("StyleBoxFlat_u8h0i") -[node name="ViewBox" type="VBoxContainer" parent="Edits"] +[node name="Viewbox" type="VBoxContainer" parent="Edits"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="Label" type="Label" parent="Edits/ViewBox"] +[node name="ViewboxButton" type="Button" parent="Edits/Viewbox"] layout_mode = 2 +size_flags_horizontal = 4 +focus_mode = 0 +theme_type_variation = &"TextButton" theme_override_fonts/font = ExtResource("2_fm5sa") theme_override_font_sizes/font_size = 12 +toggle_mode = true text = "viewBox" -horizontal_alignment = 1 +script = ExtResource("4_7r848") +hover_pressed_font_color = Color(1, 1, 1, 0.4) + +[node name="Rect" type="HBoxContainer" parent="Edits/Viewbox"] +layout_mode = 2 + +[node name="ViewboxEditX" parent="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")] +custom_minimum_size = Vector2(48, 22) +layout_mode = 2 + +[node name="ViewboxEditW" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +custom_minimum_size = Vector2(48, 22) +layout_mode = 2 + +[node name="ViewboxEditH" parent="Edits/Viewbox/Rect" instance=ExtResource("3_1gu7n")] +custom_minimum_size = Vector2(48, 22) +layout_mode = 2 +[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/CoupleButton" to="." method="_on_couple_button_toggled"] +[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"] diff --git a/src/ui_parts/settings_menu.gd b/src/ui_parts/settings_menu.gd index 80b90488..30d96447 100644 --- a/src/ui_parts/settings_menu.gd +++ b/src/ui_parts/settings_menu.gd @@ -1,26 +1,18 @@ extends Dialog -@onready var window_mode_button: CheckBox = %WindowMode -@onready var svg_button: CheckBox = %SVG +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") +const PaletteConfigWidget = preload("res://src/ui_parts/palette_config.tscn") +const plus_icon = preload("res://visual/icons/Plus.svg") + @onready var lang_button: Button = %Language -@onready var lang_popup: Popup = $LangPopup @onready var invert_zoom: CheckBox = %InvertZoom +@onready var palette_container: VBoxContainer = %PaletteContainer + func _ready() -> void: update_language_button() - var buttons_arr: Array[Button] = [] - for lang in ["en", "bg", "de"]: - var button := Button.new() - button.text = TranslationServer.get_locale_name(lang) + " (" + lang + ")" - button.pressed.connect(_on_language_chosen.bind(lang)) - button.mouse_default_cursor_shape = CURSOR_POINTING_HAND - button.alignment = HORIZONTAL_ALIGNMENT_LEFT - buttons_arr.append(button) - lang_popup.set_btn_array(buttons_arr) - - window_mode_button.button_pressed = GlobalSettings.save_window_mode - svg_button.button_pressed = GlobalSettings.save_svg invert_zoom.button_pressed = GlobalSettings.invert_zoom + rebuild_color_palettes() func _on_window_mode_pressed() -> void: GlobalSettings.save_window_mode = not GlobalSettings.save_window_mode @@ -28,20 +20,70 @@ func _on_window_mode_pressed() -> void: func _on_svg_pressed() -> void: GlobalSettings.save_svg = not GlobalSettings.save_svg +func _on_invert_zoom_pressed() -> void: + GlobalSettings.invert_zoom = not GlobalSettings.invert_zoom + func _on_close_pressed() -> void: queue_free() func _on_language_pressed() -> void: - lang_popup.popup(Utils.calculate_popup_rect(lang_button.global_position, - lang_button.size, lang_popup.size)) + var buttons_arr: Array[Button] = [] + for lang in ["en", "bg", "de"]: + var button := Button.new() + button.text = TranslationServer.get_locale_name(lang) + " (" + lang + ")" + button.pressed.connect(_on_language_chosen.bind(lang)) + button.mouse_default_cursor_shape = CURSOR_POINTING_HAND + button.alignment = HORIZONTAL_ALIGNMENT_LEFT + buttons_arr.append(button) + var lang_popup := ContextPopup.instantiate() + add_child(lang_popup) + lang_popup.set_btn_array(buttons_arr) + Utils.popup_under_control(lang_popup, lang_button) func _on_language_chosen(locale: String) -> void: - lang_popup.hide() GlobalSettings.language = locale update_language_button() func update_language_button() -> void: lang_button.text = tr(&"#language") + ": " + TranslationServer.get_locale().to_upper() -func _on_invert_zoom_pressed() -> void: - GlobalSettings.invert_zoom = not GlobalSettings.invert_zoom + +func add_palette() -> void: + for palette in GlobalSettings.get_palettes(): + # If there's an unnamed pallete, don't add a new one (there'll be a name clash). + if palette.name.is_empty(): + return + + GlobalSettings.get_palettes().append(ColorPalette.new()) + GlobalSettings.save_user_data() + rebuild_color_palettes() + +func rebuild_color_palettes() -> void: + for palette_config in palette_container.get_children(): + palette_config.queue_free() + + for palette in GlobalSettings.get_palettes(): + var palette_config := PaletteConfigWidget.instantiate() + palette_container.add_child(palette_config) + palette_config.assign_palette(palette) + palette_config.deleted.connect(rebuild_color_palettes) + # The button for adding a new palette looks quite unusual and should be on the bottom. + # So I'm setting up its own theming here. + var normal_sb := StyleBoxFlat.new() + var hover_sb := StyleBoxFlat.new() + var pressed_sb := StyleBoxFlat.new() + normal_sb.bg_color = Color("#def1") + hover_sb.bg_color = Color("#def2") + pressed_sb.bg_color = Color("#def4") + for sb: StyleBoxFlat in [normal_sb, hover_sb, pressed_sb]: + sb.set_corner_radius_all(5) + sb.set_content_margin_all(4) + var add_palette_button := Button.new() + add_palette_button.add_theme_stylebox_override(&"normal", normal_sb) + add_palette_button.add_theme_stylebox_override(&"hover", hover_sb) + add_palette_button.add_theme_stylebox_override(&"pressed", pressed_sb) + add_palette_button.icon = plus_icon + add_palette_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER + add_palette_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + palette_container.add_child(add_palette_button) + add_palette_button.pressed.connect(add_palette) diff --git a/src/ui_parts/settings_menu.tscn b/src/ui_parts/settings_menu.tscn index 89ff00ee..157ca55b 100644 --- a/src/ui_parts/settings_menu.tscn +++ b/src/ui_parts/settings_menu.tscn @@ -1,15 +1,14 @@ -[gd_scene load_steps=6 format=3 uid="uid://1rylg17uwltw"] +[gd_scene load_steps=5 format=3 uid="uid://1rylg17uwltw"] [ext_resource type="Script" path="res://src/ui_parts/settings_menu.gd" id="1_1gf4m"] [ext_resource type="Texture2D" uid="uid://c528knojuxbw6" path="res://visual/icons/Languages.svg" id="2_ndyp7"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="3_fc4ea"] [ext_resource type="Script" path="res://src/ui_elements/BetterTabContainer.gd" id="3_vslgx"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hto7q"] content_margin_left = 8.0 -content_margin_top = 8.0 +content_margin_top = 10.0 content_margin_right = 8.0 -content_margin_bottom = 8.0 +content_margin_bottom = 10.0 bg_color = Color(0.005, 0.005, 0.05, 1) border_width_left = 2 border_width_top = 2 @@ -31,7 +30,7 @@ color = Color(0, 0, 0, 0.4) script = ExtResource("1_1gf4m") [node name="MainPanel" type="PanelContainer" parent="."] -custom_minimum_size = Vector2(240, 172) +custom_minimum_size = Vector2(360, 256) layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 @@ -56,41 +55,32 @@ layout_mode = 2 size_flags_horizontal = 4 focus_mode = 0 mouse_default_cursor_shape = 2 -theme_type_variation = &"IconButton" icon = ExtResource("2_ndyp7") [node name="TabContainer" type="TabContainer" parent="MainPanel/VBoxContainer"] layout_mode = 2 script = ExtResource("3_vslgx") -[node name="#session_tab" type="MarginContainer" parent="MainPanel/VBoxContainer/TabContainer"] +[node name="#input_tab" type="MarginContainer" parent="MainPanel/VBoxContainer/TabContainer"] layout_mode = 2 theme_override_constants/margin_left = 4 theme_override_constants/margin_top = 4 theme_override_constants/margin_right = 4 theme_override_constants/margin_bottom = 4 -[node name="VBox" type="VBoxContainer" parent="MainPanel/VBoxContainer/TabContainer/#session_tab"] +[node name="VBox" type="VBoxContainer" parent="MainPanel/VBoxContainer/TabContainer/#input_tab"] layout_mode = 2 theme_override_constants/separation = 0 -[node name="SVG" type="CheckBox" parent="MainPanel/VBoxContainer/TabContainer/#session_tab/VBox"] -unique_name_in_owner = true -layout_mode = 2 -tooltip_text = "#remember_svg_desc" -focus_mode = 0 -mouse_default_cursor_shape = 2 -text = "#remember_svg_setting" - -[node name="WindowMode" type="CheckBox" parent="MainPanel/VBoxContainer/TabContainer/#session_tab/VBox"] +[node name="InvertZoom" type="CheckBox" parent="MainPanel/VBoxContainer/TabContainer/#input_tab/VBox"] unique_name_in_owner = true layout_mode = 2 -tooltip_text = "#remember_window_mode_desc" +tooltip_text = "#invert_zoom_desc" focus_mode = 0 mouse_default_cursor_shape = 2 -text = "#remember_window_mode_settings" +text = "#invert_zoom" -[node name="#input_tab" type="MarginContainer" parent="MainPanel/VBoxContainer/TabContainer"] +[node name="#palettes" type="MarginContainer" parent="MainPanel/VBoxContainer/TabContainer"] visible = false layout_mode = 2 theme_override_constants/margin_left = 4 @@ -98,31 +88,25 @@ theme_override_constants/margin_top = 4 theme_override_constants/margin_right = 4 theme_override_constants/margin_bottom = 4 -[node name="VBox" type="VBoxContainer" parent="MainPanel/VBoxContainer/TabContainer/#input_tab"] +[node name="ScrollContainer" type="ScrollContainer" parent="MainPanel/VBoxContainer/TabContainer/#palettes"] +custom_minimum_size = Vector2(0, 120) layout_mode = 2 -theme_override_constants/separation = 0 +horizontal_scroll_mode = 0 -[node name="InvertZoom" type="CheckBox" parent="MainPanel/VBoxContainer/TabContainer/#input_tab/VBox"] +[node name="PaletteContainer" type="VBoxContainer" parent="MainPanel/VBoxContainer/TabContainer/#palettes/ScrollContainer"] unique_name_in_owner = true layout_mode = 2 -tooltip_text = "#invert_zoom_desc" -focus_mode = 0 -mouse_default_cursor_shape = 2 -text = "#invert_zoom" +size_flags_horizontal = 3 +theme_override_constants/separation = 4 -[node name="Close" type="Button" parent="MainPanel"] +[node name="Close" type="Button" parent="MainPanel/VBoxContainer"] layout_mode = 2 size_flags_horizontal = 4 -size_flags_vertical = 8 +size_flags_vertical = 10 focus_mode = 0 mouse_default_cursor_shape = 2 text = "#close" -[node name="LangPopup" parent="." instance=ExtResource("3_fc4ea")] -visible = false - [connection signal="pressed" from="MainPanel/VBoxContainer/Language" to="." method="_on_language_pressed"] -[connection signal="pressed" from="MainPanel/VBoxContainer/TabContainer/#session_tab/VBox/SVG" to="." method="_on_svg_pressed"] -[connection signal="pressed" from="MainPanel/VBoxContainer/TabContainer/#session_tab/VBox/WindowMode" to="." method="_on_window_mode_pressed"] [connection signal="pressed" from="MainPanel/VBoxContainer/TabContainer/#input_tab/VBox/InvertZoom" to="." method="_on_invert_zoom_pressed"] -[connection signal="pressed" from="MainPanel/Close" to="." method="_on_close_pressed"] +[connection signal="pressed" from="MainPanel/VBoxContainer/Close" to="." method="_on_close_pressed"] diff --git a/src/ui_parts/svg_code_edit.gd b/src/ui_parts/svg_code_edit.gd index 40066f54..5886f2d3 100644 --- a/src/ui_parts/svg_code_edit.gd +++ b/src/ui_parts/svg_code_edit.gd @@ -1,19 +1,5 @@ # This changes a few things about the SVG TextEdit to make it nicer to use. -extends TextEdit - -const code_font = preload("res://visual/fonts/FontMono.ttf") -const caret_color = Color("defd") - -var surface := RenderingServer.canvas_item_create() -var timer := Timer.new() - -func _ready() -> void: - RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) - add_child(timer) - timer.timeout.connect(blink) - get_v_scroll_bar().value_changed.connect(redraw_caret.unbind(1)) - get_h_scroll_bar().value_changed.connect(redraw_caret.unbind(1)) - +extends BetterTextEdit # I'd prefer to block non-ASCII inputs. SVG syntax is ASCII only, and while # text blocks and comments allow non-ASCII, they are still difficult to deal with @@ -24,37 +10,3 @@ func _ready() -> void: func _handle_unicode_input(unicode_char: int, caret_index: int) -> void: if unicode_char <= 127: insert_text_at_caret(char(unicode_char), caret_index) - - -func redraw_caret() -> void: - await get_tree().process_frame # Buggy with backspace otherwise, likely a Godot bug. - blonk = false - blink() - timer.start(0.6) - RenderingServer.canvas_item_clear(surface) - if has_focus(): - var char_size := code_font.get_char_size(69, - get_theme_font_size(&"TextEdit", &"font_size")) - for caret in get_caret_count(): - # FIXME There's a bug(?) causing the draw pos to sometimes not update - # when outside of the screen. - var caret_draw_pos := get_caret_draw_pos(caret) - if is_overtype_mode_enabled(): - RenderingServer.canvas_item_add_line(surface, caret_draw_pos - Vector2(1, 0), - caret_draw_pos + Vector2(char_size.x - 2, 0), caret_color, 1) - else: - RenderingServer.canvas_item_add_line(surface, caret_draw_pos - Vector2(0, 1), - caret_draw_pos - Vector2(0, char_size.y - 2), caret_color, 1) - - -var blonk := true -func blink() -> void: - blonk = not blonk - RenderingServer.canvas_item_set_visible(surface, blonk) - - -func _on_focus_entered() -> void: - timer.start(0.6) - -func _on_focus_exited() -> void: - timer.stop() diff --git a/src/ui_parts/tag_editor.gd b/src/ui_parts/tag_editor.gd index e645a06d..67447aa7 100644 --- a/src/ui_parts/tag_editor.gd +++ b/src/ui_parts/tag_editor.gd @@ -1,10 +1,11 @@ -extends PanelContainer +extends VBoxContainer -const shape_attributes = ["cx", "cy", "x", "y", "r", "rx", "ry", "width", "height", "d", - "x1", "y1", "x2", "y2"] +const geometry_attributes = ["cx", "cy", "x", "y", "r", "rx", "ry", "width", "height", + "d", "x1", "y1", "x2", "y2"] const unknown_icon = preload("res://visual/icons/tag/unknown.svg") +const ContextPopup = preload("res://src/ui_elements/context_popup.tscn") const TagEditor = preload("tag_editor.tscn") const NumberField = preload("res://src/ui_elements/number_field.tscn") const NumberSlider = preload("res://src/ui_elements/number_field_with_slider.tscn") @@ -13,35 +14,27 @@ const PathField = preload("res://src/ui_elements/path_field.tscn") const EnumField = preload("res://src/ui_elements/enum_field.tscn") const UnknownField = preload("res://src/ui_elements/unknown_field.tscn") -# This is needed for the hover detection hack. -@onready var first_ancestor_scroll_container := find_first_ancestor_scroll_container() - -func find_first_ancestor_scroll_container() -> ScrollContainer: - var ancestor := get_parent() - while not ancestor is ScrollContainer: - if not ancestor is Control: - return null - ancestor = ancestor.get_parent() - return ancestor - +@onready var v_box_container: VBoxContainer = $Content/MainContainer @onready var paint_container: FlowContainer = %AttributeContainer/PaintAttributes @onready var shape_container: FlowContainer = %AttributeContainer/ShapeAttributes @onready var unknown_container: HFlowContainer = %AttributeContainer/UnknownAttributes -@onready var title_button: Button = %TitleButton -@onready var title_button_icon: TextureRect = %TitleButtonIcon -@onready var title_button_label: Label = %TitleButtonLabel -@onready var tag_context: Popup = $ContextPopup -@onready var margin_container: MarginContainer = $MarginContainer -@onready var child_tags_container: VBoxContainer = %ChildTags +@onready var title_bar: PanelContainer = $Title +@onready var content: PanelContainer = $Content +@onready var title_icon: TextureRect = $Title/TitleBox/TitleIcon +@onready var title_label: Label = $Title/TitleBox/TitleLabel +@onready var title_button: Button = $Title/TitleBox/TitleButton var tid: PackedInt32Array var tag: Tag func _ready() -> void: - determine_selection_highlight() - tag.attribute_changed.connect(select_conditionally) + title_label.text = tag.name + title_icon.texture = unknown_icon if tag is TagUnknown\ + else load("res://visual/icons/tag/" + tag.name + ".svg") + tag.attribute_changed.connect(select_conditionally.unbind(1)) Indications.selection_changed.connect(determine_selection_highlight) Indications.hover_changed.connect(determine_selection_highlight) + determine_selection_highlight() # Fill up the containers. Start with unknown attributes, if there are any. if not tag.unknown_attributes.is_empty(): unknown_container.show() @@ -51,11 +44,6 @@ func _ready() -> void: input_field.attribute_name = attribute.name unknown_container.add_child(input_field) # Continue with supported attributes. - title_button_label.text = tag.name - if title_button_label.text.length() > 7: - title_button_label.add_theme_font_size_override(&"font_size", 11) - title_button_icon.texture = unknown_icon if tag is TagUnknown\ - else load("res://visual/icons/tag/" + tag.name + ".svg") for attribute_key in tag.attributes: var attribute: Attribute = tag.attributes[attribute_key] var input_field: AttributeEditor @@ -82,15 +70,14 @@ func _ready() -> void: input_field.attribute = attribute input_field.attribute_name = attribute_key # Add the attribute to its corresponding container. - if attribute_key in shape_attributes: + if attribute_key in geometry_attributes: shape_container.add_child(input_field) else: paint_container.add_child(input_field) - determine_selection_highlight() - - if not tag.child_tags.is_empty(): - child_tags_container.show() + if not tag.is_standalone(): + var child_tags_container := VBoxContainer.new() + v_box_container.add_child(child_tags_container) for tag_idx in tag.get_child_count(): var child_tag := tag.child_tags[tag_idx] @@ -102,7 +89,7 @@ func _ready() -> void: child_tags_container.add_child(tag_editor) -func tag_context_populate() -> void: +func create_tag_context() -> Popup: var parent_tid := Utils.get_parent_tid(tid) var tag_count := SVG.root_tag.get_by_tid(parent_tid).get_child_count() var buttons_arr: Array[Button] = [] @@ -110,7 +97,6 @@ func tag_context_populate() -> void: var duplicate_button := Button.new() duplicate_button.text = tr(&"#duplicate") duplicate_button.icon = load("res://visual/icons/Duplicate.svg") - duplicate_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND duplicate_button.alignment = HORIZONTAL_ALIGNMENT_LEFT duplicate_button.pressed.connect(_on_duplicate_button_pressed) buttons_arr.append(duplicate_button) @@ -119,7 +105,6 @@ func tag_context_populate() -> void: var move_up_button := Button.new() move_up_button.text = tr(&"#move_up") move_up_button.icon = load("res://visual/icons/MoveUp.svg") - move_up_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND move_up_button.alignment = HORIZONTAL_ALIGNMENT_LEFT move_up_button.pressed.connect(_on_move_up_button_pressed) buttons_arr.append(move_up_button) @@ -127,7 +112,6 @@ func tag_context_populate() -> void: var move_down_button := Button.new() move_down_button.text = tr(&"#move_down") move_down_button.icon = load("res://visual/icons/MoveDown.svg") - move_down_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND move_down_button.alignment = HORIZONTAL_ALIGNMENT_LEFT move_down_button.pressed.connect(_on_move_down_button_pressed) buttons_arr.append(move_down_button) @@ -135,34 +119,31 @@ func tag_context_populate() -> void: var delete_button := Button.new() delete_button.text = tr(&"#delete") delete_button.icon = load("res://visual/icons/Delete.svg") - delete_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND delete_button.alignment = HORIZONTAL_ALIGNMENT_LEFT delete_button.pressed.connect(_on_delete_button_pressed) buttons_arr.append(delete_button) + var tag_context := ContextPopup.instantiate() + add_child(tag_context) tag_context.set_btn_array(buttons_arr) + return tag_context + func _on_title_button_pressed() -> void: - Indications.set_selection(tid) - tag_context_populate() - tag_context.popup(Utils.calculate_popup_rect(title_button.global_position, - title_button.size, tag_context.size)) + Indications.normal_select(tid) + var tag_context := create_tag_context() + Utils.popup_under_control_centered(tag_context, title_button) -# FIXME fix move commands func _on_move_up_button_pressed() -> void: - tag_context.hide() - SVG.root_tag.move_tags([tid], -1) + SVG.root_tag.move_tags_in_parent([tid], false) func _on_move_down_button_pressed() -> void: - tag_context.hide() - SVG.root_tag.move_tags([tid], 1) + SVG.root_tag.move_tags_in_parent([tid], true) func _on_delete_button_pressed() -> void: - tag_context.hide() SVG.root_tag.delete_tags([tid]) func _on_duplicate_button_pressed() -> void: - tag_context.hide() SVG.root_tag.duplicate_tags([tid]) @@ -170,66 +151,83 @@ func _on_gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.is_pressed(): if event.button_index == MOUSE_BUTTON_LEFT: if event.ctrl_pressed: - Indications.toggle_selection(tid) + Indications.ctrl_select(tid) + elif event.shift_pressed: + Indications.shift_select(tid) else: - Indications.set_selection(tid) + Indications.normal_select(tid) elif event.button_index == MOUSE_BUTTON_RIGHT: - Indications.set_selection(tid) - tag_context_populate() - tag_context.popup(Utils.calculate_popup_rect(get_global_mouse_position(), - Vector2.ZERO, tag_context.size, true)) - - -var mouse_inside := false: - set(new_value): - if mouse_inside != new_value: - mouse_inside = new_value - if mouse_inside: - Indications.set_hovered(tid) - else: - Indications.remove_hovered(tid) + Indications.normal_select(tid) + var tag_context := create_tag_context() + Utils.popup_under_mouse(tag_context, get_global_mouse_position()) + -func _input(event: InputEvent) -> void: - if event is InputEventMouseMotion and event.button_mask == 0: - mouse_inside = get_global_rect().has_point(get_global_mouse_position()) and\ - first_ancestor_scroll_container.get_global_rect().has_point( - get_global_mouse_position()) and Indications.semi_hovered_tid != tid and\ - not Utils.is_tid_parent(tid, Indications.hovered_tid) +func _on_mouse_entered(): + if Indications.semi_hovered_tid != tid and\ + not Utils.is_tid_parent(tid, Indications.hovered_tid): + Indications.set_hovered(tid) + +func _on_mouse_exited(): + Indications.remove_hovered(tid) func determine_selection_highlight() -> void: - var stylebox := StyleBoxFlat.new() - stylebox.set_corner_radius_all(4) - stylebox.set_border_width_all(2) + var title_sb := StyleBoxFlat.new() + title_sb.corner_radius_top_left = 4 + title_sb.corner_radius_top_right = 4 + title_sb.set_border_width_all(2) + title_sb.set_content_margin_all(4) + + var content_sb := StyleBoxFlat.new() + content_sb.corner_radius_bottom_left = 4 + content_sb.corner_radius_bottom_right = 4 + content_sb.border_width_left = 2 + content_sb.border_width_right = 2 + content_sb.border_width_bottom = 2 + content_sb.content_margin_top = 5 + content_sb.content_margin_left = 7 + 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 if is_selected: if is_hovered: - stylebox.bg_color = Color(0.12, 0.15, 0.24).lightened(0.015) + content_sb.bg_color = Color.from_hsv(0.625, 0.48, 0.27) + title_sb.bg_color = Color.from_hsv(0.625, 0.5, 0.38) else: - stylebox.bg_color = Color(0.12, 0.15, 0.24) - stylebox.border_color = Color(0.2, 0.32, 0.5) + content_sb.bg_color = Color.from_hsv(0.625, 0.5, 0.25) + title_sb.bg_color = Color.from_hsv(0.625, 0.6, 0.35) + content_sb.border_color = Color.from_hsv(0.6, 0.75, 0.75) + title_sb.border_color = Color.from_hsv(0.6, 0.75, 0.75) elif is_hovered: - stylebox.bg_color = Color(0.065, 0.085, 0.15).lightened(0.02) - stylebox.border_color = Color(0.065, 0.085, 0.15).lightened(0.09) + content_sb.bg_color = Color.from_hsv(0.625, 0.57, 0.19) + title_sb.bg_color = Color.from_hsv(0.625, 0.4, 0.2) + content_sb.border_color = Color.from_hsv(0.6, 0.55, 0.45) + title_sb.border_color = Color.from_hsv(0.6, 0.55, 0.45) else: - stylebox.bg_color = Color(0.065, 0.085, 0.15) - stylebox.border_color = Color(0.065, 0.085, 0.15).lightened(0.04) + content_sb.bg_color = Color.from_hsv(0.625, 0.6, 0.16) + title_sb.bg_color = Color.from_hsv(0.625, 0.45, 0.17) + 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_tint := depth * 0.12 if depth > 0: - stylebox.bg_color = Color.from_hsv(stylebox.bg_color.h + depth * 0.12, - stylebox.bg_color.s, stylebox.bg_color.v) - stylebox.border_color = Color.from_hsv(stylebox.border_color.h + depth * 0.12, - stylebox.border_color.s, stylebox.border_color.v) - add_theme_stylebox_override(&"panel", stylebox) + content_sb.bg_color = Color.from_hsv(content_sb.bg_color.h + depth_tint, + content_sb.bg_color.s, content_sb.bg_color.v) + content_sb.border_color = Color.from_hsv(content_sb.border_color.h + depth_tint, + content_sb.border_color.s, content_sb.border_color.v) + title_sb.bg_color = Color.from_hsv(title_sb.bg_color.h + depth_tint, + title_sb.bg_color.s, title_sb.bg_color.v) + title_sb.border_color = Color.from_hsv(title_sb.border_color.h + depth_tint, + title_sb.border_color.s, title_sb.border_color.v) + + content.add_theme_stylebox_override(&"panel", content_sb) + title_bar.add_theme_stylebox_override(&"panel", title_sb) + func select_conditionally() -> void: if Indications.semi_selected_tid != tid: - Indications.set_selection(tid) - - -func _on_title_button_container_draw() -> void: - title_button.custom_minimum_size = title_button.get_child(0).size + Indications.normal_select(tid) diff --git a/src/ui_parts/tag_editor.tscn b/src/ui_parts/tag_editor.tscn index f20b4bfb..20209b45 100644 --- a/src/ui_parts/tag_editor.tscn +++ b/src/ui_parts/tag_editor.tscn @@ -1,160 +1,74 @@ -[gd_scene load_steps=7 format=3 uid="uid://cksx526iftj5d"] +[gd_scene load_steps=4 format=3 uid="uid://cksx526iftj5d"] [ext_resource type="Script" path="res://src/ui_parts/tag_editor.gd" id="1_7i0c4"] [ext_resource type="FontFile" uid="uid://dtb4wkus51hxs" path="res://visual/fonts/FontMono.ttf" id="2_0lxvf"] -[ext_resource type="PackedScene" uid="uid://wp77eqhikp6k" path="res://src/ui_elements/context_popup.tscn" id="3_qx6wd"] +[ext_resource type="Texture2D" uid="uid://cmepkbqde0jh0" path="res://visual/icons/SmallMore.svg" id="2_2n846"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xmwqw"] -content_margin_left = 4.0 -content_margin_top = 5.0 -content_margin_right = 4.0 -content_margin_bottom = 5.0 -bg_color = Color(0.129412, 0.129412, 0.2, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.1785, 0.26425, 0.35, 1) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_50r0v"] -content_margin_left = 4.0 -content_margin_top = 5.0 -content_margin_right = 4.0 -content_margin_bottom = 5.0 -bg_color = Color(0.1625, 0.1625, 0.25, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.208, 0.3008, 0.4, 1) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dfilu"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.321569, 0.439216, 0.8, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.541176, 0.721569, 0.901961, 1) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[node name="TagEditor" type="PanelContainer"] -offset_left = 1020.0 -offset_right = 1024.0 +[node name="TagEditor" type="VBoxContainer"] +offset_right = 49.0 offset_bottom = 30.0 +theme_override_constants/separation = 0 script = ExtResource("1_7i0c4") -[node name="MarginContainer" type="MarginContainer" parent="."] -layout_mode = 2 -theme_override_constants/margin_left = 4 -theme_override_constants/margin_top = 3 -theme_override_constants/margin_right = 5 -theme_override_constants/margin_bottom = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"] -custom_minimum_size = Vector2(50, 0) +[node name="Title" type="PanelContainer" parent="."] layout_mode = 2 -theme_override_constants/margin_left = 0 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 2 -theme_override_constants/margin_bottom = 4 -[node name="TitleButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer"] -unique_name_in_owner = true +[node name="TitleBox" type="HBoxContainer" parent="Title"] layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 4 -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme_override_styles/normal = SubResource("StyleBoxFlat_xmwqw") -theme_override_styles/hover = SubResource("StyleBoxFlat_50r0v") -theme_override_styles/pressed = SubResource("StyleBoxFlat_dfilu") -theme_override_styles/disabled = SubResource("StyleBoxFlat_xmwqw") - -[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton"] -layout_mode = 0 -offset_right = 40.0 -offset_bottom = 40.0 -mouse_filter = 2 -theme_override_constants/margin_left = 6 -theme_override_constants/margin_top = 4 -theme_override_constants/margin_right = 6 -theme_override_constants/margin_bottom = 3 +theme_override_constants/separation = 6 +alignment = 1 -[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton/MarginContainer"] -layout_mode = 2 -mouse_filter = 2 -theme_override_constants/separation = 0 - -[node name="TitleButtonIcon" type="TextureRect" parent="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton/MarginContainer/VBoxContainer"] -unique_name_in_owner = true +[node name="TitleIcon" type="TextureRect" parent="Title/TitleBox"] custom_minimum_size = Vector2(18, 18) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 0 mouse_filter = 2 -[node name="TitleButtonLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton/MarginContainer/VBoxContainer"] -unique_name_in_owner = true -custom_minimum_size = Vector2(24, 0) +[node name="TitleLabel" type="Label" parent="Title/TitleBox"] layout_mode = 2 size_flags_horizontal = 4 theme_override_fonts/font = ExtResource("2_0lxvf") theme_override_font_sizes/font_size = 12 horizontal_alignment = 1 -[node name="RightContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"] +[node name="TitleButton" type="Button" parent="Title/TitleBox"] +layout_mode = 2 +focus_mode = 0 +mouse_default_cursor_shape = 2 +theme_type_variation = &"FlatButton" +icon = ExtResource("2_2n846") + +[node name="Content" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="MainContainer" type="VBoxContainer" parent="Content"] layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 12 -[node name="AttributeContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/RightContainer"] +[node name="AttributeContainer" type="VBoxContainer" parent="Content/MainContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 -[node name="UnknownAttributes" type="HFlowContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/RightContainer/AttributeContainer"] +[node name="UnknownAttributes" type="HFlowContainer" parent="Content/MainContainer/AttributeContainer"] visible = false layout_mode = 2 size_flags_horizontal = 3 -[node name="PaintAttributes" type="HFlowContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/RightContainer/AttributeContainer"] +[node name="PaintAttributes" type="HFlowContainer" parent="Content/MainContainer/AttributeContainer"] layout_mode = 2 size_flags_horizontal = 3 -[node name="ShapeAttributes" type="HFlowContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/RightContainer/AttributeContainer"] +[node name="ShapeAttributes" type="HFlowContainer" parent="Content/MainContainer/AttributeContainer"] layout_mode = 2 size_flags_horizontal = 3 -[node name="ChildTags" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 2 - -[node name="ContextPopup" parent="." instance=ExtResource("3_qx6wd")] -visible = false - -[connection signal="gui_input" from="." to="." method="_on_gui_input"] -[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton" to="." method="_on_title_button_pressed"] -[connection signal="draw" from="MarginContainer/VBoxContainer/HBoxContainer/MarginContainer/TitleButton/MarginContainer/VBoxContainer" to="." method="_on_title_button_container_draw"] +[connection signal="gui_input" from="Title" to="." method="_on_gui_input"] +[connection signal="mouse_entered" from="Title" to="." method="_on_mouse_entered"] +[connection signal="mouse_exited" from="Title" to="." method="_on_mouse_exited"] +[connection signal="pressed" from="Title/TitleBox/TitleButton" to="." method="_on_title_button_pressed"] +[connection signal="gui_input" from="Content" to="." method="_on_gui_input"] +[connection signal="mouse_entered" from="Content" to="." method="_on_mouse_entered"] +[connection signal="mouse_exited" from="Content" to="." method="_on_mouse_exited"] diff --git a/src/ui_parts/view_camera.gd b/src/ui_parts/view_camera.gd index 4f893b08..d1d7bc49 100644 --- a/src/ui_parts/view_camera.gd +++ b/src/ui_parts/view_camera.gd @@ -1,70 +1,74 @@ extends Camera2D const default_font = preload("res://visual/fonts/Font.ttf") -const main_line_color = Color(0.5, 0.5, 0.5, 0.8) -const primary_grid_color = Color(0.5, 0.5, 0.5, 0.4) -const pixel_grid_color = Color(0.5, 0.5, 0.5, 0.16) +const axis_line_color = Color(0.5, 0.5, 0.5, 0.75) +const major_grid_color = Color(0.5, 0.5, 0.5, 0.35) +const minor_grid_color = Color(0.5, 0.5, 0.5, 0.15) const ticks_interval = 4 var surface := RenderingServer.canvas_item_create() # Used for drawing the numbers. func _ready() -> void: RenderingServer.canvas_item_set_parent(surface, get_canvas_item()) - SVG.root_tag.attribute_changed.connect(queue_redraw) + SVG.root_tag.resized.connect(queue_redraw) # Don't ask me to explain this. func _draw() -> void: - var size: Vector2 = get_parent().size / %ZoomMenu.zoom_level - draw_line(Vector2(-position.x, 0), Vector2(-position.x, size.y), main_line_color) - draw_line(Vector2(0, -position.y), Vector2(size.x, -position.y), main_line_color) + var size: Vector2 = get_parent().size * 1.0 / zoom + draw_line(Vector2(-position.x, 0), Vector2(-position.x, size.y), axis_line_color) + draw_line(Vector2(0, -position.y), Vector2(size.x, -position.y), axis_line_color) - var primary_points := PackedVector2Array() - var pixel_points := PackedVector2Array() + var major_points := PackedVector2Array() + var minor_points := PackedVector2Array() var x_offset := fmod(-position.x, 1.0) var y_offset := fmod(-position.y, 1.0) var tick_distance := float(ticks_interval) - var viewport_scale: float = %ZoomMenu.zoom_level - var draw_pixel_lines := viewport_scale >= 3.0 - var rate := nearest_po2(roundi(maxf(64.0 / (ticks_interval * viewport_scale), 1.0))) + var zoom_level := zoom.x + var draw_minor_lines := zoom_level >= 3.0 + var rate := nearest_po2(roundi(maxf(64.0 / (ticks_interval * zoom_level), 1.0))) # The grid lines are always 1px wide, but the numbers need to be resized. RenderingServer.canvas_item_clear(surface) RenderingServer.canvas_item_set_transform(surface, - Transform2D(0, Vector2(1, 1) / viewport_scale, 0, Vector2.ZERO)) + Transform2D(0, Vector2(1, 1) / zoom, 0, Vector2.ZERO)) var i := x_offset while i <= size.x: if fposmod(-position.x, tick_distance) != fposmod(i, tick_distance): - if draw_pixel_lines: - pixel_points.append(Vector2(i, 0)) - pixel_points.append(Vector2(i, size.y)) + if draw_minor_lines: + minor_points.append(Vector2(i, 0)) + minor_points.append(Vector2(i, size.y)) else: var coord := snappedi(i + position.x, ticks_interval) + @warning_ignore('integer_division') if (coord / ticks_interval) % rate == 0: - primary_points.append(Vector2(i, 0)) - primary_points.append(Vector2(i, size.y)) - default_font.draw_string(surface, Vector2(i * viewport_scale + 4, 14), - str(coord), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, main_line_color) + major_points.append(Vector2(i, 0)) + major_points.append(Vector2(i, size.y)) + default_font.draw_string(surface, Vector2(i * zoom_level + 4, 14), + String.num_int64(coord), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, + axis_line_color) elif coord % rate == 0: - pixel_points.append(Vector2(i, 0)) - pixel_points.append(Vector2(i, size.y)) + minor_points.append(Vector2(i, 0)) + minor_points.append(Vector2(i, size.y)) i += 1.0 i = y_offset while i < size.y: if fposmod(-position.y, tick_distance) != fposmod(i, tick_distance): - if draw_pixel_lines: - pixel_points.append(Vector2(0, i)) - pixel_points.append(Vector2(size.x, i)) + if draw_minor_lines: + minor_points.append(Vector2(0, i)) + minor_points.append(Vector2(size.x, i)) else: var coord := snappedi(i + position.y, ticks_interval) + @warning_ignore('integer_division') if int(coord / ticks_interval) % rate == 0: - primary_points.append(Vector2(0, i)) - primary_points.append(Vector2(size.x, i)) - default_font.draw_string(surface, Vector2(4, i * viewport_scale + 14), - str(coord), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, main_line_color) + major_points.append(Vector2(0, i)) + major_points.append(Vector2(size.x, i)) + default_font.draw_string(surface, Vector2(4, i * zoom_level + 14), + String.num_int64(coord), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, + axis_line_color) elif coord % rate == 0: - pixel_points.append(Vector2(0, i)) - pixel_points.append(Vector2(size.x, i)) + minor_points.append(Vector2(0, i)) + minor_points.append(Vector2(size.x, i)) i += 1.0 - draw_multiline(primary_points, primary_grid_color) - draw_multiline(pixel_points, pixel_grid_color) + draw_multiline(major_points, major_grid_color) + draw_multiline(minor_points, minor_grid_color) diff --git a/src/ui_parts/viewport.gd b/src/ui_parts/viewport.gd index d1445a9b..66e27779 100644 --- a/src/ui_parts/viewport.gd +++ b/src/ui_parts/viewport.gd @@ -1,42 +1,42 @@ extends SubViewport +const ZoomMenuType = preload("res://src/ui_parts/zoom_menu.gd") + const buffer_view_space = 0.8 +const zoom_reset_buffer = 0.875 + +var zoom := 1.0 @onready var display: TextureRect = %Checkerboard @onready var view: Camera2D = $ViewCamera @onready var controls: Control = %Checkerboard/Controls -@onready var display_texture: TextureRect = %Checkerboard/DisplayTexture -@onready var zoom_menu: HBoxContainer = %ZoomMenu -@onready var main_node: VBoxContainer = %Display +@onready var display_texture: Control = %Checkerboard/DisplayTexture +@onready var zoom_menu: ZoomMenuType = %ZoomMenu func _ready() -> void: - SVG.root_tag.attribute_changed.connect(resize) - SVG.root_tag.changed_unknown.connect(resize) - zoom_menu.zoom_reset() + SVG.root_tag.resized.connect(resize) resize() - update_view_limits() - -func update_view_limits() -> void: - view.limit_left = int(-size_2d_override.x * buffer_view_space) - view.limit_right = int(size_2d_override.x * buffer_view_space + display.size.x) - view.limit_top = int(-size_2d_override.y * buffer_view_space) - view.limit_bottom = int(size_2d_override.y * buffer_view_space + display.size.y) - set_view(view.position) # Ensure the view is still clamped. + await get_tree().process_frame + zoom_menu.zoom_reset() # Top left corner. func set_view(new_position: Vector2) -> void: view.position = new_position.clamp(Vector2(view.limit_left, view.limit_top), - Vector2(view.limit_right, view.limit_bottom) - size_2d_override * 1.0) + Vector2(view.limit_right, view.limit_bottom) - size / zoom) +# Adjust the SVG dimensions. func resize() -> void: - var svg_attribs := SVG.root_tag.attributes - display.size = Vector2(svg_attribs.width.get_value(), svg_attribs.height.get_value()) - center_frame() + display.size = SVG.root_tag.get_size() + zoom_menu.zoom_reset() func center_frame() -> void: - view.position = (display.size - size_2d_override * 1.0) / 2.0 + var available_size := size * zoom_reset_buffer + var w_ratio: float = available_size.x / SVG.root_tag.get_width() + var h_ratio: float = available_size.y / SVG.root_tag.get_height() + zoom_menu.zoom_level = nearest_po2(ceili(minf(w_ratio, h_ratio) * 32)) / 64.0 + set_view((SVG.root_tag.get_size() - size / zoom) / 2) func _unhandled_input(event: InputEvent) -> void: @@ -45,13 +45,13 @@ func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseMotion and\ event.button_mask in [MOUSE_BUTTON_MASK_LEFT, MOUSE_BUTTON_MASK_MIDDLE]: - set_view(view.position - event.relative) + set_view(view.position - event.relative / zoom) if event is InputEventPanGesture: if event.ctrl_pressed: zoom_menu.zoom_level *= 1 + event.delta.y / 2 else: - set_view(view.position + event.delta * 32) + set_view(view.position + event.delta * 32 / zoom) if event is InputEventMagnifyGesture: zoom_menu.zoom_level *= event.factor @@ -72,13 +72,31 @@ func _unhandled_input(event: InputEvent) -> void: pass + func _on_zoom_changed(zoom_level: float) -> void: - var old_size_2d_override := size_2d_override - size_2d_override = size / zoom_level - set_view(view.position + (old_size_2d_override - size_2d_override) / 2.0) + zoom = zoom_level + adjust_view() + display.material.set_shader_parameter(&"uv_scale", - nearest_po2(int(zoom_level * 32)) / 32.0) - update_view_limits() - controls.zoom = zoom_level - display_texture.zoom = zoom_level + nearest_po2(int(zoom * 32)) / 32.0) + controls.zoom = zoom + display_texture.zoom = zoom view.queue_redraw() + +var last_size_adjusted := size / zoom +func adjust_view() -> void: + var old_size := last_size_adjusted + last_size_adjusted = size / zoom + + var svg_size := SVG.root_tag.get_size() + var zoomed_size := buffer_view_space * size / zoom + view.zoom = Vector2(zoom, zoom) + view.limit_left = int(-zoomed_size.x) + view.limit_right = int(zoomed_size.x + svg_size.x) + view.limit_top = int(-zoomed_size.y) + view.limit_bottom = int(zoomed_size.y + svg_size.y) + set_view(view.position + (old_size - size / zoom) / 2.0) + +func _on_size_changed() -> void: + if is_node_ready(): + adjust_view() diff --git a/src/ui_parts/zoom_menu.gd b/src/ui_parts/zoom_menu.gd index 51ea2b3b..72f20e35 100644 --- a/src/ui_parts/zoom_menu.gd +++ b/src/ui_parts/zoom_menu.gd @@ -4,7 +4,7 @@ const MIN_ZOOM = 0.125 const MAX_ZOOM = 64.0 signal zoom_changed(zoom_level: float) -signal zoom_reset_pressed() +signal zoom_reset_pressed @onready var zoom_out_button: Button = $ZoomOut @onready var zoom_in_button: Button = $ZoomIn @@ -23,11 +23,8 @@ func zoom_out() -> void: func zoom_in() -> void: zoom_level *= sqrt(2) -# Choose an appropriate zoom level and center the camera. +# This needs a custom implementation to whatever is listening to the signal. func zoom_reset() -> void: - var svg_attribs := SVG.root_tag.attributes - zoom_level = float(nearest_po2(int(8192 / maxf(svg_attribs.width.get_value(), - svg_attribs.height.get_value()))) / 32.0) zoom_reset_pressed.emit() @@ -35,10 +32,13 @@ func update_buttons_appearance() -> void: zoom_reset_button.text = String.num(zoom_level * 100, 2 if zoom_level < 0.1 else 1 if zoom_level < 10.0 else 0) + "%" - zoom_in_button.disabled = zoom_level >= MAX_ZOOM + var is_max_zoom := zoom_level > MAX_ZOOM or is_equal_approx(zoom_level, MAX_ZOOM) + var is_min_zoom := zoom_level < MIN_ZOOM or is_equal_approx(zoom_level, MIN_ZOOM) + + zoom_in_button.disabled = is_max_zoom zoom_in_button.mouse_default_cursor_shape = Control.CURSOR_ARROW if\ - zoom_in_button.disabled else Control.CURSOR_POINTING_HAND + is_max_zoom else Control.CURSOR_POINTING_HAND - zoom_out_button.disabled = zoom_level <= MIN_ZOOM + zoom_out_button.disabled = is_min_zoom zoom_out_button.mouse_default_cursor_shape = Control.CURSOR_ARROW if\ - zoom_out_button.disabled else Control.CURSOR_POINTING_HAND + is_min_zoom else Control.CURSOR_POINTING_HAND diff --git a/src/ui_parts/zoom_menu.tscn b/src/ui_parts/zoom_menu.tscn index 47b7dfd3..152c6da2 100644 --- a/src/ui_parts/zoom_menu.tscn +++ b/src/ui_parts/zoom_menu.tscn @@ -32,35 +32,37 @@ unicode = 61 events = [SubResource("InputEventKey_gqh1f")] [node name="ZoomMenu" type="HBoxContainer"] +offset_right = 114.0 +offset_bottom = 24.0 alignment = 1 script = ExtResource("1_18ab8") [node name="ZoomOut" type="Button" parent="."] -custom_minimum_size = Vector2(28, 0) layout_mode = 2 tooltip_text = "#zoom_out" focus_mode = 0 mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" shortcut = SubResource("Shortcut_ntgv0") icon = ExtResource("1_8ggy2") icon_alignment = 1 [node name="ZoomReset" type="Button" parent="."] -custom_minimum_size = Vector2(60, 0) +custom_minimum_size = Vector2(58, 0) layout_mode = 2 tooltip_text = "#zoom_reset" focus_mode = 0 mouse_default_cursor_shape = 2 -theme_override_font_sizes/font_size = 14 +theme_override_font_sizes/font_size = 13 shortcut = SubResource("Shortcut_4v7wx") text = "100%" [node name="ZoomIn" type="Button" parent="."] -custom_minimum_size = Vector2(28, 0) layout_mode = 2 tooltip_text = "#zoom_in" focus_mode = 0 mouse_default_cursor_shape = 2 +theme_type_variation = &"IconButton" shortcut = SubResource("Shortcut_y6ouu") icon = ExtResource("2_284x5") icon_alignment = 1 diff --git a/translations/translation_sheet.csv b/translations/translation_sheet.csv index dfa3f862..8dc1abd3 100644 --- a/translations/translation_sheet.csv +++ b/translations/translation_sheet.csv @@ -1,12 +1,7 @@ ,en,bg,de -#remember_svg_setting,Remember SVG,Запомни SVG,SVG merken -#remember_svg_desc,"When this box is checked, the SVG will persist between sessions.","Когато тази кутийка е отметната, SVG-то ще бъде запомнено между сесиите.","Wenn diese Option aktiviert ist, wird das SVG zwischen Sitzungen wiederhergestellt." -#remember_window_mode_settings,Remember window mode,Запомни режима на прозореца,Fenstermodus wiederherstellen -#remember_window_mode_desc,"When this box is checked, the window can remain restored between sessions.","Когато тази кутийка е отметната, прозорецът може да остане малък между сесиите.","Wenn diese Option aktiviert ist, wird das Fenster zwischen Sitzungen wiederhergestellt." #language,Language,Език,Sprache #invert_zoom,Invert zoom direction,Обърни посоката на увеличение,Zoomrichtung umkehren #invert_zoom_desc,Swaps zoom in and zoom out with the mouse wheel,Разменя увеличението и намалянето с колелцето на мишката,Vertauscht Hinein- und Herauszoomen mit dem Mausrad -#session_tab,Session,Сесия,Sitzung #input_tab,Input,Входни сигнали,Eingabe #open,Open,Отвори,Öffnen #close,Close,Затвори,Schließen @@ -33,6 +28,7 @@ #move_down,Move Down,Премести надолу,Nach unten #delete,Delete,Премахни,Löschen #insert_after,Insert After,Вмъкни отпред,Danach einsetzen +#convert_to,Convert To,Превърни в, #err_empty_svg,SVG is empty.,Текстът е празен.,SVG ist leer. #err_not_svg,Doesn’t describe a SVG.,Текстът не описва SVG.,Beschreibt kein SVG. #err_improper_nesting,Improper nesting.,Несъвместими тагове.,Ungültige Formatierung. @@ -48,13 +44,31 @@ #import_problems,Import Problems,Проблеми в импортирането,Probleme beim Importieren #unknown_tooltip,GodSVG doesn’t recognize this attribute,GodSVG не разпознава този атрибут,GodSVG erkennt diese Attribute nicht #export,Export,Експортирай,Exportieren -#format,Format,Формат,Formatieren +#format,Format,Формат,Format #scale,Scale,Мащаб,Skalieren #size,Size,Размер,Größe #final_size,Final size,Краен размер,Endgültige Größe -#export_configuration,Export Configuration,Конфигурация на експорта,Exportierungskonfiguration +#export_configuration,Export Configuration,Конфигурация на експорта,Export-Konfiguration #add_tag,Add new tag,Добави нов таг,Neues Element hinzufügen -#license_tab,License,,Lizenz -#third-party-licenses_tab,Third-party-licenses,,Drittanbieter-Lizenzen -#authors_tab,Authors,,Autoren -#kbd_shortcuts_tab,Shortcuts,,Tastenkombinationen +#license_tab,License,Лиценз,Lizenz +#third-party-licenses_tab,Third-party licenses,Лицензи от трети партии,Drittanbieter-Lizenzen +#authors_tab,Authors,Автори,Autoren +#kbd_shortcuts_tab,Shortcuts,Бързи клавиши,Tastenkombinationen +#palettes,Palettes,Палети,Paletten +#color_picker,Color Picker,Цветно колело,Farbauswahl +#add_color,Add color,Добави цвят,Farbe hinzufügen +#edit_palette_name,Edit palette name,Промени името на палетата,Palettennamen ändern +#delete_palette,Delete the palette,Изтрий палетата,Palette löschen +#unnamed,Unnamed,Неименуван,Unbenannt +#edit_color_name,Edit color name,Промени името на цвета,Farbnamen ändern +#enable_color,Enable the color,Включи цвета,Farbe aktivieren +#disable_color,Disable the color,Изключи цвета,Farbe deaktivieren +#godot_third_party,Godot third-party components,Компоненти в Godot от трети партии,Godot Drittanbieter-Komponente +#undo,Undo,Върни назад, +#redo,Redo,Върни напред, +#copy,Copy,Копирай, +#paste,Paste,Постави, +#cut,Cut,Изрежи, +#alert,Alert!,Предупреждение!, +#file_open_fail_message,"The file couldn't be opened.\nTry checking the file path, ensure that the file is not deleted, or choose a different file.","Файлът не може да бъде отворен.\nОпитайте да проверите пътя към файла, уверете се, че файла не е изтрит, или изберете различен файл.", +#ok,OK,Добре, diff --git a/visual/CheckerboardMini.svg b/visual/CheckerboardMini.svg index cfe09230..0f1f3d13 100644 --- a/visual/CheckerboardMini.svg +++ b/visual/CheckerboardMini.svg @@ -1 +1 @@ - + diff --git a/visual/CheckerboardMini.svg.import b/visual/CheckerboardMini.svg.import index f38439eb..0747e93f 100644 --- a/visual/CheckerboardMini.svg.import +++ b/visual/CheckerboardMini.svg.import @@ -2,7 +2,7 @@ importer="texture" type="CompressedTexture2D" -uid="uid://y0l74x73w0co" +uid="uid://stpallv5q0rb" path="res://.godot/imported/CheckerboardMini.svg-bd002ec76a5e0a8a642802368dfffc3e.ctex" metadata={ "vram_texture": false diff --git a/visual/ColorButtonBG.svg b/visual/ColorButtonBG.svg new file mode 100644 index 00000000..cfe09230 --- /dev/null +++ b/visual/ColorButtonBG.svg @@ -0,0 +1 @@ + diff --git a/visual/ColorButtonBG.svg.import b/visual/ColorButtonBG.svg.import new file mode 100644 index 00000000..333d5171 --- /dev/null +++ b/visual/ColorButtonBG.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y0l74x73w0co" +path="res://.godot/imported/ColorButtonBG.svg-28ace1164dd31cbc3b8acf7004130ca8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/ColorButtonBG.svg" +dest_files=["res://.godot/imported/ColorButtonBG.svg-28ace1164dd31cbc3b8acf7004130ca8.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/icon.png b/visual/icon.png index 38fc127b..c099bb7a 100644 Binary files a/visual/icon.png and b/visual/icon.png differ diff --git a/visual/icon.svg b/visual/icon.svg new file mode 100644 index 00000000..5581d478 --- /dev/null +++ b/visual/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/visual/icons/Rect.svg.import b/visual/icon.svg.import similarity index 73% rename from visual/icons/Rect.svg.import rename to visual/icon.svg.import index 0e4d6cb4..b25e82e4 100644 --- a/visual/icons/Rect.svg.import +++ b/visual/icon.svg.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://cso2l5nvm6gm" -path="res://.godot/imported/Rect.svg-6904e70903749c997c6b2c3ea969dbec.ctex" +uid="uid://barsurula6j8n" +path="res://.godot/imported/icon.svg-4135024f1e47fffc8bac3039c30445db.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://visual/icons/Rect.svg" -dest_files=["res://.godot/imported/Rect.svg-6904e70903749c997c6b2c3ea969dbec.ctex"] +source_file="res://visual/icon.svg" +dest_files=["res://.godot/imported/icon.svg-4135024f1e47fffc8bac3039c30445db.ctex"] [params] diff --git a/visual/icons/Arrow.svg b/visual/icons/Arrow.svg index f4294fa4..8aa72c0a 100644 --- a/visual/icons/Arrow.svg +++ b/visual/icons/Arrow.svg @@ -1 +1 @@ - + diff --git a/visual/icons/ColorReset.svg b/visual/icons/ColorReset.svg new file mode 100644 index 00000000..6b83938c --- /dev/null +++ b/visual/icons/ColorReset.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/SmallCross.svg.import b/visual/icons/ColorReset.svg.import similarity index 71% rename from visual/icons/SmallCross.svg.import rename to visual/icons/ColorReset.svg.import index 8e78bf37..d09f12d3 100644 --- a/visual/icons/SmallCross.svg.import +++ b/visual/icons/ColorReset.svg.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://lpv1kxrwexmi" -path="res://.godot/imported/SmallCross.svg-3847356f267a2d78d7635c4479088a0d.ctex" +uid="uid://6gxmko0r4bly" +path="res://.godot/imported/ColorReset.svg-97811d3dd3bcbc70d39f4047304c0ab8.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://visual/icons/SmallCross.svg" -dest_files=["res://.godot/imported/SmallCross.svg-3847356f267a2d78d7635c4479088a0d.ctex"] +source_file="res://visual/icons/ColorReset.svg" +dest_files=["res://.godot/imported/ColorReset.svg-97811d3dd3bcbc70d39f4047304c0ab8.ctex"] [params] diff --git a/visual/icons/Edit.svg b/visual/icons/Edit.svg new file mode 100644 index 00000000..df6250d8 --- /dev/null +++ b/visual/icons/Edit.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/Edit.svg.import b/visual/icons/Edit.svg.import new file mode 100644 index 00000000..1b2757e4 --- /dev/null +++ b/visual/icons/Edit.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr2erka82g6j4" +path="res://.godot/imported/Edit.svg-e5dee2c17c2a6c10301ffeb8557d98f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/Edit.svg" +dest_files=["res://.godot/imported/Edit.svg-e5dee2c17c2a6c10301ffeb8557d98f5.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/GearOutlined.svg b/visual/icons/GearOutlined.svg new file mode 100644 index 00000000..9c032aa0 --- /dev/null +++ b/visual/icons/GearOutlined.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/GearOutlined.svg.import b/visual/icons/GearOutlined.svg.import new file mode 100644 index 00000000..db07644f --- /dev/null +++ b/visual/icons/GearOutlined.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhxr0nv6bs5j2" +path="res://.godot/imported/GearOutlined.svg-fb7b428da24b6bf77179a772ac33e058.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/GearOutlined.svg" +dest_files=["res://.godot/imported/GearOutlined.svg-fb7b428da24b6bf77179a772ac33e058.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/HandleBig.svg b/visual/icons/HandleBig.svg index 44e421dd..3d7352e6 100644 --- a/visual/icons/HandleBig.svg +++ b/visual/icons/HandleBig.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleBigHovered.svg b/visual/icons/HandleBigHovered.svg index 7b529f70..49715a6d 100644 --- a/visual/icons/HandleBigHovered.svg +++ b/visual/icons/HandleBigHovered.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleBigHoveredSelected.svg b/visual/icons/HandleBigHoveredSelected.svg index da8a6d07..0adfb1ba 100644 --- a/visual/icons/HandleBigHoveredSelected.svg +++ b/visual/icons/HandleBigHoveredSelected.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleBigSelected.svg b/visual/icons/HandleBigSelected.svg index 24f404b1..7b7d5818 100644 --- a/visual/icons/HandleBigSelected.svg +++ b/visual/icons/HandleBigSelected.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleSmall.svg b/visual/icons/HandleSmall.svg index 08034bef..c2f2fefb 100644 --- a/visual/icons/HandleSmall.svg +++ b/visual/icons/HandleSmall.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleSmallHovered.svg b/visual/icons/HandleSmallHovered.svg index 71c83b60..858ea96c 100644 --- a/visual/icons/HandleSmallHovered.svg +++ b/visual/icons/HandleSmallHovered.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleSmallHoveredSelected.svg b/visual/icons/HandleSmallHoveredSelected.svg index cbbf6225..868dfe5d 100644 --- a/visual/icons/HandleSmallHoveredSelected.svg +++ b/visual/icons/HandleSmallHoveredSelected.svg @@ -1 +1 @@ - + diff --git a/visual/icons/HandleSmallSelected.svg b/visual/icons/HandleSmallSelected.svg index 78cb2bb3..c5df2c65 100644 --- a/visual/icons/HandleSmallSelected.svg +++ b/visual/icons/HandleSmallSelected.svg @@ -1 +1 @@ - + diff --git a/visual/icons/NoneColor.svg b/visual/icons/NoneColor.svg new file mode 100644 index 00000000..0ec4cf12 --- /dev/null +++ b/visual/icons/NoneColor.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/NoneColor.svg.import b/visual/icons/NoneColor.svg.import new file mode 100644 index 00000000..de43ac35 --- /dev/null +++ b/visual/icons/NoneColor.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d36qn2f7a0nok" +path="res://.godot/imported/NoneColor.svg-035e27ac232b9837a5db250d09cebaa9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/NoneColor.svg" +dest_files=["res://.godot/imported/NoneColor.svg-035e27ac232b9837a5db250d09cebaa9.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/Path.svg b/visual/icons/Path.svg deleted file mode 100644 index 0659d4ec..00000000 --- a/visual/icons/Path.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/visual/icons/PickColor.svg b/visual/icons/PickColor.svg new file mode 100644 index 00000000..431722cb --- /dev/null +++ b/visual/icons/PickColor.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/PickColor.svg.import b/visual/icons/PickColor.svg.import new file mode 100644 index 00000000..e127cf08 --- /dev/null +++ b/visual/icons/PickColor.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxxx6pip4p26u" +path="res://.godot/imported/PickColor.svg-99a0f61ca2e2f40c88b3338078e9a8a9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/PickColor.svg" +dest_files=["res://.godot/imported/PickColor.svg-99a0f61ca2e2f40c88b3338078e9a8a9.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/Rect.svg b/visual/icons/Rect.svg deleted file mode 100644 index 2af3ba94..00000000 --- a/visual/icons/Rect.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/visual/icons/SideSliderArrow.svg b/visual/icons/SideSliderArrow.svg new file mode 100644 index 00000000..7b5670e0 --- /dev/null +++ b/visual/icons/SideSliderArrow.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/SideSliderArrow.svg.import b/visual/icons/SideSliderArrow.svg.import new file mode 100644 index 00000000..63f9793f --- /dev/null +++ b/visual/icons/SideSliderArrow.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmu6r4ipti60x" +path="res://.godot/imported/SideSliderArrow.svg-71eb756fca2bc3834582408862523b24.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/SideSliderArrow.svg" +dest_files=["res://.godot/imported/SideSliderArrow.svg-71eb756fca2bc3834582408862523b24.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/SliderArrow.svg b/visual/icons/SliderArrow.svg new file mode 100644 index 00000000..4f8c8193 --- /dev/null +++ b/visual/icons/SliderArrow.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/SliderArrow.svg.import b/visual/icons/SliderArrow.svg.import new file mode 100644 index 00000000..d45b6b3a --- /dev/null +++ b/visual/icons/SliderArrow.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnq1u5hrcqrho" +path="res://.godot/imported/SliderArrow.svg-6b7d47b794eb3da71fd45e482d52179a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/SliderArrow.svg" +dest_files=["res://.godot/imported/SliderArrow.svg-6b7d47b794eb3da71fd45e482d52179a.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/tag/g.svg b/visual/icons/tag/g.svg new file mode 100644 index 00000000..02f33f54 --- /dev/null +++ b/visual/icons/tag/g.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/Path.svg.import b/visual/icons/tag/g.svg.import similarity index 73% rename from visual/icons/Path.svg.import rename to visual/icons/tag/g.svg.import index 3a293dbe..43f393bf 100644 --- a/visual/icons/Path.svg.import +++ b/visual/icons/tag/g.svg.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://xpm1e34xuk06" -path="res://.godot/imported/Path.svg-463b3b227d5308713e4d0e5b6b7f4271.ctex" +uid="uid://v4diytxx43vs" +path="res://.godot/imported/g.svg-ab7bc64ba1a21529757498010411b71e.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://visual/icons/Path.svg" -dest_files=["res://.godot/imported/Path.svg-463b3b227d5308713e4d0e5b6b7f4271.ctex"] +source_file="res://visual/icons/tag/g.svg" +dest_files=["res://.godot/imported/g.svg-ab7bc64ba1a21529757498010411b71e.ctex"] [params] diff --git a/visual/icons/tag/linearGradient.svg b/visual/icons/tag/linearGradient.svg new file mode 100644 index 00000000..325de317 --- /dev/null +++ b/visual/icons/tag/linearGradient.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/tag/linearGradient.svg.import b/visual/icons/tag/linearGradient.svg.import new file mode 100644 index 00000000..e85e9ff5 --- /dev/null +++ b/visual/icons/tag/linearGradient.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ld6hoxtl7q6y" +path="res://.godot/imported/linearGradient.svg-abec7a2d0e886794114aeb8930fd6a61.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/tag/linearGradient.svg" +dest_files=["res://.godot/imported/linearGradient.svg-abec7a2d0e886794114aeb8930fd6a61.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/FolderUp.svg b/visual/icons/theme/FolderUp.svg similarity index 100% rename from visual/icons/FolderUp.svg rename to visual/icons/theme/FolderUp.svg diff --git a/visual/icons/FolderUp.svg.import b/visual/icons/theme/FolderUp.svg.import similarity index 74% rename from visual/icons/FolderUp.svg.import rename to visual/icons/theme/FolderUp.svg.import index abe92ee2..8f80bf9d 100644 --- a/visual/icons/FolderUp.svg.import +++ b/visual/icons/theme/FolderUp.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://rrhdja8l17cn" -path="res://.godot/imported/FolderUp.svg-ea80ed3af2557e3c1b26b83ba445e18e.ctex" +path="res://.godot/imported/FolderUp.svg-b4eb7c260434a037353caaa49dc2e4f8.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://visual/icons/FolderUp.svg" -dest_files=["res://.godot/imported/FolderUp.svg-ea80ed3af2557e3c1b26b83ba445e18e.ctex"] +source_file="res://visual/icons/theme/FolderUp.svg" +dest_files=["res://.godot/imported/FolderUp.svg-b4eb7c260434a037353caaa49dc2e4f8.ctex"] [params] diff --git a/visual/icons/theme/SplitGrabber.svg b/visual/icons/theme/SplitGrabber.svg index 0caa6f7a..7b0f65b3 100644 --- a/visual/icons/theme/SplitGrabber.svg +++ b/visual/icons/theme/SplitGrabber.svg @@ -1 +1 @@ - + diff --git a/visual/icons/theme/SplitGrabber2.svg b/visual/icons/theme/SplitGrabber2.svg new file mode 100644 index 00000000..2fa615e1 --- /dev/null +++ b/visual/icons/theme/SplitGrabber2.svg @@ -0,0 +1 @@ + diff --git a/visual/icons/theme/SplitGrabber2.svg.import b/visual/icons/theme/SplitGrabber2.svg.import new file mode 100644 index 00000000..34d5bd24 --- /dev/null +++ b/visual/icons/theme/SplitGrabber2.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co75w07yqmcro" +path="res://.godot/imported/SplitGrabber2.svg-18d40ab670e2b5a72dfeedf569d260c4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/icons/theme/SplitGrabber2.svg" +dest_files=["res://.godot/imported/SplitGrabber2.svg-18d40ab670e2b5a72dfeedf569d260c4.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/main_theme.tres b/visual/main_theme.tres index 9d33cc6f..014d9317 100644 --- a/visual/main_theme.tres +++ b/visual/main_theme.tres @@ -1,9 +1,9 @@ -[gd_resource type="Theme" load_steps=66 format=3 uid="uid://der6fnkliqqto"] +[gd_resource type="Theme" load_steps=72 format=3 uid="uid://der6fnkliqqto"] [ext_resource type="Texture2D" uid="uid://wdrpwa7gwmg" path="res://visual/icons/theme/GuiBoxChecked.svg" id="1_agkhv"] [ext_resource type="Texture2D" uid="uid://d3a3xgsb8klyk" path="res://visual/icons/theme/GuiBoxUnchecked.svg" id="2_xwib4"] [ext_resource type="Texture2D" uid="uid://dwlr4bgptgwho" path="res://visual/icons/theme/GuiToggleChecked.svg" id="3_ggukc"] -[ext_resource type="Texture2D" uid="uid://rrhdja8l17cn" path="res://visual/icons/FolderUp.svg" id="4_1t13q"] +[ext_resource type="Texture2D" uid="uid://rrhdja8l17cn" path="res://visual/icons/theme/FolderUp.svg" id="4_1t13q"] [ext_resource type="Texture2D" uid="uid://cvh3kwbucf2n1" path="res://visual/icons/Reload.svg" id="4_8qjet"] [ext_resource type="Texture2D" uid="uid://kkxyv1gyrjgj" path="res://visual/icons/Visuals.svg" id="4_j0oku"] [ext_resource type="Texture2D" uid="uid://c12tg3dnydily" path="res://visual/icons/theme/GuiToggleUnchecked.svg" id="4_w667w"] @@ -31,9 +31,9 @@ corner_detail = 2 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4tonr"] content_margin_left = 6.0 -content_margin_top = 4.0 +content_margin_top = 3.0 content_margin_right = 6.0 -content_margin_bottom = 4.0 +content_margin_bottom = 3.0 bg_color = Color(0.0352941, 0.0352941, 0.0509804, 1) border_width_left = 2 border_width_top = 2 @@ -44,60 +44,66 @@ corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_gu1eq"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ay4gh"] +bg_color = Color(1, 1, 1, 0.0666667) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.4, 0.8, 1, 0.8) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h5umu"] content_margin_left = 6.0 -content_margin_top = 4.0 +content_margin_top = 3.0 content_margin_right = 6.0 -content_margin_bottom = 4.0 -bg_color = Color(0.0901961, 0.0901961, 0.2, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.137255, 0.156863, 0.25098, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.262745, 0.337255, 0.478431, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yq2fk"] content_margin_left = 6.0 -content_margin_top = 4.0 +content_margin_top = 3.0 content_margin_right = 6.0 -content_margin_bottom = 4.0 -bg_color = Color(0.0705882, 0.0705882, 0.2, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.109804, 0.117647, 0.219608, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.105882, 0.14902, 0.301961, 1) +border_color = Color(0.192157, 0.219608, 0.34902, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gnyjn"] content_margin_left = 6.0 -content_margin_top = 4.0 +content_margin_top = 3.0 content_margin_right = 6.0 -content_margin_bottom = 4.0 -bg_color = Color(0.243137, 0.356863, 0.701961, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.239216, 0.329412, 0.6, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.425, 0.6375, 0.85, 1) +border_color = Color(0.376471, 0.560784, 0.74902, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ehixj"] content_margin_left = 4.0 @@ -134,12 +140,38 @@ content_margin_right = 4.0 content_margin_bottom = 2.0 [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ixekj"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qrkud"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ky4q6"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(1, 1, 1, 0.0666667) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_q84qj"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6kdq7"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jayv"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(1, 1, 1, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qepg8"] content_margin_left = 4.0 @@ -156,63 +188,59 @@ corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c4o10"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 content_margin_bottom = 4.0 -bg_color = Color(0.0901961, 0.0901961, 0.2, 1) +bg_color = Color(0.137255, 0.156863, 0.25098, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.262745, 0.337255, 0.478431, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ecs6u"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 content_margin_bottom = 4.0 -bg_color = Color(0.0705882, 0.0705882, 0.2, 1) +bg_color = Color(0.121569, 0.129412, 0.219608, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.105882, 0.14902, 0.301961, 1) +border_color = Color(0.192157, 0.219608, 0.34902, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ye6kl"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 content_margin_bottom = 4.0 -bg_color = Color(0.243137, 0.356863, 0.701961, 1) +bg_color = Color(0.239216, 0.329412, 0.6, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.425, 0.6375, 0.85, 1) +border_color = Color(0.376471, 0.560784, 0.74902, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8vw7r"] content_margin_left = 3.0 content_margin_top = 4.0 -content_margin_right = 6.0 +content_margin_right = 5.0 content_margin_bottom = 4.0 bg_color = Color(0.0352941, 0.0352941, 0.0509804, 1) border_width_left = 1 @@ -222,52 +250,48 @@ border_width_bottom = 2 border_color = Color(0.0901961, 0.0901961, 0.101961, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rcthl"] content_margin_left = 3.0 content_margin_top = 4.0 -content_margin_right = 6.0 +content_margin_right = 5.0 content_margin_bottom = 4.0 -bg_color = Color(0.0901961, 0.0901961, 0.2, 1) +bg_color = Color(0.0941176, 0.0941176, 0.14902, 1) border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.227451, 0.227451, 0.301961, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2dyv3"] content_margin_left = 3.0 content_margin_top = 4.0 -content_margin_right = 6.0 +content_margin_right = 5.0 content_margin_bottom = 4.0 -bg_color = Color(0.0705882, 0.0705882, 0.2, 1) +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.105882, 0.14902, 0.301961, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m8uy4"] content_margin_left = 3.0 content_margin_top = 4.0 -content_margin_right = 6.0 +content_margin_right = 5.0 content_margin_bottom = 4.0 -bg_color = Color(0.243137, 0.356863, 0.701961, 1) +bg_color = Color(0.192157, 0.207843, 0.34902, 1) border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.425, 0.6375, 0.85, 1) +border_color = Color(0.329412, 0.403922, 0.54902, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x2qq7"] content_margin_left = 4.0 @@ -282,7 +306,6 @@ border_width_bottom = 2 border_color = Color(0.0901961, 0.0901961, 0.101961, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_haqea"] content_margin_left = 4.0 @@ -294,10 +317,9 @@ border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.227451, 0.227451, 0.301961, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ms2eo"] content_margin_left = 4.0 @@ -309,10 +331,9 @@ border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.105882, 0.14902, 0.301961, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o3p5c"] content_margin_left = 4.0 @@ -324,22 +345,20 @@ border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.541176, 0.721569, 0.901961, 1) +border_color = Color(0.329412, 0.403922, 0.54902, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1x4s"] content_margin_left = 5.0 -bg_color = Color(0.062, 0.062, 0.1, 1) +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) border_width_left = 1 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.1368, 0.1368, 0.18, 1) +border_color = Color(0.152, 0.152, 0.2, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ff1pj"] content_margin_left = 5.0 @@ -351,23 +370,21 @@ border_width_bottom = 2 border_color = Color(0.0901961, 0.0901961, 0.101961, 1) corner_radius_top_right = 5 corner_radius_bottom_right = 5 -corner_detail = 16 [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ri0uc"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_730xo"] content_margin_left = 5.0 -bg_color = Color(0.062, 0.062, 0.1, 1) +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.1368, 0.1368, 0.18, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_efvcd"] content_margin_left = 5.0 @@ -381,14 +398,13 @@ corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rmu68"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iof82"] content_margin_left = 4.0 content_margin_bottom = 0.0 -bg_color = Color(0.062, 0.062, 0.1, 1) +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) border_width_bottom = 2 border_color = Color(0.228, 0.228, 0.3, 1) corner_radius_top_left = 3 @@ -406,20 +422,19 @@ corner_radius_top_right = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c2si1"] content_margin_left = 2.0 content_margin_right = 2.0 -bg_color = Color(0.0975, 0.0975, 0.15, 1) +bg_color = Color(0.0980392, 0.0980392, 0.14902, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.225, 0.225, 0.3, 1) +border_color = Color(0.254902, 0.254902, 0.34902, 1) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8bmk3"] -content_margin_left = 6.0 +content_margin_left = 5.0 content_margin_top = 4.0 content_margin_right = 3.0 content_margin_bottom = 4.0 @@ -431,52 +446,48 @@ border_width_bottom = 2 border_color = Color(0.0901961, 0.0901961, 0.101961, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v2cqs"] -content_margin_left = 6.0 +content_margin_left = 5.0 content_margin_top = 4.0 content_margin_right = 3.0 content_margin_bottom = 4.0 -bg_color = Color(0.0901961, 0.0901961, 0.2, 1) +bg_color = Color(0.137255, 0.156863, 0.25098, 1) border_width_left = 2 border_width_top = 2 border_width_right = 1 border_width_bottom = 2 -border_color = Color(0.203922, 0.254902, 0.4, 1) +border_color = Color(0.262745, 0.337255, 0.478431, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v3rbs"] -content_margin_left = 6.0 +content_margin_left = 5.0 content_margin_top = 4.0 content_margin_right = 3.0 content_margin_bottom = 4.0 -bg_color = Color(0.0705882, 0.0705882, 0.2, 1) +bg_color = Color(0.109804, 0.117647, 0.219608, 1) border_width_left = 2 border_width_top = 2 border_width_right = 1 border_width_bottom = 2 -border_color = Color(0.105882, 0.14902, 0.301961, 1) +border_color = Color(0.192157, 0.219608, 0.34902, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s605b"] -content_margin_left = 6.0 +content_margin_left = 5.0 content_margin_top = 4.0 content_margin_right = 3.0 content_margin_bottom = 4.0 -bg_color = Color(0.243137, 0.356863, 0.701961, 1) +bg_color = Color(0.239216, 0.329412, 0.6, 1) border_width_left = 2 border_width_top = 2 border_width_right = 1 border_width_bottom = 2 -border_color = Color(0.425, 0.6375, 0.85, 1) +border_color = Color(0.376471, 0.560784, 0.74902, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i5hko"] content_margin_left = 5.0 @@ -485,10 +496,9 @@ border_width_left = 2 border_width_top = 2 border_width_right = 1 border_width_bottom = 2 -border_color = Color(0.1368, 0.1368, 0.18, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fuf3i"] content_margin_left = 5.0 @@ -500,30 +510,63 @@ border_width_bottom = 2 border_color = Color(0.0901961, 0.0901961, 0.101961, 1) corner_radius_top_left = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7qkcb"] -bg_color = Color(0.036, 0.0514, 0.12, 1) +bg_color = Color(0.078, 0.0828, 0.15, 1) +border_width_left = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.14902, 0.164706, 0.301961, 1) +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_auw38"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kohom"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_r2aev"] content_margin_left = 12.0 +content_margin_top = 3.0 content_margin_right = 12.0 -bg_color = Color(0.1, 0.1125, 0.25, 1) -border_color = Color(0.203922, 0.254902, 0.4, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.121569, 0.129412, 0.219608, 1) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nhytu"] content_margin_left = 12.0 +content_margin_top = 3.0 content_margin_right = 12.0 -bg_color = Color(0.1225, 0.179375, 0.35, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.16, 0.186667, 0.32, 1) border_width_top = 2 -border_color = Color(0.541176, 0.721569, 0.901961, 1) +border_color = Color(0.376471, 0.560784, 0.74902, 1) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c7arg"] content_margin_left = 12.0 +content_margin_top = 3.0 content_margin_right = 12.0 -bg_color = Color(0.0705882, 0.0705882, 0.2, 1) +content_margin_bottom = 3.0 +bg_color = Color(0.09, 0.0975, 0.18, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jd7jj"] +bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_usmkl"] +content_margin_left = 2.0 +content_margin_right = 2.0 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p0ubs"] +content_margin_left = 2.0 +content_margin_right = 2.0 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rx78m"] +content_margin_left = 2.0 +content_margin_right = 2.0 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_sjt13"] +content_margin_left = 2.0 +content_margin_right = 2.0 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1gwcd"] draw_center = false @@ -532,26 +575,29 @@ border_width_top = 2 border_width_right = 2 border_width_bottom = 2 border_color = Color(0.301961, 0.45098, 0.6, 1) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 -corner_detail = 16 +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ichfn"] content_margin_left = 5.0 bg_color = Color(0.0627451, 0.0627451, 0.101961, 1) border_width_left = 2 +border_width_top = 2 border_width_right = 2 border_width_bottom = 2 -border_color = Color(0.1368, 0.1368, 0.18, 1) +border_color = Color(0.152941, 0.152941, 0.2, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -corner_detail = 16 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ghiib"] -content_margin_left = 8.0 -content_margin_right = 8.0 +content_margin_left = 6.0 +content_margin_top = 1.0 +content_margin_right = 6.0 +content_margin_bottom = 3.0 bg_color = Color(0.129412, 0.14902, 0.207843, 1) border_width_left = 2 border_width_top = 2 @@ -562,9 +608,10 @@ corner_radius_top_left = 2 corner_radius_top_right = 2 corner_radius_bottom_right = 2 corner_radius_bottom_left = 2 -corner_detail = 2 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dptuh"] +content_margin_top = 2.0 +content_margin_bottom = 2.0 bg_color = Color(0.203922, 0.254902, 0.4, 1) corner_radius_top_left = 3 corner_radius_top_right = 3 @@ -572,6 +619,8 @@ corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jo7ks"] +content_margin_top = 2.0 +content_margin_bottom = 2.0 bg_color = Color(0.275, 0.335, 0.5, 1) corner_radius_top_left = 3 corner_radius_top_right = 3 @@ -579,7 +628,9 @@ corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k6fme"] -bg_color = Color(0.423529, 0.639216, 0.85098, 1) +content_margin_top = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(0.376471, 0.560784, 0.74902, 1) corner_radius_top_left = 3 corner_radius_top_right = 3 corner_radius_bottom_right = 3 @@ -587,9 +638,7 @@ corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wn4u7"] content_margin_left = 4.0 -content_margin_top = 0.0 content_margin_right = 4.0 -content_margin_bottom = 0.0 bg_color = Color(0.06, 0.06, 0.1, 0.6) corner_radius_top_left = 3 corner_radius_top_right = 3 @@ -609,7 +658,6 @@ corner_radius_top_left = 6 corner_radius_top_right = 6 corner_radius_bottom_right = 6 corner_radius_bottom_left = 6 -corner_detail = 16 expand_margin_left = 8.0 expand_margin_top = 32.0 expand_margin_right = 8.0 @@ -621,7 +669,7 @@ default_font_size = 13 AcceptDialog/styles/panel = SubResource("StyleBoxFlat_i4np8") Button/constants/h_separation = 6 Button/styles/disabled = SubResource("StyleBoxFlat_4tonr") -Button/styles/focus = SubResource("StyleBoxEmpty_gu1eq") +Button/styles/focus = SubResource("StyleBoxFlat_ay4gh") Button/styles/hover = SubResource("StyleBoxFlat_h5umu") Button/styles/normal = SubResource("StyleBoxFlat_yq2fk") Button/styles/pressed = SubResource("StyleBoxFlat_gnyjn") @@ -636,10 +684,14 @@ CheckButton/icons/unchecked = ExtResource("4_w667w") FileDialog/icons/parent_folder = ExtResource("4_1t13q") FileDialog/icons/reload = ExtResource("4_8qjet") FileDialog/icons/toggle_hidden = ExtResource("4_j0oku") +FlatButton/base_type = &"Button" +FlatButton/colors/icon_hover_color = Color(1, 1, 1, 1) +FlatButton/colors/icon_normal_color = Color(0.74902, 0.74902, 0.74902, 1) +FlatButton/colors/icon_pressed_color = Color(0.74902, 0.87451, 1, 1) FlatButton/styles/disabled = SubResource("StyleBoxEmpty_ixekj") -FlatButton/styles/hover = SubResource("StyleBoxEmpty_qrkud") +FlatButton/styles/hover = SubResource("StyleBoxFlat_ky4q6") FlatButton/styles/normal = SubResource("StyleBoxEmpty_q84qj") -FlatButton/styles/pressed = SubResource("StyleBoxEmpty_6kdq7") +FlatButton/styles/pressed = SubResource("StyleBoxFlat_5jayv") IconButton/base_type = &"Button" IconButton/styles/disabled = SubResource("StyleBoxFlat_qepg8") IconButton/styles/hover = SubResource("StyleBoxFlat_c4o10") @@ -647,6 +699,9 @@ IconButton/styles/normal = SubResource("StyleBoxFlat_ecs6u") IconButton/styles/pressed = SubResource("StyleBoxFlat_ye6kl") Label/font_sizes/font_size = 15 LeftConnectedButton/base_type = &"Button" +LeftConnectedButton/colors/icon_hover_color = Color(1, 1, 1, 1) +LeftConnectedButton/colors/icon_normal_color = Color(0.74902, 0.74902, 0.74902, 1) +LeftConnectedButton/colors/icon_pressed_color = Color(0.74902, 0.87451, 1, 1) LeftConnectedButton/styles/disabled = SubResource("StyleBoxFlat_8vw7r") LeftConnectedButton/styles/hover = SubResource("StyleBoxFlat_rcthl") LeftConnectedButton/styles/normal = SubResource("StyleBoxFlat_2dyv3") @@ -661,7 +716,7 @@ LeftConnectedLineEdit/font_sizes/font_size = 12 LeftConnectedLineEdit/fonts/font = ExtResource("5_nfqug") LeftConnectedLineEdit/styles/normal = SubResource("StyleBoxFlat_k1x4s") LeftConnectedLineEdit/styles/read_only = SubResource("StyleBoxFlat_ff1pj") -LineEdit/colors/caret_color = Color(0.94902, 0.854902, 0.854902, 1) +LineEdit/colors/caret_color = Color(0.866667, 0.933333, 1, 0.866667) LineEdit/colors/font_color = Color(0.866667, 0.933333, 1, 1) LineEdit/colors/selection_color = Color(0.4, 0.54902, 1, 0.4) LineEdit/font_sizes/font_size = 12 @@ -679,11 +734,12 @@ MiniLineEdit/styles/focus = SubResource("StyleBoxEmpty_rmu68") MiniLineEdit/styles/normal = SubResource("StyleBoxFlat_iof82") MiniLineEdit/styles/read_only = SubResource("StyleBoxFlat_c4urh") PanelContainer/styles/panel = SubResource("StyleBoxFlat_c2si1") -ProgressBar/styles/background = null -ProgressBar/styles/fill = null RichTextLabel/colors/selection_color = Color(0.4, 0.55, 1, 0.4) RichTextLabel/fonts/bold_font = ExtResource("9_ugiac") RightConnectedButton/base_type = &"Button" +RightConnectedButton/colors/icon_hover_color = Color(1, 1, 1, 1) +RightConnectedButton/colors/icon_normal_color = Color(0.74902, 0.74902, 0.74902, 1) +RightConnectedButton/colors/icon_pressed_color = Color(0.74902, 0.87451, 1, 1) RightConnectedButton/styles/disabled = SubResource("StyleBoxFlat_8bmk3") RightConnectedButton/styles/hover = SubResource("StyleBoxFlat_v2cqs") RightConnectedButton/styles/normal = SubResource("StyleBoxFlat_v3rbs") @@ -696,10 +752,20 @@ RightConnectedLineEdit/styles/read_only = SubResource("StyleBoxFlat_fuf3i") TabContainer/constants/side_margin = 0 TabContainer/font_sizes/font_size = 14 TabContainer/styles/panel = SubResource("StyleBoxFlat_7qkcb") +TabContainer/styles/tab_disabled = SubResource("StyleBoxEmpty_auw38") TabContainer/styles/tab_focus = SubResource("StyleBoxEmpty_kohom") TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_r2aev") TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_nhytu") TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_c7arg") +TabContainer/styles/tabbar_background = SubResource("StyleBoxFlat_jd7jj") +TextButton/base_type = &"Button" +TextButton/colors/font_color = Color(0.501961, 0.501961, 0.501961, 1) +TextButton/colors/font_hover_color = Color(0.501961, 0.501961, 0.501961, 1) +TextButton/colors/font_pressed_color = Color(0.866667, 0.933333, 1, 0.866667) +TextButton/styles/disabled = SubResource("StyleBoxEmpty_usmkl") +TextButton/styles/hover = SubResource("StyleBoxEmpty_p0ubs") +TextButton/styles/normal = SubResource("StyleBoxEmpty_rx78m") +TextButton/styles/pressed = SubResource("StyleBoxEmpty_sjt13") TextEdit/colors/caret_color = Color(0, 0, 0, 0) TextEdit/colors/selection_color = Color(0.4, 0.54902, 1, 0.4) TextEdit/font_sizes/font_size = 12 @@ -707,6 +773,7 @@ TextEdit/fonts/font = ExtResource("5_nfqug") TextEdit/styles/focus = SubResource("StyleBoxFlat_1gwcd") TextEdit/styles/normal = SubResource("StyleBoxFlat_ichfn") TooltipLabel/colors/font_color = Color(0.866667, 0.933333, 1, 1) +TooltipLabel/font_sizes/font_size = 14 TooltipLabel/fonts/font = ExtResource("11_ccloq") TooltipPanel/styles/panel = SubResource("StyleBoxFlat_ghiib") VScrollBar/styles/grabber = SubResource("StyleBoxFlat_dptuh") diff --git a/visual/splash.png b/visual/splash.png new file mode 100644 index 00000000..059ca170 Binary files /dev/null and b/visual/splash.png differ diff --git a/visual/splash.png.import b/visual/splash.png.import new file mode 100644 index 00000000..3852703e --- /dev/null +++ b/visual/splash.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bom8y28xu652f" +path="res://.godot/imported/splash.png-2e737774984f5a274381090076e4098a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/splash.png" +dest_files=["res://.godot/imported/splash.png-2e737774984f5a274381090076e4098a.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 diff --git a/visual/splash.svg b/visual/splash.svg new file mode 100644 index 00000000..7bc44c0a --- /dev/null +++ b/visual/splash.svg @@ -0,0 +1 @@ + diff --git a/visual/splash.svg.import b/visual/splash.svg.import new file mode 100644 index 00000000..76f6a06b --- /dev/null +++ b/visual/splash.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rhqnuun4e7cj" +path="res://.godot/imported/splash.svg-aaef44ef3b35ccb214e050febfc889ae.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://visual/splash.svg" +dest_files=["res://.godot/imported/splash.svg-aaef44ef3b35ccb214e050febfc889ae.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/xport/prealpha_profile b/xport/prealpha_profile.build similarity index 97% rename from xport/prealpha_profile rename to xport/prealpha_profile.build index 7ecde7c4..c52838d1 100644 --- a/xport/prealpha_profile +++ b/xport/prealpha_profile.build @@ -4,7 +4,8 @@ "disable_3d": true, "disable_3d_physics": true, "disable_navigation": true, - "openxr": false + "openxr": false, + "rendering_device": false }, "disabled_classes": [ "AESContext", @@ -44,12 +45,12 @@ "BaseMaterial3D", "BitMap", "BoneMap", - "ButtonGroup", "CameraAttributes", "CameraFeed", "CameraServer", "CameraTexture", "CanvasItemEditor", + "CanvasItemEditorSelectedItem", "CanvasItemEditorViewport", "CanvasItemMaterial", "CanvasLayer", @@ -59,11 +60,13 @@ "CharFXTransform", "CheckButton", "ClassDB", + "CodeEdit", "CodeHighlighter", "CodeTextEditor", "CollisionShape2DEditor", "ColorPicker", "ColorPickerButton", + "ColorPresetButton", "ConfigFile", "ConnectDialog", "ConnectDialogBinds", @@ -72,6 +75,7 @@ "ControlEditorPopupButton", "ControlEditorPresetPicker", "ControlEditorToolbar", + "ControlPositioningWarning", "CreateDialog", "Crypto", "CryptoKey", @@ -116,6 +120,7 @@ "EditorFileSystem", "EditorFileSystemDirectory", "EditorFileSystemImportFormatSupportQuery", + "EditorHelp", "EditorHelpBit", "EditorHelpSearch", "EditorImportBlendRunner", @@ -137,7 +142,9 @@ "EditorPluginSettings", "EditorProfiler", "EditorProperty", + "EditorPropertyArrayObject", "EditorPropertyDictionaryObject", + "EditorPropertyLayersGrid", "EditorPropertyNameProcessor", "EditorQuickOpen", "EditorResourceConversionPlugin", @@ -175,13 +182,13 @@ "FBXImporterManager", "FileAccess", "FileSystemDock", + "FindBar", "FindInFiles", "FindInFilesDialog", "FindInFilesPanel", "FindReplaceBar", "FogMaterial", "FontVariation", - "FramebufferCacheRD", "GDExtension", "GDExtensionManager", "GDScriptNativeClass", @@ -221,7 +228,6 @@ "GroupDialog", "GroupsEditor", "HMACContext", - "HSplitContainer", "HTTPClient", "HTTPRequest", "HashingContext", @@ -266,6 +272,7 @@ "LocalizationEditor", "MainLoop", "Marshalls", + "MaterialEditor", "MenuBar", "MenuButton", "Mesh", @@ -280,6 +287,7 @@ "MovieWriter", "MultiMesh", "MultiMeshEditor", + "MultiNodeEdit", "MultiplayerAPI", "MultiplayerSpawner", "MultiplayerSynchronizer", @@ -453,9 +461,11 @@ "SpriteFrames", "StreamPeer", "StyleBoxLine", + "StyleBoxPreview", "StyleBoxTexture", "SubViewportContainer", "SurfaceTool", + "SurfaceUpgradeTool", "SystemFont", "TCPServer", "TLSOptions", @@ -506,7 +516,6 @@ "UPNP", "UPNPDevice", "UndoRedo", - "UniformSetCacheRD", "VFlowContainer", "VideoStream", "VideoStreamPlayback", @@ -535,4 +544,4 @@ "ZIPReader" ], "type": "build_profile" -} \ No newline at end of file +}