first commit
This commit is contained in:
280
addons/zylann.hterrain/tools/brush/settings_dialog/brush_settings_dialog.gd
Executable file
280
addons/zylann.hterrain/tools/brush/settings_dialog/brush_settings_dialog.gd
Executable file
@@ -0,0 +1,280 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
const HT_Util = preload("../../../util/util.gd")
|
||||
const HT_Brush = preload("../brush.gd")
|
||||
const HT_Logger = preload("../../../util/logger.gd")
|
||||
const HT_EditorUtil = preload("../../util/editor_util.gd")
|
||||
const HT_SpinSlider = preload("../../util/spin_slider.gd")
|
||||
const HT_Scratchpad = preload("./preview_scratchpad.gd")
|
||||
|
||||
@onready var _scratchpad : HT_Scratchpad = $VB/HB/VB3/PreviewScratchpad
|
||||
|
||||
@onready var _shape_list : ItemList = $VB/HB/VB/ShapeList
|
||||
@onready var _remove_shape_button : Button = $VB/HB/VB/HBoxContainer/RemoveShape
|
||||
@onready var _change_shape_button : Button = $VB/HB/VB/ChangeShape
|
||||
|
||||
@onready var _size_slider : HT_SpinSlider = $VB/HB/VB2/Settings/Size
|
||||
@onready var _opacity_slider : HT_SpinSlider = $VB/HB/VB2/Settings/Opacity
|
||||
@onready var _pressure_enabled_checkbox : CheckBox = $VB/HB/VB2/Settings/PressureEnabled
|
||||
@onready var _pressure_over_size_slider : HT_SpinSlider = $VB/HB/VB2/Settings/PressureOverSize
|
||||
@onready var _pressure_over_opacity_slider : HT_SpinSlider = $VB/HB/VB2/Settings/PressureOverOpacity
|
||||
@onready var _frequency_distance_slider : HT_SpinSlider = $VB/HB/VB2/Settings/FrequencyDistance
|
||||
@onready var _frequency_time_slider : HT_SpinSlider = $VB/HB/VB2/Settings/FrequencyTime
|
||||
@onready var _random_rotation_checkbox : CheckBox = $VB/HB/VB2/Settings/RandomRotation
|
||||
@onready var _shape_cycling_checkbox : CheckBox = $VB/HB/VB2/Settings/ShapeCycling
|
||||
|
||||
var _brush : HT_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 = HT_Logger.get_for(self)
|
||||
|
||||
|
||||
func _ready():
|
||||
if HT_Util.is_in_edited_scene(self):
|
||||
return
|
||||
|
||||
_size_slider.set_max_value(HT_Brush.MAX_SIZE_FOR_SLIDERS)
|
||||
_size_slider.set_greater_max_value(HT_Brush.MAX_SIZE)
|
||||
|
||||
# TESTING
|
||||
if not Engine.is_editor_hint():
|
||||
setup_dialogs(self)
|
||||
call_deferred("popup")
|
||||
|
||||
|
||||
func set_brush(brush : HT_Brush):
|
||||
assert(brush != null)
|
||||
_brush = brush
|
||||
_update_controls_from_brush()
|
||||
|
||||
|
||||
# `base_control` can no longer be hinted as a `Control` because in Godot 4 it could be a
|
||||
# window or dialog, which are no longer controls...
|
||||
func setup_dialogs(base_control: Node):
|
||||
assert(_load_image_dialog == null)
|
||||
_load_image_dialog = HT_EditorUtil.create_open_file_dialog()
|
||||
_load_image_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
|
||||
_load_image_dialog.add_filter("*.exr ; EXR files")
|
||||
_load_image_dialog.unresizable = false
|
||||
_load_image_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
_load_image_dialog.current_dir = HT_Brush.SHAPES_DIR
|
||||
_load_image_dialog.file_selected.connect(_on_LoadImageDialog_file_selected)
|
||||
_load_image_dialog.files_selected.connect(_on_LoadImageDialog_files_selected)
|
||||
#base_control.add_child(_load_image_dialog)
|
||||
# When a dialog opens another dialog, we get this error:
|
||||
# "Transient parent has another exclusive child."
|
||||
# Which is worked around by making the other dialog a child of the first one (I don't know why)
|
||||
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[Texture2D]:
|
||||
var shapes : Array[Texture2D] = []
|
||||
for i in _shape_list.get_item_count():
|
||||
var icon : Texture2D = _shape_list.get_item_icon(i)
|
||||
assert(icon != null)
|
||||
shapes.append(icon)
|
||||
return shapes
|
||||
|
||||
|
||||
func _update_shapes_gui(shapes: Array[Texture2D]):
|
||||
_shape_list.clear()
|
||||
for shape in shapes:
|
||||
assert(shape != null)
|
||||
assert(shape is Texture2D)
|
||||
_shape_list.add_icon_item(shape)
|
||||
_update_shape_list_buttons()
|
||||
|
||||
|
||||
func _on_AddShape_pressed():
|
||||
_load_image_index = -1
|
||||
_load_image_dialog.file_mode = EditorFileDialog.FILE_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: int):
|
||||
_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.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
|
||||
_load_image_dialog.popup_centered_ratio(0.7)
|
||||
|
||||
|
||||
func _on_LoadImageDialog_files_selected(fpaths: PackedStringArray):
|
||||
var shapes := _get_shapes_from_gui()
|
||||
|
||||
for fpath in fpaths:
|
||||
var tex := HT_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 := HT_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:
|
||||
# Testing the scratchpad because visibility can not only change before entering the tree
|
||||
# since Godot 4 port, it can also change between entering the tree and being _ready...
|
||||
if visible and _scratchpad != null:
|
||||
_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.button_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 / maxf(0.1, float(brush.get_frequency_time_ms())), false)
|
||||
_random_rotation_checkbox.button_pressed = brush.is_random_rotation_enabled()
|
||||
_shape_cycling_checkbox.button_pressed = brush.is_shape_cycling_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 _on_shape_cycling_toggled(button_pressed: bool):
|
||||
for brush in _get_brushes():
|
||||
brush.set_shape_cycling_enabled(button_pressed)
|
||||
|
||||
|
||||
func _get_brushes() -> Array[HT_Brush]:
|
||||
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()
|
||||
for brush in _get_brushes():
|
||||
brush.set_shape_index(index)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
func _on_shape_list_empty_clicked(at_position, mouse_button_index):
|
||||
_update_shape_list_buttons()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1b35q1fijabj
|
||||
@@ -0,0 +1,211 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://d2rt3wj8xkhp2"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://addons/zylann.hterrain/tools/util/spin_slider.tscn" id="2"]
|
||||
[ext_resource type="Script" uid="uid://b1b35q1fijabj" path="res://addons/zylann.hterrain/tools/brush/settings_dialog/brush_settings_dialog.gd" id="3"]
|
||||
[ext_resource type="PackedScene" uid="uid://ng00jipfeucy" path="res://addons/zylann.hterrain/tools/brush/settings_dialog/preview_scratchpad.tscn" id="4"]
|
||||
|
||||
[node name="BrushSettingsDialog" type="AcceptDialog"]
|
||||
title = "Brush settings"
|
||||
size = Vector2i(700, 422)
|
||||
min_size = Vector2i(700, 400)
|
||||
script = ExtResource("3")
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="VB"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="VB/HB"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VB/HB/VB"]
|
||||
layout_mode = 2
|
||||
text = "Shapes"
|
||||
|
||||
[node name="ShapeList" type="ItemList" parent="VB/HB/VB"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
fixed_icon_size = Vector2i(100, 100)
|
||||
|
||||
[node name="ChangeShape" type="Button" parent="VB/HB/VB"]
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Change..."
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VB/HB/VB"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddShape" type="Button" parent="VB/HB/VB/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Add..."
|
||||
|
||||
[node name="RemoveShape" type="Button" parent="VB/HB/VB/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Remove"
|
||||
|
||||
[node name="VB2" type="VBoxContainer" parent="VB/HB"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VB/HB/VB2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Settings" type="VBoxContainer" parent="VB/HB/VB2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Size" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
value = 32.0
|
||||
min_value = 2.0
|
||||
max_value = 500.0
|
||||
prefix = "Size:"
|
||||
suffix = "px"
|
||||
rounded = true
|
||||
centered = true
|
||||
allow_greater = true
|
||||
greater_max_value = 4000.0
|
||||
|
||||
[node name="Opacity" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
value = 100.0
|
||||
max_value = 100.0
|
||||
prefix = "Opacity"
|
||||
suffix = "%"
|
||||
rounded = true
|
||||
centered = true
|
||||
greater_max_value = 10000.0
|
||||
|
||||
[node name="PressureEnabled" type="CheckBox" parent="VB/HB/VB2/Settings"]
|
||||
layout_mode = 2
|
||||
text = "Enable pressure (pen tablets)"
|
||||
|
||||
[node name="PressureOverSize" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
value = 50.0
|
||||
max_value = 100.0
|
||||
prefix = "Pressure affects size:"
|
||||
suffix = "%"
|
||||
centered = true
|
||||
greater_max_value = 10000.0
|
||||
|
||||
[node name="PressureOverOpacity" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
value = 50.0
|
||||
max_value = 100.0
|
||||
prefix = "Pressure affects opacity:"
|
||||
suffix = "%"
|
||||
centered = true
|
||||
greater_max_value = 10000.0
|
||||
|
||||
[node name="FrequencyTime" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
value = 60.0
|
||||
min_value = 1.0
|
||||
max_value = 60.0
|
||||
prefix = "Frequency time:"
|
||||
suffix = "fps"
|
||||
centered = true
|
||||
greater_max_value = 10000.0
|
||||
|
||||
[node name="FrequencyDistance" parent="VB/HB/VB2/Settings" instance=ExtResource("2")]
|
||||
custom_minimum_size = Vector2(32, 28)
|
||||
layout_mode = 2
|
||||
max_value = 100.0
|
||||
prefix = "Frequency distance:"
|
||||
suffix = "px"
|
||||
centered = true
|
||||
greater_max_value = 4000.0
|
||||
|
||||
[node name="RandomRotation" type="CheckBox" parent="VB/HB/VB2/Settings"]
|
||||
layout_mode = 2
|
||||
text = "Random rotation"
|
||||
|
||||
[node name="ShapeCycling" type="CheckBox" parent="VB/HB/VB2/Settings"]
|
||||
layout_mode = 2
|
||||
text = "Shape cycling"
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VB/HB/VB2/Settings"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SizeLimitHB" type="HBoxContainer" parent="VB/HB/VB2/Settings"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VB/HB/VB2/Settings/SizeLimitHB"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 0
|
||||
text = "Size limit:"
|
||||
|
||||
[node name="SizeLimit" type="SpinBox" parent="VB/HB/VB2/Settings/SizeLimitHB"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 1.0
|
||||
max_value = 1000.0
|
||||
value = 200.0
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="VB/HB/VB2/Settings"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="VB/HB/VB2/Settings"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Button" type="Button" parent="VB/HB/VB2/Settings/HB"]
|
||||
layout_mode = 2
|
||||
text = "Load preset..."
|
||||
|
||||
[node name="Button2" type="Button" parent="VB/HB/VB2/Settings/HB"]
|
||||
layout_mode = 2
|
||||
text = "Save preset..."
|
||||
|
||||
[node name="VB3" type="VBoxContainer" parent="VB/HB"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VB/HB/VB3"]
|
||||
layout_mode = 2
|
||||
text = "Scratchpad"
|
||||
|
||||
[node name="PreviewScratchpad" parent="VB/HB/VB3" instance=ExtResource("4")]
|
||||
custom_minimum_size = Vector2(200, 300)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ClearScratchpad" type="Button" parent="VB/HB/VB3"]
|
||||
layout_mode = 2
|
||||
text = "Clear"
|
||||
|
||||
[connection signal="empty_clicked" from="VB/HB/VB/ShapeList" to="." method="_on_shape_list_empty_clicked"]
|
||||
[connection signal="item_activated" from="VB/HB/VB/ShapeList" to="." method="_on_ShapeList_item_activated"]
|
||||
[connection signal="item_selected" from="VB/HB/VB/ShapeList" to="." method="_on_ShapeList_item_selected"]
|
||||
[connection signal="pressed" from="VB/HB/VB/ChangeShape" to="." method="_on_ChangeShape_pressed"]
|
||||
[connection signal="pressed" from="VB/HB/VB/HBoxContainer/AddShape" to="." method="_on_AddShape_pressed"]
|
||||
[connection signal="pressed" from="VB/HB/VB/HBoxContainer/RemoveShape" to="." method="_on_RemoveShape_pressed"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/Size" to="." method="_on_Size_value_changed"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/Opacity" to="." method="_on_Opacity_value_changed"]
|
||||
[connection signal="toggled" from="VB/HB/VB2/Settings/PressureEnabled" to="." method="_on_PressureEnabled_toggled"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/PressureOverSize" to="." method="_on_PressureOverSize_value_changed"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/PressureOverOpacity" to="." method="_on_PressureOverOpacity_value_changed"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/FrequencyTime" to="." method="_on_FrequencyTime_value_changed"]
|
||||
[connection signal="value_changed" from="VB/HB/VB2/Settings/FrequencyDistance" to="." method="_on_FrequencyDistance_value_changed"]
|
||||
[connection signal="toggled" from="VB/HB/VB2/Settings/RandomRotation" to="." method="_on_RandomRotation_toggled"]
|
||||
[connection signal="toggled" from="VB/HB/VB2/Settings/ShapeCycling" to="." method="_on_shape_cycling_toggled"]
|
||||
[connection signal="pressed" from="VB/HB/VB3/ClearScratchpad" to="." method="_on_ClearScratchpad_pressed"]
|
||||
41
addons/zylann.hterrain/tools/brush/settings_dialog/preview_painter.gd
Executable file
41
addons/zylann.hterrain/tools/brush/settings_dialog/preview_painter.gd
Executable file
@@ -0,0 +1,41 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
const HT_Painter = preload("./../painter.gd")
|
||||
const HT_Brush = preload("./../brush.gd")
|
||||
|
||||
const HT_ColorShader = preload("../shaders/color.gdshader")
|
||||
|
||||
var _painter : HT_Painter
|
||||
var _brush : HT_Brush
|
||||
|
||||
|
||||
func _init():
|
||||
var p = HT_Painter.new()
|
||||
# The name is just for debugging
|
||||
p.set_name("Painter")
|
||||
add_child(p)
|
||||
_painter = p
|
||||
|
||||
_brush = HT_Brush.new()
|
||||
|
||||
|
||||
func set_image_texture(image: Image, texture: ImageTexture):
|
||||
_painter.set_image(image, texture)
|
||||
|
||||
|
||||
func get_brush() -> HT_Brush:
|
||||
return _brush
|
||||
|
||||
|
||||
# This may be called from an `_input` callback
|
||||
func paint_input(position: Vector2, pressure: float):
|
||||
var p : HT_Painter = _painter
|
||||
|
||||
if not _brush.configure_paint_input([p], position, pressure):
|
||||
return
|
||||
|
||||
p.set_brush_shader(HT_ColorShader)
|
||||
p.set_brush_shader_param("u_color", Color(0,0,0,1))
|
||||
#p.set_image(_image, _texture)
|
||||
p.paint_input(position)
|
||||
@@ -0,0 +1 @@
|
||||
uid://mbww7v2v25nn
|
||||
70
addons/zylann.hterrain/tools/brush/settings_dialog/preview_scratchpad.gd
Executable file
70
addons/zylann.hterrain/tools/brush/settings_dialog/preview_scratchpad.gd
Executable file
@@ -0,0 +1,70 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const HT_PreviewPainter = preload("./preview_painter.gd")
|
||||
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
|
||||
#const HT_DefaultBrushTexture = preload("../shapes/round2.exr")
|
||||
const HT_Brush = preload("../brush.gd")
|
||||
const HT_Logger = preload("../../../util/logger.gd")
|
||||
const HT_EditorUtil = preload("../../util/editor_util.gd")
|
||||
const HT_Util = preload("../../../util/util.gd")
|
||||
|
||||
@onready var _texture_rect : TextureRect = $TextureRect
|
||||
@onready var _painter : HT_PreviewPainter = $Painter
|
||||
|
||||
var _logger := HT_Logger.get_for(self)
|
||||
|
||||
|
||||
func _ready():
|
||||
if HT_Util.is_in_edited_scene(self):
|
||||
# If it runs in the edited scene,
|
||||
# saving the scene would also save the ImageTexture in it...
|
||||
return
|
||||
reset_image()
|
||||
# Default so it doesn't crash when painting and can be tested
|
||||
var default_brush_texture = \
|
||||
HT_EditorUtil.load_texture(HT_Brush.DEFAULT_BRUSH_TEXTURE_PATH, _logger)
|
||||
_painter.get_brush().set_shapes([default_brush_texture])
|
||||
|
||||
|
||||
func reset_image():
|
||||
var image = Image.create(_texture_rect.size.x, _texture_rect.size.y, false, Image.FORMAT_RGB8)
|
||||
image.fill(Color(1,1,1))
|
||||
|
||||
# TEST
|
||||
# var fnl = FastNoiseLite.new()
|
||||
# for y in image.get_height():
|
||||
# for x in image.get_width():
|
||||
# var g = 0.5 + 0.5 * fnl.get_noise_2d(x, y)
|
||||
# image.set_pixel(x, y, Color(g, g, g, 1.0))
|
||||
|
||||
var texture = ImageTexture.create_from_image(image)
|
||||
_texture_rect.texture = texture
|
||||
_painter.set_image_texture(image, texture)
|
||||
|
||||
|
||||
func get_painter() -> HT_PreviewPainter:
|
||||
return _painter
|
||||
|
||||
|
||||
func _gui_input(event):
|
||||
if event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
||||
_painter.paint_input(event.position, event.pressure)
|
||||
queue_redraw()
|
||||
|
||||
elif event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
# TODO `pressure` is not available on button events
|
||||
# So I have to assume zero... which means clicks do not paint anything?
|
||||
_painter.paint_input(event.position, 0.0)
|
||||
else:
|
||||
_painter.get_brush().on_paint_end()
|
||||
|
||||
|
||||
func _draw():
|
||||
var mpos = get_local_mouse_position()
|
||||
var brush = _painter.get_brush()
|
||||
draw_arc(mpos, 0.5 * brush.get_size(), -PI, PI, 32, Color(1, 0.2, 0.2), 2.0, true)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://1xc5c77jq82e
|
||||
@@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ng00jipfeucy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://1xc5c77jq82e" path="res://addons/zylann.hterrain/tools/brush/settings_dialog/preview_scratchpad.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://mbww7v2v25nn" path="res://addons/zylann.hterrain/tools/brush/settings_dialog/preview_painter.gd" id="2"]
|
||||
|
||||
[node name="PreviewScratchpad" type="Control"]
|
||||
clip_contents = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 274.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Painter" type="Node" parent="."]
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
show_behind_parent = true
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
stretch_mode = 5
|
||||
Reference in New Issue
Block a user