tool extends AcceptDialog const Util = preload("../../../util/util.gd") const Brush = preload("../brush.gd") const Logger = preload("../../../util/logger.gd") const EditorUtil = preload("../../util/editor_util.gd") onready var _scratchpad = $VB/HB/VB3/PreviewScratchpad onready var _shape_list = $VB/HB/VB/ShapeList onready var _remove_shape_button = $VB/HB/VB/HBoxContainer/RemoveShape onready var _change_shape_button = $VB/HB/VB/ChangeShape onready var _size_slider = $VB/HB/VB2/Settings/Size onready var _opacity_slider = $VB/HB/VB2/Settings/Opacity onready var _pressure_enabled_checkbox = $VB/HB/VB2/Settings/PressureEnabled onready var _pressure_over_size_slider = $VB/HB/VB2/Settings/PressureOverSize onready var _pressure_over_opacity_slider = $VB/HB/VB2/Settings/PressureOverOpacity onready var _frequency_distance_slider = $VB/HB/VB2/Settings/FrequencyDistance onready var _frequency_time_slider = $VB/HB/VB2/Settings/FrequencyTime onready var _random_rotation_checkbox = $VB/HB/VB2/Settings/RandomRotation var _brush : Brush # This is a `EditorFileDialog`, # but cannot type it because I want to be able to test it by running the scene. # And when I run it, Godot does not allow to use `EditorFileDialog`. var _load_image_dialog # -1 means add, otherwise replace var _load_image_index := -1 var _logger = Logger.get_for(self) func _ready(): if Util.is_in_edited_scene(self): return _size_slider.set_max_value(Brush.MAX_SIZE_FOR_SLIDERS) _size_slider.set_greater_max_value(Brush.MAX_SIZE) # TESTING if not Engine.editor_hint: setup_dialogs(self) call_deferred("popup") func set_brush(brush : Brush): assert(brush != null) _brush = brush _update_controls_from_brush() func setup_dialogs(base_control: Control): assert(_load_image_dialog == null) _load_image_dialog = EditorUtil.create_open_file_dialog() _load_image_dialog.mode = EditorFileDialog.MODE_OPEN_FILE _load_image_dialog.add_filter("*.exr ; EXR files") _load_image_dialog.resizable = true _load_image_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM _load_image_dialog.current_dir = Brush.SHAPES_DIR _load_image_dialog.connect("file_selected", self, "_on_LoadImageDialog_file_selected") _load_image_dialog.connect("files_selected", self, "_on_LoadImageDialog_files_selected") base_control.add_child(_load_image_dialog) func _exit_tree(): if _load_image_dialog != null: _load_image_dialog.queue_free() _load_image_dialog = null func _get_shapes_from_gui() -> Array: var shapes = [] for i in _shape_list.get_item_count(): var icon = _shape_list.get_item_icon(i) assert(icon != null) shapes.append(icon) return shapes func _update_shapes_gui(shapes: Array): _shape_list.clear() for shape in shapes: assert(shape != null) assert(shape is Texture) _shape_list.add_icon_item(shape) _update_shape_list_buttons() func _on_AddShape_pressed(): _load_image_index = -1 _load_image_dialog.mode = EditorFileDialog.MODE_OPEN_FILES _load_image_dialog.popup_centered_ratio(0.7) func _on_RemoveShape_pressed(): var selected_indices = _shape_list.get_selected_items() if len(selected_indices) == 0: return var index : int = selected_indices[0] _shape_list.remove_item(index) var shapes = _get_shapes_from_gui() for brush in _get_brushes(): brush.set_shapes(shapes) _update_shape_list_buttons() func _on_ShapeList_item_activated(index): _request_modify_shape(index) func _on_ChangeShape_pressed(): var selected = _shape_list.get_selected_items() if len(selected) == 0: return _request_modify_shape(selected[0]) func _request_modify_shape(index: int): _load_image_index = index _load_image_dialog.mode = EditorFileDialog.MODE_OPEN_FILE _load_image_dialog.popup_centered_ratio(0.7) func _on_LoadImageDialog_files_selected(fpaths: PoolStringArray): var shapes := _get_shapes_from_gui() for fpath in fpaths: var tex := Brush.load_shape_from_image_file(fpath, _logger) if tex == null: # Failed continue shapes.append(tex) for brush in _get_brushes(): brush.set_shapes(shapes) _update_shapes_gui(shapes) func _on_LoadImageDialog_file_selected(fpath: String): var tex := Brush.load_shape_from_image_file(fpath, _logger) if tex == null: # Failed return var shapes := _get_shapes_from_gui() if _load_image_index == -1 or _load_image_index >= len(shapes): # Add shapes.append(tex) else: # Replace assert(_load_image_index >= 0) shapes[_load_image_index] = tex for brush in _get_brushes(): brush.set_shapes(shapes) _update_shapes_gui(shapes) func _notification(what: int): if what == NOTIFICATION_VISIBILITY_CHANGED: if visible: _update_controls_from_brush() func _update_controls_from_brush(): var brush := _brush if brush == null: # To allow testing brush = _scratchpad.get_painter().get_brush() _update_shapes_gui(brush.get_shapes()) _size_slider.set_value(brush.get_size(), false) _opacity_slider.set_value(brush.get_opacity() * 100.0, false) _pressure_enabled_checkbox.pressed = brush.is_pressure_enabled() _pressure_over_size_slider.set_value(brush.get_pressure_over_scale() * 100.0, false) _pressure_over_opacity_slider.set_value(brush.get_pressure_over_opacity() * 100.0, false) _frequency_distance_slider.set_value(brush.get_frequency_distance(), false) _frequency_time_slider.set_value(1000.0 / max(0.1, float(brush.get_frequency_time_ms())), false) _random_rotation_checkbox.pressed = brush.is_random_rotation_enabled() func _on_ClearScratchpad_pressed(): _scratchpad.reset_image() func _on_Size_value_changed(value: float): for brush in _get_brushes(): brush.set_size(value) func _on_Opacity_value_changed(value): for brush in _get_brushes(): brush.set_opacity(value / 100.0) func _on_PressureEnabled_toggled(button_pressed): for brush in _get_brushes(): brush.set_pressure_enabled(button_pressed) func _on_PressureOverSize_value_changed(value): for brush in _get_brushes(): brush.set_pressure_over_scale(value / 100.0) func _on_PressureOverOpacity_value_changed(value): for brush in _get_brushes(): brush.set_pressure_over_opacity(value / 100.0) func _on_FrequencyDistance_value_changed(value): for brush in _get_brushes(): brush.set_frequency_distance(value) func _on_FrequencyTime_value_changed(fps): fps = max(1.0, fps) var ms = 1000.0 / fps if is_equal_approx(fps, 60.0): ms = 0 for brush in _get_brushes(): brush.set_frequency_time_ms(ms) func _on_RandomRotation_toggled(button_pressed: bool): for brush in _get_brushes(): brush.set_random_rotation_enabled(button_pressed) func _get_brushes() -> Array: if _brush != null: # We edit both the preview brush and the terrain brush # TODO Could we simply share the brush? return [_brush, _scratchpad.get_painter().get_brush()] # When testing the dialog in isolation, the edited brush might be null return [_scratchpad.get_painter().get_brush()] func _on_ShapeList_item_selected(index): _update_shape_list_buttons() func _on_ShapeList_nothing_selected(): _update_shape_list_buttons() func _update_shape_list_buttons(): var selected_count = len(_shape_list.get_selected_items()) # There must be at least one shape _remove_shape_button.disabled = _shape_list.get_item_count() == 1 or selected_count == 0 _change_shape_button.disabled = selected_count == 0