Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ keytool -printcert -jarfile <APK-file>

GodSVG also runs on web - here's a link to the official web editor: https://godsvg.com/editor

To run the latest unreleased version, you can download Godot from https://godotengine.org (development is currently happening in v4.5.1). After getting the repository files on your machine, you must open Godot, click on the "Import" button, and import the `project.godot` folder.
To run the latest unreleased version, you can download Godot from https://godotengine.org (development is currently happening in v4.6). After getting the repository files on your machine, you must open Godot, click on the "Import" button, and import the `project.godot` folder.

Another way to run the latest dev build is to open a recent commit and download its artifacts (Checks > export-optimized > Summary > Artifacts). You must log into Github for that.

Expand Down
2 changes: 1 addition & 1 deletion assets/fonts/BoldFont.tres
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_resource type="FontVariation" load_steps=4 format=3 uid="uid://o6egawwljhgy"]
[gd_resource type="FontVariation" format=3 uid="uid://o6egawwljhgy"]

[ext_resource type="FontFile" uid="uid://cdke2b8vyur24" path="res://assets/fonts/original/NotoSans-ExtraBold.woff2" id="1_c6ro6"]
[ext_resource type="FontFile" uid="uid://jkpbay57t50t" path="res://assets/fonts/original/DroidSansFallback.woff2" id="2_5dil2"]
Expand Down
16 changes: 8 additions & 8 deletions godot_only/scripts/tests.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Run with Ctrl+Shift+X.
# A script intended to test sensitive parts of the codebase.
# This isn't an invitation to add random tests, please discuss before doing so.
# This is reserved to things that are easy to test and fairly implementation-agnostic.
# It isn't an invitation to add random tests, please discuss before doing so.
@tool
extends EditorScript

Expand All @@ -10,7 +11,7 @@ func _run() -> void:
pathdata_tests()
transform_list_tests()
if report.is_empty():
print_rich("[rainbow sat=0.4 val=1.0]Success[/rainbow]")
print_rich("[rainbow sat=0.4 val=1.0]All tests successful[/rainbow]")
else:
for report_line in report:
print_rich(report_line)
Expand All @@ -20,10 +21,9 @@ func add_to_report(test_category: String, test: String, result: String, expected


# This test is dependent on specifics of the Formatter and AttributePathdata classes.
# But its logic would likely not change enough in the future to make the tests obsolete.
# https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
# We have a difference in logic where we don't require starting with MoveTo
# in order to make pathdata easier to edit.
# But their logic will likely not change enough in the future to make the tests obsolete.
# SVG specification: https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
# Our logic is a bit different, as it considers MoveTo at the beginning as valid and doesn't collapse anything.
# ClosePath are also merged, so you don't end up with invalid syntax after commands are deleted.
func pathdata_tests() -> void:
var spacious_formatter := Formatter.new()
Expand Down Expand Up @@ -73,8 +73,8 @@ func pathdata_tests() -> void:
add_to_report("Pathdata parser", test, result, tests[test])

# This test is dependent on specifics of the Formatter and AttributeTransformList classes.
# But its logic would likely not change enough in the future to make the tests obsolete.
# https://www.w3.org/TR/SVG11/coords.html#TransformAttribute
# But their logic will likely not change enough in the future to make the tests obsolete.
# SVG specification: https://www.w3.org/TR/SVG11/coords.html#TransformAttribute
func transform_list_tests() -> void:
var spacious_formatter := Formatter.new()
spacious_formatter.transform_list_compress_numbers = false
Expand Down
12 changes: 6 additions & 6 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@

config_version=5

[animation]

compatibility/default_parent_skeleton_in_mesh_instance_3d=true

[application]

config/name="GodSVG"
config/version="1.0-alpha13"
config/tags=PackedStringArray("project")
run/main_scene="res://src/ui_parts/editor_scene.tscn"
config/use_custom_user_dir=true
config/features=PackedStringArray("4.5")
config/features=PackedStringArray("4.6")
run/low_processor_mode=true
boot_splash/bg_color=Color(0.1065, 0.1181, 0.15, 1)
boot_splash/fullsize=false
boot_splash/stretch_mode=0
boot_splash/image="res://assets/logos/splash.png"
config/icon="res://assets/logos/icon.png"
config/macos_native_icon="res://assets/logos/icon.icns"
Expand Down Expand Up @@ -61,10 +65,6 @@ import/fbx2gltf/enabled=false
import/blender/enabled=false
import/fbx/enabled=false

[gui]

timers/tooltip_delay_sec=0.4

[input]

optimize={
Expand Down
10 changes: 10 additions & 0 deletions src/autoload/State.gd
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,16 @@ func respond_to_key_input(path_cmd_char: String) -> void:

# Operations on selected elements.

func set_selected_attribute(attribute_name: String, new_value: String) -> void:
for xid in selected_xids:
if not root_element.get_xnode(xid).is_element():
return

for xid in selected_xids:
var element: Element = root_element.get_xnode(xid)
element.set_attribute(attribute_name, new_value)


func delete_selected() -> void:
if not selected_xids.is_empty():
root_element.delete_xnodes(selected_xids)
Expand Down
17 changes: 11 additions & 6 deletions src/config_classes/SaveData.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class_name SaveData extends ConfigResource

enum ThemePreset {DARK, LIGHT, BLACK}
enum ThemePreset {DARK, LIGHT, BLACK, GRAY}
enum HighlighterPreset {DEFAULT_DARK, DEFAULT_LIGHT}

const GoodColorPicker = preload("res://src/ui_widgets/good_color_picker.gd")
Expand All @@ -20,14 +20,16 @@ func get_setting_default(setting: String) -> Variant:
ThemePreset.DARK: return Color("10101d")
ThemePreset.LIGHT: return Color("e6f0ff")
ThemePreset.BLACK: return Color("000")
ThemePreset.GRAY: return Color("262626")
"accent_color":
match theme_preset:
ThemePreset.DARK: return Color("69f")
ThemePreset.LIGHT: return Color("0053a6")
ThemePreset.BLACK: return Color("7c8dbf")
ThemePreset.GRAY: return Color("80aaff")
"highlighter_preset":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return HighlighterPreset.DEFAULT_DARK
ThemePreset.DARK, ThemePreset.BLACK, ThemePreset.GRAY: return HighlighterPreset.DEFAULT_DARK
ThemePreset.LIGHT: return HighlighterPreset.DEFAULT_LIGHT
"highlighting_symbol_color":
match highlighter_preset:
Expand Down Expand Up @@ -63,22 +65,22 @@ func get_setting_default(setting: String) -> Variant:
HighlighterPreset.DEFAULT_LIGHT: return Color("cc0000")
"basic_color_valid":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return Color("9f9")
ThemePreset.DARK, ThemePreset.BLACK, ThemePreset.GRAY: return Color("9f9")
ThemePreset.LIGHT: return Color("2b2")
"basic_color_error":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return Color("f99")
ThemePreset.DARK, ThemePreset.BLACK, ThemePreset.GRAY: return Color("f99")
ThemePreset.LIGHT: return Color("b22")
"basic_color_warning":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return Color("ee6")
ThemePreset.DARK, ThemePreset.BLACK, ThemePreset.GRAY: return Color("ee6")
ThemePreset.LIGHT: return Color("991")
"handle_size": return 1.0 if OS.get_name() != "Android" else 2.0
"handle_inner_color": return Color("fff")
"handle_color": return Color("111")
"handle_hovered_color":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return Color("aaa")
ThemePreset.DARK, ThemePreset.BLACK, ThemePreset.GRAY: return Color("aaa")
ThemePreset.LIGHT: return Color("808080")
"handle_selected_color": return Color("46f")
"handle_hovered_selected_color": return Color("f44")
Expand All @@ -92,10 +94,12 @@ func get_setting_default(setting: String) -> Variant:
ThemePreset.DARK: return Color("1f2233")
ThemePreset.LIGHT: return Color("fff")
ThemePreset.BLACK: return Color("000")
ThemePreset.GRAY: return Color("404040")
"grid_color":
match theme_preset:
ThemePreset.DARK, ThemePreset.BLACK: return Color("808080")
ThemePreset.LIGHT: return Color("666")
ThemePreset.GRAY: return Color("999")

# Tab bar
"tab_mmb_close": return true
Expand Down Expand Up @@ -165,6 +169,7 @@ static func get_theme_preset_value_text_map() -> Dictionary:
ThemePreset.DARK: Translator.translate("Dark"),
ThemePreset.LIGHT: Translator.translate("Light"),
ThemePreset.BLACK: Translator.translate("Black (OLED)"),
ThemePreset.GRAY: Translator.translate("Gray"),
}

const HIGHLIGHTING_ITEMS: PackedStringArray = [
Expand Down
9 changes: 3 additions & 6 deletions src/data_classes/AttributeNumeric.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func _sync() -> void:

func set_num(new_number: float) -> void:
_number = new_number
super.set_value(num_to_text(new_number))
super.set_value(NumberParser.num_to_text(new_number, Configs.savedata.editor_formatter))

func get_num() -> float:
return _number
Expand All @@ -23,12 +23,9 @@ func is_percentage() -> bool:
func format(text: String, formatter: Formatter) -> String:
var num := text_to_num(text)
if text_check_percentage(text):
return num_to_text(num * 100.0, formatter) + "%"
return NumberParser.num_to_text(num * 100.0, formatter) + "%"
else:
return num_to_text(num, formatter)

func num_to_text(number: float, formatter := Configs.savedata.editor_formatter) -> String:
return NumberParser.num_to_text(number, formatter)
return NumberParser.num_to_text(num, formatter)

static func text_to_num(text: String) -> float:
text = text.strip_edges(false, true)
Expand Down
3 changes: 3 additions & 0 deletions src/data_classes/Element.gd
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ func get_config_warnings() -> PackedStringArray:
{"element": own_name, "allowed": "[%s]" % ", ".join(DB.get_valid_parents(own_name))}))
return warnings

func is_attribute_necessary(attribute_name: String) -> bool:
return get_attribute_value(attribute_name) == get_default(attribute_name)

func user_setup(_what = null) -> void:
return

Expand Down
14 changes: 5 additions & 9 deletions src/ui_parts/inspector.gd
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
extends VTitledPanel

const InvalidSyntaxWarning = preload("res://src/ui_widgets/invalid_syntax_warning.tscn")
const InspectorContent = preload("res://src/ui_parts/inspector_content.tscn")

@onready var xnodes_container: VBoxContainer = %RootChildren
@onready var add_button: Button = $ActionContainer/AddButton
@onready var main_inspector: Control = $MainInspector

var is_unstable := false

Expand Down Expand Up @@ -31,14 +32,9 @@ func sync_localization() -> void:


func full_rebuild() -> void:
for node in xnodes_container.get_children():
node.queue_free()

if is_unstable:
xnodes_container.add_child(InvalidSyntaxWarning.instantiate())
else:
for xnode_editor in XNodeChildrenBuilder.create(State.root_element):
xnodes_container.add_child(xnode_editor)
for child in main_inspector.get_children():
child.queue_free()
main_inspector.add_child((InvalidSyntaxWarning if is_unstable else InspectorContent).instantiate())

func add_element(element_name: String) -> void:
var new_element := DB.element_with_setup(element_name, [])
Expand Down
48 changes: 2 additions & 46 deletions src/ui_parts/inspector.tscn
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
[gd_scene load_steps=6 format=3 uid="uid://ccynisiuyn5qn"]
[gd_scene load_steps=3 format=3 uid="uid://ccynisiuyn5qn"]

[ext_resource type="Script" uid="uid://csl2me44lu3yd" path="res://src/ui_parts/inspector.gd" id="1_16ggy"]
[ext_resource type="PackedScene" uid="uid://bktmk76u7dsu0" path="res://src/ui_parts/root_element_editor.tscn" id="2_jnl50"]
[ext_resource type="Script" uid="uid://b7nxmncbtpjvt" path="res://src/ui_parts/element_container.gd" id="3_qeptj"]
[ext_resource type="Texture2D" uid="uid://eif2ioi0mw17" path="res://assets/icons/Plus.svg" id="3_vo6hf"]
[ext_resource type="Script" uid="uid://b04padjc3w1s8" path="res://src/ui_parts/move_to_overlay.gd" id="5_otlmf"]

[node name="Inspector" type="Container"]
anchors_preset = 9
Expand Down Expand Up @@ -33,48 +30,7 @@ mouse_default_cursor_shape = 2
theme_override_constants/h_separation = 4
icon = ExtResource("3_vo6hf")

[node name="ElementContainer" type="Control" parent="."]
custom_minimum_size = Vector2(0, 240)
layout_mode = 2
size_flags_vertical = 3
script = ExtResource("3_qeptj")

[node name="ScrollContainer" type="ScrollContainer" parent="ElementContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3

[node name="MarginContainer" type="MarginContainer" parent="ElementContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_bottom = 3

[node name="AllElements" type="VBoxContainer" parent="ElementContainer/ScrollContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8

[node name="RootElementEditor" parent="ElementContainer/ScrollContainer/MarginContainer/AllElements" instance=ExtResource("2_jnl50")]
layout_mode = 2

[node name="RootChildren" type="VBoxContainer" parent="ElementContainer/ScrollContainer/MarginContainer/AllElements"]
unique_name_in_owner = true
[node name="MainInspector" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 5

[node name="MoveToOverlay" type="Control" parent="ElementContainer"]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("5_otlmf")
81 changes: 81 additions & 0 deletions src/ui_parts/inspector_content.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
extends VSplitContainer

const ElementButtonScene = preload("res://src/ui_widgets/element_button.tscn")
const AttributeContentScene = preload("res://src/ui_widgets/attribute_content.tscn")

@onready var hierarchy_tree: VBoxContainer = $ScrollContainer/HierarchyTree
@onready var description_panel: PanelContainer = %DescriptionPanel
@onready var attribute_area: PanelContainer = %AttributeArea
@onready var attribute_content: MarginContainer = %AttributeContent

func _ready() -> void:
State.selection_changed.connect(adapt_to_selection)
adapt_to_selection()
Configs.theme_changed.connect(sync_theming)
sync_theming()
hierarchy_tree.gui_input.connect(_on_hierarchy_tree_gui_input)

var hierarchy: Array[XNode] = [State.root_element]
hierarchy += State.root_element.get_all_xnode_descendants()
for xnode in hierarchy:
var margin_container := MarginContainer.new()
margin_container.add_theme_constant_override("margin_left", 12 * xnode.xid.size())
var element_button := ElementButtonScene.instantiate()
element_button.element = xnode
margin_container.add_child(element_button)
hierarchy_tree.add_child(margin_container)

func sync_theming() -> void:
var description_panel_stylebox := get_theme_stylebox("tabbar_background", "TabContainer").duplicate()
description_panel_stylebox.expand_margin_top = 8.0
description_panel.add_theme_stylebox_override("panel", description_panel_stylebox)

func adapt_to_selection() -> void:
for child in attribute_content.get_children():
child.queue_free()

for child in description_panel.get_children():
child.queue_free()

if State.selected_xids.is_empty():
attribute_area.remove_theme_stylebox_override("panel")
return

var sb := attribute_area.get_theme_stylebox("panel").duplicate()
sb.bg_color = ThemeUtils.hover_overlay_color
attribute_area.add_theme_stylebox_override("panel", sb)

if State.selected_xids.size() == 1:
var xnode := State.root_element.get_xnode(State.selected_xids[0])

var center_container := CenterContainer.new()
description_panel.add_child(center_container)
var margin_container := MarginContainer.new()
margin_container.begin_bulk_theme_override()
margin_container.add_theme_constant_override("margin_top", -3)
margin_container.add_theme_constant_override("margin_bottom", 5)
margin_container.end_bulk_theme_override()
center_container.add_child(margin_container)
var hbox_container := HBoxContainer.new()
margin_container.add_child(hbox_container)
var texture_rect := TextureRect.new()
texture_rect.texture = DB.get_element_icon(xnode.name)
texture_rect.modulate = ThemeUtils.tinted_contrast_color
hbox_container.add_child(texture_rect)
var label := Label.new()
label.text = xnode.name
label.begin_bulk_theme_override()
label.add_theme_font_override("font", ThemeUtils.mono_font)
label.add_theme_color_override("font_color", ThemeUtils.tinted_contrast_color)
label.add_theme_constant_override("line_spacing", 0)
label.end_bulk_theme_override()
hbox_container.add_child(label)

attribute_content.add_child(AttributeContentScene.instantiate())
else:
attribute_content.add_child(AttributeContentScene.instantiate())

func _on_hierarchy_tree_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.button_index in [MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE] and event.is_pressed():
State.clear_all_selections()
1 change: 1 addition & 0 deletions src/ui_parts/inspector_content.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://lvhkdqxenafn
Loading