diff --git a/addons/material_maker/examples/bricks.ptex b/addons/material_maker/examples/bricks.ptex
index 98adb62..a8957c6 100644
--- a/addons/material_maker/examples/bricks.ptex
+++ b/addons/material_maker/examples/bricks.ptex
@@ -1 +1 @@
-{"connections":[{"from":"Perlin","from_port":0,"to":"Warp","to_port":1},{"from":"Warp","from_port":0,"to":"colorize_2","to_port":0},{"from":"colorize_2","from_port":0,"to":"blend_0","to_port":2},{"from":"colorize_1","from_port":0,"to":"blend_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"blend_0","to_port":1},{"from":"blend_0","from_port":0,"to":"Material","to_port":0},{"from":"colorize_2","from_port":0,"to":"colorize_4","to_port":0},{"from":"colorize_4","from_port":0,"to":"Material","to_port":2},{"from":"Bricks","from_port":0,"to":"Warp","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_1","to_port":0},{"from":"Bricks","from_port":1,"to":"blend_1","to_port":1},{"from":"Warp","from_port":0,"to":"blend_2","to_port":0},{"from":"Perlin","from_port":0,"to":"blend_2","to_port":1},{"from":"blend_2","from_port":0,"to":"colorize_3","to_port":0},{"from":"blend_2","from_port":0,"to":"colorize_6","to_port":0},{"from":"Perlin","from_port":0,"to":"blend_1","to_port":0},{"from":"Perlin","from_port":0,"to":"colorize_0","to_port":0},{"from":"uniform_0","from_port":0,"to":"Material","to_port":1},{"from":"colorize_3","from_port":0,"to":"Material","to_port":5},{"from":"uniform_0","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_4","from_port":0,"to":"combine_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"colorize_6","from_port":0,"to":"normal_map_0","to_port":0}],"nodes":[{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_0","node_position":{"x":560.943665,"y":50},"type":"colorize"},{"gradient":[{"b":0.0016,"g":0.0016,"pos":0,"r":0.307292},{"b":0,"g":0.180135,"pos":0.2,"r":0.606771},{"b":0,"g":0,"pos":0.345455,"r":0.3125},{"b":0,"g":0.19869,"pos":0.545455,"r":0.669271},{"b":0.019368,"g":0.060224,"pos":0.745455,"r":0.309896},{"b":0,"g":0.180135,"pos":1,"r":0.606771}],"name":"colorize_1","node_position":{"x":562.943665,"y":-65},"type":"colorize"},{"amount":0.04,"name":"Warp","node_position":{"x":384,"y":10.75},"type":"warp"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":0,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":1081,"y":208},"normal_scale":1,"resolution":1,"roughness":1,"type":"material"},{"amount":0.5,"blend_type":0,"name":"blend_2","node_position":{"x":536,"y":331},"type":"blend"},{"amount":0.5,"blend_type":0,"name":"blend_0","node_position":{"x":836.943726,"y":-71},"type":"blend"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":0.1,"r":1}],"name":"colorize_2","node_position":{"x":544.943665,"y":159},"type":"colorize"},{"amount":0.5,"blend_type":6,"name":"blend_1","node_position":{"x":349,"y":215},"type":"blend"},{"color":{"a":1,"b":0,"g":0,"r":0,"type":"Color"},"name":"uniform_0","node_position":{"x":764,"y":164},"type":"uniform"},{"name":"combine_0","node_position":{"x":896,"y":86},"type":"combine"},{"name":"export_0","node_position":{"x":1050,"y":79},"resolution":1,"suffix":"mr","type":"export"},{"bevel":0.2,"columns":3,"mortar":0.05,"name":"Bricks","node_position":{"x":69,"y":-46},"pattern":0,"repeat":1,"row_offset":0.5,"rows":6,"type":"bricks"},{"iterations":6,"name":"Perlin","node_position":{"x":70,"y":192},"persistence":0.85,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":1,"g":1,"pos":0,"r":1},{"b":0.546875,"g":0.546875,"pos":1,"r":0.546875}],"name":"colorize_4","node_position":{"x":711,"y":215},"type":"colorize"},{"gradient":[{"b":1,"g":1,"pos":0,"r":1},{"b":0,"g":0,"pos":1,"r":0}],"name":"colorize_6","node_position":{"x":708,"y":289},"type":"colorize"},{"gradient":[{"b":0.838542,"g":0.838542,"pos":0.190909,"r":0.838542},{"b":1,"g":1,"pos":0.536364,"r":1}],"name":"colorize_3","node_position":{"x":711,"y":361},"type":"colorize"},{"amount":0.35,"name":"normal_map_0","node_position":{"x":881.045654,"y":309.691162},"size":4,"type":"normal_map"}]}
\ No newline at end of file
+{"connections":[{"from":"Perlin","from_port":0,"to":"Warp","to_port":1},{"from":"Warp","from_port":0,"to":"colorize_2","to_port":0},{"from":"colorize_2","from_port":0,"to":"blend_0","to_port":2},{"from":"colorize_0","from_port":0,"to":"blend_0","to_port":1},{"from":"blend_0","from_port":0,"to":"Material","to_port":0},{"from":"colorize_2","from_port":0,"to":"colorize_4","to_port":0},{"from":"colorize_4","from_port":0,"to":"Material","to_port":2},{"from":"Bricks","from_port":0,"to":"Warp","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_1","to_port":0},{"from":"Bricks","from_port":1,"to":"blend_1","to_port":1},{"from":"Warp","from_port":0,"to":"blend_2","to_port":0},{"from":"Perlin","from_port":0,"to":"blend_2","to_port":1},{"from":"blend_2","from_port":0,"to":"colorize_3","to_port":0},{"from":"blend_2","from_port":0,"to":"colorize_6","to_port":0},{"from":"Perlin","from_port":0,"to":"blend_1","to_port":0},{"from":"Perlin","from_port":0,"to":"colorize_0","to_port":0},{"from":"uniform_0","from_port":0,"to":"Material","to_port":1},{"from":"colorize_3","from_port":0,"to":"Material","to_port":5},{"from":"uniform_0","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_4","from_port":0,"to":"combine_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"colorize_6","from_port":0,"to":"normal_map_0","to_port":0},{"from":"colorize_1","from_port":0,"to":"adjust_hsv_0","to_port":0},{"from":"adjust_hsv_0","from_port":0,"to":"blend_0","to_port":0}],"nodes":[{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_0","node_position":{"x":560.943665,"y":50},"type":"colorize"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":0,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":1081,"y":208},"normal_scale":1,"resolution":1,"roughness":1,"type":"material"},{"amount":0.5,"blend_type":0,"name":"blend_2","node_position":{"x":536,"y":331},"type":"blend"},{"amount":0.5,"blend_type":0,"name":"blend_0","node_position":{"x":836.943726,"y":-71},"type":"blend"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":0.1,"r":1}],"name":"colorize_2","node_position":{"x":544.943665,"y":159},"type":"colorize"},{"color":{"a":1,"b":0,"g":0,"r":0,"type":"Color"},"name":"uniform_0","node_position":{"x":764,"y":164},"type":"uniform"},{"name":"combine_0","node_position":{"x":896,"y":86},"type":"combine"},{"name":"export_0","node_position":{"x":1050,"y":79},"resolution":1,"suffix":"mr","type":"export"},{"iterations":6,"name":"Perlin","node_position":{"x":70,"y":192},"persistence":0.85,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":1,"g":1,"pos":0,"r":1},{"b":0.546875,"g":0.546875,"pos":1,"r":0.546875}],"name":"colorize_4","node_position":{"x":711,"y":215},"type":"colorize"},{"gradient":[{"b":1,"g":1,"pos":0,"r":1},{"b":0,"g":0,"pos":1,"r":0}],"name":"colorize_6","node_position":{"x":708,"y":289},"type":"colorize"},{"gradient":[{"b":0.838542,"g":0.838542,"pos":0.145455,"r":0.838542},{"b":1,"g":1,"pos":0.536364,"r":1}],"name":"colorize_3","node_position":{"x":711,"y":361},"type":"colorize"},{"amount":0.35,"name":"normal_map_0","node_position":{"x":881.045654,"y":309.691162},"size":4,"type":"normal_map"},{"amount":0.04,"name":"Warp","node_position":{"x":306,"y":55.75},"type":"warp"},{"hue":0,"name":"adjust_hsv_0","node_position":{"x":583.224792,"y":-74.268188},"saturation":1,"type":"adjust_hsv","value":1},{"gradient":[{"b":0.0016,"g":0.0016,"pos":0,"r":0.307292},{"b":0,"g":0.180135,"pos":0.2,"r":0.606771},{"b":0,"g":0,"pos":0.345455,"r":0.3125},{"b":0,"g":0.19869,"pos":0.545455,"r":0.669271},{"b":0.019368,"g":0.060224,"pos":0.745455,"r":0.309896},{"b":0,"g":0.180135,"pos":1,"r":0.606771}],"name":"colorize_1","node_position":{"x":406.943665,"y":-80},"type":"colorize"},{"bevel":0.2,"columns":4,"mortar":0.05,"name":"Bricks","node_position":{"x":79,"y":-36},"pattern":0,"repeat":1,"row_offset":0.5,"rows":8,"type":"bricks"},{"amount":0.5,"blend_type":6,"name":"blend_1","node_position":{"x":316,"y":217},"type":"blend"},{"editable":true,"node_position":{"x":496.669189,"y":-340.657043},"type":"remote","widgets":[{"configurations":{"Basket weave":[{"node":"Bricks","value":3,"widget":"pattern"},{"node":"Bricks","value":2,"widget":"repeat"},{"node":"Bricks","value":2,"widget":"rows"},{"node":"Bricks","value":2,"widget":"columns"}],"Herring bone":[{"node":"Bricks","value":2,"widget":"pattern"},{"node":"Bricks","value":2,"widget":"repeat"},{"node":"Bricks","value":2,"widget":"rows"},{"node":"Bricks","value":2,"widget":"columns"}],"Running bond 1":[{"node":"Bricks","value":0,"widget":"pattern"},{"node":"Bricks","value":1,"widget":"repeat"},{"node":"Bricks","value":8,"widget":"rows"},{"node":"Bricks","value":4,"widget":"columns"}],"Running bond 2":[{"node":"Bricks","value":1,"widget":"pattern"},{"node":"Bricks","value":1,"widget":"repeat"},{"node":"Bricks","value":8,"widget":"rows"},{"node":"Bricks","value":4,"widget":"columns"}],"Spanish bond":[{"node":"Bricks","value":4,"widget":"pattern"},{"node":"Bricks","value":3,"widget":"repeat"},{"node":"Bricks","value":2,"widget":"rows"},{"node":"Bricks","value":2,"widget":"columns"}]},"linked_widgets":[{"node":"Bricks","widget":"pattern"},{"node":"Bricks","widget":"repeat"},{"node":"Bricks","widget":"rows"},{"node":"Bricks","widget":"columns"}],"type":"config_control"},{"linked_widgets":[{"node":"adjust_hsv_0","widget":"hue"}],"type":"linked_control"},{"linked_widgets":[{"node":"adjust_hsv_0","widget":"saturation"}],"type":"linked_control"},{"linked_widgets":[{"node":"adjust_hsv_0","widget":"value"}],"type":"linked_control"}]}]}
\ No newline at end of file
diff --git a/addons/material_maker/examples/mosaic.ptex b/addons/material_maker/examples/mosaic.ptex
new file mode 100644
index 0000000..b307d96
--- /dev/null
+++ b/addons/material_maker/examples/mosaic.ptex
@@ -0,0 +1 @@
+{"connections":[{"from":"blend_0","from_port":0,"to":"Material","to_port":0},{"from":"voronoi_0","from_port":2,"to":"blend_0","to_port":1},{"from":"voronoi_0","from_port":1,"to":"colorize_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"blend_0","to_port":0},{"from":"voronoi_0","from_port":1,"to":"colorize_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"normal_map_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"uniform_0","from_port":0,"to":"Material","to_port":1},{"from":"voronoi_0","from_port":1,"to":"colorize_2","to_port":0},{"from":"colorize_2","from_port":0,"to":"Material","to_port":2}],"nodes":[{"intensity":1,"name":"voronoi_0","node_position":{"x":-543.5,"y":-11.5},"scale_x":32,"scale_y":32,"type":"voronoi"},{"amount":0.5,"name":"normal_map_0","node_position":{"x":-165.5,"y":133.5},"size":5,"type":"normal_map"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":1,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":30,"y":-44},"normal_scale":1,"resolution":1,"roughness":1,"type":"material"},{"gradient":[{"b":1,"g":1,"pos":0.045455,"r":1},{"b":0,"g":0,"pos":0.109091,"r":0}],"name":"colorize_1","node_position":{"x":-349.5,"y":138.5},"type":"colorize"},{"gradient":[{"b":1,"g":1,"pos":0.063636,"r":1},{"b":0,"g":0,"pos":0.072727,"r":0}],"name":"colorize_0","node_position":{"x":-317.5,"y":-169.5},"type":"colorize"},{"color":{"a":1,"b":0.109375,"g":0.109375,"r":0.109375,"type":"Color"},"name":"uniform_0","node_position":{"x":-127.5,"y":-3.5},"type":"uniform"},{"gradient":[{"b":1,"g":1,"pos":0,"r":1},{"b":0.109375,"g":0.109375,"pos":0.236364,"r":0.109375}],"name":"colorize_2","node_position":{"x":-306.5,"y":39.5},"type":"colorize"},{"amount":1,"blend_type":3,"name":"blend_0","node_position":{"x":-294.5,"y":-94.5},"type":"blend"}]}
\ No newline at end of file
diff --git a/addons/material_maker/icons/add.png b/addons/material_maker/icons/add.png
new file mode 100644
index 0000000..c4b0c58
Binary files /dev/null and b/addons/material_maker/icons/add.png differ
diff --git a/addons/material_maker/icons/add_config.png b/addons/material_maker/icons/add_config.png
new file mode 100644
index 0000000..358c235
Binary files /dev/null and b/addons/material_maker/icons/add_config.png differ
diff --git a/addons/material_maker/icons/add_link.png b/addons/material_maker/icons/add_link.png
new file mode 100644
index 0000000..6555533
Binary files /dev/null and b/addons/material_maker/icons/add_link.png differ
diff --git a/addons/material_maker/icons/config.png b/addons/material_maker/icons/config.png
new file mode 100644
index 0000000..c565d96
Binary files /dev/null and b/addons/material_maker/icons/config.png differ
diff --git a/addons/material_maker/icons/icons.svg b/addons/material_maker/icons/icons.svg
new file mode 100644
index 0000000..4a56982
--- /dev/null
+++ b/addons/material_maker/icons/icons.svg
@@ -0,0 +1,121 @@
+
+
+
+
diff --git a/addons/material_maker/icons/link.png b/addons/material_maker/icons/link.png
new file mode 100644
index 0000000..062da20
Binary files /dev/null and b/addons/material_maker/icons/link.png differ
diff --git a/addons/material_maker/icons/remove.png b/addons/material_maker/icons/remove.png
new file mode 100644
index 0000000..8fceffe
Binary files /dev/null and b/addons/material_maker/icons/remove.png differ
diff --git a/addons/material_maker/library/base.json b/addons/material_maker/library/base.json
index 2deb903..e55df49 100644
--- a/addons/material_maker/library/base.json
+++ b/addons/material_maker/library/base.json
@@ -197,5 +197,9 @@
{
"tree_item":"Miscellaneous/Export",
"type":"export"
+ },
+ {
+ "tree_item":"Miscellaneous/Remote",
+ "type":"remote"
}
]}
\ No newline at end of file
diff --git a/addons/material_maker/main_window.tscn b/addons/material_maker/main_window.tscn
index 5ae8cf6..43850a8 100644
--- a/addons/material_maker/main_window.tscn
+++ b/addons/material_maker/main_window.tscn
@@ -6,7 +6,7 @@
[ext_resource path="res://addons/material_maker/widgets/tabs.gd" type="Script" id=4]
[ext_resource path="res://addons/material_maker/renderer.tscn" type="PackedScene" id=5]
-[node name="MainWindow" type="Panel" index="0"]
+[node name="MainWindow" type="Panel"]
anchor_left = 0.0
anchor_top = 0.0
diff --git a/addons/material_maker/nodes/remote.gd b/addons/material_maker/nodes/remote.gd
new file mode 100644
index 0000000..0808b6f
--- /dev/null
+++ b/addons/material_maker/nodes/remote.gd
@@ -0,0 +1,63 @@
+tool
+extends "res://addons/material_maker/node_base.gd"
+
+const LinkedControl = preload("res://addons/material_maker/widgets/linked_widgets/linked_control.tscn")
+const ConfigControl = preload("res://addons/material_maker/widgets/linked_widgets/config_control.tscn")
+
+func _ready():
+ pass
+
+func _get_shader_code(uv):
+ var rv = { defs="", code="" }
+ rv.rgb = "vec3(1.0)"
+ return rv
+
+func add_control(widget):
+ var controls = widget.get_associated_controls()
+ $Controls.add_child(controls.label)
+ $Controls.add_child(widget)
+ $Controls.add_child(controls.buttons)
+
+
+func _on_AddLink_pressed():
+ var widget = LinkedControl.instance()
+ add_control(widget)
+ widget.pick_linked()
+
+func _on_AddConfig_pressed():
+ var widget = ConfigControl.instance()
+ add_control(widget)
+ widget.pick_linked()
+
+func _on_Remote_resize_request(new_minsize):
+ print("_on_Remote_resize_request")
+ rect_size = new_minsize
+
+func _on_HBoxContainer_minimum_size_changed():
+ print("_on_HBoxContainer_minimum_size_changed "+str($HBoxContainer.rect_min_size))
+
+func serialize():
+ var widgets = []
+ for i in range(1, $Controls.get_child_count(), 3):
+ widgets.append($Controls.get_child(i).serialize())
+ var data = { type="remote", node_position={x=offset.x,y=offset.y}, editable=true, widgets=widgets }
+ return data
+
+func deserialize(data):
+ if data.has("node_position"):
+ offset.x = data.node_position.x
+ offset.y = data.node_position.y
+ call_deferred("do_deserialize", data)
+
+func do_deserialize(data):
+ if data.has("widgets"):
+ for w in data.widgets:
+ var widget
+ if w.type == "linked_control":
+ widget = LinkedControl.instance()
+ elif w.type == "config_control":
+ widget = ConfigControl.instance()
+ else:
+ continue
+ add_control(widget)
+ widget.deserialize(w)
diff --git a/addons/material_maker/nodes/remote.tscn b/addons/material_maker/nodes/remote.tscn
new file mode 100644
index 0000000..e19728c
--- /dev/null
+++ b/addons/material_maker/nodes/remote.tscn
@@ -0,0 +1,139 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://addons/material_maker/nodes/remote.gd" type="Script" id=1]
+[ext_resource path="res://addons/material_maker/icons/link.png" type="Texture" id=2]
+[ext_resource path="res://addons/material_maker/icons/config.png" type="Texture" id=3]
+
+[sub_resource type="Theme" id=1]
+
+
+[node name="Remote" type="GraphNode"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 95.0
+margin_bottom = 55.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+theme = SubResource( 1 )
+title = "Remote"
+offset = Vector2( 0, 0 )
+show_close = true
+resizable = false
+selected = false
+comment = false
+overlay = 0
+slot/0/left_enabled = false
+slot/0/left_type = 0
+slot/0/left_color = Color( 0.5, 0.5, 1, 1 )
+slot/0/right_enabled = false
+slot/0/right_type = 0
+slot/0/right_color = Color( 0.5, 0.5, 1, 1 )
+slot/1/left_enabled = false
+slot/1/left_type = 0
+slot/1/left_color = Color( 1, 1, 1, 1 )
+slot/1/right_enabled = false
+slot/1/right_type = 0
+slot/1/right_color = Color( 1, 1, 1, 1 )
+script = ExtResource( 1 )
+_sections_unfolded = [ "Theme", "slot" ]
+
+[node name="Controls" type="GridContainer" parent="." index="0"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 16.0
+margin_top = 24.0
+margin_right = 79.0
+margin_bottom = 24.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+columns = 3
+_sections_unfolded = [ "Hint" ]
+
+[node name="HBoxContainer" type="HBoxContainer" parent="." index="1"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 16.0
+margin_top = 24.0
+margin_right = 79.0
+margin_bottom = 46.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+alignment = 0
+
+[node name="AddLink" type="Button" parent="HBoxContainer" index="0"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 28.0
+margin_bottom = 22.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+hint_tooltip = "Add linked control"
+focus_mode = 2
+mouse_filter = 0
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 0
+size_flags_vertical = 1
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+group = null
+icon = ExtResource( 2 )
+flat = false
+align = 1
+_sections_unfolded = [ "Hint", "Rect", "Size Flags" ]
+
+[node name="AddConfig" type="Button" parent="HBoxContainer" index="1"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 32.0
+margin_right = 60.0
+margin_bottom = 22.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+hint_tooltip = "Add configurations"
+focus_mode = 2
+mouse_filter = 0
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+group = null
+icon = ExtResource( 3 )
+flat = false
+align = 1
+_sections_unfolded = [ "Hint" ]
+
+[connection signal="pressed" from="HBoxContainer/AddLink" to="." method="_on_AddLink_pressed"]
+
+[connection signal="pressed" from="HBoxContainer/AddConfig" to="." method="_on_AddConfig_pressed"]
+
+
diff --git a/addons/material_maker/widgets/line_dialog.gd b/addons/material_maker/widgets/line_dialog.gd
index bebcb6e..add7dc0 100644
--- a/addons/material_maker/widgets/line_dialog.gd
+++ b/addons/material_maker/widgets/line_dialog.gd
@@ -6,6 +6,15 @@ signal ok
func _ready():
pass
+func set_texts(title, label):
+ window_title = title
+ $VBoxContainer/Label.text = label
+ $VBoxContainer/LineEdit.grab_focus()
+ $VBoxContainer/LineEdit.grab_click_focus()
+
func _on_OK_pressed():
- emit_signal("ok", $VBoxContainer/LineEdit.text)
+ _on_LineEdit_text_entered($VBoxContainer/LineEdit.text)
+
+func _on_LineEdit_text_entered(new_text):
+ emit_signal("ok", new_text)
queue_free()
diff --git a/addons/material_maker/widgets/line_dialog.tscn b/addons/material_maker/widgets/line_dialog.tscn
index d0573c1..d008b5d 100644
--- a/addons/material_maker/widgets/line_dialog.tscn
+++ b/addons/material_maker/widgets/line_dialog.tscn
@@ -146,6 +146,8 @@ flat = false
align = 1
_sections_unfolded = [ "Rect" ]
+[connection signal="text_entered" from="VBoxContainer/LineEdit" to="." method="_on_LineEdit_text_entered"]
+
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OK" to="." method="_on_OK_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Cancel" to="." method="queue_free"]
diff --git a/addons/material_maker/widgets/linked_widgets/config_control.gd b/addons/material_maker/widgets/linked_widgets/config_control.gd
new file mode 100644
index 0000000..dd9562c
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/config_control.gd
@@ -0,0 +1,121 @@
+tool
+extends "res://addons/material_maker/widgets/linked_widgets/linked_control_base.gd"
+
+var configurations = {}
+
+var current = null
+onready var button = null
+
+func _ready():
+ update_options()
+
+func update_options():
+ # Seems "clear" might cause crashes, so we create a new button...
+ if button != null:
+ button.hide()
+ button.queue_free()
+ button = OptionButton.new()
+ button.connect("item_selected", self, "_on_item_selected")
+ button.connect("mouse_entered", self, "_on_mouse_entered")
+ button.connect("mouse_exited", self, "_on_mouse_exited")
+ add_child(button)
+ # Create list of configurations
+ var keys = configurations.keys()
+ keys.sort()
+ # if no configuration is selected, select the first one
+ if current == null and !keys.empty():
+ current = keys[0]
+ for c in keys:
+ button.add_item(c)
+ button.add_separator()
+ button.add_item("")
+ if current != null:
+ button.add_separator()
+ button.add_item("")
+ button.add_item("")
+ for i in range(button.get_item_count()):
+ if button.get_item_text(i) == current:
+ button.selected = i
+ break
+
+func add_linked(node, widget):
+ linked_widgets.append({ node=node, widget=widget })
+
+func apply_configuration(c):
+ for w in configurations[c]:
+ w.widget.set(WIDGETS[get_widget_type(w.widget)].value_attr , w.value)
+ w.node.set(w.widget.name, w.value)
+ var graph_node = get_parent()
+ while !(graph_node is GraphNode):
+ graph_node = graph_node.get_parent()
+ graph_node.update_shaders()
+
+func do_update_configuration(name):
+ var configuration = []
+ for w in linked_widgets:
+ configuration.append({ node=w.node, widget=w.widget, value=w.node.get(w.widget.name) })
+ configurations[name] = configuration
+ current = name
+ update_options()
+
+func update_configuration():
+ var dialog = preload("res://addons/material_maker/widgets/line_dialog.tscn").instance()
+ add_child(dialog)
+ dialog.set_texts("Configuration", "Enter a name for the new configuration")
+ dialog.connect("ok", self, "do_update_configuration", [])
+ dialog.popup_centered()
+
+func _on_item_selected(ID):
+ var count = configurations.keys().size()
+ if ID >= 0 && ID < count:
+ current = button.get_item_text(ID)
+ update_options()
+ apply_configuration(current)
+ elif ID == count+1:
+ button.selected = 0
+ update_configuration()
+ elif ID == count+3:
+ do_update_configuration(current)
+ else:
+ configurations.erase(current)
+ current = null
+ update_options()
+
+func serialize():
+ var data = .serialize()
+ data.type = "config_control"
+ var data_configurations = {}
+ var keys = configurations.keys()
+ for k in keys:
+ var c = configurations[k]
+ var data_configuration = []
+ for e in c:
+ data_configuration.append({ node=e.node.name, widget=e.widget.name, value=e.value })
+ data_configurations[k] = data_configuration
+ data.configurations = data_configurations
+ return data
+
+func deserialize(data):
+ .deserialize(data)
+ graph_edit = get_parent()
+ while graph_edit != null && !(graph_edit is GraphEdit):
+ graph_edit = graph_edit.get_parent()
+ if graph_edit == null:
+ return
+ var keys = data.configurations.keys()
+ for k in keys:
+ var c = data.configurations[k]
+ var configuration = []
+ for e in c:
+ var node = graph_edit.get_node(e.node)
+ var widget = null
+ for w in node.property_widgets:
+ if w.name == e.widget:
+ widget = w
+ break
+ configuration.append({ node=node, widget=widget, value=e.value })
+ configurations[k] = configuration
+ update_options()
+
+
+
diff --git a/addons/material_maker/widgets/linked_widgets/config_control.tscn b/addons/material_maker/widgets/linked_widgets/config_control.tscn
new file mode 100644
index 0000000..df482b1
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/config_control.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/material_maker/widgets/linked_widgets/config_control.gd" type="Script" id=1]
+
+[node name="ConfigControl" type="VBoxContainer"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+rect_min_size = Vector2( 70, 0 )
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 3
+size_flags_vertical = 1
+alignment = 0
+script = ExtResource( 1 )
+_sections_unfolded = [ "Rect", "Size Flags" ]
+
+
diff --git a/addons/material_maker/widgets/linked_widgets/link.gd b/addons/material_maker/widgets/linked_widgets/link.gd
new file mode 100644
index 0000000..18e5a3e
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/link.gd
@@ -0,0 +1,22 @@
+extends Node2D
+
+var end
+var source = null
+var target = null
+
+func closest(rect, point):
+ return Vector2(max(rect.position.x, min(rect.end.x, point.x)), max(rect.position.y, min(rect.end.y, point.y)))
+
+func _draw():
+ var start = source.rect_global_position+0.5*source.rect_size*source.get_global_transform().get_scale()
+ var color = Color(1, 0.5, 0.5, 0.5)
+ var rect
+ if target != null:
+ color = Color(0.5, 1, 0.5, 0.5)
+ rect = Rect2(target.rect_global_position, target.rect_size*target.get_global_transform().get_scale())
+ draw_rect(rect, color, false)
+ end = closest(rect, start)
+ rect = Rect2(source.rect_global_position, source.rect_size*source.get_global_transform().get_scale())
+ draw_rect(rect, color, false)
+ start = closest(rect, end)
+ draw_line(start, end, color, 1, true)
diff --git a/addons/material_maker/widgets/linked_widgets/linked_control.gd b/addons/material_maker/widgets/linked_widgets/linked_control.gd
new file mode 100644
index 0000000..852a49a
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/linked_control.gd
@@ -0,0 +1,52 @@
+tool
+extends "res://addons/material_maker/widgets/linked_widgets/linked_control_base.gd"
+
+func add_linked(node, widget):
+ if linked_widgets.empty():
+ var new_widget = null
+ var type
+ if widget is SpinBox:
+ new_widget = SpinBox.new()
+ type = "SpinBox"
+ elif widget is ColorPickerButton:
+ new_widget = ColorPickerButton.new()
+ type = "ColorPickerButton"
+ elif widget is HSlider:
+ new_widget = HSlider.new()
+ type = "HSlider"
+ elif widget is OptionButton:
+ new_widget = OptionButton.new()
+ type = "OptionButton"
+ for i in range(widget.get_item_count()):
+ new_widget.add_item(widget.get_item_text(i), widget.get_item_id(i))
+ if new_widget != null:
+ add_child(new_widget)
+ mirror(new_widget, widget, type)
+ new_widget.connect("mouse_entered", self, "_on_mouse_entered")
+ new_widget.connect("mouse_exited", self, "_on_mouse_exited")
+ new_widget.connect(WIDGETS[type].sig, self, WIDGETS[type].sig_handler)
+ linked_widgets.append({ node=node, widget=widget })
+
+func mirror(to, from, type):
+ for a in WIDGETS[type].attrs:
+ to.set(a, from.get(a))
+
+func _on_value_changed(v):
+ for l in linked_widgets:
+ l.widget.value = v
+ l.node.set(l.widget.name, v)
+
+func _on_color_changed(c):
+ for l in linked_widgets:
+ l.widget.color = c
+ l.node.set(l.widget.name, c)
+
+func _on_item_selected(i):
+ for l in linked_widgets:
+ l.widget.selected = i
+ l.node.set(l.widget.name, i)
+
+func serialize():
+ var data = .serialize()
+ data.type = "linked_control"
+ return data
diff --git a/addons/material_maker/widgets/linked_widgets/linked_control.tscn b/addons/material_maker/widgets/linked_widgets/linked_control.tscn
new file mode 100644
index 0000000..c55268c
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/linked_control.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/material_maker/widgets/linked_widgets/linked_control.gd" type="Script" id=1]
+
+[node name="LinkedControl" type="VBoxContainer"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+rect_min_size = Vector2( 70, 0 )
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 3
+size_flags_vertical = 1
+alignment = 0
+script = ExtResource( 1 )
+_sections_unfolded = [ "Rect", "Size Flags" ]
+
+
diff --git a/addons/material_maker/widgets/linked_widgets/linked_control_base.gd b/addons/material_maker/widgets/linked_widgets/linked_control_base.gd
new file mode 100644
index 0000000..827dca8
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/linked_control_base.gd
@@ -0,0 +1,134 @@
+tool
+extends VBoxContainer
+
+var label = null
+var buttons = null
+
+var linked_widgets = []
+var links = null
+
+const Link = preload("res://addons/material_maker/widgets/linked_widgets/link.gd")
+
+const WIDGETS = {
+ SpinBox={ attrs=[ "min_value", "max_value", "step", "value" ], value_attr="value", sig="value_changed", sig_handler="_on_value_changed" },
+ HSlider={ attrs=[ "min_value", "max_value", "step", "value" ], value_attr="value", sig="value_changed", sig_handler="_on_value_changed" },
+ ColorPickerButton={ attrs=[ "edit_alpha", "color" ], value_attr="color", sig="color_changed", sig_handler="_on_color_changed" },
+ OptionButton={ attrs=[ "selected" ], value_attr="selected", sig="item_selected", sig_handler="_on_item_selected" }
+}
+
+func get_widget_type(widget):
+ if widget is SpinBox:
+ return "SpinBox"
+ elif widget is ColorPickerButton:
+ return "ColorPickerButton"
+ elif widget is OptionButton:
+ return "OptionButton"
+ else:
+ return null
+
+func _ready():
+ set_process_input(false)
+
+func get_associated_controls():
+ label = Label.new()
+ label.set_text("Unnamed")
+ buttons = preload("res://addons/material_maker/widgets/linked_widgets/linked_control_buttons.tscn").instance()
+ buttons.control = self
+ return { label=label, buttons=buttons }
+
+func delete():
+ var parent = get_parent()
+ label.queue_free()
+ buttons.queue_free()
+ queue_free()
+
+func _on_mouse_entered():
+ _on_mouse_exited()
+ links = []
+ var viewport = get_viewport()
+ for w in linked_widgets:
+ var link = Link.new()
+ link.source = self
+ link.target = w.widget
+ viewport.add_child(link)
+ links.append(link)
+
+func _on_mouse_exited():
+ if links != null:
+ for l in links:
+ l.queue_free()
+ links = null
+
+# Capture mouse to link to a control
+
+var link = null
+var graph_edit = null
+var pointed_control = null
+
+const Link = preload("res://addons/material_maker/widgets/linked_widgets/link.gd")
+
+func find_control(gp):
+ for c in graph_edit.get_children():
+ if c is GraphNode:
+ if c.get("property_widgets") != null:
+ for w in c.property_widgets:
+ if Rect2(w.rect_global_position, w.rect_size*w.get_global_transform().get_scale()).has_point(gp):
+ return { node=c, widget=w }
+ return null
+
+func _input(event):
+ if event is InputEventKey:
+ if event.scancode == KEY_ESCAPE:
+ set_process_input(false)
+ link.queue_free()
+ link = null
+ elif event is InputEventMouseMotion:
+ var control = find_control(event.global_position)
+ link.end = event.global_position
+ link.target = control.widget if control != null else null
+ link.update()
+ elif event is InputEventMouseButton:
+ if event.pressed:
+ if event.button_index == BUTTON_LEFT:
+ var control = find_control(event.global_position)
+ if control != null:
+ add_linked(control.node, control.widget)
+ set_process_input(false)
+ link.queue_free()
+ link = null
+ get_tree().set_input_as_handled()
+
+func pick_linked():
+ # Verify we are in a graph edit
+ graph_edit = get_parent()
+ while graph_edit != null && !(graph_edit is GraphEdit):
+ graph_edit = graph_edit.get_parent()
+ if graph_edit == null:
+ return
+ # Create line that will be shown when looking for a target
+ var viewport = get_viewport()
+ link = Link.new()
+ link.source = self
+ link.end = rect_global_position+0.5*rect_size*get_global_transform().get_scale()
+ viewport.add_child(link)
+ set_process_input(true)
+ pointed_control = null
+
+func serialize():
+ var data = { linked_widgets=[] }
+ for w in linked_widgets:
+ data.linked_widgets.append( { node=w.node.name, widget=w.widget.name } )
+ return data
+
+func deserialize(data):
+ if !data.has("linked_widgets"):
+ return
+ var graph_edit = get_parent()
+ while !(graph_edit is GraphEdit):
+ graph_edit = graph_edit.get_parent()
+ for w in data.linked_widgets:
+ var node = graph_edit.get_node(w.node)
+ if node != null && node.get("property_widgets") != null:
+ for widget in node.property_widgets:
+ if widget.name == w.widget:
+ add_linked(node, widget)
diff --git a/addons/material_maker/widgets/linked_widgets/linked_control_buttons.gd b/addons/material_maker/widgets/linked_widgets/linked_control_buttons.gd
new file mode 100644
index 0000000..d4fcfdc
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/linked_control_buttons.gd
@@ -0,0 +1,15 @@
+tool
+extends HBoxContainer
+
+var control = null
+
+func _ready():
+ # Called when the node is added to the scene for the first time.
+ # Initialization here
+ pass
+
+func _on_Link_pressed():
+ control.pick_linked()
+
+func _on_Remove_pressed():
+ control.delete()
diff --git a/addons/material_maker/widgets/linked_widgets/linked_control_buttons.tscn b/addons/material_maker/widgets/linked_widgets/linked_control_buttons.tscn
new file mode 100644
index 0000000..df95de9
--- /dev/null
+++ b/addons/material_maker/widgets/linked_widgets/linked_control_buttons.tscn
@@ -0,0 +1,75 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/material_maker/widgets/linked_widgets/linked_control_buttons.gd" type="Script" id=1]
+[ext_resource path="res://addons/material_maker/icons/link.png" type="Texture" id=2]
+[ext_resource path="res://addons/material_maker/icons/remove.png" type="Texture" id=3]
+
+[node name="Buttons" type="HBoxContainer"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 60.0
+margin_bottom = 22.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+alignment = 0
+script = ExtResource( 1 )
+
+[node name="Link" type="Button" parent="." index="0"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 28.0
+margin_bottom = 22.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+focus_mode = 2
+mouse_filter = 0
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+group = null
+icon = ExtResource( 2 )
+flat = false
+align = 1
+
+[node name="Remove" type="Button" parent="." index="1"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 32.0
+margin_right = 60.0
+margin_bottom = 22.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+focus_mode = 2
+mouse_filter = 0
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+toggle_mode = false
+enabled_focus_mode = 2
+shortcut = null
+group = null
+icon = ExtResource( 3 )
+flat = false
+align = 1
+
+[connection signal="pressed" from="Link" to="." method="_on_Link_pressed"]
+
+[connection signal="pressed" from="Remove" to="." method="_on_Remove_pressed"]
+
+