wobble sync groups !build

This commit is contained in:
2025-07-20 10:24:53 +10:00
parent 5dfe68cb81
commit cebef2f06b
19 changed files with 1046 additions and 43 deletions

View File

@@ -94,6 +94,9 @@ var affectChildrenOpacity = false
#Vis toggle
var toggle = "null"
#Wobble Sync Group
var wobbleSyncGroup = ""
func _ready():
Global.main.spriteVisToggles.connect(visToggle)
@@ -171,6 +174,8 @@ func _ready():
setClip(clipped)
# Always initialize opacity shader
updateShaderOpacity()
if Global.filtering:
sprite.texture_filter = 2
@@ -235,6 +240,16 @@ func _process(delta):
grabArea.visible = false
originSprite.visible = false
# Disable grabArea input and detection when dialogs are open
if Global.main and Global.main.fileSystemOpen:
grabArea.input_pickable = false
grabArea.monitoring = false
grabArea.monitorable = false
else:
grabArea.input_pickable = true
grabArea.monitoring = true
grabArea.monitorable = true
var glob = dragger.global_position
if ignoreBounce:
glob.y -= Global.main.bounceChange
@@ -282,7 +297,12 @@ func talkBlink():
# Only handle talk/blink/edit visibility with modulate
# Opacity slider effects are handled by shader
sprite.self_modulate.a = 1.0 # Always keep original texture alpha for clipping
var previousAlpha = sprite.modulate.a
sprite.modulate.a = baseAlpha # Only apply talk/blink/edit visibility
# Update shader if modulate alpha changed (for editor dimming)
if previousAlpha != baseAlpha:
updateShaderOpacity()
func calculateParentOpacityMultiplier():
var multiplier = 1.0
@@ -296,7 +316,40 @@ func calculateParentOpacityMultiplier():
return multiplier
## Update wobble parameters and sync to group if needed
func updateWobbleParameter(parameter: String, value: float):
print("DEBUG: updateWobbleParameter called - parameter: ", parameter, ", value: ", value, ", sprite ID: ", id)
print("DEBUG: Current wobbleSyncGroup: '", wobbleSyncGroup, "'")
# Update this sprite's parameter first
match parameter:
"xFrq": xFrq = value
"xAmp": xAmp = value
"yFrq": yFrq = value
"yAmp": yAmp = value
"rFrq": rFrq = value
"rAmp": rAmp = value
print("DEBUG: Parameter updated on sprite. New values - xFrq:", xFrq, " xAmp:", xAmp, " yFrq:", yFrq, " yAmp:", yAmp, " rFrq:", rFrq, " rAmp:", rAmp)
# If this sprite is in a sync group, update the entire group
if wobbleSyncGroup != "" and WobbleSyncManager:
print("DEBUG: Sprite is in sync group '", wobbleSyncGroup, "' - updating group wobble parameters")
WobbleSyncManager.updateGroupWobble(wobbleSyncGroup, xFrq, xAmp, yFrq, yAmp, rFrq, rAmp)
# Don't refresh UI immediately - let the sync process complete first
# The wobble sync control will handle UI updates when needed
else:
print("DEBUG: Sprite not in sync group or WobbleSyncManager not available")
## Check if this sprite is synced to a group
func isSynced() -> bool:
return wobbleSyncGroup != ""
func delete():
# Clean up from wobble sync groups
if wobbleSyncGroup != "" and WobbleSyncManager:
WobbleSyncManager.onSpriteDeleted(id)
queue_free()
func _physics_process(delta):
@@ -460,17 +513,17 @@ func updateShaderOpacity():
# Calculate total opacity from user settings and parent hierarchy
var totalOpacity = spriteOpacity * calculateParentOpacityMultiplier()
if totalOpacity >= 1.0:
# No shader needed for 100% opacity
sprite.material = null
else:
# Create or update shader material for opacity less than 100%
if opacityMaterial == null:
opacityMaterial = ShaderMaterial.new()
opacityMaterial.shader = opacityShader
opacityMaterial.set_shader_parameter("opacity", totalOpacity)
sprite.material = opacityMaterial
# Also factor in the modulate alpha for editor dimming/onion skinning
var editorAlpha = sprite.modulate.a
var finalOpacity = totalOpacity * editorAlpha
# Always apply the opacity shader to ensure it's loaded
if opacityMaterial == null:
opacityMaterial = ShaderMaterial.new()
opacityMaterial.shader = opacityShader
opacityMaterial.set_shader_parameter("opacity", finalOpacity)
sprite.material = opacityMaterial
func updateChildrenOpacityRecursively():
var linkedSprites = getAllLinkedSprites()

View File

@@ -201,8 +201,9 @@ 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()
var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
if streamdeck_node != null:
streamdeck_node.refresh_connection()
Global.pushUpdate("StreamDeck integration " + ("enabled" if button_pressed else "disabled") + ".")
## Handle experimental microphone loudness toggle

View File

@@ -9,11 +9,16 @@ extends Node2D
@onready var coverCollider = $Area2D/CollisionShape2D
@onready var wobbleSyncControl = null # Will be assigned in _ready()
func _ready():
Global.spriteEdit = self
# Find the wobble sync control node
wobbleSyncControl = find_child("WobbleSyncControl")
if wobbleSyncControl == null:
print("Warning: WobbleSyncControl node not found in sprite viewer")
func setImage():
if Global.heldSprite == null:
return
@@ -83,6 +88,16 @@ func setImage():
setLayerButtons()
# Update wobble sync control
if wobbleSyncControl:
wobbleSyncControl.setSprite(Global.heldSprite)
# Force a refresh to ensure dropdown is populated
await get_tree().process_frame
wobbleSyncControl.updateUI()
# Update wobble control states based on sync status
updateWobbleControlStates()
if Global.heldSprite.parentId == null:
$Buttons/Unlink.visible = false
parentSpin.visible = false
@@ -98,6 +113,30 @@ func setImage():
parentSpin.pixel_size = 1.5 / nodes[0].imageData.get_size().y
parentSpin.hframes = nodes[0].frames
parentSpin.visible = true
## Update wobble control states based on sync status
func updateWobbleControlStates():
if Global.heldSprite == null:
return
var isSynced = Global.heldSprite.isSynced()
# Keep wobble controls enabled even when synced (they edit group settings)
$WobbleControl/xFrq.editable = true
$WobbleControl/xAmp.editable = true
$WobbleControl/yFrq.editable = true
$WobbleControl/yAmp.editable = true
$WobbleControl/rFrq.editable = true
$WobbleControl/rAmp.editable = true
# Visual feedback for sync state - slight tint to indicate group editing
var tint = Color(1.0, 1.0, 0.9) if isSynced else Color.WHITE
$WobbleControl/xFrq.modulate = tint
$WobbleControl/xAmp.modulate = tint
$WobbleControl/yFrq.modulate = tint
$WobbleControl/yAmp.modulate = tint
$WobbleControl/rFrq.modulate = tint
$WobbleControl/rAmp.modulate = tint
func _process(delta):
@@ -131,31 +170,55 @@ func _on_drag_slider_value_changed(value):
func _on_x_frq_value_changed(value):
print("DEBUG: _on_x_frq_value_changed called with value: ", value)
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
Global.heldSprite.xFrq = value
Global.heldSprite.updateWobbleParameter("xFrq", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_x_amp_value_changed(value):
print("DEBUG: _on_x_amp_value_changed called with value: ", value)
$WobbleControl/xAmpLabel.text = "x amplitude: " + str(value)
Global.heldSprite.xAmp = value
Global.heldSprite.updateWobbleParameter("xAmp", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_y_frq_value_changed(value):
print("DEBUG: _on_y_frq_value_changed called with value: ", value)
$WobbleControl/yFrqLabel.text = "y frequency: " + str(value)
Global.heldSprite.yFrq = value
Global.heldSprite.updateWobbleParameter("yFrq", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_y_amp_value_changed(value):
print("DEBUG: _on_y_amp_value_changed called with value: ", value)
$WobbleControl/yAmpLabel.text = "y amplitude: " + str(value)
Global.heldSprite.yAmp = value
Global.heldSprite.updateWobbleParameter("yAmp", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_r_frq_value_changed(value):
print("DEBUG: _on_r_frq_value_changed called with value: ", value)
$WobbleControl/rFrqLabel.text = "rotation frequency: " + str(value)
Global.heldSprite.rFrq = value
Global.heldSprite.updateWobbleParameter("rFrq", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_r_amp_value_changed(value):
print("DEBUG: _on_r_amp_value_changed called with value: ", value)
$WobbleControl/rAmpLabel.text = "rotation amplitude: " + str(value) + "%"
Global.heldSprite.rAmp = value
Global.heldSprite.updateWobbleParameter("rAmp", value)
# Refresh wobble sync control after parameter change
if wobbleSyncControl:
wobbleSyncControl.updateUI()
func _on_r_drag_value_changed(value):
@@ -180,6 +243,10 @@ func _on_blinking_pressed():
func _on_trash_pressed():
# Clean up wobble sync group membership before deleting
if Global.heldSprite.wobbleSyncGroup != "" and WobbleSyncManager:
WobbleSyncManager.onSpriteDeleted(Global.heldSprite.id)
Global.heldSprite.queue_free()
Global.heldSprite = null

View File

@@ -1,9 +1,10 @@
[gd_scene load_steps=75 format=4 uid="uid://d3anahesvdgfh"]
[gd_scene load_steps=76 format=4 uid="uid://d3anahesvdgfh"]
[ext_resource type="Texture2D" uid="uid://4tbpehro4x5p" path="res://ui_scenes/spriteEditMenu/border.png" id="1_b54o8"]
[ext_resource type="Script" uid="uid://bgoaqt86bc73p" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gd" id="1_hp0e3"]
[ext_resource type="Shader" uid="uid://bfxkb7p0y73yn" path="res://ui_scenes/spriteEditMenu/sprite_viewer.gdshader" id="3_ky0lu"]
[ext_resource type="Texture2D" uid="uid://xxhqegm7rq6n" path="res://ui_scenes/button sprites/add.png" id="3_tjnto"]
[ext_resource type="PackedScene" uid="uid://bkwf3j2pdspxw" path="res://ui_scenes/spriteEditMenu/wobble_sync_control.tscn" id="3_wobble_sync"]
[ext_resource type="Texture2D" uid="uid://bd68uiemrcy5v" path="res://ui_scenes/spriteEditMenu/speaking.png" id="4_shsbk"]
[ext_resource type="Texture2D" uid="uid://doxw3dy86i2e4" path="res://ui_scenes/spriteEditMenu/blink.png" id="5_ed14x"]
[ext_resource type="Texture2D" uid="uid://dn5hs0pfutv1l" path="res://ui_scenes/spriteEditMenu/trash.png" id="6_304pj"]
@@ -29,7 +30,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, 1455)
size = Vector2(245, 1550)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"]
shader = ExtResource("3_ky0lu")
@@ -14285,7 +14286,7 @@ script = ExtResource("1_hp0e3")
z_index = -2
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
position = Vector2(122.5, 727.5)
position = Vector2(122.5, 775)
shape = SubResource("RectangleShape2D_21kva")
[node name="Border" type="Sprite2D" parent="."]
@@ -14469,11 +14470,11 @@ scrollable = false
[node name="WobbleControl" type="Node2D" parent="."]
z_index = 1
position = Vector2(-3, 486)
position = Vector2(-3, 491)
[node name="animationBox" type="Sprite2D" parent="WobbleControl"]
position = Vector2(125, 384.5)
scale = Vector2(1, 1.25769)
position = Vector2(125, 431)
scale = Vector2(1, 1.65769)
texture = ExtResource("15_wqi8b")
[node name="xFrqLabel" type="Label" parent="WobbleControl"]
@@ -14489,7 +14490,7 @@ offset_top = 263.0
offset_right = 235.0
offset_bottom = 283.0
theme = SubResource("Theme_mrbyn")
max_value = 0.1
max_value = 1.0
step = 0.001
scrollable = false
@@ -14522,7 +14523,7 @@ offset_top = 364.0
offset_right = 235.0
offset_bottom = 384.0
theme = SubResource("Theme_mrbyn")
max_value = 0.1
max_value = 1.0
step = 0.001
scrollable = false
@@ -14555,7 +14556,7 @@ offset_top = 465.0
offset_right = 235.0
offset_bottom = 485.0
theme = SubResource("Theme_mrbyn")
max_value = 0.1
max_value = 1.0
step = 0.001
scrollable = false
@@ -14574,6 +14575,12 @@ offset_bottom = 528.0
theme = SubResource("Theme_mrbyn")
scrollable = false
[node name="WobbleSyncControl" parent="WobbleControl" instance=ExtResource("3_wobble_sync")]
offset_left = 12.0
offset_top = 559.0
offset_right = 235.0
offset_bottom = 624.0
[node name="Rotation" type="Node2D" parent="."]
position = Vector2(-1, 224)
@@ -14613,7 +14620,7 @@ scrollable = false
[node name="RotationalLimits" type="Node2D" parent="."]
z_index = -1
position = Vector2(0, 435)
position = Vector2(0, 532)
[node name="RotBack" type="Sprite2D" parent="RotationalLimits"]
clip_children = 2
@@ -14762,7 +14769,7 @@ offset_bottom = 1166.0
theme = SubResource("Theme_mrbyn")
[node name="Layers" type="Node2D" parent="."]
position = Vector2(0, 443)
position = Vector2(0, 538)
[node name="Layer1" type="Sprite2D" parent="Layers"]
position = Vector2(23, 878)
@@ -14899,7 +14906,7 @@ position = Vector2(23, 878)
texture = ExtResource("20_px0dt")
[node name="VisToggle" type="Node2D" parent="."]
position = Vector2(11, 1404)
position = Vector2(11, 1496)
[node name="setToggle" type="Button" parent="VisToggle"]
custom_minimum_size = Vector2(0, 37.125)

View File

@@ -0,0 +1,207 @@
extends Control
## WobbleSyncControl - UI component for managing wobble sync groups
## Designed to integrate into the sprite editor panel
@onready var groupDropdown = $VBoxContainer/GroupRow/GroupDropdown
@onready var syncIndicator = $VBoxContainer/SyncIndicator
@onready var createGroupDialog = $CreateGroupDialog
@onready var groupNameInput = $CreateGroupDialog/VBoxContainer/LineEdit
var currentSprite = null
var updating_dropdown = false # Flag to prevent recursive calls
signal group_changed(sprite_id: int, group_name: String)
func _ready():
# Wait for autoload to be ready
await get_tree().process_frame
# Connect UI signals - no add button to connect
groupDropdown.item_selected.connect(_on_group_dropdown_selected)
createGroupDialog.confirmed.connect(_on_create_group_confirmed)
createGroupDialog.canceled.connect(_on_create_group_cancelled)
# Connect to wobble sync manager signals
if WobbleSyncManager:
WobbleSyncManager.group_created.connect(_on_group_created)
WobbleSyncManager.group_deleted.connect(_on_group_deleted)
# Setup create group dialog
createGroupDialog.title = "Create Wobble Sync Group"
createGroupDialog.size = Vector2(300, 150)
createGroupDialog.unresizable = false
createGroupDialog.transient = true # Prevent click-through
createGroupDialog.exclusive = true # Make modal
createGroupDialog.popup_window = false # Keep as embedded dialog
createGroupDialog.always_on_top = true
updateUI()
## Update the UI based on current sprite selection
func updateUI():
updateGroupDropdown()
updateSyncIndicator()
## Update the group dropdown with available options
func updateGroupDropdown():
updating_dropdown = true # Prevent recursive calls
groupDropdown.clear()
# Add "None" option
groupDropdown.add_item("None")
# Add existing groups
if WobbleSyncManager:
var groups = WobbleSyncManager.getGroupNames()
print("DEBUG: updateGroupDropdown - available groups: ", groups)
for group_name in groups:
groupDropdown.add_item(group_name)
else:
print("DEBUG: updateGroupDropdown - WobbleSyncManager not available")
# Add "Create New..." option
groupDropdown.add_separator()
groupDropdown.add_item("Create New...")
# Select current group if sprite is synced
if currentSprite != null and currentSprite.wobbleSyncGroup != "":
print("DEBUG: updateGroupDropdown - sprite has group '", currentSprite.wobbleSyncGroup, "'")
var group_index = -1
for i in range(groupDropdown.get_item_count()):
if groupDropdown.get_item_text(i) == currentSprite.wobbleSyncGroup:
group_index = i
break
if group_index != -1:
print("DEBUG: updateGroupDropdown - selecting group at index ", group_index)
groupDropdown.selected = group_index
else:
print("DEBUG: updateGroupDropdown - group '", currentSprite.wobbleSyncGroup, "' not found in dropdown")
groupDropdown.selected = 0 # Select "None"
else:
print("DEBUG: updateGroupDropdown - sprite has no group, selecting 'None'")
groupDropdown.selected = 0 # Select "None"
updating_dropdown = false
## Update the sync indicator
func updateSyncIndicator():
if currentSprite == null:
syncIndicator.visible = false
return
if currentSprite.isSynced():
syncIndicator.visible = true
syncIndicator.text = "⚡ Synced to \"" + currentSprite.wobbleSyncGroup + "\""
else:
syncIndicator.visible = false
## Set the current sprite to manage
func setSprite(sprite):
print("DEBUG: WobbleSyncControl.setSprite called with sprite ID: ", sprite.id if sprite else "null")
currentSprite = sprite
if currentSprite:
print("DEBUG: Current sprite wobbleSyncGroup: '", currentSprite.wobbleSyncGroup, "'")
print("DEBUG: Current sprite isSynced(): ", currentSprite.isSynced())
updateUI()
## Get available groups for the dropdown (excluding "None" and "Create New...")
func getAvailableGroups() -> Array:
var groups = []
for i in range(1, groupDropdown.get_item_count() - 2): # Skip "None" and "Create New..."
groups.append(groupDropdown.get_item_text(i))
return groups
## Handle group dropdown selection
func _on_group_dropdown_selected(index: int):
if currentSprite == null or updating_dropdown:
return
var selected_text = groupDropdown.get_item_text(index)
print("DEBUG: Dropdown selected: '", selected_text, "' at index ", index)
match selected_text:
"None":
# Remove sprite from any group
if currentSprite.wobbleSyncGroup != "" and WobbleSyncManager:
print("DEBUG: Removing sprite ID ", currentSprite.id, " from group '", currentSprite.wobbleSyncGroup, "'")
WobbleSyncManager.removeSpriteFromAllGroups(currentSprite.id)
Global.pushUpdate("Removed sprite from wobble sync group")
"Create New...":
print("DEBUG: User selected 'Create New...' from dropdown")
# Show create group dialog
groupNameInput.text = ""
groupNameInput.placeholder_text = "Enter group name..."
createGroupDialog.popup_centered_clamped(Vector2i(300, 150))
groupNameInput.grab_focus()
# Reset dropdown to current selection
updateGroupDropdown()
_:
# Join selected group
if WobbleSyncManager and WobbleSyncManager.hasGroup(selected_text):
print("DEBUG: Adding sprite ID ", currentSprite.id, " to group '", selected_text, "'")
WobbleSyncManager.addSpriteToGroup(currentSprite.id, selected_text)
# Refresh sprite editor to show updated wobble values
if Global.spriteEdit:
Global.spriteEdit.setImage()
else:
print("DEBUG: Group '", selected_text, "' not found or WobbleSyncManager not available")
updateSyncIndicator()
## Handle create group dialog confirmation
func _on_create_group_confirmed():
var group_name = groupNameInput.text.strip_edges()
print("DEBUG: Create group confirmed with name: '", group_name, "'")
if group_name == "":
print("DEBUG: Group name is empty")
Global.pushUpdate("Group name cannot be empty")
return
if WobbleSyncManager and WobbleSyncManager.hasGroup(group_name):
print("DEBUG: Group '", group_name, "' already exists")
Global.pushUpdate("Group \"" + group_name + "\" already exists")
return
# Create group with current sprite
if currentSprite != null and WobbleSyncManager:
print("DEBUG: Creating group '", group_name, "' with sprite ID ", currentSprite.id)
if WobbleSyncManager.createGroup(group_name, currentSprite.id):
print("DEBUG: Group created successfully")
createGroupDialog.hide()
updateUI()
# Refresh sprite editor to show sync status
if Global.spriteEdit:
Global.spriteEdit.setImage()
else:
print("DEBUG: Failed to create group")
else:
print("DEBUG: No current sprite to add to group")
## Handle create group dialog cancellation
func _on_create_group_cancelled():
createGroupDialog.hide()
updateGroupDropdown() # Reset dropdown to current selection
## Handle group creation signal from manager
func _on_group_created(group_name: String):
updateGroupDropdown()
## Handle group deletion signal from manager
func _on_group_deleted(group_name: String):
updateGroupDropdown()
updateSyncIndicator()
## Check if current sprite can have wobble syncing applied
func canSync() -> bool:
return currentSprite != null
## Force refresh the UI - useful after loading saves
func forceRefresh():
print("DEBUG: WobbleSyncControl.forceRefresh called")
updateUI()

View File

@@ -0,0 +1 @@
uid://ykov43vxml0k

View File

@@ -0,0 +1,59 @@
[gd_scene load_steps=2 format=3 uid="uid://bkwf3j2pdspxw"]
[ext_resource type="Script" uid="uid://ykov43vxml0k" path="res://ui_scenes/spriteEditMenu/wobble_sync_control.gd" id="1_k8h4v"]
[node name="WobbleSyncControl" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_k8h4v")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="GroupRow" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="GroupLabel" type="Label" parent="VBoxContainer/GroupRow"]
layout_mode = 2
size_flags_horizontal = 0
text = "Group:"
[node name="GroupDropdown" type="OptionButton" parent="VBoxContainer/GroupRow"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SyncIndicator" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "⚡ Synced to \"Group Name\""
autowrap_mode = 2
[node name="CreateGroupDialog" type="ConfirmationDialog" parent="."]
process_mode = 3
title = "Create Wobble Sync Group"
size = Vector2i(300, 150)
[node name="VBoxContainer" type="VBoxContainer" parent="CreateGroupDialog"]
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="Label" type="Label" parent="CreateGroupDialog/VBoxContainer"]
layout_mode = 2
text = "Enter name for new wobble sync group:"
[node name="LineEdit" type="LineEdit" parent="CreateGroupDialog/VBoxContainer"]
layout_mode = 2
placeholder_text = "Group name..."