From c74dccf1cf335e935d2c8157862edf39c1805af3 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Sat, 19 Jul 2025 21:52:49 +1000 Subject: [PATCH] adding mask options --- .vscode/launch.json | 33 ++++++ .vscode/tasks.json | 121 ++++++++++++++++++++ build-windows.bat | 1 - main_scenes/main.gd | 45 +++++++- main_scenes/main.tscn | 2 +- project.godot | 2 + shader/wobble.gdshader | 18 ++- shader/wobble.gdshader.uid | 2 +- ui_scenes/selectedSprite/mask.gdshader | 23 ++++ ui_scenes/selectedSprite/mask.gdshader.uid | 1 + ui_scenes/selectedSprite/outline.gdshader | 15 +-- ui_scenes/selectedSprite/spriteObject.gd | 59 ++++++++++ ui_scenes/settings/settings_menu.gd | 4 +- ui_scenes/spriteEditMenu/sprite_viewer.gd | 36 +++++- ui_scenes/spriteEditMenu/sprite_viewer.tscn | 68 ++++++++++- 15 files changed, 397 insertions(+), 33 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 ui_scenes/selectedSprite/mask.gdshader create mode 100644 ui_scenes/selectedSprite/mask.gdshader.uid diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..991b657 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch PNGTuber Plus (Godot)", + "type": "godot", + "request": "launch", + "port": 6007, + "address": "127.0.0.1" + }, + { + "name": "Attach to Running Godot", + "type": "godot", + "request": "attach", + "port": 6007, + "address": "127.0.0.1" + }, + { + "name": "Launch Built Executable", + "type": "PowerShell", + "request": "launch", + "script": "if (Test-Path '${workspaceFolder}\\build\\windows\\PNGTuber-Plus.exe') { Start-Process '${workspaceFolder}\\build\\windows\\PNGTuber-Plus.exe' } else { Write-Host 'Executable not found. Build the project first.' }", + "cwd": "${workspaceFolder}" + }, + { + "name": "Build and Launch", + "type": "PowerShell", + "request": "launch", + "script": ".\\build-windows.bat; if (Test-Path '.\\build\\windows\\PNGTuber-Plus.exe') { Start-Process '.\\build\\windows\\PNGTuber-Plus.exe' }", + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5182b61 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,121 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build Windows (Local)", + "type": "shell", + "command": ".\\build-windows.bat", + "group": { + "kind": "build", + "isDefault": true + }, + "args": [], + "isBackground": false, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Build Windows executable using local build script with template management" + }, + { + "label": "Build Windows (CI)", + "type": "shell", + "command": ".\\build-windows-ci.bat", + "group": "build", + "args": [], + "isBackground": false, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Build Windows executable using CI build script (no template download)" + }, + { + "label": "Export Windows (Godot Direct)", + "type": "shell", + "command": "D:\\Godot\\Godot_v4.4.1-stable_win64.exe", + "group": "build", + "args": [ + "--headless", + "--export-release", + "\"Windows Desktop\"", + "\"build/windows/PNGTuber-Plus.exe\"" + ], + "isBackground": false, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Export Windows build directly using Godot (requires templates already installed)" + }, + { + "label": "Clean Build Directory", + "type": "shell", + "command": "if (Test-Path 'build') { Remove-Item -Recurse -Force 'build' }; Write-Host 'Build directory cleaned'", + "group": "build", + "args": [], + "isBackground": false, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Clean the build output directory" + }, + { + "label": "Open Build Directory", + "type": "shell", + "command": "if (Test-Path 'build\\windows') { explorer 'build\\windows' } else { Write-Host 'Build directory does not exist. Run a build first.' }", + "group": "build", + "args": [], + "isBackground": false, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Open the Windows build output directory in Explorer" + }, + { + "label": "Run Built Executable", + "type": "shell", + "command": "if (Test-Path 'build\\windows\\PNGTuber-Plus.exe') { Start-Process 'build\\windows\\PNGTuber-Plus.exe' } else { Write-Host 'Executable not found. Build the project first.' }", + "group": "test", + "args": [], + "isBackground": true, + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "detail": "Run the built Windows executable for testing" + } + ] +} \ No newline at end of file diff --git a/build-windows.bat b/build-windows.bat index d4483e9..1dab021 100644 --- a/build-windows.bat +++ b/build-windows.bat @@ -47,4 +47,3 @@ if %ERRORLEVEL% EQU 0 ( echo. ) -pause diff --git a/main_scenes/main.gd b/main_scenes/main.gd index 23b1292..c4297f0 100644 --- a/main_scenes/main.gd +++ b/main_scenes/main.gd @@ -30,6 +30,7 @@ var editMode = true @onready var spriteObject = preload("res://ui_scenes/selectedSprite/spriteObject.tscn") var saveLoaded = false +var isSelectingMask = false #Motion var yVel = 0 @@ -62,7 +63,9 @@ func _ready(): Global.connect("startSpeaking",onSpeak) - ElgatoStreamDeck.on_key_down.connect(changeCostumeStreamDeck) + var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck") + if streamdeck_node: + streamdeck_node.on_key_down.connect(changeCostumeStreamDeck) if Saving.settings["newUser"]: _on_load_dialog_file_selected("default") @@ -153,6 +156,9 @@ func _process(delta): followShadow() func followShadow(): + shadow.visible = false # Disabled drop shadow + return + shadow.visible = is_instance_valid(Global.heldSprite) if !shadow.visible: return @@ -171,6 +177,9 @@ func isFileSystemOpen(): if obj.visible: if obj == replaceDialog: return true + # Don't clear held sprite when selecting mask + if obj == fileDialog and isSelectingMask: + return true Global.heldSprite = null return true return false @@ -288,7 +297,20 @@ func _on_add_button_pressed(): #Runs when selecting image in File Dialog func _on_file_dialog_file_selected(path): - add_image(path) + if isSelectingMask: + # Handle mask selection + if Global.heldSprite != null: + Global.heldSprite.setMaskTexture(path) + Global.spriteEdit.updateMaskDisplay(path) + isSelectingMask = false + else: + # Handle normal sprite addition + add_image(path) + +#Opens File Dialog for mask selection +func openMaskSelection(): + isSelectingMask = true + fileDialog.visible = true func _on_save_button_pressed(): $SaveDialog.visible = true @@ -363,6 +385,14 @@ func _on_load_dialog_file_selected(path): if data[item].has("toggle"): sprite.toggle = data[item]["toggle"] + # Load mask properties + if data[item].has("mask_texture_path"): + sprite.mask_texture_path = data[item]["mask_texture_path"] + if data[item].has("use_mask"): + sprite.use_mask = data[item]["use_mask"] + if data[item].has("mask_opacity"): + sprite.mask_opacity = data[item]["mask_opacity"] + origin.add_child(sprite) sprite.position = str_to_var(data[item]["pos"]) @@ -424,6 +454,11 @@ func _on_save_dialog_file_selected(path): data[id]["toggle"] = child.toggle + # Save mask properties + data[id]["mask_texture_path"] = child.mask_texture_path + data[id]["use_mask"] = child.use_mask + data[id]["mask_opacity"] = child.mask_opacity + id += 1 Saving.settings["lastAvatar"] = path @@ -547,7 +582,7 @@ func moveSpriteMenu(delta): var size = get_viewport().get_visible_rect().size - var windowLength = 1400 + var windowLength = 1800 $ViewerArrows/Arrows.position.y = size.y - 25 @@ -604,7 +639,7 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed): var has_ctrl = keys_pressed.has(KEY_CTRL) and keys_pressed[KEY_CTRL] var has_shift = keys_pressed.has(KEY_SHIFT) and keys_pressed[KEY_SHIFT] var has_alt = keys_pressed.has(KEY_ALT) and keys_pressed[KEY_ALT] - var has_meta = keys_pressed.has(KEY_META) and keys_pressed[KEY_META] # Command key on macOS + var meta_pressed = keys_pressed.has(KEY_META) and keys_pressed[KEY_META] # Command key on macOS # Build modifier prefix once if has_ctrl: @@ -613,7 +648,7 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed): modifiers.append("Shift") if has_alt: modifiers.append("Alt") - if has_meta: + if meta_pressed: modifiers.append("Cmd") # Command key on macOS var modifier_prefix = "" diff --git a/main_scenes/main.tscn b/main_scenes/main.tscn index 4293a11..44b9bc3 100644 --- a/main_scenes/main.tscn +++ b/main_scenes/main.tscn @@ -14744,5 +14744,5 @@ z_index = -4096 [connection signal="visibility_changed" from="ReplaceDialog" to="." method="_on_replace_dialog_visibility_changed"] [connection signal="file_selected" from="SaveDialog" to="." method="_on_save_dialog_file_selected"] [connection signal="file_selected" from="LoadDialog" to="." method="_on_load_dialog_file_selected"] -[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="bgInputSprite"] [connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="_on_background_input_capture_bg_key_pressed"] +[connection signal="bg_key_pressed" from="BackgroundInputCapture" to="." method="bgInputSprite"] diff --git a/project.godot b/project.godot index 30db985..ad6be4e 100644 --- a/project.godot +++ b/project.godot @@ -147,4 +147,6 @@ textures/canvas_textures/default_texture_filter=0 textures/canvas_textures/default_texture_repeat=1 renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" +shading/overrides/force_vertex_shading=true +shading/overrides/force_lambert_over_burley=true environment/defaults/default_clear_color=Color(0.376471, 0.376471, 0.376471, 1) diff --git a/shader/wobble.gdshader b/shader/wobble.gdshader index aa0fa34..1207836 100644 --- a/shader/wobble.gdshader +++ b/shader/wobble.gdshader @@ -1,11 +1,21 @@ shader_type canvas_item; -uniform sampler2D noise_texture:repeat_enable; -uniform float distortion_strengh: hint_range(0, 0.1) = 1.0; +uniform sampler2D noise_texture: source_color, repeat_enable; +uniform float distortion_strengh: hint_range(0, 0.1) = 0.01; uniform float speed: hint_range(0.1, 10) = 1.0; +uniform bool use_noise_texture: hint_default_true = false; void fragment() { - vec4 noise_pixel = texture(noise_texture, UV + floor(TIME*speed)/3.0); - vec2 uv_offset = (noise_pixel.rg * 2.0 - 1.0) * distortion_strengh; + vec2 uv_offset = vec2(0.0); + + if (use_noise_texture) { + vec4 noise_pixel = texture(noise_texture, UV + floor(TIME * speed) / 3.0); + uv_offset = (noise_pixel.rg * 2.0 - 1.0) * distortion_strengh; + } else { + // Fallback procedural noise when no texture is provided + float noise = sin(UV.x * 20.0 + TIME * speed) * cos(UV.y * 20.0 + TIME * speed * 0.7); + uv_offset = vec2(noise) * distortion_strengh; + } + COLOR = texture(TEXTURE, UV + uv_offset); } \ No newline at end of file diff --git a/shader/wobble.gdshader.uid b/shader/wobble.gdshader.uid index 6a1efb3..fbfb14a 100644 --- a/shader/wobble.gdshader.uid +++ b/shader/wobble.gdshader.uid @@ -1 +1 @@ -uid://b342og0l0k1p +uid://4a1uhohwy43l diff --git a/ui_scenes/selectedSprite/mask.gdshader b/ui_scenes/selectedSprite/mask.gdshader new file mode 100644 index 0000000..74d84fa --- /dev/null +++ b/ui_scenes/selectedSprite/mask.gdshader @@ -0,0 +1,23 @@ +shader_type canvas_item; + +uniform sampler2D mask_texture : source_color; +uniform bool use_mask = false; +uniform float mask_opacity : hint_range(0.0, 1.0) = 1.0; + +void fragment() { + // Get the original sprite color + vec4 sprite_color = texture(TEXTURE, UV); + + if (use_mask) { + // Sample the mask texture + vec4 mask_sample = texture(mask_texture, UV); + + // Use the mask's alpha channel to determine visibility + float mask_alpha = mask_sample.a * mask_opacity; + + // Apply the mask by multiplying the sprite's alpha with the mask alpha + sprite_color.a *= mask_alpha; + } + + COLOR = sprite_color; +} diff --git a/ui_scenes/selectedSprite/mask.gdshader.uid b/ui_scenes/selectedSprite/mask.gdshader.uid new file mode 100644 index 0000000..4c00b3a --- /dev/null +++ b/ui_scenes/selectedSprite/mask.gdshader.uid @@ -0,0 +1 @@ +uid://btd72itp0cy6b diff --git a/ui_scenes/selectedSprite/outline.gdshader b/ui_scenes/selectedSprite/outline.gdshader index b4cee71..c5c04b1 100644 --- a/ui_scenes/selectedSprite/outline.gdshader +++ b/ui_scenes/selectedSprite/outline.gdshader @@ -1,17 +1,8 @@ shader_type canvas_item; -uniform float speed; - -uniform sampler2D backImg: repeat_enable; - -uniform sampler2D palette; -uniform float palette_speed = 0.1; +uniform vec4 outline_color : source_color = vec4(1.0, 0.0, 0.0, 0.3); void fragment(){ - - vec2 scroll = vec2(1.0,0.0) * TIME * speed; - vec4 tex = texture(TEXTURE, (UV)); - float palette_swap = mod(tex.r - TIME * palette_speed, 1.0); - COLOR = vec4(texture(palette, vec2(palette_swap, 0)).rgb, tex.a); - + vec4 tex = texture(TEXTURE, UV); + COLOR = vec4(outline_color.rgb, tex.a * outline_color.a); } \ No newline at end of file diff --git a/ui_scenes/selectedSprite/spriteObject.gd b/ui_scenes/selectedSprite/spriteObject.gd index b0686b6..21aa2a2 100644 --- a/ui_scenes/selectedSprite/spriteObject.gd +++ b/ui_scenes/selectedSprite/spriteObject.gd @@ -77,6 +77,12 @@ var ignoreBounce = false var frames = 1 var animSpeed = 0 +#Masking +var mask_texture_path = "" +var mask_texture = null +var use_mask = false +var mask_opacity = 1.0 + var remadePolygon = false var clipped = false @@ -119,6 +125,9 @@ func _ready(): sprite.texture = tex + # Initialize masking + setupMasking() + var bitmap = BitMap.new() bitmap.create_from_image_alpha(imageData) @@ -409,3 +418,53 @@ func visToggle(keys): func makeVis(): $WobbleOrigin/DragOrigin.visible = true + +## Setup masking system +func setupMasking(): + if use_mask and mask_texture_path != "": + loadMaskTexture() + updateMaskShader() + +## Load mask texture from file path +func loadMaskTexture(): + if mask_texture_path == "": + return + + var img = Image.new() + var err = img.load(mask_texture_path) + if err != OK: + print_debug("Failed to load mask texture: " + mask_texture_path) + return + + mask_texture = ImageTexture.create_from_image(img) + +## Apply or remove mask shader +func updateMaskShader(): + if use_mask and mask_texture != null: + # Create mask material with shader + var mask_shader = load("res://ui_scenes/selectedSprite/mask.gdshader") + var mask_material = ShaderMaterial.new() + mask_material.shader = mask_shader + mask_material.set_shader_parameter("mask_texture", mask_texture) + mask_material.set_shader_parameter("use_mask", true) + mask_material.set_shader_parameter("mask_opacity", mask_opacity) + + sprite.material = mask_material + else: + # Remove mask material + sprite.material = null + +## Set mask texture from external source +func setMaskTexture(texture_path: String, opacity: float = 1.0): + mask_texture_path = texture_path + mask_opacity = opacity + use_mask = true + loadMaskTexture() + updateMaskShader() + +## Remove masking +func removeMask(): + use_mask = false + mask_texture = null + mask_texture_path = "" + updateMaskShader() diff --git a/ui_scenes/settings/settings_menu.gd b/ui_scenes/settings/settings_menu.gd index 927dc4f..4043bda 100644 --- a/ui_scenes/settings/settings_menu.gd +++ b/ui_scenes/settings/settings_menu.gd @@ -202,8 +202,8 @@ func _on_costume_check_toggled(button_pressed): ## @param button_pressed: bool - Whether StreamDeck should be enabled func _on_stream_deck_check_toggled(button_pressed): Saving.settings["useStreamDeck"] = button_pressed - if ElgatoStreamDeck: - ElgatoStreamDeck.refresh_connection() + if get_node_or_null("/root/ElgatoStreamDeck"): + get_node("/root/ElgatoStreamDeck").refresh_connection() Global.pushUpdate("StreamDeck integration " + ("enabled" if button_pressed else "disabled") + ".") diff --git a/ui_scenes/spriteEditMenu/sprite_viewer.gd b/ui_scenes/spriteEditMenu/sprite_viewer.gd index fe4d250..e5caab8 100644 --- a/ui_scenes/spriteEditMenu/sprite_viewer.gd +++ b/ui_scenes/spriteEditMenu/sprite_viewer.gd @@ -75,6 +75,15 @@ func setImage(): $VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\"" + # Update masking controls + $Masking/useMaskCheckbox.button_pressed = Global.heldSprite.use_mask + $Masking/maskOpacityLabel.text = "mask opacity: " + str(Global.heldSprite.mask_opacity) + $Masking/maskOpacity.value = Global.heldSprite.mask_opacity + if Global.heldSprite.mask_texture_path.is_empty(): + $Masking/maskPathLabel.text = "mask: (none)" + else: + $Masking/maskPathLabel.text = "mask: " + Global.heldSprite.mask_texture_path.get_file() + changeRotLimit() setLayerButtons() @@ -364,8 +373,25 @@ func _on_delete_pressed(): func _on_set_toggle_pressed(): $VisToggle/setToggle/Label.text = "toggle: AWAITING INPUT" await Global.main.fatfuckingballs - - var keys = await Global.main.spriteVisToggles - var key = keys[0] - Global.heldSprite.toggle = key - $VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\"" + +## Masking signal handlers +func _on_use_mask_checkbox_toggled(button_pressed): + Global.heldSprite.use_mask = button_pressed + Global.heldSprite.updateMaskShader() + +func _on_mask_opacity_value_changed(value): + Global.heldSprite.mask_opacity = value + $Masking/maskOpacityLabel.text = "mask opacity: " + str(value) + Global.heldSprite.updateMaskShader() + +func _on_select_mask_pressed(): + Global.main.openMaskSelection() + +func updateMaskDisplay(path: String): + $Masking/maskPathLabel.text = "mask: " + path.get_file() + $Masking/useMaskCheckbox.button_pressed = true + +func _on_clear_mask_pressed(): + Global.heldSprite.removeMask() + $Masking/maskPathLabel.text = "mask: (none)" + $Masking/useMaskCheckbox.button_pressed = false diff --git a/ui_scenes/spriteEditMenu/sprite_viewer.tscn b/ui_scenes/spriteEditMenu/sprite_viewer.tscn index f3b4a4b..455f516 100644 --- a/ui_scenes/spriteEditMenu/sprite_viewer.tscn +++ b/ui_scenes/spriteEditMenu/sprite_viewer.tscn @@ -29,7 +29,7 @@ [ext_resource type="Texture2D" uid="uid://gflmeocxfnrq" path="res://ui_scenes/settings/deleteHotkey.png" id="27_fgu6g"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_21kva"] -size = Vector2(245, 1341) +size = Vector2(245, 1596.5) [sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"] shader = ExtResource("3_ky0lu") @@ -14285,7 +14285,7 @@ script = ExtResource("1_hp0e3") z_index = -2 [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] -position = Vector2(122.5, 670.5) +position = Vector2(122.5, 725) shape = SubResource("RectangleShape2D_21kva") [node name="Border" type="Sprite2D" parent="."] @@ -14910,6 +14910,66 @@ position = Vector2(-2, -2) texture = ExtResource("27_fgu6g") centered = false +[node name="Masking" type="Node2D" parent="."] +position = Vector2(0, 1360.83) + +[node name="animationBox" type="Sprite2D" parent="Masking"] +position = Vector2(125, 92.4052) +scale = Vector2(1, 0.905768) +texture = ExtResource("15_wqi8b") + +[node name="maskingLabel" type="Label" parent="Masking"] +offset_left = 20.0 +offset_right = 120.0 +offset_bottom = 26.0 +text = "MASKING" +horizontal_alignment = 1 + +[node name="useMaskCheckbox" type="CheckBox" parent="Masking"] +offset_left = 20.0 +offset_top = 30.0 +offset_right = 200.0 +offset_bottom = 54.0 +text = "Use Mask" + +[node name="maskOpacityLabel" type="Label" parent="Masking"] +offset_left = 20.0 +offset_top = 60.0 +offset_right = 200.0 +offset_bottom = 86.0 +text = "mask opacity: 1.0" + +[node name="maskOpacity" type="HSlider" parent="Masking"] +offset_left = 20.0 +offset_top = 90.0 +offset_right = 200.0 +offset_bottom = 106.0 +max_value = 1.0 +step = 0.01 +value = 1.0 + +[node name="maskPathLabel" type="Label" parent="Masking"] +offset_left = 20.0 +offset_top = 120.0 +offset_right = 400.0 +offset_bottom = 146.0 +text = "mask: (none)" +autowrap_mode = 2 + +[node name="selectMask" type="Button" parent="Masking"] +offset_left = 20.0 +offset_top = 150.0 +offset_right = 120.0 +offset_bottom = 181.0 +text = "Select Mask" + +[node name="clearMask" type="Button" parent="Masking"] +offset_left = 130.0 +offset_top = 150.0 +offset_right = 220.0 +offset_bottom = 181.0 +text = "Clear Mask" + [connection signal="pressed" from="Buttons/Speaking/speaking" to="." method="_on_speaking_pressed"] [connection signal="pressed" from="Buttons/Blinking/blinking" to="." method="_on_blinking_pressed"] [connection signal="pressed" from="Buttons/Trash/trash" to="." method="_on_trash_pressed"] @@ -14941,3 +15001,7 @@ centered = false [connection signal="pressed" from="Layers/Layer10/layerButton10" to="." method="_on_layer_button_10_pressed"] [connection signal="pressed" from="VisToggle/setToggle" to="." method="_on_set_toggle_pressed"] [connection signal="pressed" from="VisToggle/setToggle/delete" to="." method="_on_delete_pressed"] +[connection signal="toggled" from="Masking/useMaskCheckbox" to="." method="_on_use_mask_checkbox_toggled"] +[connection signal="value_changed" from="Masking/maskOpacity" to="." method="_on_mask_opacity_value_changed"] +[connection signal="pressed" from="Masking/selectMask" to="." method="_on_select_mask_pressed"] +[connection signal="pressed" from="Masking/clearMask" to="." method="_on_clear_mask_pressed"]