mat_maker_gd/material_maker/nodes/generic/generic.gd

466 lines
16 KiB
GDScript

extends MMGraphNodeBase
class_name MMGraphNodeGeneric
var controls = {}
var ignore_parameter_change = ""
var output_count = 0
var preview : ColorRect
var preview_position : int
var preview_timer : Timer = Timer.new()
func _draw() -> void:
._draw()
if generator != null and generator.preview >= 0:
var conn_pos = get_connection_output_position(generator.preview)
conn_pos /= get_global_transform().get_scale()
draw_texture(preload("res://material_maker/icons/output_preview.tres"), conn_pos-Vector2(8, 8), get_color("title_color"))
func set_generator(g : MMGenBase) -> void:
.set_generator(g)
generator.connect("parameter_changed", self, "on_parameter_changed")
update_node()
func on_parameter_changed(p : String, v) -> void:
if ignore_parameter_change == p:
return
if p == "__update_all__":
update_node()
elif controls.has(p):
var o = controls[p]
if o is Control and o.filename == "res://material_maker/widgets/float_edit/float_edit.tscn":
o.value = v
elif o is LineEdit:
o.text = v
elif o is SizeOptionButton:
o.size_value = v
elif o is OptionButton:
o.selected = v
elif o is CheckBox:
o.pressed = v
elif o is ColorPickerButton:
o.color = MMType.deserialize_value(v)
elif o is Control and o.filename == "res://material_maker/widgets/file_picker_button/file_picker_button.tscn":
o.path = v
elif o is Control and o.filename == "res://material_maker/widgets/gradient_editor/gradient_editor.tscn":
var gradient : MMGradient = MMGradient.new()
gradient.deserialize(v)
o.value = gradient
else:
print("unsupported widget "+str(o))
get_parent().set_need_save()
func initialize_properties() -> void:
var parameter_names = []
for p in generator.get_parameter_defs():
parameter_names.push_back(p.name)
for c in controls:
if parameter_names.find(c) == -1:
continue
var o = controls[c]
if generator.parameters.has(c):
on_parameter_changed(c, generator.parameters[c])
if o is Control and o.filename == "res://material_maker/widgets/float_edit/float_edit.tscn":
o.connect("value_changed", self, "_on_value_changed", [ o.name ])
elif o is LineEdit:
o.connect("text_changed", self, "_on_text_changed", [ o.name ])
elif o is SizeOptionButton:
o.connect("size_value_changed", self, "_on_value_changed", [ o.name ])
elif o is OptionButton:
o.connect("item_selected", self, "_on_value_changed", [ o.name ])
elif o is CheckBox:
o.connect("toggled", self, "_on_value_changed", [ o.name ])
elif o is ColorPickerButton:
o.connect("color_changed", self, "_on_color_changed", [ o.name ])
elif o is Control and o.filename == "res://material_maker/widgets/file_picker_button/file_picker_button.tscn":
o.connect("on_file_selected", self, "_on_file_changed", [ o.name ])
elif o is Control and o.filename == "res://material_maker/widgets/gradient_editor/gradient_editor.tscn":
o.connect("updated", self, "_on_gradient_changed", [ o.name ])
else:
print("unsupported widget "+str(o))
func _on_text_changed(new_text, variable : String) -> void:
ignore_parameter_change = variable
generator.set_parameter(variable, new_text)
ignore_parameter_change = ""
get_parent().set_need_save()
func _on_value_changed(new_value, variable : String) -> void:
ignore_parameter_change = variable
generator.set_parameter(variable, new_value)
ignore_parameter_change = ""
get_parent().set_need_save()
func _on_color_changed(new_color, variable : String) -> void:
ignore_parameter_change = variable
generator.set_parameter(variable, new_color)
ignore_parameter_change = ""
get_parent().set_need_save()
func _on_file_changed(new_file, variable : String) -> void:
ignore_parameter_change = variable
generator.set_parameter(variable, new_file)
ignore_parameter_change = ""
get_parent().set_need_save()
func _on_gradient_changed(new_gradient, variable : String) -> void:
ignore_parameter_change = variable
generator.set_parameter(variable, new_gradient.duplicate())
ignore_parameter_change = ""
get_parent().set_need_save()
func create_parameter_control(p : Dictionary) -> Control:
var control = null
if p.type == "float":
control = preload("res://material_maker/widgets/float_edit/float_edit.tscn").instance()
if ! generator.accept_float_expressions():
control.float_only = true
control.min_value = p.min
control.max_value = p.max
control.step = 0.005 if !p.has("step") else p.step
if p.has("default"):
control.value = p.default
control.rect_min_size.x = 80
elif p.type == "size":
control = SizeOptionButton.new()
control.min_size = p.first
control.max_size = p.last
control.size_value = p.first if !p.has("default") else p.default
elif p.type == "enum":
control = OptionButton.new()
for i in range(p.values.size()):
var value = p.values[i]
control.add_item(value.name)
control.selected = 0 if !p.has("default") else p.default
elif p.type == "boolean":
control = CheckBox.new()
elif p.type == "color":
control = ColorPickerButton.new()
control.set_script(preload("res://material_maker/widgets/color_picker_button/color_picker_button.gd"))
control.rect_min_size.x = 40
elif p.type == "gradient":
control = preload("res://material_maker/widgets/gradient_editor/gradient_editor.tscn").instance()
elif p.type == "string":
control = LineEdit.new()
elif p.type == "file":
control = preload("res://material_maker/widgets/file_picker_button/file_picker_button.tscn").instance()
if p.has("filters"):
for f in p.filters:
control.add_filter(f)
var tooltip : String
if p.has("shortdesc"):
tooltip = p.shortdesc+" ("+p.name+")"
if p.has("longdesc"):
tooltip += "\n"+p.longdesc
elif p.has("longdesc"):
tooltip += p.longdesc
control.hint_tooltip = wrap_string(tooltip)
return control
func save_preview_widget() -> void:
if preview != null and preview.is_inside_tree():
preview.get_parent().remove_child(preview)
if preview_timer != null:
preview_timer.stop()
func restore_preview_widget() -> void:
if generator == null or generator.preview == -1:
if preview != null and preview.is_inside_tree():
preview.get_parent().remove_child(preview)
rect_size = Vector2(0, 0)
else:
if preview == null:
preview = preload("res://material_maker/panels/preview_2d/preview_2d_node.tscn").instance()
preview_timer.one_shot = true
preview_timer.connect("timeout", self, "do_update_preview")
preview.add_child(preview_timer)
var child_count = get_child_count()
var preview_parent = get_child(child_count-1)
while preview_parent is Container:
child_count = preview_parent.get_child_count()
preview_parent = preview_parent.get_child(child_count-1)
if preview_parent == null:
preview_parent = Control.new()
get_child(get_child_count()-1).add_child(preview_parent)
preview_parent.add_child(preview)
preview.visible = false
update_preview()
rect_size = Vector2(100, 96)
func update_preview() -> void:
if generator == null or generator.preview == -1:
return
preview_timer.start(0.2)
func do_update_preview() -> void:
if !preview.is_inside_tree():
restore_preview_widget()
preview.set_generator(generator, generator.preview)
var pos = Vector2(0, 0)
var parent = preview.get_parent()
while parent != self:
pos += parent.rect_position
parent = parent.get_parent()
preview.rect_position = Vector2(18, 24)-pos
preview.rect_size = rect_size-Vector2(38, 28)
preview.visible = true
func update_rendering_time(t : int) -> void:
.update_rendering_time(t)
update_title()
func update_title() -> void:
if rendering_time < 0:
title = generator.get_type_name()
else:
title = generator.get_type_name()+" ("+str(rendering_time)+"ms)"
if generator == null or generator.minimized:
var font : Font = get_font("default_font")
var max_title_width = 28
if font.get_string_size(title).x > max_title_width:
for i in range(1, title.length()-1):
if font.get_string_size(title.left(i)+"...").x > max_title_width:
title = title.left(i-1)+"..."
break
func update_node() -> void:
# Clean node
clear_all_slots()
save_preview_widget()
for c in get_children():
remove_child(c)
c.free()
# Show or hide the close button
show_close = generator.can_be_deleted()
# Rebuild node
update_title()
# Resize to minimum
rect_size = Vector2(0, 0)
# Regex for labels
var regex = RegEx.new()
regex.compile("^(\\d+):(.*)")
# Inputs
var inputs = generator.get_input_defs()
var index = -1
for i in range(inputs.size()):
var input = inputs[i]
var enable_left = false
var color_left = Color(0.5, 0.5, 0.5)
var type_left = 0
if typeof(input) == TYPE_DICTIONARY:
enable_left = true
if mm_io_types.types.has(input.type):
color_left = mm_io_types.types[input.type].color
type_left = mm_io_types.types[input.type].slot_type
var label = ""
if !generator.minimized:
if input.has("label"):
label = input.label
var result = regex.search(label)
if result:
index = result.get_string(1).to_int()-1
label = result.get_string(2)
else:
index += 1
var hsizer : HBoxContainer
while get_child_count() < index:
hsizer = HBoxContainer.new()
hsizer.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
add_child(hsizer)
hsizer.add_child(Control.new())
set_slot(get_child_count()-1, false, 0, Color(), false, 0, Color())
hsizer = HBoxContainer.new()
hsizer.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
add_child(hsizer)
if !generator.minimized and label != "":
var label_widget : Label = Label.new()
label_widget.text = label
hsizer.add_child(label_widget)
else:
var control : Control = Control.new()
control.rect_min_size.y = 12
hsizer.add_child(control)
set_slot(index, enable_left, type_left, color_left, false, 0, Color())
var input_names_width : int = 0
for c in get_children():
var width = c.get_child(0).rect_size.x
if width > input_names_width:
input_names_width = width
if input_names_width > 0:
input_names_width += 3
for c in get_children():
c.get_child(0).rect_min_size.x = input_names_width
# Parameters
if !generator.minimized:
controls = {}
index = -1
for p in generator.get_parameter_defs():
if !p.has("name") or !p.has("type"):
continue
var control = create_parameter_control(p)
if control != null:
var label = p.name
control.name = label
controls[control.name] = control
if p.has("label"):
label = p.label
var result = regex.search(label)
if result:
index = result.get_string(1).to_int()-1
label = result.get_string(2)
else:
index += 1
var hsizer : HBoxContainer
while index >= get_child_count():
hsizer = HBoxContainer.new()
hsizer.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
var empty_control : Control = Control.new()
empty_control.rect_min_size.x = input_names_width
hsizer.add_child(empty_control)
add_child(hsizer)
hsizer = get_child(index)
if label != "":
var label_widget = Label.new()
label_widget.text = label
label_widget.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
hsizer.add_child(label_widget)
control.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
if hsizer != null:
hsizer.add_child(control)
initialize_properties()
# Outputs
var outputs = generator.get_output_defs()
var button_width = 0
output_count = outputs.size()
for i in range(output_count):
var output = outputs[i]
var enable_right : bool = true
var color_right : Color = Color(0.5, 0.5, 0.5)
var type_right : int = 0
assert(typeof(output) == TYPE_DICTIONARY)
assert(output.has("type"))
enable_right = true
if mm_io_types.types.has(output.type):
color_right = mm_io_types.types[output.type].color
type_right = mm_io_types.types[output.type].slot_type
set_slot(i, is_slot_enabled_left(i), get_slot_type_left(i), get_slot_color_left(i), enable_right, type_right, color_right)
var hsizer : HBoxContainer
while i >= get_child_count():
hsizer = HBoxContainer.new()
hsizer.size_flags_horizontal = SIZE_EXPAND | SIZE_FILL
add_child(hsizer)
hsizer = get_child(i)
if hsizer.get_child_count() == 0:
hsizer.rect_min_size.y = 12
if !outputs.empty():
for i in range(output_count, get_child_count()):
var hsizer : HBoxContainer = get_child(i)
var empty_control : Control = Control.new()
empty_control.rect_min_size.x = button_width
hsizer.add_child(empty_control)
# Edit buttons
if generator.is_editable():
var edit_buttons = preload("res://material_maker/nodes/edit_buttons.tscn").instance()
add_child(edit_buttons)
edit_buttons.connect_buttons(self, "edit_generator", "load_generator", "save_generator")
set_slot(edit_buttons.get_index(), false, 0, Color(0.0, 0.0, 0.0), false, 0, Color(0.0, 0.0, 0.0))
if generator.minimized:
rect_size = Vector2(96, 96)
# Preview
restore_preview_widget()
func edit_generator() -> void:
if generator.has_method("edit"):
generator.edit(self)
func update_generator(shader_model) -> void:
generator.set_shader_model(shader_model)
update_node()
get_parent().set_need_save()
func load_generator() -> void:
var dialog = FileDialog.new()
add_child(dialog)
dialog.rect_min_size = Vector2(500, 500)
dialog.access = FileDialog.ACCESS_FILESYSTEM
dialog.mode = FileDialog.MODE_OPEN_FILE
dialog.add_filter("*.mmg;Material Maker Generator")
if get_node("/root/MainWindow") != null:
var config_cache = get_node("/root/MainWindow").config_cache
if config_cache.has_section_key("path", "template"):
dialog.current_dir = config_cache.get_value("path", "template")
dialog.connect("file_selected", self, "do_load_generator")
dialog.connect("popup_hide", dialog, "queue_free")
dialog.popup_centered()
func do_load_generator(file_name : String) -> void:
if get_node("/root/MainWindow") != null:
var config_cache = get_node("/root/MainWindow").config_cache
config_cache.set_value("path", "template", file_name.get_base_dir())
var new_generator = null
if file_name.ends_with(".mmn"):
var file = File.new()
if file.open(file_name, File.READ) == OK:
new_generator = MMGenShader.new()
new_generator.set_shader_model(parse_json(file.get_as_text()))
file.close()
else:
new_generator = mm_loader.load_gen(file_name)
if new_generator != null:
var gen_name = mm_loader.generator_name_from_path(file_name)
if gen_name != "":
new_generator.model = gen_name
var parent_generator = generator.get_parent()
parent_generator.replace_generator(generator, new_generator)
generator = new_generator
update_node()
func save_generator() -> void:
var dialog = FileDialog.new()
add_child(dialog)
dialog.rect_min_size = Vector2(500, 500)
dialog.access = FileDialog.ACCESS_FILESYSTEM
dialog.mode = FileDialog.MODE_SAVE_FILE
dialog.add_filter("*.mmg;Material Maker Generator")
if get_node("/root/MainWindow") != null:
var config_cache = get_node("/root/MainWindow").config_cache
if config_cache.has_section_key("path", "template"):
dialog.current_dir = config_cache.get_value("path", "template")
dialog.connect("file_selected", self, "do_save_generator")
dialog.connect("popup_hide", dialog, "queue_free")
dialog.popup_centered()
func do_save_generator(file_name : String) -> void:
if get_node("/root/MainWindow") != null:
var config_cache = get_node("/root/MainWindow").config_cache
config_cache.set_value("path", "template", file_name.get_base_dir())
var file = File.new()
if file.open(file_name, File.WRITE) == OK:
var data = generator.serialize()
data.name = file_name.get_file().get_basename()
data.node_position = { x=0, y=0 }
file.store_string(JSON.print(data, "\t", true))
file.close()
mm_loader.update_predefined_generators()
func on_clicked_output(index : int) -> void:
if generator.preview == index:
generator.preview = -1
disconnect("mouse_entered", self, "on_mouse_entered")
disconnect("mouse_exited", self, "on_mouse_exited")
else:
generator.preview = index
connect("mouse_entered", self, "on_mouse_entered")
connect("mouse_exited", self, "on_mouse_exited")
update_preview()
restore_preview_widget()
update()
func on_mouse_entered():
if !generator.minimized:
preview.visible = false
func on_mouse_exited():
if !generator.minimized and !get_global_rect().has_point(get_global_mouse_position()):
preview.visible = true