mirror of
https://github.com/litruv/PNGTuber-Plus.git
synced 2026-07-24 10:36:01 +10:00
Compare commits
20 Commits
v2025.07.2
...
v2025.07.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 95476cfd57 | |||
| 722fed9b94 | |||
| e8620611df | |||
| 85a208a729 | |||
| 92fc5dda89 | |||
| af17a1dee0 | |||
| 43dfc410dc | |||
| ef8ecb3732 | |||
| 803e867e20 | |||
| cebef2f06b | |||
| e0420a6583 | |||
| 56e76c61e6 | |||
| 5dfe68cb81 | |||
| 6e02a67114 | |||
| 32a67a6617 | |||
| fb0c7a9e3b | |||
| f0ce00b23b | |||
| d4a9f5a0b0 | |||
| 085d6a21a0 | |||
| 99d2d3f508 |
39
README.md
39
README.md
@@ -15,6 +15,14 @@ Added a proper opacity slider for sprites, with an "affect children" toggle. Cli
|
||||

|
||||
|
||||
|
||||
### 🎨 Blend Modes
|
||||
|
||||
Photoshop-style blend modes for sprites! Choose from Normal, Multiply, Screen, Overlay, Soft Light, Hard Light, Color Dodge, Color Burn, Darken, Lighten, Add, and Subtract. Perfect for lighting effects, shadows, or artistic styling.
|
||||
|
||||
* Dropdown selector in the sprite viewer menu
|
||||
* Works alongside opacity controls
|
||||
|
||||
|
||||
### 🌀 Rotation Wobble
|
||||
|
||||
Sprites can now do a subtle (or chaotic) rotational wobble. Frequency + amplitude sliders included.
|
||||
@@ -22,6 +30,17 @@ Sprites can now do a subtle (or chaotic) rotational wobble. Frequency + amplitud
|
||||

|
||||
|
||||
|
||||
### 🔗 Wobble Sync Groups
|
||||
|
||||
Keep multiple sprites in sync with wobble sync groups. Create groups, assign sprites, and all wobble parameters (X/Y/Rotation frequency, amplitude) automatically sync across group members. Perfect for ears, hair, accessories, or any elements that should move together.
|
||||
|
||||
* Create custom sync groups with descriptive names
|
||||
* Assign multiple sprites to the same group
|
||||
* Edit any group member's wobble settings to update the entire group
|
||||
* Visual indicators show which sprites are synced
|
||||
* Groups persist in save files
|
||||
|
||||
|
||||
### ⌨️ Enhanced Modifier Keys
|
||||
|
||||
Supports combos like Ctrl+Shift+Alt+Cmd. For people with more keybinds than fingers.
|
||||
@@ -29,6 +48,25 @@ Supports combos like Ctrl+Shift+Alt+Cmd. For people with more keybinds than fing
|
||||

|
||||
|
||||
|
||||
### ↗️ Editor Middle Mouse Button Panning
|
||||
|
||||
Hold MMB in the editor to pan around the scene, it'll reset when you go back to live.
|
||||
|
||||

|
||||
|
||||
### ↗️ View Mode MMB Shake
|
||||
|
||||
Hold MMB in view mode to shake your character, Show off them physics!
|
||||
|
||||

|
||||
|
||||
|
||||
### 🎞️ Delta Time Physics
|
||||
|
||||
Physics are now framerate-independent. Low FPS won’t make your character move in slow motion anymore.
|
||||
Just smooth, consistent jank if that’s your thing. Great for stop-motion vibes.
|
||||
|
||||
|
||||
### 🎤 Microphone Fixes
|
||||
|
||||
Merged in [k0ffinz’s](https://github.com/k0ffinz/PNGTuber-Plus) audio tweaks:
|
||||
@@ -46,6 +84,7 @@ Updated to **Godot 4.4.1** — no more editor nags or compatibility weirdness.
|
||||
### 🎚️ StreamDeck Integration
|
||||
|
||||
StreamDeck plugin was already in, this just wires up the buttons.
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Credits
|
||||
|
||||
@@ -4,4 +4,4 @@ name="Godot Git Plugin"
|
||||
description="This plugin lets you interact with Git without leaving the Godot editor. More information can be found at https://github.com/godotengine/godot-git-plugin/wiki"
|
||||
author="twaritwaikar"
|
||||
version="v3.1.0"
|
||||
script="godot-git-plugin.gd"
|
||||
script=""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
58
autoload/blend_mode_manager.gd
Normal file
58
autoload/blend_mode_manager.gd
Normal file
@@ -0,0 +1,58 @@
|
||||
extends Node
|
||||
|
||||
## Blend Mode Manager
|
||||
##
|
||||
## Manages blend mode constants and provides utility functions for
|
||||
## working with blend modes in the PNGTuber application.
|
||||
|
||||
## Blend mode constants
|
||||
enum BlendMode {
|
||||
NORMAL = 0,
|
||||
MULTIPLY = 1,
|
||||
SCREEN = 2,
|
||||
OVERLAY = 3,
|
||||
SOFT_LIGHT = 4,
|
||||
HARD_LIGHT = 5,
|
||||
COLOR_DODGE = 6,
|
||||
COLOR_BURN = 7,
|
||||
DARKEN = 8,
|
||||
LIGHTEN = 9,
|
||||
ADD = 10, # Linear Dodge
|
||||
SUBTRACT = 11
|
||||
}
|
||||
|
||||
## Returns an array of blend mode names for use in dropdowns
|
||||
static func get_blend_mode_names() -> Array:
|
||||
return [
|
||||
"Normal",
|
||||
"Multiply",
|
||||
"Screen",
|
||||
"Overlay",
|
||||
"Soft Light",
|
||||
"Hard Light",
|
||||
"Color Dodge",
|
||||
"Color Burn",
|
||||
"Darken",
|
||||
"Lighten",
|
||||
"Add",
|
||||
"Subtract"
|
||||
]
|
||||
|
||||
## Returns the string name for a given blend mode enum value
|
||||
static func get_blend_mode_name(mode: BlendMode) -> String:
|
||||
var names = get_blend_mode_names()
|
||||
if mode >= 0 and mode < names.size():
|
||||
return names[mode]
|
||||
return "Normal"
|
||||
|
||||
## Returns the blend mode enum value for a given name
|
||||
static func get_blend_mode_from_name(name: String) -> BlendMode:
|
||||
var names = get_blend_mode_names()
|
||||
var index = names.find(name)
|
||||
if index != -1:
|
||||
return index as BlendMode
|
||||
return BlendMode.NORMAL
|
||||
|
||||
## Returns whether a blend mode is valid
|
||||
static func is_valid_blend_mode(mode: int) -> bool:
|
||||
return mode >= BlendMode.NORMAL and mode <= BlendMode.SUBTRACT
|
||||
1
autoload/blend_mode_manager.gd.uid
Normal file
1
autoload/blend_mode_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bm1aeoarbpp6b
|
||||
@@ -280,7 +280,6 @@ func blinking():
|
||||
blinkTick = -12
|
||||
|
||||
func epicFail(err):
|
||||
print(fail)
|
||||
if fail == null:
|
||||
return
|
||||
|
||||
|
||||
336
autoload/wobble_sync_manager.gd
Normal file
336
autoload/wobble_sync_manager.gd
Normal file
@@ -0,0 +1,336 @@
|
||||
extends Node
|
||||
|
||||
## WobbleSyncGroupManager - Manages synchronized wobble groups for sprites
|
||||
## Follows class-based OOP pattern as requested
|
||||
|
||||
var syncGroups: Dictionary = {} # groupName: WobbleSyncGroup
|
||||
var spriteGroupMembership: Dictionary = {} # spriteId: groupName
|
||||
|
||||
signal group_created(group_name: String)
|
||||
signal group_deleted(group_name: String)
|
||||
signal sprite_joined_group(sprite_id: int, group_name: String)
|
||||
signal sprite_left_group(sprite_id: int, group_name: String)
|
||||
|
||||
func _ready():
|
||||
# Manager initialization complete
|
||||
pass
|
||||
|
||||
class WobbleSyncGroup:
|
||||
var name: String
|
||||
var xFrq: float = 0.0
|
||||
var xAmp: float = 0.0
|
||||
var yFrq: float = 0.0
|
||||
var yAmp: float = 0.0
|
||||
var rFrq: float = 0.0
|
||||
var rAmp: float = 0.0
|
||||
var memberSprites: Array = []
|
||||
|
||||
func _init(group_name: String):
|
||||
name = group_name
|
||||
|
||||
## Set wobble parameters for the entire group
|
||||
func setWobbleParameters(x_frq: float, x_amp: float, y_frq: float, y_amp: float, r_frq: float, r_amp: float, manager_ref):
|
||||
xFrq = x_frq
|
||||
xAmp = x_amp
|
||||
yFrq = y_frq
|
||||
yAmp = y_amp
|
||||
rFrq = r_frq
|
||||
rAmp = r_amp
|
||||
|
||||
# Update all member sprites using the manager reference
|
||||
var sprites = manager_ref.get_tree().get_nodes_in_group("saved")
|
||||
|
||||
var updated_count = 0
|
||||
for sprite in sprites:
|
||||
if sprite.id in memberSprites:
|
||||
# Preserve the sprite's group membership
|
||||
var current_group = sprite.wobbleSyncGroup
|
||||
sprite.xFrq = xFrq
|
||||
sprite.xAmp = xAmp
|
||||
sprite.yFrq = yFrq
|
||||
sprite.yAmp = yAmp
|
||||
sprite.rFrq = rFrq
|
||||
sprite.rAmp = rAmp
|
||||
# Restore group membership in case it got cleared
|
||||
sprite.wobbleSyncGroup = current_group
|
||||
updated_count += 1
|
||||
|
||||
## Add sprite to this group
|
||||
func addSprite(sprite_id): # Remove type constraint to handle float IDs
|
||||
if sprite_id not in memberSprites:
|
||||
memberSprites.append(sprite_id)
|
||||
else:
|
||||
pass # Sprite already in group
|
||||
|
||||
## Remove sprite from this group
|
||||
func removeSprite(sprite_id): # Remove type constraint
|
||||
memberSprites.erase(sprite_id)
|
||||
|
||||
## Check if group is empty
|
||||
func isEmpty() -> bool:
|
||||
return memberSprites.size() == 0
|
||||
|
||||
## Get group data for saving
|
||||
func toSaveData() -> Dictionary:
|
||||
return {
|
||||
"name": name,
|
||||
"xFrq": xFrq,
|
||||
"xAmp": xAmp,
|
||||
"yFrq": yFrq,
|
||||
"yAmp": yAmp,
|
||||
"rFrq": rFrq,
|
||||
"rAmp": rAmp,
|
||||
"memberSprites": memberSprites
|
||||
}
|
||||
|
||||
## Load group data from save
|
||||
func fromSaveData(data: Dictionary):
|
||||
name = data.get("name", "")
|
||||
xFrq = data.get("xFrq", 0.0)
|
||||
xAmp = data.get("xAmp", 0.0)
|
||||
yFrq = data.get("yFrq", 0.0)
|
||||
yAmp = data.get("yAmp", 0.0)
|
||||
rFrq = data.get("rFrq", 0.0)
|
||||
rAmp = data.get("rAmp", 0.0)
|
||||
memberSprites = data.get("memberSprites", [])
|
||||
|
||||
## Create a new wobble sync group
|
||||
func createGroup(group_name: String, sprite_id = -1) -> bool: # Remove type constraint
|
||||
if group_name.strip_edges() == "" or syncGroups.has(group_name):
|
||||
return false
|
||||
|
||||
var group = WobbleSyncGroup.new(group_name)
|
||||
syncGroups[group_name] = group
|
||||
|
||||
# If a sprite ID is provided, add it and copy its wobble values
|
||||
if sprite_id != -1:
|
||||
var sprite = getSpriteById(sprite_id)
|
||||
if sprite:
|
||||
# Store the sprite's raw wobble values directly
|
||||
group.setWobbleParameters(sprite.xFrq, sprite.xAmp, sprite.yFrq, sprite.yAmp, sprite.rFrq, sprite.rAmp, self)
|
||||
addSpriteToGroup(sprite_id, group_name)
|
||||
|
||||
group_created.emit(group_name)
|
||||
Global.pushUpdate("Created wobble sync group: " + group_name)
|
||||
return true
|
||||
|
||||
## Delete a wobble sync group
|
||||
func deleteGroup(group_name: String):
|
||||
if not syncGroups.has(group_name):
|
||||
return
|
||||
|
||||
var group = syncGroups[group_name]
|
||||
|
||||
# Remove all sprites from the group
|
||||
for sprite_id in group.memberSprites.duplicate():
|
||||
removeSpriteFromGroup(sprite_id, group_name)
|
||||
|
||||
syncGroups.erase(group_name)
|
||||
group_deleted.emit(group_name)
|
||||
Global.pushUpdate("Deleted wobble sync group: " + group_name)
|
||||
|
||||
## Add sprite to a group
|
||||
func addSpriteToGroup(sprite_id, group_name: String) -> bool: # Remove type constraint
|
||||
|
||||
if not syncGroups.has(group_name):
|
||||
return false
|
||||
|
||||
# Remove sprite from any existing group first
|
||||
removeSpriteFromAllGroups(sprite_id)
|
||||
|
||||
var group = syncGroups[group_name]
|
||||
group.addSprite(sprite_id)
|
||||
spriteGroupMembership[sprite_id] = group_name
|
||||
|
||||
# Sync the sprite's wobble parameters to the group
|
||||
var sprite = getSpriteById(sprite_id)
|
||||
if sprite:
|
||||
sprite.wobbleSyncGroup = group_name
|
||||
sprite.xFrq = group.xFrq
|
||||
sprite.xAmp = group.xAmp
|
||||
sprite.yFrq = group.yFrq
|
||||
sprite.yAmp = group.yAmp
|
||||
sprite.rFrq = group.rFrq
|
||||
sprite.rAmp = group.rAmp
|
||||
|
||||
sprite_joined_group.emit(sprite_id, group_name)
|
||||
Global.pushUpdate("Added sprite to wobble sync group: " + group_name)
|
||||
return true
|
||||
|
||||
## Remove sprite from a specific group
|
||||
func removeSpriteFromGroup(sprite_id, group_name: String): # Remove type constraint
|
||||
if not syncGroups.has(group_name):
|
||||
return
|
||||
|
||||
var group = syncGroups[group_name]
|
||||
group.removeSprite(sprite_id)
|
||||
spriteGroupMembership.erase(sprite_id)
|
||||
|
||||
# Clear the sprite's sync group reference
|
||||
var sprite = getSpriteById(sprite_id)
|
||||
if sprite:
|
||||
sprite.wobbleSyncGroup = ""
|
||||
else:
|
||||
pass # Sprite not found
|
||||
|
||||
sprite_left_group.emit(sprite_id, group_name)
|
||||
|
||||
# Auto-delete empty groups
|
||||
if group.isEmpty():
|
||||
deleteGroup(group_name)
|
||||
else:
|
||||
Global.pushUpdate("Removed sprite from wobble sync group: " + group_name)
|
||||
|
||||
## Remove sprite from all groups
|
||||
func removeSpriteFromAllGroups(sprite_id): # Remove type constraint
|
||||
if spriteGroupMembership.has(sprite_id):
|
||||
var group_name = spriteGroupMembership[sprite_id]
|
||||
removeSpriteFromGroup(sprite_id, group_name)
|
||||
else:
|
||||
pass # Sprite not in any group
|
||||
|
||||
## Update wobble parameters for a group
|
||||
func updateGroupWobble(group_name: String, x_frq: float, x_amp: float, y_frq: float, y_amp: float, r_frq: float, r_amp: float):
|
||||
|
||||
if not syncGroups.has(group_name):
|
||||
return
|
||||
|
||||
var group = syncGroups[group_name]
|
||||
group.setWobbleParameters(x_frq, x_amp, y_frq, y_amp, r_frq, r_amp, self)
|
||||
|
||||
## Get sprite's current group name
|
||||
func getSpriteGroupName(sprite_id: int) -> String:
|
||||
return spriteGroupMembership.get(sprite_id, "")
|
||||
|
||||
## Get all group names
|
||||
func getGroupNames() -> Array:
|
||||
return syncGroups.keys()
|
||||
|
||||
## Test function to manually load wobble sync groups
|
||||
func testManualLoad():
|
||||
var test_data = {
|
||||
"ears": {
|
||||
"name": "ears",
|
||||
"xFrq": 0.0,
|
||||
"xAmp": 0.0,
|
||||
"yFrq": 0.019,
|
||||
"yAmp": 3.0,
|
||||
"rFrq": 0.399,
|
||||
"rAmp": 47.0,
|
||||
"memberSprites": [3291286625.0]
|
||||
}
|
||||
}
|
||||
await loadSaveData(test_data)
|
||||
|
||||
## Check if a group exists
|
||||
func hasGroup(group_name: String) -> bool:
|
||||
return syncGroups.has(group_name)
|
||||
|
||||
## Get sprite by ID
|
||||
func getSpriteById(sprite_id): # Remove type constraint to handle float IDs
|
||||
var sprites = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in sprites:
|
||||
if sprite.id == sprite_id:
|
||||
return sprite
|
||||
return null
|
||||
|
||||
## Clean up orphaned sprites (sprites that no longer exist)
|
||||
func cleanupOrphanedSprites():
|
||||
var existing_sprite_ids = []
|
||||
var sprites = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in sprites:
|
||||
existing_sprite_ids.append(sprite.id)
|
||||
|
||||
# Check each group for orphaned sprite IDs
|
||||
for group_name in syncGroups.keys():
|
||||
var group = syncGroups[group_name]
|
||||
var orphaned_ids = []
|
||||
|
||||
for sprite_id in group.memberSprites:
|
||||
if sprite_id not in existing_sprite_ids:
|
||||
orphaned_ids.append(sprite_id)
|
||||
|
||||
# Remove orphaned IDs
|
||||
for orphaned_id in orphaned_ids:
|
||||
group.removeSprite(orphaned_id)
|
||||
spriteGroupMembership.erase(orphaned_id)
|
||||
|
||||
# Delete empty groups
|
||||
if group.isEmpty():
|
||||
deleteGroup(group_name)
|
||||
|
||||
## Get save data for all groups
|
||||
func getSaveData() -> Dictionary:
|
||||
var save_data = {}
|
||||
for group_name in syncGroups.keys():
|
||||
var group = syncGroups[group_name]
|
||||
save_data[group_name] = group.toSaveData()
|
||||
return save_data
|
||||
|
||||
## Load save data for all groups
|
||||
func loadSaveData(save_data: Dictionary):
|
||||
|
||||
# Check if save_data is valid
|
||||
if save_data == null or typeof(save_data) != TYPE_DICTIONARY:
|
||||
return
|
||||
|
||||
if save_data.size() == 0:
|
||||
return
|
||||
|
||||
# Clear existing groups
|
||||
syncGroups.clear()
|
||||
spriteGroupMembership.clear()
|
||||
|
||||
# Load groups from save data
|
||||
var loaded_groups = 0
|
||||
for group_name in save_data.keys():
|
||||
var group_data = save_data[group_name]
|
||||
|
||||
if typeof(group_data) == TYPE_DICTIONARY:
|
||||
var group = WobbleSyncGroup.new(group_name)
|
||||
group.fromSaveData(group_data)
|
||||
syncGroups[group_name] = group
|
||||
loaded_groups += 1
|
||||
|
||||
# Update sprite group membership
|
||||
for sprite_id in group.memberSprites:
|
||||
spriteGroupMembership[sprite_id] = group_name
|
||||
else:
|
||||
pass # Invalid group data
|
||||
|
||||
|
||||
# If no groups were loaded, clear any orphaned group references on sprites
|
||||
if syncGroups.size() == 0:
|
||||
var sprites = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in sprites:
|
||||
if sprite.wobbleSyncGroup != "":
|
||||
sprite.wobbleSyncGroup = ""
|
||||
else:
|
||||
# Apply wobble parameters to sprites with better timing
|
||||
await get_tree().process_frame # Wait for sprites to be loaded
|
||||
await get_tree().process_frame # Extra frame to ensure everything is ready
|
||||
|
||||
for group_name in syncGroups.keys():
|
||||
var group = syncGroups[group_name]
|
||||
for sprite_id in group.memberSprites:
|
||||
var sprite = getSpriteById(sprite_id)
|
||||
if sprite:
|
||||
sprite.wobbleSyncGroup = group_name
|
||||
sprite.xFrq = group.xFrq
|
||||
sprite.xAmp = group.xAmp
|
||||
sprite.yFrq = group.yFrq
|
||||
sprite.yAmp = group.yAmp
|
||||
sprite.rFrq = group.rFrq
|
||||
sprite.rAmp = group.rAmp
|
||||
# Force a complete opacity update after modifying sprite properties
|
||||
if sprite.has_method("updateOpacity"):
|
||||
sprite.updateOpacity()
|
||||
if sprite.has_method("updateShaderOpacity"):
|
||||
sprite.updateShaderOpacity()
|
||||
else:
|
||||
pass # Sprite not found
|
||||
|
||||
|
||||
## Called when a sprite is deleted to clean up references
|
||||
func onSpriteDeleted(sprite_id): # Remove type constraint
|
||||
removeSpriteFromAllGroups(sprite_id)
|
||||
1
autoload/wobble_sync_manager.gd.uid
Normal file
1
autoload/wobble_sync_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccl0r2eov68sy
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,8 @@ extends Node2D
|
||||
@onready var editButton = $Edit/Button
|
||||
|
||||
func _process(delta):
|
||||
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||
if Rect2(editButton.get_parent().position-Vector2(24,24),editButton.size).has_point(get_local_mouse_position()):
|
||||
editSprite.scale = lerp(editSprite.scale,Vector2(1.2,1.2),0.2)
|
||||
editSprite.scale = lerp(editSprite.scale, Vector2(1.2,1.2), delta * uiAnimSpeed)
|
||||
else:
|
||||
editSprite.scale = lerp(editSprite.scale,Vector2(1.0,1.0),0.2)
|
||||
editSprite.scale = lerp(editSprite.scale, Vector2(1.0,1.0), delta * uiAnimSpeed)
|
||||
|
||||
@@ -30,7 +30,8 @@ func _process(delta):
|
||||
if buttons[b] == null:
|
||||
continue
|
||||
if Rect2(buttons[b].get_parent().position-Vector2(24,24),buttons[b].size).has_point(get_local_mouse_position()):
|
||||
sprites[s].scale = lerp(sprites[s].scale,Vector2(1.2,1.2),0.2)
|
||||
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||
sprites[s].scale = lerp(sprites[s].scale, Vector2(1.2,1.2), delta * uiAnimSpeed)
|
||||
if Input.is_action_pressed("mouse_left"):
|
||||
sprites[s].scale = Vector2(0.6,0.6)
|
||||
match b:
|
||||
@@ -50,7 +51,8 @@ func _process(delta):
|
||||
Global.mouse.text = "Duplicate sprite"
|
||||
|
||||
else:
|
||||
sprites[s].scale = lerp(sprites[s].scale,Vector2(1.0,1.0),0.2)
|
||||
var uiAnimSpeed = Global.main.physicsTimeMultiplier * 20.0 if Global.main else 120.0
|
||||
sprites[s].scale = lerp(sprites[s].scale, Vector2(1.0,1.0), delta * uiAnimSpeed)
|
||||
s += 1
|
||||
|
||||
var newColor = Color.DARK_SLATE_GRAY if Global.heldSprite == null else Color.WHITE
|
||||
|
||||
@@ -36,6 +36,10 @@ var yVel = 0
|
||||
var bounceSlider = 250
|
||||
var bounceGravity = 1000
|
||||
|
||||
# Physics timing multiplier - adjust this to fine-tune physics speed
|
||||
# 1.0 = very slow, 10.0 = reasonable, 60.0 = old frame-based speed
|
||||
var physicsTimeMultiplier = 6.0
|
||||
|
||||
#Costumes
|
||||
var costume = 1
|
||||
var bounceOnCostumeChange = false
|
||||
@@ -49,6 +53,12 @@ var initialPanPosition = Vector2.ZERO
|
||||
var initialCameraOffset = Vector2.ZERO
|
||||
var cameraOffset = Vector2.ZERO
|
||||
|
||||
#View Mode Panning
|
||||
var viewModePanning = false
|
||||
var initialViewPanPosition = Vector2.ZERO
|
||||
var viewPanOffset = Vector2.ZERO
|
||||
var viewPanLerpSpeed = 3.0
|
||||
|
||||
var bounceChange = 0.0
|
||||
|
||||
#IMPORTANT
|
||||
@@ -65,17 +75,23 @@ func _ready():
|
||||
Global.main = self
|
||||
Global.fail = $Failed
|
||||
|
||||
var newUser = Saving.settings["newUser"]
|
||||
|
||||
Global.connect("startSpeaking",onSpeak)
|
||||
|
||||
ElgatoStreamDeck.on_key_down.connect(changeCostumeStreamDeck)
|
||||
# Connect to StreamDeck if available
|
||||
var streamdeck_node = get_node_or_null("/root/ElgatoStreamDeck")
|
||||
if streamdeck_node != null:
|
||||
streamdeck_node.on_key_down.connect(changeCostumeStreamDeck)
|
||||
|
||||
if Saving.settings["newUser"]:
|
||||
_on_load_dialog_file_selected("default")
|
||||
Saving.settings["newUser"] = false
|
||||
saveLoaded = true
|
||||
if newUser:
|
||||
loadDefaultAvatar()
|
||||
else:
|
||||
_on_load_dialog_file_selected(Saving.settings["lastAvatar"])
|
||||
var lastAvatar = Saving.settings.get("lastAvatar", "")
|
||||
if lastAvatar != null and lastAvatar != "" and typeof(lastAvatar) == TYPE_STRING and FileAccess.file_exists(lastAvatar):
|
||||
loadAvatar(lastAvatar)
|
||||
else:
|
||||
loadDefaultAvatar()
|
||||
|
||||
$ControlPanel/volumeSlider.value = Saving.settings["volume"]
|
||||
$ControlPanel/sensitiveSlider.value = Saving.settings["sense"]
|
||||
@@ -141,18 +157,28 @@ func _ready():
|
||||
func _process(delta):
|
||||
var hold = origin.get_parent().position.y
|
||||
|
||||
origin.get_parent().position.y += yVel * 0.0166
|
||||
origin.get_parent().position.y += yVel * delta * physicsTimeMultiplier
|
||||
if origin.get_parent().position.y > 0:
|
||||
origin.get_parent().position.y = 0
|
||||
bounceChange = hold - origin.get_parent().position.y
|
||||
|
||||
yVel += bounceGravity*0.0166
|
||||
yVel += bounceGravity * delta * physicsTimeMultiplier
|
||||
|
||||
# Handle view mode panning lerp back to center
|
||||
if !editMode:
|
||||
if !viewModePanning:
|
||||
# Lerp back to center when not actively panning
|
||||
viewPanOffset = viewPanOffset.lerp(Vector2.ZERO, viewPanLerpSpeed * delta)
|
||||
|
||||
# Apply view pan offset to the root character (affects physics)
|
||||
origin.position = (get_viewport().get_visible_rect().size * 0.5) + viewPanOffset
|
||||
updateCameraPosition()
|
||||
|
||||
if Input.is_action_just_pressed("openFolder"):
|
||||
OS.shell_open(ProjectSettings.globalize_path("user://"))
|
||||
|
||||
moveSpriteMenu(delta)
|
||||
zoomScene()
|
||||
zoomScene(delta)
|
||||
|
||||
fileSystemOpen = isFileSystemOpen()
|
||||
|
||||
@@ -173,15 +199,24 @@ func _input(event):
|
||||
isPanning = false
|
||||
else:
|
||||
if event.pressed:
|
||||
# Reset camera in view mode
|
||||
resetCameraPosition()
|
||||
# Start view mode panning
|
||||
viewModePanning = true
|
||||
initialViewPanPosition = event.global_position
|
||||
else:
|
||||
# Stop view mode panning
|
||||
viewModePanning = false
|
||||
|
||||
# Handle mouse movement during panning
|
||||
# Handle mouse movement during edit mode panning
|
||||
elif editMode and event is InputEventMouseMotion and isPanning:
|
||||
var totalDelta = event.global_position - initialPanPosition
|
||||
|
||||
# Calculate new camera offset from initial position plus total movement
|
||||
cameraOffset = initialCameraOffset - totalDelta
|
||||
# Scale pan distance based on zoom level for more intuitive panning
|
||||
# Higher zoom (closer) = less movement, lower zoom (farther) = more movement
|
||||
var zoomScale = 1.0 / camera.zoom.x
|
||||
var scaledDelta = totalDelta * zoomScale
|
||||
|
||||
# Calculate new camera offset from initial position plus scaled movement
|
||||
cameraOffset = initialCameraOffset - scaledDelta
|
||||
|
||||
# Update camera position immediately
|
||||
var s = get_viewport().get_visible_rect().size
|
||||
@@ -194,6 +229,15 @@ func _input(event):
|
||||
viewerArrows.position = editControls.position
|
||||
pushUpdates.position.y = controlPanel.position.y
|
||||
pushUpdates.position.x = editControls.position.x
|
||||
|
||||
# Handle mouse movement during view mode panning
|
||||
elif !editMode and event is InputEventMouseMotion and viewModePanning:
|
||||
var totalDelta = event.global_position - initialViewPanPosition
|
||||
|
||||
# Scale pan distance for view mode (less sensitive than edit mode)
|
||||
# Invert the movement so dragging right moves character right (not left)
|
||||
var panSensitivity = 0.5
|
||||
viewPanOffset = -totalDelta * panSensitivity
|
||||
|
||||
func followShadow():
|
||||
# Shadow disabled for selected sprites
|
||||
@@ -207,6 +251,11 @@ func isFileSystemOpen():
|
||||
return true
|
||||
Global.heldSprite = null
|
||||
return true
|
||||
|
||||
# Check wobble sync group dialog
|
||||
if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl and Global.spriteEdit.wobbleSyncControl.createGroupDialog and Global.spriteEdit.wobbleSyncControl.createGroupDialog.visible:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
#Displays control panel whether or not application is focused
|
||||
@@ -235,7 +284,7 @@ func onWindowSizeChange():
|
||||
updateCameraPosition()
|
||||
spriteList.position.x = s.x - 233
|
||||
|
||||
func zoomScene():
|
||||
func zoomScene(delta):
|
||||
#Handles Zooming
|
||||
if Input.is_action_pressed("control"):
|
||||
if Input.is_action_just_pressed("scrollUp"):
|
||||
@@ -249,7 +298,7 @@ func zoomScene():
|
||||
scaleOverall -= 10
|
||||
changeZoom()
|
||||
|
||||
$ControlPanel/ZoomLabel.modulate.a = lerp($ControlPanel/ZoomLabel.modulate.a,0.0,0.02)
|
||||
$ControlPanel/ZoomLabel.modulate.a = lerp($ControlPanel/ZoomLabel.modulate.a, 0.0, delta * (physicsTimeMultiplier * 1.2))
|
||||
|
||||
func changeZoom():
|
||||
var newZoom = Vector2(1.0,1.0) / camera.zoom
|
||||
@@ -261,6 +310,16 @@ func changeZoom():
|
||||
pushUpdates.scale = newZoom
|
||||
Global.mouse.scale = newZoom
|
||||
|
||||
# Update UI positions to account for new zoom level
|
||||
# Force update even during panning since zoom affects UI positioning calculations
|
||||
var s = get_viewport().get_visible_rect().size
|
||||
controlPanel.position = camera.position + (s/(camera.zoom*2.0))
|
||||
tutorial.position = controlPanel.position
|
||||
editControls.position = camera.position - (s/(camera.zoom*2.0))
|
||||
viewerArrows.position = editControls.position
|
||||
pushUpdates.position.y = controlPanel.position.y
|
||||
pushUpdates.position.x = editControls.position.x
|
||||
|
||||
$ControlPanel/ZoomLabel.modulate.a = 6.0
|
||||
$ControlPanel/ZoomLabel.text = "Zoom : " + str(scaleOverall) + "%"
|
||||
|
||||
@@ -286,6 +345,8 @@ func updateCameraPosition():
|
||||
func resetCameraPosition():
|
||||
cameraOffset = Vector2.ZERO
|
||||
isPanning = false
|
||||
viewPanOffset = Vector2.ZERO
|
||||
viewModePanning = false
|
||||
updateCameraPosition()
|
||||
Global.pushUpdate("Camera reset to center.")
|
||||
|
||||
@@ -305,6 +366,10 @@ func swapMode():
|
||||
# Reset camera when entering view mode
|
||||
if !editMode:
|
||||
resetCameraPosition()
|
||||
else:
|
||||
# Reset view mode panning when entering edit mode
|
||||
viewPanOffset = Vector2.ZERO
|
||||
viewModePanning = false
|
||||
|
||||
get_viewport().transparent_bg = !editMode
|
||||
if Global.backgroundColor.a != 0.0:
|
||||
@@ -353,6 +418,11 @@ func _on_load_button_pressed():
|
||||
|
||||
#LOAD AVATAR
|
||||
func _on_load_dialog_file_selected(path):
|
||||
# Handle case where path might be a Dictionary instead of string
|
||||
if typeof(path) == TYPE_DICTIONARY:
|
||||
push_error("Expected string path but got Dictionary: " + str(path))
|
||||
return
|
||||
|
||||
var data = Saving.read_save(path)
|
||||
|
||||
if data == null:
|
||||
@@ -364,8 +434,19 @@ func _on_load_dialog_file_selected(path):
|
||||
origin = new
|
||||
|
||||
for item in data:
|
||||
if typeof(data[item]) != TYPE_DICTIONARY:
|
||||
continue
|
||||
|
||||
if not data[item].has("path"):
|
||||
continue
|
||||
|
||||
var sprite = spriteObject.instantiate()
|
||||
sprite.path = data[item]["path"]
|
||||
|
||||
# Load display name (with backward compatibility)
|
||||
if data[item].has("displayName"):
|
||||
sprite.displayName = data[item]["displayName"]
|
||||
|
||||
sprite.id = data[item]["identification"]
|
||||
sprite.parentId = data[item]["parentId"]
|
||||
|
||||
@@ -396,9 +477,9 @@ func _on_load_dialog_file_selected(path):
|
||||
|
||||
if data[item].has("costumeLayers"):
|
||||
sprite.costumeLayers = str_to_var(data[item]["costumeLayers"]).duplicate()
|
||||
if sprite.costumeLayers.size() < 8:
|
||||
for i in range(5):
|
||||
sprite.costumeLayers.append(1)
|
||||
# Ensure we have exactly 10 costume layers
|
||||
while sprite.costumeLayers.size() < 10:
|
||||
sprite.costumeLayers.append(1)
|
||||
|
||||
if data[item].has("stretchAmount"):
|
||||
sprite.stretchAmount = data[item]["stretchAmount"]
|
||||
@@ -420,6 +501,12 @@ func _on_load_dialog_file_selected(path):
|
||||
sprite.spriteOpacity = data[item]["spriteOpacity"]
|
||||
if data[item].has("affectChildrenOpacity"):
|
||||
sprite.affectChildrenOpacity = data[item]["affectChildrenOpacity"]
|
||||
if data[item].has("blendMode"):
|
||||
sprite.blendMode = data[item]["blendMode"]
|
||||
|
||||
# Load wobble sync group (with backward compatibility)
|
||||
if data[item].has("wobbleSyncGroup"):
|
||||
sprite.wobbleSyncGroup = data[item]["wobbleSyncGroup"]
|
||||
|
||||
origin.add_child(sprite)
|
||||
sprite.position = str_to_var(data[item]["pos"])
|
||||
@@ -428,7 +515,20 @@ func _on_load_dialog_file_selected(path):
|
||||
Saving.settings["lastAvatar"] = path
|
||||
Global.spriteList.updateData()
|
||||
|
||||
# Update opacity shaders for all loaded sprites after they're all created
|
||||
# Load wobble sync groups
|
||||
if data.has("wobbleSyncGroups") and WobbleSyncManager:
|
||||
await WobbleSyncManager.loadSaveData(data["wobbleSyncGroups"])
|
||||
# Refresh wobble sync UI after groups are loaded
|
||||
if Global.spriteEdit and Global.spriteEdit.wobbleSyncControl:
|
||||
Global.spriteEdit.wobbleSyncControl.updateUI()
|
||||
# Force refresh in case timing issues
|
||||
await get_tree().process_frame
|
||||
Global.spriteEdit.wobbleSyncControl.forceRefresh()
|
||||
# Force refresh in case timing issues
|
||||
await get_tree().process_frame
|
||||
Global.spriteEdit.wobbleSyncControl.forceRefresh()
|
||||
|
||||
# Update opacity shaders for all loaded sprites after wobble sync data is applied
|
||||
await get_tree().process_frame # Wait for all sprites to be fully initialized
|
||||
var allSprites = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in allSprites:
|
||||
@@ -438,7 +538,15 @@ func _on_load_dialog_file_selected(path):
|
||||
Global.pushUpdate("Loaded avatar at: " + path)
|
||||
|
||||
onWindowSizeChange()
|
||||
|
||||
|
||||
## Load the default avatar data
|
||||
func loadDefaultAvatar():
|
||||
_on_load_dialog_file_selected("default")
|
||||
|
||||
## Load avatar from a specific file path
|
||||
func loadAvatar(path: String):
|
||||
_on_load_dialog_file_selected(path)
|
||||
|
||||
#SAVE AVATAR
|
||||
func _on_save_dialog_file_selected(path):
|
||||
var data = {}
|
||||
@@ -450,6 +558,7 @@ func _on_save_dialog_file_selected(path):
|
||||
data[id] = {}
|
||||
data[id]["type"] = "sprite"
|
||||
data[id]["path"] = child.path
|
||||
data[id]["displayName"] = child.displayName
|
||||
data[id]["imageData"] = Marshalls.raw_to_base64(child.imageData.save_png_to_buffer())
|
||||
data[id]["identification"] = child.id
|
||||
data[id]["parentId"] = child.parentId
|
||||
@@ -491,9 +600,17 @@ func _on_save_dialog_file_selected(path):
|
||||
|
||||
data[id]["spriteOpacity"] = child.spriteOpacity
|
||||
data[id]["affectChildrenOpacity"] = child.affectChildrenOpacity
|
||||
data[id]["blendMode"] = child.blendMode
|
||||
|
||||
# Save wobble sync group
|
||||
data[id]["wobbleSyncGroup"] = child.wobbleSyncGroup
|
||||
|
||||
id += 1
|
||||
|
||||
# Save wobble sync groups
|
||||
if WobbleSyncManager:
|
||||
data["wobbleSyncGroups"] = WobbleSyncManager.getSaveData()
|
||||
|
||||
Saving.settings["lastAvatar"] = path
|
||||
|
||||
Saving.data = data.duplicate()
|
||||
@@ -570,6 +687,10 @@ func _on_duplicate_button_pressed():
|
||||
|
||||
sprite.spriteOpacity = Global.heldSprite.spriteOpacity
|
||||
sprite.affectChildrenOpacity = Global.heldSprite.affectChildrenOpacity
|
||||
sprite.blendMode = Global.heldSprite.blendMode
|
||||
|
||||
# Copy wobble sync group
|
||||
sprite.wobbleSyncGroup = Global.heldSprite.wobbleSyncGroup
|
||||
|
||||
origin.add_child(sprite)
|
||||
sprite.position = Global.heldSprite.position + Vector2(16,16)
|
||||
@@ -597,9 +718,11 @@ func changeCostumeStreamDeck(id: String):
|
||||
"9":changeCostume(9)
|
||||
"10":changeCostume(10)
|
||||
|
||||
func changeCostume(newCostume):
|
||||
func changeCostume(newCostume, preserve_selection: bool = false):
|
||||
costume = newCostume
|
||||
Global.heldSprite = null
|
||||
# Only clear heldSprite for actual costume changes, not layer updates
|
||||
if not preserve_selection:
|
||||
Global.heldSprite = null
|
||||
var nodes = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in nodes:
|
||||
if sprite.costumeLayers[newCostume-1] == 1:
|
||||
@@ -622,7 +745,7 @@ func moveSpriteMenu(delta):
|
||||
|
||||
var size = get_viewport().get_visible_rect().size
|
||||
|
||||
var windowLength = 1500
|
||||
var windowLength = 2000
|
||||
|
||||
$ViewerArrows/Arrows.position.y = size.y - 25
|
||||
|
||||
@@ -650,9 +773,9 @@ func moveSpriteMenu(delta):
|
||||
|
||||
|
||||
if $EditControls/MoveMenuUp.overlaps_area(Global.mouse.area):
|
||||
Global.spriteEdit.position.y += (delta*432.0)
|
||||
Global.spriteEdit.position.y += (delta * 400.0)
|
||||
elif $EditControls/MoveMenuDown.overlaps_area(Global.mouse.area):
|
||||
Global.spriteEdit.position.y -= (delta*432.0)
|
||||
Global.spriteEdit.position.y -= (delta * 400.0)
|
||||
|
||||
if Global.spriteEdit.position.y > 66:
|
||||
Global.spriteEdit.position.y = 66
|
||||
|
||||
@@ -14357,37 +14357,70 @@ offset_bottom = -51.0
|
||||
text = "Zoom : 100%"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="VersionLabels" type="Node2D" parent="ControlPanel"]
|
||||
[node name="VersionLabels" type="Control" parent="ControlPanel"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -870.0
|
||||
offset_top = -107.0
|
||||
offset_right = -610.0
|
||||
offset_bottom = -7.0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="Label" type="Label" parent="ControlPanel/VersionLabels"]
|
||||
offset_left = -713.0
|
||||
offset_top = -94.0
|
||||
offset_right = -575.0
|
||||
offset_bottom = -60.0
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ControlPanel/VersionLabels"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -100.0
|
||||
offset_right = 138.0
|
||||
offset_bottom = 1.0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="Label" type="Label" parent="ControlPanel/VersionLabels/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "PNGTuber Plus"
|
||||
label_settings = SubResource("LabelSettings_w0f1f")
|
||||
|
||||
[node name="Label2" type="Label" parent="ControlPanel/VersionLabels"]
|
||||
offset_left = -713.0
|
||||
offset_top = -123.0
|
||||
offset_right = -575.0
|
||||
offset_bottom = -89.0
|
||||
[node name="Label2" type="Label" parent="ControlPanel/VersionLabels/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "kaiakairos'"
|
||||
label_settings = SubResource("LabelSettings_qg0do")
|
||||
|
||||
[node name="versionNo" type="Label" parent="ControlPanel/VersionLabels/Label2"]
|
||||
[node name="versionNo" type="Label" parent="ControlPanel/VersionLabels/VBoxContainer/Label2"]
|
||||
layout_mode = 0
|
||||
offset_left = 144.0
|
||||
offset_top = 40.0
|
||||
offset_right = 282.0
|
||||
offset_bottom = 74.0
|
||||
offset_left = 104.0
|
||||
offset_top = 11.0
|
||||
offset_right = 242.0
|
||||
offset_bottom = 54.0
|
||||
text = "v1.4.5"
|
||||
label_settings = SubResource("LabelSettings_xvf50")
|
||||
|
||||
[node name="Label3" type="Label" parent="ControlPanel/VersionLabels/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Litruv Edition"
|
||||
label_settings = SubResource("LabelSettings_qg0do")
|
||||
|
||||
[node name="EditControls" type="Node2D" parent="."]
|
||||
visible = false
|
||||
z_index = 4090
|
||||
script = ExtResource("17_bf1ic")
|
||||
|
||||
[node name="Container" type="Container" parent="EditControls"]
|
||||
clip_contents = true
|
||||
anchors_preset = 9
|
||||
anchor_bottom = 1.0
|
||||
offset_top = 70.0
|
||||
offset_right = 273.485
|
||||
offset_bottom = 20070.0
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="SpriteViewer" parent="EditControls/Container" instance=ExtResource("9_uqv8p")]
|
||||
z_index = 4094
|
||||
position = Vector2(9, 5)
|
||||
|
||||
[node name="Add" type="Sprite2D" parent="EditControls"]
|
||||
position = Vector2(104, 32)
|
||||
texture = ExtResource("4_xenui")
|
||||
@@ -14549,11 +14582,6 @@ position = Vector2(2132, 928.5)
|
||||
shape = SubResource("RectangleShape2D_ebmai")
|
||||
disabled = true
|
||||
|
||||
[node name="SpriteViewer" parent="EditControls" instance=ExtResource("9_uqv8p")]
|
||||
visible = false
|
||||
z_index = 4094
|
||||
position = Vector2(9, 66)
|
||||
|
||||
[node name="MoveMenuUp" type="Area2D" parent="EditControls"]
|
||||
collision_layer = 0
|
||||
collision_mask = 2048
|
||||
@@ -14610,7 +14638,6 @@ patch_margin_right = 8
|
||||
patch_margin_bottom = 8
|
||||
|
||||
[node name="Label" type="Label" parent="Tutorial/NinePatchRect"]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 15.0
|
||||
offset_top = 14.0
|
||||
|
||||
@@ -29,6 +29,8 @@ buses/channel_disable_time=0.0
|
||||
|
||||
Saving="*res://autoload/saving.gd"
|
||||
Global="*res://autoload/global.gd"
|
||||
WobbleSyncManager="*res://autoload/wobble_sync_manager.gd"
|
||||
BlendModeManager="*res://autoload/blend_mode_manager.gd"
|
||||
ElgatoStreamDeck="*res://addons/godot-streamdeck-addon/singleton.gd"
|
||||
DefaultAvatarData="*res://autoload/defaultAvatarData.gd"
|
||||
|
||||
@@ -48,11 +50,6 @@ window/vsync/vsync_mode=0
|
||||
|
||||
project/assembly_name="PNGTuberPlus"
|
||||
|
||||
[editor]
|
||||
|
||||
version_control/plugin_name="GitPlugin"
|
||||
version_control/autoload_on_startup=true
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/godot-streamdeck-addon/plugin.cfg")
|
||||
|
||||
BIN
ui_scenes/button sprites/checkmark.png
Normal file
BIN
ui_scenes/button sprites/checkmark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
34
ui_scenes/button sprites/checkmark.png.import
Normal file
34
ui_scenes/button sprites/checkmark.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://41u8egcau00i"
|
||||
path="res://.godot/imported/checkmark.png-7bf604eba3017c173b17bad2b166951a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui_scenes/button sprites/checkmark.png"
|
||||
dest_files=["res://.godot/imported/checkmark.png-7bf604eba3017c173b17bad2b166951a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
105
ui_scenes/components/IMPLEMENTATION_COMPLETE.md
Normal file
105
ui_scenes/components/IMPLEMENTATION_COMPLETE.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Implementation Complete: Sprite Viewer Slider Components
|
||||
|
||||
## ✅ **Successfully Implemented**
|
||||
|
||||
### **🎯 Components Created**
|
||||
- **LabeledSlider** - Base reusable slider component
|
||||
- **WobbleSlider** - Specialized for wobble parameters with sync support
|
||||
- **ParameterSlider** - Generic parameter binding for sprite properties
|
||||
- **SliderManager** - Centralized management system
|
||||
|
||||
### **🔄 Replaced in sprite_viewer.tscn**
|
||||
|
||||
| **Old Slider** | **New Component** | **Benefits** |
|
||||
|----------------|-------------------|--------------|
|
||||
| Drag Slider | ParameterSlider | Auto label updates, consistent formatting |
|
||||
| X Frequency | WobbleSlider | Sync group support, auto parameter binding |
|
||||
| X Amplitude | WobbleSlider | Consistent wobble handling |
|
||||
| Y Frequency | WobbleSlider | Integrated with wobble sync system |
|
||||
| Y Amplitude | WobbleSlider | Visual feedback for sync status |
|
||||
| Rotation Frequency | WobbleSlider | Proper Hz display formatting |
|
||||
| Rotation Amplitude | WobbleSlider | Percentage display with % suffix |
|
||||
| Rotation Drag | ParameterSlider | Simplified parameter binding |
|
||||
| Squash | ParameterSlider | Decimal precision formatting |
|
||||
| Rotation Limit Min | ParameterSlider | Degree (°) suffix display |
|
||||
| Rotation Limit Max | ParameterSlider | Consistent limit handling |
|
||||
| Animation Frames | ParameterSlider | Integer-only values |
|
||||
| Animation Speed | ParameterSlider | Decimal speed values |
|
||||
| Opacity | ParameterSlider | Custom percentage display |
|
||||
|
||||
### **🎨 Key Features Implemented**
|
||||
|
||||
✅ **Automatic Label Updates** - No more manual label text management
|
||||
✅ **Consistent Formatting** - All sliders use the same styling
|
||||
✅ **Parameter Binding** - Direct sprite property connections
|
||||
✅ **Wobble Sync Integration** - Visual feedback and group handling
|
||||
✅ **Type Safety** - Proper class inheritance
|
||||
✅ **Value Formatting** - Decimal places, suffixes (Hz, %, °)
|
||||
✅ **Signal Management** - Components handle their own connections
|
||||
✅ **Backwards Compatibility** - Old functions cleaned up
|
||||
|
||||
### **🔧 Technical Implementation**
|
||||
|
||||
#### **Sprite Viewer Updates:**
|
||||
- Added SliderManager for centralized control
|
||||
- Implemented `_setup_slider_components()`
|
||||
- Updated `setImage()` to use component system
|
||||
- Modified `updateWobbleControlStates()` for visual sync feedback
|
||||
- Removed redundant old signal handlers
|
||||
- Added new component signal connections
|
||||
|
||||
#### **Scene File Changes:**
|
||||
- Added external resource references for new components
|
||||
- Replaced 14 individual slider+label combinations
|
||||
- Configured each component with appropriate parameters
|
||||
- Removed outdated signal connections
|
||||
- Optimized layout spacing
|
||||
|
||||
#### **Component Features:**
|
||||
- **LabeledSlider**: Base functionality with export properties
|
||||
- **WobbleSlider**: Inherits from LabeledSlider, adds wobble-specific logic
|
||||
- **ParameterSlider**: Generic sprite property binding with special cases
|
||||
- **SliderManager**: Bulk operations and consistent styling
|
||||
|
||||
### **🎯 Benefits Achieved**
|
||||
|
||||
1. **Consistency** - All sliders look and behave identically
|
||||
2. **Maintainability** - One place to modify slider behavior
|
||||
3. **Reusability** - Components work across different scenes
|
||||
4. **Reduced Code** - Less repetitive slider setup and management
|
||||
5. **Better UX** - Automatic updates, proper formatting, visual feedback
|
||||
6. **Type Safety** - Class-based components with proper inheritance
|
||||
|
||||
### **📝 Usage Example**
|
||||
|
||||
**Before (Old System):**
|
||||
```gdscript
|
||||
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
|
||||
$WobbleControl/xFrq.value = value
|
||||
# Repeat for every slider...
|
||||
```
|
||||
|
||||
**After (New Components):**
|
||||
```gdscript
|
||||
slider_manager.update_all_sliders(Global.heldSprite)
|
||||
# All sliders update automatically with proper formatting
|
||||
```
|
||||
|
||||
### **🔧 Migration Notes**
|
||||
|
||||
- All old individual sliders have been replaced
|
||||
- Legacy signal handlers removed
|
||||
- Component system handles parameter updates automatically
|
||||
- Visual sync feedback integrated into wobble sliders
|
||||
- Opacity slider has custom percentage display logic
|
||||
|
||||
### **🚀 Ready to Use**
|
||||
|
||||
The sprite viewer now uses a modern, component-based slider system that is:
|
||||
- More maintainable
|
||||
- More consistent
|
||||
- Easier to extend
|
||||
- Better organized
|
||||
- Type-safe
|
||||
|
||||
All sliders in the sprite viewer are now implemented as reusable components! 🎉
|
||||
73
ui_scenes/components/LABEL_FIX.md
Normal file
73
ui_scenes/components/LABEL_FIX.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 🔧 Fixed: Slider Labels Now Show Correct Text
|
||||
|
||||
## ✅ **Problem Solved**
|
||||
|
||||
The issue was that slider labels were showing "Value" instead of the actual `label_text` property (like "X Frequency", "Drag", etc.) in the Godot editor.
|
||||
|
||||
## 🎯 **Root Cause**
|
||||
|
||||
1. **Editor Execution**: Components needed `@tool` to work properly in the editor
|
||||
2. **Initialization Timing**: Label updates happened before exported properties were set
|
||||
3. **Property Setter Timing**: Label wasn't updated when properties were set by the editor
|
||||
|
||||
## 🔧 **Solution Applied**
|
||||
|
||||
### **1. Added `@tool` Directive**
|
||||
Added `@tool` to all component scripts to enable editor execution:
|
||||
- `LabeledSlider.gd`
|
||||
- `WobbleSlider.gd`
|
||||
- `ParameterSlider.gd`
|
||||
|
||||
### **2. Improved Property Setters**
|
||||
Updated all export property setters to check if the node is ready:
|
||||
```gdscript
|
||||
func set_label_text(new_text: String):
|
||||
label_text = new_text
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
else:
|
||||
call_deferred("_update_label")
|
||||
```
|
||||
|
||||
### **3. Enhanced Initialization**
|
||||
- Added `NOTIFICATION_SCENE_INSTANTIATED` handler
|
||||
- Improved `_ready()` function timing
|
||||
- Added deferred label updates for proper timing
|
||||
|
||||
### **4. Fixed Base Template**
|
||||
Updated `LabeledSlider.tscn` to have cleaner default text:
|
||||
```
|
||||
text = "Value" # Instead of "Value: 0"
|
||||
```
|
||||
|
||||
### **5. Robust Label Updates**
|
||||
Enhanced `_update_label()` to handle missing nodes gracefully:
|
||||
```gdscript
|
||||
func _update_label():
|
||||
if not label:
|
||||
label = get_node_or_null("Label")
|
||||
if not label:
|
||||
call_deferred("_update_label")
|
||||
return
|
||||
# ... update logic
|
||||
```
|
||||
|
||||
## 🎉 **Result**
|
||||
|
||||
Now in the Godot editor, all slider labels should properly display:
|
||||
- ✅ "X Frequency: 0.000 Hz"
|
||||
- ✅ "Y Amplitude: 0"
|
||||
- ✅ "Drag: 0.0"
|
||||
- ✅ "Opacity: 100%"
|
||||
- ✅ "Rotational Limit Min: -180°"
|
||||
|
||||
Instead of just "Value" everywhere!
|
||||
|
||||
## 🔄 **How to Test**
|
||||
|
||||
1. Open `sprite_viewer.tscn` in the Godot editor
|
||||
2. Check the slider components in the scene tree
|
||||
3. Labels should now show the correct text for each parameter
|
||||
4. Properties can be modified in the inspector and labels update immediately
|
||||
|
||||
The components now work properly both in the editor preview and at runtime! 🚀
|
||||
140
ui_scenes/components/LabeledSlider.gd
Normal file
140
ui_scenes/components/LabeledSlider.gd
Normal file
@@ -0,0 +1,140 @@
|
||||
@tool
|
||||
## A reusable component that combines a label and slider
|
||||
## Provides consistent formatting and behavior for all sliders in the UI
|
||||
extends VBoxContainer
|
||||
class_name LabeledSlider
|
||||
|
||||
## Emitted when the slider value changes
|
||||
signal value_changed(value: float)
|
||||
|
||||
## The base text that appears in the label (without the value)
|
||||
@export var label_text: String = "Value" : set = set_label_text
|
||||
## The minimum value of the slider
|
||||
@export var min_value: float = 0.0 : set = set_min_value
|
||||
## The maximum value of the slider
|
||||
@export var max_value: float = 100.0 : set = set_max_value
|
||||
## The step value for the slider
|
||||
@export var step: float = 1.0 : set = set_step
|
||||
## The current value of the slider
|
||||
@export var value: float = 0.0 : set = set_value
|
||||
## Whether to show the value in the label
|
||||
@export var show_value_in_label: bool = true : set = set_show_value_in_label
|
||||
## Format string for the value display (e.g., "%.1f" for one decimal place)
|
||||
@export var value_format: String = "%.0f" : set = set_value_format
|
||||
## Optional suffix to add after the value (e.g., "%", "Hz")
|
||||
@export var value_suffix: String = "" : set = set_value_suffix
|
||||
## Whether the slider is editable
|
||||
@export var editable: bool = true : set = set_editable
|
||||
|
||||
@onready var label: Label = $Label
|
||||
@onready var slider: HSlider = $HSlider
|
||||
|
||||
func _ready():
|
||||
if slider:
|
||||
slider.value_changed.connect(_on_slider_value_changed)
|
||||
_update_slider_properties()
|
||||
|
||||
# Ensure label is updated after all properties are set
|
||||
call_deferred("_update_label")
|
||||
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_SCENE_INSTANTIATED:
|
||||
# Update label when scene is instantiated (important for editor)
|
||||
call_deferred("_update_label")
|
||||
|
||||
## Set the label text
|
||||
func set_label_text(new_text: String):
|
||||
label_text = new_text
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
else:
|
||||
call_deferred("_update_label")
|
||||
|
||||
## Set the minimum value
|
||||
func set_min_value(new_min: float):
|
||||
min_value = new_min
|
||||
if is_inside_tree():
|
||||
_update_slider_properties()
|
||||
|
||||
## Set the maximum value
|
||||
func set_max_value(new_max: float):
|
||||
max_value = new_max
|
||||
if is_inside_tree():
|
||||
_update_slider_properties()
|
||||
|
||||
## Set the step value
|
||||
func set_step(new_step: float):
|
||||
step = new_step
|
||||
if is_inside_tree():
|
||||
_update_slider_properties()
|
||||
|
||||
## Set the current value
|
||||
func set_value(new_value: float):
|
||||
value = new_value
|
||||
if slider:
|
||||
slider.value = value
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
|
||||
## Set whether to show value in label
|
||||
func set_show_value_in_label(show: bool):
|
||||
show_value_in_label = show
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
|
||||
## Set the value format string
|
||||
func set_value_format(format: String):
|
||||
value_format = format
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
|
||||
## Set the value suffix
|
||||
func set_value_suffix(suffix: String):
|
||||
value_suffix = suffix
|
||||
if is_inside_tree():
|
||||
_update_label()
|
||||
|
||||
## Set whether the slider is editable
|
||||
func set_editable(is_editable: bool):
|
||||
editable = is_editable
|
||||
if slider:
|
||||
slider.editable = editable
|
||||
|
||||
## Update the slider properties
|
||||
func _update_slider_properties():
|
||||
if not slider:
|
||||
return
|
||||
|
||||
slider.min_value = min_value
|
||||
slider.max_value = max_value
|
||||
slider.step = step
|
||||
slider.value = value
|
||||
slider.editable = editable
|
||||
|
||||
## Update the label text
|
||||
func _update_label():
|
||||
if not label:
|
||||
# If label isn't ready yet, try to find it
|
||||
label = get_node_or_null("Label")
|
||||
if not label:
|
||||
# Call again after a frame when nodes are ready
|
||||
call_deferred("_update_label")
|
||||
return
|
||||
|
||||
var text = label_text
|
||||
if show_value_in_label:
|
||||
var formatted_value = value_format % value
|
||||
text += ": " + formatted_value + value_suffix
|
||||
|
||||
label.text = text
|
||||
|
||||
## Handle slider value changes
|
||||
func _on_slider_value_changed(new_value: float):
|
||||
value = new_value
|
||||
_update_label()
|
||||
value_changed.emit(value)
|
||||
|
||||
## Set the modulate color for visual feedback
|
||||
func set_modulate_color(color: Color):
|
||||
if slider:
|
||||
slider.modulate = color
|
||||
1
ui_scenes/components/LabeledSlider.gd.uid
Normal file
1
ui_scenes/components/LabeledSlider.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://combq5rrok31i
|
||||
22
ui_scenes/components/LabeledSlider.tscn
Normal file
22
ui_scenes/components/LabeledSlider.tscn
Normal file
@@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bxm5q6n8x7p2q"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://combq5rrok31i" path="res://ui_scenes/components/LabeledSlider.gd" id="1_labeled_slider"]
|
||||
|
||||
[node name="LabeledSlider" type="VBoxContainer"]
|
||||
offset_right = 200.0
|
||||
offset_bottom = 40.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
theme_override_constants/separation = 2
|
||||
script = ExtResource("1_labeled_slider")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
text = "Value: 0"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="HSlider" type="HSlider" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
81
ui_scenes/components/ParameterSlider.gd
Normal file
81
ui_scenes/components/ParameterSlider.gd
Normal file
@@ -0,0 +1,81 @@
|
||||
@tool
|
||||
## A parameter slider component for general sprite parameters
|
||||
## Handles updating sprite properties with proper formatting
|
||||
extends LabeledSlider
|
||||
class_name ParameterSlider
|
||||
|
||||
## The sprite parameter name this slider controls
|
||||
@export var parameter_name: String = ""
|
||||
## The property path in the sprite object (e.g., "dragSpeed", "rdragStr")
|
||||
@export var sprite_property: String = ""
|
||||
|
||||
## Reference to the sprite being edited
|
||||
var target_sprite = null
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
# Connect to value changes to update parameters
|
||||
value_changed.connect(_on_parameter_value_changed)
|
||||
|
||||
func _notification(what):
|
||||
super._notification(what)
|
||||
if what == NOTIFICATION_SCENE_INSTANTIATED:
|
||||
# Update label for opacity specially
|
||||
if sprite_property == "spriteOpacity":
|
||||
call_deferred("_update_opacity_label")
|
||||
|
||||
## Set the sprite this slider controls
|
||||
func set_target_sprite(sprite):
|
||||
target_sprite = sprite
|
||||
if sprite:
|
||||
_update_from_sprite()
|
||||
|
||||
## Update the slider from the sprite's current values
|
||||
func _update_from_sprite():
|
||||
if not target_sprite or sprite_property == "":
|
||||
return
|
||||
|
||||
var current_value = target_sprite.get(sprite_property)
|
||||
if current_value != null:
|
||||
set_value(current_value)
|
||||
|
||||
# Special handling for opacity display
|
||||
if sprite_property == "spriteOpacity":
|
||||
_update_opacity_label()
|
||||
|
||||
## Update label text with special opacity handling
|
||||
func _update_opacity_label():
|
||||
if sprite_property == "spriteOpacity" and label:
|
||||
var percentage = int(value * 100)
|
||||
label.text = label_text + ": " + str(percentage) + "%"
|
||||
|
||||
## Override base update_label for special cases
|
||||
func _update_label():
|
||||
if sprite_property == "spriteOpacity":
|
||||
_update_opacity_label()
|
||||
else:
|
||||
super._update_label()
|
||||
|
||||
## Handle parameter changes
|
||||
func _on_parameter_value_changed(new_value: float):
|
||||
if not target_sprite or sprite_property == "":
|
||||
return
|
||||
|
||||
|
||||
target_sprite.set(sprite_property, new_value)
|
||||
|
||||
# Call specific update methods if they exist
|
||||
_call_update_method()
|
||||
|
||||
## Call specific update methods for certain parameters
|
||||
func _call_update_method():
|
||||
if not target_sprite:
|
||||
return
|
||||
|
||||
match sprite_property:
|
||||
"spriteOpacity":
|
||||
target_sprite.updateOpacity()
|
||||
"frames":
|
||||
target_sprite.changeFrames()
|
||||
"blendMode":
|
||||
target_sprite.updateBlendMode(value)
|
||||
1
ui_scenes/components/ParameterSlider.gd.uid
Normal file
1
ui_scenes/components/ParameterSlider.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6maee7dhl7vl
|
||||
9
ui_scenes/components/ParameterSlider.tscn
Normal file
9
ui_scenes/components/ParameterSlider.tscn
Normal file
@@ -0,0 +1,9 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://fgee1pq0yqav"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bxm5q6n8x7p2q" path="res://ui_scenes/components/LabeledSlider.tscn" id="1_base_slider"]
|
||||
[ext_resource type="Script" uid="uid://6maee7dhl7vl" path="res://ui_scenes/components/ParameterSlider.gd" id="2_parameter_script"]
|
||||
|
||||
[node name="ParameterSlider" instance=ExtResource("1_base_slider")]
|
||||
script = ExtResource("2_parameter_script")
|
||||
parameter_name = ""
|
||||
sprite_property = ""
|
||||
220
ui_scenes/components/README.md
Normal file
220
ui_scenes/components/README.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Slider Components Documentation
|
||||
|
||||
This document explains the new slider component system that replaces the individual sliders in the sprite viewer with reusable, consistent components.
|
||||
|
||||
## Components Overview
|
||||
|
||||
### 1. LabeledSlider (Base Component)
|
||||
**File:** `ui_scenes/components/LabeledSlider.tscn`
|
||||
**Script:** `ui_scenes/components/LabeledSlider.gd`
|
||||
|
||||
The base slider component that combines a label and slider into one reusable unit.
|
||||
|
||||
**Key Features:**
|
||||
- Automatic label updates with current value
|
||||
- Configurable value formatting
|
||||
- Optional value suffix (%, Hz, etc.)
|
||||
- Consistent styling
|
||||
- Signal emission on value changes
|
||||
|
||||
**Properties:**
|
||||
- `label_text`: Base text for the label
|
||||
- `min_value`, `max_value`, `step`: Slider configuration
|
||||
- `value`: Current value
|
||||
- `show_value_in_label`: Whether to display value in label
|
||||
- `value_format`: Format string for value display (e.g., "%.1f")
|
||||
- `value_suffix`: Suffix to add after value (e.g., "%", "Hz")
|
||||
- `editable`: Whether the slider can be modified
|
||||
|
||||
### 2. WobbleSlider (Specialized Component)
|
||||
**File:** `ui_scenes/components/WobbleSlider.tscn`
|
||||
**Script:** `ui_scenes/components/WobbleSlider.gd`
|
||||
|
||||
Extends LabeledSlider specifically for wobble parameters.
|
||||
|
||||
**Key Features:**
|
||||
- Automatic synchronization with sprite wobble parameters
|
||||
- Visual feedback for sync group status
|
||||
- Integrated with wobble sync system
|
||||
- Parameter-specific configuration
|
||||
|
||||
**Properties:**
|
||||
- `parameter_name`: The wobble parameter this controls ("xFrq", "xAmp", etc.)
|
||||
- All LabeledSlider properties
|
||||
|
||||
### 3. ParameterSlider (Generic Component)
|
||||
**File:** `ui_scenes/components/ParameterSlider.tscn`
|
||||
**Script:** `ui_scenes/components/ParameterSlider.gd`
|
||||
|
||||
Extends LabeledSlider for general sprite parameters.
|
||||
|
||||
**Key Features:**
|
||||
- Generic parameter binding to sprite properties
|
||||
- Automatic update method calling for specific parameters
|
||||
- Property path configuration
|
||||
|
||||
**Properties:**
|
||||
- `sprite_property`: The sprite property to bind to
|
||||
- `parameter_name`: Display name for the parameter
|
||||
- All LabeledSlider properties
|
||||
|
||||
### 4. WobbleControlPanel (Composite Component)
|
||||
**File:** `ui_scenes/components/WobbleControlPanel.tscn`
|
||||
**Script:** `ui_scenes/components/WobbleControlPanel.gd`
|
||||
|
||||
A complete panel containing all wobble sliders.
|
||||
|
||||
**Key Features:**
|
||||
- Pre-configured wobble sliders
|
||||
- Centralized sprite management
|
||||
- Integrated sync feedback
|
||||
- Easy integration with existing wobble sync controls
|
||||
|
||||
### 5. SliderManager (Management System)
|
||||
**Script:** `ui_scenes/components/SliderManager.gd`
|
||||
|
||||
Manages collections of slider components.
|
||||
|
||||
**Key Features:**
|
||||
- Centralized slider updates
|
||||
- Consistent styling application
|
||||
- Bulk operations on sliders
|
||||
- Integration with sprite viewer
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic LabeledSlider
|
||||
```gdscript
|
||||
# In your scene
|
||||
@onready var my_slider: LabeledSlider = $LabeledSlider
|
||||
|
||||
func _ready():
|
||||
my_slider.label_text = "Volume"
|
||||
my_slider.min_value = 0.0
|
||||
my_slider.max_value = 100.0
|
||||
my_slider.value_suffix = "%"
|
||||
my_slider.value_changed.connect(_on_volume_changed)
|
||||
|
||||
func _on_volume_changed(value: float):
|
||||
print("Volume changed to: ", value)
|
||||
```
|
||||
|
||||
### WobbleSlider Configuration
|
||||
```gdscript
|
||||
# In scene setup
|
||||
@onready var x_freq_slider: WobbleSlider = $XFrequencySlider
|
||||
|
||||
func _ready():
|
||||
x_freq_slider.label_text = "X Frequency"
|
||||
x_freq_slider.parameter_name = "xFrq"
|
||||
x_freq_slider.min_value = 0.0
|
||||
x_freq_slider.max_value = 10.0
|
||||
x_freq_slider.step = 0.1
|
||||
x_freq_slider.value_format = "%.1f"
|
||||
x_freq_slider.value_suffix = " Hz"
|
||||
|
||||
func set_sprite(sprite):
|
||||
x_freq_slider.set_target_sprite(sprite)
|
||||
```
|
||||
|
||||
### Using WobbleControlPanel
|
||||
```gdscript
|
||||
# Replace individual wobble sliders with the panel
|
||||
@onready var wobble_panel: WobbleControlPanel = $WobbleControlPanel
|
||||
|
||||
func setImage():
|
||||
if Global.heldSprite:
|
||||
wobble_panel.set_target_sprite(Global.heldSprite)
|
||||
wobble_panel.update_from_sprite()
|
||||
```
|
||||
|
||||
### SliderManager Integration
|
||||
```gdscript
|
||||
# In sprite viewer
|
||||
var slider_manager: SliderManager
|
||||
|
||||
func _ready():
|
||||
slider_manager = SliderManager.new(self)
|
||||
_setup_sliders()
|
||||
|
||||
func _setup_sliders():
|
||||
# Add sliders to manager
|
||||
slider_manager.add_slider($DragSlider)
|
||||
slider_manager.add_slider($OpacitySlider)
|
||||
# etc.
|
||||
|
||||
func setImage():
|
||||
if slider_manager:
|
||||
slider_manager.update_all_sliders(Global.heldSprite)
|
||||
```
|
||||
|
||||
## Migration from Legacy Sliders
|
||||
|
||||
### Step 1: Replace Scene Nodes
|
||||
Replace individual HSlider + Label combinations with LabeledSlider instances:
|
||||
|
||||
**Before:**
|
||||
```
|
||||
- Node2D
|
||||
- Label (for "x frequency: 5.0")
|
||||
- HSlider (for value input)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
- Node2D
|
||||
- LabeledSlider (handles both label and slider)
|
||||
```
|
||||
|
||||
### Step 2: Update Script References
|
||||
**Before:**
|
||||
```gdscript
|
||||
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
|
||||
$WobbleControl/xFrq.value = value
|
||||
```
|
||||
|
||||
**After:**
|
||||
```gdscript
|
||||
$WobbleControl/XFrequencySlider.set_value(value)
|
||||
# Label updates automatically
|
||||
```
|
||||
|
||||
### Step 3: Connect Signals
|
||||
**Before:**
|
||||
```gdscript
|
||||
func _on_x_frq_value_changed(value):
|
||||
Global.heldSprite.xFrq = value
|
||||
```
|
||||
|
||||
**After:**
|
||||
```gdscript
|
||||
# Handled automatically by WobbleSlider component
|
||||
# Or connect to the component's value_changed signal
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Consistency**: All sliders look and behave the same way
|
||||
2. **Maintainability**: Changes to slider behavior only need to be made in one place
|
||||
3. **Reusability**: Components can be used across different parts of the application
|
||||
4. **Type Safety**: Proper class inheritance with specific functionality
|
||||
5. **Reduced Code**: Less repetitive slider setup code
|
||||
6. **Better Organization**: Clear separation of concerns
|
||||
|
||||
## Integration Notes
|
||||
|
||||
- The new system is designed to be backwards compatible
|
||||
- Legacy slider code is maintained as fallback
|
||||
- Components can be gradually adopted
|
||||
- SliderManager provides centralized control
|
||||
- Easy to extend for new parameter types
|
||||
|
||||
## Custom Styling
|
||||
|
||||
To apply consistent styling across all sliders, modify the base LabeledSlider component or use the SliderManager's styling methods:
|
||||
|
||||
```gdscript
|
||||
func apply_custom_theme():
|
||||
for slider in slider_manager.sliders:
|
||||
slider.slider.add_theme_stylebox_override("slider", custom_style)
|
||||
```
|
||||
47
ui_scenes/components/SampleWobbleControl.tscn
Normal file
47
ui_scenes/components/SampleWobbleControl.tscn
Normal file
@@ -0,0 +1,47 @@
|
||||
[gd_scene load_steps=4 format=4 uid="uid://bh7n2k1q5m8ya"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c8q7x2n9p4r5s" path="res://ui_scenes/components/WobbleSlider.tscn" id="1_wobble_x_frq"]
|
||||
[ext_resource type="PackedScene" uid="uid://c8q7x2n9p4r5s" path="res://ui_scenes/components/WobbleSlider.tscn" id="2_wobble_x_amp"]
|
||||
[ext_resource type="PackedScene" uid="uid://d9t8y3m0k6l1n" path="res://ui_scenes/components/ParameterSlider.tscn" id="3_drag_slider"]
|
||||
|
||||
[node name="SampleWobbleControl" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_right = -1680.0
|
||||
offset_bottom = -1080.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="XFrequencySlider" parent="VBoxContainer" instance=ExtResource("1_wobble_x_frq")]
|
||||
layout_mode = 2
|
||||
label_text = "X Frequency"
|
||||
min_value = 0.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value_format = "%.1f"
|
||||
value_suffix = " Hz"
|
||||
parameter_name = "xFrq"
|
||||
|
||||
[node name="XAmplitudeSlider" parent="VBoxContainer" instance=ExtResource("2_wobble_x_amp")]
|
||||
layout_mode = 2
|
||||
label_text = "X Amplitude"
|
||||
min_value = 0.0
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value_format = "%.0f"
|
||||
parameter_name = "xAmp"
|
||||
|
||||
[node name="DragSlider" parent="VBoxContainer" instance=ExtResource("3_drag_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "Drag"
|
||||
min_value = 0.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value_format = "%.1f"
|
||||
sprite_property = "dragSpeed"
|
||||
0
ui_scenes/components/SliderConfig.gd
Normal file
0
ui_scenes/components/SliderConfig.gd
Normal file
1
ui_scenes/components/SliderConfig.gd.uid
Normal file
1
ui_scenes/components/SliderConfig.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://db4gh5lnm7j5k
|
||||
60
ui_scenes/components/SliderManager.gd
Normal file
60
ui_scenes/components/SliderManager.gd
Normal file
@@ -0,0 +1,60 @@
|
||||
## Manages all slider components in the sprite viewer
|
||||
## Provides a centralized way to update and configure sliders
|
||||
extends RefCounted
|
||||
class_name SliderManager
|
||||
|
||||
## Array of all slider components
|
||||
var sliders: Array[LabeledSlider] = []
|
||||
## Reference to the sprite viewer
|
||||
var sprite_viewer = null
|
||||
|
||||
func _init(viewer):
|
||||
sprite_viewer = viewer
|
||||
|
||||
## Add a slider to be managed
|
||||
func add_slider(slider: LabeledSlider):
|
||||
if slider not in sliders:
|
||||
sliders.append(slider)
|
||||
|
||||
## Remove a slider from management
|
||||
func remove_slider(slider: LabeledSlider):
|
||||
sliders.erase(slider)
|
||||
|
||||
## Update all sliders with the current sprite data
|
||||
func update_all_sliders(sprite):
|
||||
for slider in sliders:
|
||||
if slider is WobbleSlider:
|
||||
slider.set_target_sprite(sprite)
|
||||
elif slider is ParameterSlider:
|
||||
slider.set_target_sprite(sprite)
|
||||
|
||||
## Update wobble control states based on sync status
|
||||
func update_wobble_control_states(sprite):
|
||||
if not sprite:
|
||||
return
|
||||
|
||||
var is_synced = sprite.isSynced()
|
||||
|
||||
for slider in sliders:
|
||||
if slider is WobbleSlider:
|
||||
# Keep wobble controls enabled even when synced (they edit group settings)
|
||||
slider.set_editable(true)
|
||||
|
||||
# Visual feedback for sync state - slight tint to indicate group editing
|
||||
var tint = Color(1.0, 1.0, 0.9) if is_synced else Color.WHITE
|
||||
slider.set_modulate_color(tint)
|
||||
|
||||
## Get a slider by its parameter name
|
||||
func get_slider_by_parameter(parameter_name: String) -> LabeledSlider:
|
||||
for slider in sliders:
|
||||
if slider is WobbleSlider and slider.parameter_name == parameter_name:
|
||||
return slider
|
||||
elif slider is ParameterSlider and slider.sprite_property == parameter_name:
|
||||
return slider
|
||||
return null
|
||||
|
||||
## Apply consistent styling to all sliders
|
||||
func apply_consistent_styling():
|
||||
for slider in sliders:
|
||||
# Apply any consistent styling here
|
||||
pass
|
||||
1
ui_scenes/components/SliderManager.gd.uid
Normal file
1
ui_scenes/components/SliderManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c0rejv3uadhfh
|
||||
0
ui_scenes/components/SpriteSliderFactory.gd
Normal file
0
ui_scenes/components/SpriteSliderFactory.gd
Normal file
1
ui_scenes/components/SpriteSliderFactory.gd.uid
Normal file
1
ui_scenes/components/SpriteSliderFactory.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c8ssre2afcija
|
||||
63
ui_scenes/components/WobbleControlPanel.gd
Normal file
63
ui_scenes/components/WobbleControlPanel.gd
Normal file
@@ -0,0 +1,63 @@
|
||||
## A complete wobble control panel using the new slider components
|
||||
## Provides a consistent interface for all wobble parameters
|
||||
extends Control
|
||||
class_name WobbleControlPanel
|
||||
|
||||
## Reference to all wobble sliders
|
||||
@onready var x_frequency_slider: WobbleSlider = $VBoxContainer/XFrequencySlider
|
||||
@onready var x_amplitude_slider: WobbleSlider = $VBoxContainer/XAmplitudeSlider
|
||||
@onready var y_frequency_slider: WobbleSlider = $VBoxContainer/YFrequencySlider
|
||||
@onready var y_amplitude_slider: WobbleSlider = $VBoxContainer/YAmplitudeSlider
|
||||
@onready var r_frequency_slider: WobbleSlider = $VBoxContainer/RFrequencySlider
|
||||
@onready var r_amplitude_slider: WobbleSlider = $VBoxContainer/RAmplitudeSlider
|
||||
|
||||
## Array of all wobble sliders for easy iteration
|
||||
var wobble_sliders: Array[WobbleSlider] = []
|
||||
|
||||
## The sprite currently being edited
|
||||
var target_sprite = null
|
||||
|
||||
func _ready():
|
||||
# Collect all wobble sliders
|
||||
wobble_sliders = [
|
||||
x_frequency_slider,
|
||||
x_amplitude_slider,
|
||||
y_frequency_slider,
|
||||
y_amplitude_slider,
|
||||
r_frequency_slider,
|
||||
r_amplitude_slider
|
||||
]
|
||||
|
||||
# Connect to wobble sync control if it exists
|
||||
var wobble_sync_control = find_child("WobbleSyncControl")
|
||||
if wobble_sync_control:
|
||||
_connect_wobble_sync_signals(wobble_sync_control)
|
||||
|
||||
## Set the target sprite for all sliders
|
||||
func set_target_sprite(sprite):
|
||||
target_sprite = sprite
|
||||
for slider in wobble_sliders:
|
||||
slider.set_target_sprite(sprite)
|
||||
|
||||
## Update all sliders from the current sprite
|
||||
func update_from_sprite():
|
||||
if target_sprite:
|
||||
for slider in wobble_sliders:
|
||||
slider._update_from_sprite()
|
||||
|
||||
## Update visual feedback for sync status
|
||||
func update_sync_feedback():
|
||||
for slider in wobble_sliders:
|
||||
slider._update_sync_feedback()
|
||||
|
||||
## Connect signals from wobble sync control
|
||||
func _connect_wobble_sync_signals(wobble_sync_control):
|
||||
# Connect value change signals to update wobble sync control
|
||||
for slider in wobble_sliders:
|
||||
slider.value_changed.connect(_on_wobble_parameter_changed)
|
||||
|
||||
## Handle wobble parameter changes to update sync control
|
||||
func _on_wobble_parameter_changed(value: float):
|
||||
var wobble_sync_control = find_child("WobbleSyncControl")
|
||||
if wobble_sync_control and wobble_sync_control.has_method("updateUI"):
|
||||
wobble_sync_control.updateUI()
|
||||
1
ui_scenes/components/WobbleControlPanel.gd.uid
Normal file
1
ui_scenes/components/WobbleControlPanel.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cq2ltb5rxsr08
|
||||
75
ui_scenes/components/WobbleControlPanel.tscn
Normal file
75
ui_scenes/components/WobbleControlPanel.tscn
Normal file
@@ -0,0 +1,75 @@
|
||||
[gd_scene load_steps=8 format=4 uid="uid://bp2q8r4l6lam3"]
|
||||
|
||||
[ext_resource type="Script" path="res://ui_scenes/components/WobbleControlPanel.gd" id="1_wobble_panel_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://c8q7x2n9p4r5s" path="res://ui_scenes/components/WobbleSlider.tscn" id="2_wobble_slider"]
|
||||
|
||||
[node name="WobbleControlPanel" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_wobble_panel_script")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="XFrequencySlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "X Frequency"
|
||||
min_value = 0.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value_format = "%.1f"
|
||||
value_suffix = " Hz"
|
||||
parameter_name = "xFrq"
|
||||
|
||||
[node name="XAmplitudeSlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "X Amplitude"
|
||||
min_value = 0.0
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value_format = "%.0f"
|
||||
parameter_name = "xAmp"
|
||||
|
||||
[node name="YFrequencySlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "Y Frequency"
|
||||
min_value = 0.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value_format = "%.1f"
|
||||
value_suffix = " Hz"
|
||||
parameter_name = "yFrq"
|
||||
|
||||
[node name="YAmplitudeSlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "Y Amplitude"
|
||||
min_value = 0.0
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value_format = "%.0f"
|
||||
parameter_name = "yAmp"
|
||||
|
||||
[node name="RFrequencySlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "Rotation Frequency"
|
||||
min_value = 0.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value_format = "%.1f"
|
||||
value_suffix = " Hz"
|
||||
parameter_name = "rFrq"
|
||||
|
||||
[node name="RAmplitudeSlider" parent="VBoxContainer" instance=ExtResource("2_wobble_slider")]
|
||||
layout_mode = 2
|
||||
label_text = "Rotation Amplitude"
|
||||
min_value = 0.0
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value_format = "%.0f"
|
||||
value_suffix = "%"
|
||||
parameter_name = "rAmp"
|
||||
60
ui_scenes/components/WobbleSlider.gd
Normal file
60
ui_scenes/components/WobbleSlider.gd
Normal file
@@ -0,0 +1,60 @@
|
||||
@tool
|
||||
## A specialized slider component for wobble parameters
|
||||
## Automatically handles syncing with wobble groups and provides appropriate feedback
|
||||
extends LabeledSlider
|
||||
class_name WobbleSlider
|
||||
|
||||
## The wobble parameter name this slider controls
|
||||
@export var parameter_name: String = ""
|
||||
|
||||
## Reference to the sprite being edited
|
||||
var target_sprite = null
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
# Connect to value changes to update wobble parameters
|
||||
value_changed.connect(_on_wobble_value_changed)
|
||||
|
||||
## Set the sprite this slider controls
|
||||
func set_target_sprite(sprite):
|
||||
target_sprite = sprite
|
||||
if sprite:
|
||||
_update_from_sprite()
|
||||
|
||||
## Update the slider from the sprite's current values
|
||||
func _update_from_sprite():
|
||||
if not target_sprite or parameter_name == "":
|
||||
return
|
||||
|
||||
match parameter_name:
|
||||
"xFrq":
|
||||
set_value(target_sprite.xFrq)
|
||||
"xAmp":
|
||||
set_value(target_sprite.xAmp)
|
||||
"yFrq":
|
||||
set_value(target_sprite.yFrq)
|
||||
"yAmp":
|
||||
set_value(target_sprite.yAmp)
|
||||
"rFrq":
|
||||
set_value(target_sprite.rFrq)
|
||||
"rAmp":
|
||||
set_value(target_sprite.rAmp)
|
||||
|
||||
## Handle wobble parameter changes
|
||||
func _on_wobble_value_changed(new_value: float):
|
||||
if not target_sprite or parameter_name == "":
|
||||
return
|
||||
|
||||
target_sprite.updateWobbleParameter(parameter_name, new_value)
|
||||
|
||||
# Update visual feedback for sync status
|
||||
_update_sync_feedback()
|
||||
|
||||
## Update visual feedback based on sync status
|
||||
func _update_sync_feedback():
|
||||
if not target_sprite:
|
||||
return
|
||||
|
||||
var is_synced = target_sprite.isSynced()
|
||||
var tint = Color(1.0, 1.0, 0.9) if is_synced else Color.WHITE
|
||||
set_modulate_color(tint)
|
||||
1
ui_scenes/components/WobbleSlider.gd.uid
Normal file
1
ui_scenes/components/WobbleSlider.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fsc2h7ajjcbs
|
||||
19
ui_scenes/components/WobbleSlider.tscn
Normal file
19
ui_scenes/components/WobbleSlider.tscn
Normal file
@@ -0,0 +1,19 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c8q7x2oap4r5s"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bxm5q6n8x7p2q" path="res://ui_scenes/components/LabeledSlider.tscn" id="1_base_slider"]
|
||||
[ext_resource type="Script" uid="uid://fsc2h7ajjcbs" path="res://ui_scenes/components/WobbleSlider.gd" id="2_wobble_script"]
|
||||
|
||||
[node name="WobbleSlider" instance=ExtResource("1_base_slider")]
|
||||
script = ExtResource("2_wobble_script")
|
||||
parameter_name = ""
|
||||
|
||||
[node name="Label" parent="." index="0"]
|
||||
anchors_preset = 10
|
||||
grow_horizontal = 2
|
||||
text = "Value: 0"
|
||||
horizontal_alignment = 0
|
||||
|
||||
[node name="HSlider" parent="." index="1"]
|
||||
anchors_preset = 12
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
122
ui_scenes/selectedSprite/opacity_blend.gdshader
Normal file
122
ui_scenes/selectedSprite/opacity_blend.gdshader
Normal file
@@ -0,0 +1,122 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform float opacity : hint_range(0.0, 1.0) = 1.0;
|
||||
uniform int blend_mode : hint_range(0, 11) = 0;
|
||||
|
||||
// Blend mode constants:
|
||||
// 0: Normal
|
||||
// 1: Multiply
|
||||
// 2: Screen
|
||||
// 3: Overlay
|
||||
// 4: Soft Light
|
||||
// 5: Hard Light
|
||||
// 6: Color Dodge
|
||||
// 7: Color Burn
|
||||
// 8: Darken
|
||||
// 9: Lighten
|
||||
// 10: Add (Linear Dodge)
|
||||
// 11: Subtract
|
||||
|
||||
vec3 multiply_blend(vec3 base, vec3 overlay) {
|
||||
return base * overlay;
|
||||
}
|
||||
|
||||
vec3 screen_blend(vec3 base, vec3 overlay) {
|
||||
return 1.0 - (1.0 - base) * (1.0 - overlay);
|
||||
}
|
||||
|
||||
vec3 overlay_blend(vec3 base, vec3 overlay) {
|
||||
vec3 result;
|
||||
result.r = base.r < 0.5 ? 2.0 * base.r * overlay.r : 1.0 - 2.0 * (1.0 - base.r) * (1.0 - overlay.r);
|
||||
result.g = base.g < 0.5 ? 2.0 * base.g * overlay.g : 1.0 - 2.0 * (1.0 - base.g) * (1.0 - overlay.g);
|
||||
result.b = base.b < 0.5 ? 2.0 * base.b * overlay.b : 1.0 - 2.0 * (1.0 - base.b) * (1.0 - overlay.b);
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 soft_light_blend(vec3 base, vec3 overlay) {
|
||||
vec3 result;
|
||||
result.r = overlay.r < 0.5 ? 2.0 * base.r * overlay.r + base.r * base.r * (1.0 - 2.0 * overlay.r) :
|
||||
sqrt(base.r) * (2.0 * overlay.r - 1.0) + 2.0 * base.r * (1.0 - overlay.r);
|
||||
result.g = overlay.g < 0.5 ? 2.0 * base.g * overlay.g + base.g * base.g * (1.0 - 2.0 * overlay.g) :
|
||||
sqrt(base.g) * (2.0 * overlay.g - 1.0) + 2.0 * base.g * (1.0 - overlay.g);
|
||||
result.b = overlay.b < 0.5 ? 2.0 * base.b * overlay.b + base.b * base.b * (1.0 - 2.0 * overlay.b) :
|
||||
sqrt(base.b) * (2.0 * overlay.b - 1.0) + 2.0 * base.b * (1.0 - overlay.b);
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 hard_light_blend(vec3 base, vec3 overlay) {
|
||||
vec3 result;
|
||||
result.r = overlay.r < 0.5 ? 2.0 * base.r * overlay.r : 1.0 - 2.0 * (1.0 - base.r) * (1.0 - overlay.r);
|
||||
result.g = overlay.g < 0.5 ? 2.0 * base.g * overlay.g : 1.0 - 2.0 * (1.0 - base.g) * (1.0 - overlay.g);
|
||||
result.b = overlay.b < 0.5 ? 2.0 * base.b * overlay.b : 1.0 - 2.0 * (1.0 - base.b) * (1.0 - overlay.b);
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 color_dodge_blend(vec3 base, vec3 overlay) {
|
||||
vec3 result;
|
||||
result.r = overlay.r >= 1.0 ? 1.0 : min(1.0, base.r / (1.0 - overlay.r));
|
||||
result.g = overlay.g >= 1.0 ? 1.0 : min(1.0, base.g / (1.0 - overlay.g));
|
||||
result.b = overlay.b >= 1.0 ? 1.0 : min(1.0, base.b / (1.0 - overlay.b));
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 color_burn_blend(vec3 base, vec3 overlay) {
|
||||
vec3 result;
|
||||
result.r = overlay.r <= 0.0 ? 0.0 : max(0.0, 1.0 - (1.0 - base.r) / overlay.r);
|
||||
result.g = overlay.g <= 0.0 ? 0.0 : max(0.0, 1.0 - (1.0 - base.g) / overlay.g);
|
||||
result.b = overlay.b <= 0.0 ? 0.0 : max(0.0, 1.0 - (1.0 - base.b) / overlay.b);
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 darken_blend(vec3 base, vec3 overlay) {
|
||||
return min(base, overlay);
|
||||
}
|
||||
|
||||
vec3 lighten_blend(vec3 base, vec3 overlay) {
|
||||
return max(base, overlay);
|
||||
}
|
||||
|
||||
vec3 add_blend(vec3 base, vec3 overlay) {
|
||||
return min(vec3(1.0), base + overlay);
|
||||
}
|
||||
|
||||
vec3 subtract_blend(vec3 base, vec3 overlay) {
|
||||
return max(vec3(0.0), base - overlay);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec4 tex_color = texture(TEXTURE, UV);
|
||||
|
||||
// Apply blend mode (only to RGB, preserve alpha channel behavior)
|
||||
vec3 final_color;
|
||||
|
||||
if (blend_mode == 0) { // Normal
|
||||
final_color = tex_color.rgb;
|
||||
} else if (blend_mode == 1) { // Multiply
|
||||
final_color = multiply_blend(vec3(1.0), tex_color.rgb);
|
||||
} else if (blend_mode == 2) { // Screen
|
||||
final_color = screen_blend(vec3(0.0), tex_color.rgb);
|
||||
} else if (blend_mode == 3) { // Overlay
|
||||
final_color = overlay_blend(vec3(0.5), tex_color.rgb);
|
||||
} else if (blend_mode == 4) { // Soft Light
|
||||
final_color = soft_light_blend(vec3(0.5), tex_color.rgb);
|
||||
} else if (blend_mode == 5) { // Hard Light
|
||||
final_color = hard_light_blend(vec3(0.5), tex_color.rgb);
|
||||
} else if (blend_mode == 6) { // Color Dodge
|
||||
final_color = color_dodge_blend(vec3(0.0), tex_color.rgb);
|
||||
} else if (blend_mode == 7) { // Color Burn
|
||||
final_color = color_burn_blend(vec3(1.0), tex_color.rgb);
|
||||
} else if (blend_mode == 8) { // Darken
|
||||
final_color = darken_blend(vec3(1.0), tex_color.rgb);
|
||||
} else if (blend_mode == 9) { // Lighten
|
||||
final_color = lighten_blend(vec3(0.0), tex_color.rgb);
|
||||
} else if (blend_mode == 10) { // Add (Linear Dodge)
|
||||
final_color = add_blend(vec3(0.0), tex_color.rgb);
|
||||
} else if (blend_mode == 11) { // Subtract
|
||||
final_color = subtract_blend(vec3(1.0), tex_color.rgb);
|
||||
} else {
|
||||
final_color = tex_color.rgb;
|
||||
}
|
||||
|
||||
COLOR = vec4(final_color, tex_color.a * opacity);
|
||||
}
|
||||
1
ui_scenes/selectedSprite/opacity_blend.gdshader.uid
Normal file
1
ui_scenes/selectedSprite/opacity_blend.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dy03x5g364v80
|
||||
@@ -6,6 +6,8 @@ var type = "sprite"
|
||||
var imageData = null
|
||||
var tex = null
|
||||
@export var path = ""
|
||||
## Custom display name for the sprite (separate from file path)
|
||||
var displayName = ""
|
||||
|
||||
var loadedImageData = null
|
||||
|
||||
@@ -28,7 +30,7 @@ var imageSize = Vector2.ZERO
|
||||
@onready var wob = $WobbleOrigin
|
||||
|
||||
@onready var outlineScene = preload("res://ui_scenes/selectedSprite/outline.tscn")
|
||||
@onready var opacityShader = preload("res://ui_scenes/selectedSprite/opacity.gdshader")
|
||||
@onready var opacityBlendShader = preload("res://ui_scenes/selectedSprite/opacity_blend.gdshader")
|
||||
|
||||
var opacityMaterial = null
|
||||
|
||||
@@ -79,20 +81,25 @@ var ignoreBounce = false
|
||||
#Animation
|
||||
var frames = 1
|
||||
var animSpeed = 0
|
||||
var animTime = 0.0 # Time accumulator for animations
|
||||
|
||||
var remadePolygon = false
|
||||
|
||||
var clipped = false
|
||||
|
||||
var tick = 0
|
||||
var time = 0.0 # Time accumulator for physics calculations
|
||||
|
||||
#Opacity
|
||||
var spriteOpacity = 1.0
|
||||
var affectChildrenOpacity = false
|
||||
var blendMode = 0 # Default to Normal blend mode
|
||||
|
||||
#Vis toggle
|
||||
var toggle = "null"
|
||||
|
||||
#Wobble Sync Group
|
||||
var wobbleSyncGroup = ""
|
||||
|
||||
func _ready():
|
||||
|
||||
Global.main.spriteVisToggles.connect(visToggle)
|
||||
@@ -170,6 +177,8 @@ func _ready():
|
||||
|
||||
setClip(clipped)
|
||||
|
||||
# Always initialize opacity shader
|
||||
updateShaderOpacity()
|
||||
|
||||
if Global.filtering:
|
||||
sprite.texture_filter = 2
|
||||
@@ -224,7 +233,7 @@ func replaceSprite(pathNew):
|
||||
remakePolygon()
|
||||
|
||||
func _process(delta):
|
||||
tick += 1
|
||||
time += delta
|
||||
if Global.heldSprite == self:
|
||||
|
||||
grabArea.visible = true
|
||||
@@ -234,6 +243,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
|
||||
@@ -251,13 +270,17 @@ func _process(delta):
|
||||
|
||||
talkBlink()
|
||||
|
||||
animation()
|
||||
animation(delta)
|
||||
|
||||
func animation():
|
||||
|
||||
var speed = max(float(animSpeed),Engine.max_fps*6.0)
|
||||
func animation(delta):
|
||||
if animSpeed > 0 and frames > 1:
|
||||
if Global.animationTick % int((speed)/float(animSpeed)) == 0:
|
||||
animTime += delta
|
||||
# Convert animSpeed to frames per second - no additional scaling needed since this controls frame rate
|
||||
var frameRate = animSpeed / 60.0 # Normalize from old frame-based system
|
||||
var frameInterval = 1.0 / frameRate
|
||||
|
||||
if animTime >= frameInterval:
|
||||
animTime -= frameInterval
|
||||
if sprite.frame == frames - 1:
|
||||
sprite.frame = 0
|
||||
else:
|
||||
@@ -277,7 +300,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
|
||||
@@ -291,7 +319,36 @@ func calculateParentOpacityMultiplier():
|
||||
|
||||
return multiplier
|
||||
|
||||
## Update wobble parameters and sync to group if needed
|
||||
func updateWobbleParameter(parameter: String, value: float):
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# If this sprite is in a sync group, update the entire group
|
||||
if wobbleSyncGroup != "" and WobbleSyncManager:
|
||||
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:
|
||||
pass # Individual sprite update - no sync needed
|
||||
|
||||
## 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):
|
||||
@@ -348,31 +405,46 @@ func drag(delta):
|
||||
if dragSpeed == 0:
|
||||
dragger.global_position = wob.global_position
|
||||
else:
|
||||
dragger.global_position = lerp(dragger.global_position,wob.global_position,1/dragSpeed)
|
||||
# Convert dragSpeed to a proper delta-time lerp factor
|
||||
# Higher dragSpeed = slower movement, so we invert it
|
||||
# Use Global.main.physicsTimeMultiplier for consistent physics timing
|
||||
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||
var lerpFactor = clamp(delta * (physicsMultiplier * 10.0 / max(dragSpeed, 0.1)), 0.0, 1.0)
|
||||
dragger.global_position = lerp(dragger.global_position, wob.global_position, lerpFactor)
|
||||
dragOrigin.global_position = dragger.global_position
|
||||
|
||||
func wobble():
|
||||
wob.position.x = sin(tick*xFrq)*xAmp
|
||||
wob.position.y = sin(tick*yFrq)*yAmp
|
||||
# Use Global.main.physicsTimeMultiplier for consistent physics timing
|
||||
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||
var scaledTime = time * physicsMultiplier
|
||||
wob.position.x = sin(scaledTime * xFrq) * xAmp
|
||||
wob.position.y = sin(scaledTime * yFrq) * yAmp
|
||||
|
||||
func rotationalDrag(length,delta):
|
||||
func rotationalDrag(length, delta):
|
||||
var yvel = (length * rdragStr)
|
||||
|
||||
#Calculate Max angle
|
||||
yvel = clamp(yvel,rLimitMin,rLimitMax)
|
||||
yvel = clamp(yvel, rLimitMin, rLimitMax)
|
||||
|
||||
# Add rotation wobble as percentage of rotational limits range
|
||||
var rotationRange = rLimitMax - rLimitMin
|
||||
var rotationWobble = sin(tick*rFrq) * (rotationRange * rAmp * 0.01)
|
||||
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||
var scaledTime = time * physicsMultiplier
|
||||
var rotationWobble = sin(scaledTime * rFrq) * (rotationRange * rAmp * 0.01)
|
||||
var totalRotation = yvel + rotationWobble
|
||||
|
||||
sprite.rotation = lerp_angle(sprite.rotation,deg_to_rad(totalRotation),0.25)
|
||||
# Use delta-based lerp for smooth rotation with slower speed
|
||||
var lerpFactor = clamp(delta * (physicsMultiplier * 1.0), 0.0, 1.0) # Reduced from 15.0 to 4.0
|
||||
sprite.rotation = lerp_angle(sprite.rotation, deg_to_rad(totalRotation), lerpFactor)
|
||||
|
||||
func stretch(length,delta):
|
||||
func stretch(length, delta):
|
||||
var yvel = (length * stretchAmount * 0.01)
|
||||
var target = Vector2(1.0-yvel,1.0+yvel)
|
||||
var target = Vector2(1.0 - yvel, 1.0 + yvel)
|
||||
|
||||
sprite.scale = lerp(sprite.scale,target,0.5)
|
||||
# Use delta-based lerp for smooth stretching
|
||||
var physicsMultiplier = Global.main.physicsTimeMultiplier if Global.main else 10.0
|
||||
var lerpFactor = clamp(delta * (physicsMultiplier * 30.0), 0.0, 1.0)
|
||||
sprite.scale = lerp(sprite.scale, target, lerpFactor)
|
||||
|
||||
func changeCollision(enable):
|
||||
grabArea.monitorable = enable
|
||||
@@ -440,17 +512,24 @@ 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 blend shader to ensure it's loaded
|
||||
if opacityMaterial == null:
|
||||
opacityMaterial = ShaderMaterial.new()
|
||||
opacityMaterial.shader = opacityBlendShader
|
||||
|
||||
opacityMaterial.set_shader_parameter("opacity", finalOpacity)
|
||||
opacityMaterial.set_shader_parameter("blend_mode", blendMode)
|
||||
sprite.material = opacityMaterial
|
||||
|
||||
## Update the blend mode and refresh the shader
|
||||
func updateBlendMode(newBlendMode: int):
|
||||
if BlendModeManager.is_valid_blend_mode(newBlendMode):
|
||||
blendMode = newBlendMode
|
||||
updateShaderOpacity() # Refresh shader with new blend mode
|
||||
|
||||
func updateChildrenOpacityRecursively():
|
||||
var linkedSprites = getAllLinkedSprites()
|
||||
|
||||
@@ -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
|
||||
@@ -210,7 +211,6 @@ func _on_stream_deck_check_toggled(button_pressed):
|
||||
func _on_experimental_mic_loudness_toggle(checked):
|
||||
Global.experimentalMicLoudness = checked
|
||||
Saving.settings["experimentalMicLoudness"] = checked
|
||||
print("Experimental mic loudness: ", checked)
|
||||
|
||||
|
||||
func _process(delta):
|
||||
|
||||
@@ -5,15 +5,138 @@ extends Node2D
|
||||
|
||||
@onready var parentSpin = $SubViewportContainer2/SubViewport/Node3D/Sprite3D
|
||||
|
||||
@onready var spriteRotDisplay = $RotationalLimits/RotBack/SpriteDisplay
|
||||
@onready var spriteRotDisplay = $VBoxContainer/RotationalLimitsSection/RotBack/SpriteDisplay
|
||||
|
||||
|
||||
@onready var coverCollider = $Area2D/CollisionShape2D
|
||||
@onready var wobbleSyncControl = null # Will be assigned in _ready()
|
||||
@onready var blendModeDropdown = $VBoxContainer/RenderingSection/blendModeContainer/blendModeDropdown
|
||||
|
||||
## Slider management system
|
||||
var slider_manager: SliderManager = null
|
||||
|
||||
func _ready():
|
||||
Global.spriteEdit = self
|
||||
|
||||
# Initialize slider management system
|
||||
slider_manager = SliderManager.new(self)
|
||||
_setup_slider_components()
|
||||
|
||||
# Find the wobble sync control node
|
||||
wobbleSyncControl = find_child("WobbleSyncControl")
|
||||
|
||||
# Set up blend mode dropdown
|
||||
setupBlendModeDropdown()
|
||||
|
||||
## Set up all slider components
|
||||
func _setup_slider_components():
|
||||
# Initialize all the new slider components and add them to the manager
|
||||
_setup_drag_slider()
|
||||
_setup_wobble_sliders()
|
||||
_setup_rotation_sliders()
|
||||
_setup_limit_sliders()
|
||||
_setup_animation_sliders()
|
||||
_setup_opacity_slider()
|
||||
|
||||
## Set up drag slider
|
||||
func _setup_drag_slider():
|
||||
var drag_slider = $VBoxContainer/PhysicsSection/DragSlider
|
||||
if drag_slider:
|
||||
slider_manager.add_slider(drag_slider)
|
||||
drag_slider.value_changed.connect(_on_drag_slider_value_changed)
|
||||
|
||||
## Set up wobble sliders
|
||||
func _setup_wobble_sliders():
|
||||
var wobble_sliders = [
|
||||
$VBoxContainer/WobbleSection/xFrqSlider,
|
||||
$VBoxContainer/WobbleSection/xAmpSlider,
|
||||
$VBoxContainer/WobbleSection/yFrqSlider,
|
||||
$VBoxContainer/WobbleSection/yAmpSlider,
|
||||
$VBoxContainer/WobbleSection/rFrqSlider,
|
||||
$VBoxContainer/WobbleSection/rAmpSlider
|
||||
]
|
||||
|
||||
for slider in wobble_sliders:
|
||||
if slider:
|
||||
slider_manager.add_slider(slider)
|
||||
slider.value_changed.connect(_on_wobble_slider_changed)
|
||||
|
||||
## Set up rotation sliders
|
||||
func _setup_rotation_sliders():
|
||||
var rotation_sliders = [
|
||||
$VBoxContainer/PhysicsSection/rDragSlider,
|
||||
$VBoxContainer/PhysicsSection/squashSlider
|
||||
]
|
||||
|
||||
for slider in rotation_sliders:
|
||||
if slider:
|
||||
slider_manager.add_slider(slider)
|
||||
slider.value_changed.connect(_on_rotation_slider_changed)
|
||||
|
||||
## Set up rotational limit sliders
|
||||
func _setup_limit_sliders():
|
||||
var limit_sliders = [
|
||||
$VBoxContainer/RotationalLimitsSection/rotLimitMinSlider,
|
||||
$VBoxContainer/RotationalLimitsSection/rotLimitMaxSlider
|
||||
]
|
||||
|
||||
for slider in limit_sliders:
|
||||
if slider:
|
||||
slider_manager.add_slider(slider)
|
||||
slider.value_changed.connect(_on_limit_slider_changed)
|
||||
|
||||
## Set up animation sliders
|
||||
func _setup_animation_sliders():
|
||||
var animation_sliders = [
|
||||
$VBoxContainer/AnimationSection/animFramesSlider,
|
||||
$VBoxContainer/AnimationSection/animSpeedSlider
|
||||
]
|
||||
|
||||
for slider in animation_sliders:
|
||||
if slider:
|
||||
slider_manager.add_slider(slider)
|
||||
slider.value_changed.connect(_on_animation_slider_changed)
|
||||
|
||||
## Set up opacity slider
|
||||
func _setup_opacity_slider():
|
||||
var opacity_slider = $VBoxContainer/RenderingSection/opacitySlider
|
||||
if opacity_slider:
|
||||
slider_manager.add_slider(opacity_slider)
|
||||
opacity_slider.value_changed.connect(_on_opacity_slider_changed)
|
||||
|
||||
## Generic slider change handlers
|
||||
func _on_drag_slider_value_changed(value: float):
|
||||
if Global.heldSprite:
|
||||
Global.heldSprite.dragSpeed = value
|
||||
|
||||
func _on_wobble_slider_changed(value: float):
|
||||
# Wobble sliders handle their own parameter updates
|
||||
# Just update the wobble sync control if needed
|
||||
if wobbleSyncControl:
|
||||
wobbleSyncControl.updateUI()
|
||||
|
||||
func _on_rotation_slider_changed(value: float):
|
||||
# Rotation sliders handle their own parameter updates
|
||||
if Global.heldSprite:
|
||||
changeRotLimit() # Update the rotation limit display
|
||||
|
||||
func _on_limit_slider_changed(value: float):
|
||||
# Limit sliders handle their own parameter updates
|
||||
if Global.heldSprite:
|
||||
changeRotLimit() # Update the rotation limit display
|
||||
|
||||
func _on_animation_slider_changed(value: float):
|
||||
# Animation sliders handle their own parameter updates
|
||||
if Global.heldSprite:
|
||||
# Special handling for frames slider
|
||||
var frames_slider = $VBoxContainer/AnimationSection/animFramesSlider
|
||||
if frames_slider and frames_slider.value != Global.heldSprite.frames:
|
||||
spriteSpin.hframes = Global.heldSprite.frames
|
||||
|
||||
func _on_opacity_slider_changed(value: float):
|
||||
# Opacity slider handles its own parameter updates via sprite_property
|
||||
pass
|
||||
|
||||
func setImage():
|
||||
if Global.heldSprite == null:
|
||||
return
|
||||
@@ -27,67 +150,48 @@ func setImage():
|
||||
var displaySize = Global.heldSprite.imageData.get_size().y
|
||||
spriteRotDisplay.scale = Vector2(1,1) * (150.0/displaySize)
|
||||
|
||||
$Slider/Label.text = "drag: " + str(Global.heldSprite.dragSpeed)
|
||||
$Slider/DragSlider.value = Global.heldSprite.dragSpeed
|
||||
# Update all slider components with the current sprite
|
||||
if slider_manager:
|
||||
slider_manager.update_all_sliders(Global.heldSprite)
|
||||
|
||||
# Update individual sliders with current values
|
||||
_update_all_slider_values()
|
||||
|
||||
$WobbleControl/xFrqLabel.text = "x frequency: " + str(Global.heldSprite.xFrq)
|
||||
$WobbleControl/xAmpLabel.text = "x amplitude: " + str(Global.heldSprite.xAmp)
|
||||
$Position/fileTitle.text = Global.heldSprite.path.replace("user://", "")
|
||||
|
||||
$WobbleControl/xFrq.value = Global.heldSprite.xFrq
|
||||
$WobbleControl/xAmp.value = Global.heldSprite.xAmp
|
||||
|
||||
$WobbleControl/yFrqLabel.text = "y frequency: " + str(Global.heldSprite.yFrq)
|
||||
$WobbleControl/yAmpLabel.text = "y amplitude: " + str(Global.heldSprite.yAmp)
|
||||
|
||||
$WobbleControl/yFrq.value = Global.heldSprite.yFrq
|
||||
$WobbleControl/yAmp.value = Global.heldSprite.yAmp
|
||||
$VBoxContainer/PhysicsSection/BounceVelocity.button_pressed = Global.heldSprite.ignoreBounce
|
||||
$VBoxContainer/RenderingSection/ClipLinked.button_pressed = Global.heldSprite.clipped
|
||||
|
||||
$WobbleControl/rFrqLabel.text = "rotation frequency: " + str(Global.heldSprite.rFrq)
|
||||
$WobbleControl/rAmpLabel.text = "rotation percentage: " + str(Global.heldSprite.rAmp) + "%"
|
||||
|
||||
$WobbleControl/rFrq.value = Global.heldSprite.rFrq
|
||||
$WobbleControl/rAmp.value = Global.heldSprite.rAmp
|
||||
$VBoxContainer/Buttons/SpeakingWrapper/Speaking.frame = Global.heldSprite.showOnTalk
|
||||
$VBoxContainer/Buttons/BlinkingWrapper/Blinking.frame = Global.heldSprite.showOnBlink
|
||||
|
||||
$Rotation/rDragLabel.text = "rotational drag: " + str(Global.heldSprite.rdragStr)
|
||||
$Rotation/rDrag.value = Global.heldSprite.rdragStr
|
||||
$VBoxContainer/RenderingSection/affectChildrenCheck.button_pressed = Global.heldSprite.affectChildrenOpacity
|
||||
$VBoxContainer/RenderingSection/blendModeContainer/blendModeDropdown.selected = Global.heldSprite.blendMode
|
||||
|
||||
$Buttons/Speaking.frame = Global.heldSprite.showOnTalk
|
||||
$Buttons/Blinking.frame = Global.heldSprite.showOnBlink
|
||||
|
||||
$RotationalLimits/rotLimitMin.value = Global.heldSprite.rLimitMin
|
||||
$RotationalLimits/RotLimitMin.text = "rotational limit min: " + str(Global.heldSprite.rLimitMin)
|
||||
$RotationalLimits/rotLimitMax.value = Global.heldSprite.rLimitMax
|
||||
$RotationalLimits/RotLimitMax.text = "rotational limit max: " + str(Global.heldSprite.rLimitMax)
|
||||
|
||||
$Rotation/squashlabel.text = "squash: " + str(Global.heldSprite.stretchAmount)
|
||||
$Rotation/squash.value = Global.heldSprite.stretchAmount
|
||||
|
||||
$Position/fileTitle.text = Global.heldSprite.path
|
||||
|
||||
$Buttons/CheckBox.button_pressed = Global.heldSprite.ignoreBounce
|
||||
$Buttons/ClipLinked.button_pressed = Global.heldSprite.clipped
|
||||
|
||||
$Animation/animSpeedLabel.text = "animation speed: " + str(Global.heldSprite.animSpeed)
|
||||
$Animation/animSpeed.value = Global.heldSprite.animSpeed
|
||||
|
||||
$Animation/animFramesLabel.text = "sprite frames: " + str(Global.heldSprite.frames)
|
||||
$Animation/animFrames.value = Global.heldSprite.frames
|
||||
|
||||
$Opacity/opacityLabel.text = "opacity: " + str(int(Global.heldSprite.spriteOpacity * 100)) + "%"
|
||||
$Opacity/opacitySlider.value = Global.heldSprite.spriteOpacity
|
||||
$Opacity/affectChildrenCheck.button_pressed = Global.heldSprite.affectChildrenOpacity
|
||||
|
||||
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||
$VBoxContainer/BoxContainer/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||
|
||||
changeRotLimit()
|
||||
|
||||
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
|
||||
$VBoxContainer/Buttons/UnlinkWrapper/Unlink.visible = false
|
||||
parentSpin.visible = false
|
||||
else:
|
||||
$Buttons/Unlink.visible = true
|
||||
$VBoxContainer/Buttons/UnlinkWrapper/Unlink.visible = true
|
||||
|
||||
var nodes = get_tree().get_nodes_in_group(str(Global.heldSprite.parentId))
|
||||
|
||||
@@ -98,6 +202,92 @@ func setImage():
|
||||
parentSpin.pixel_size = 1.5 / nodes[0].imageData.get_size().y
|
||||
parentSpin.hframes = nodes[0].frames
|
||||
parentSpin.visible = true
|
||||
|
||||
## Update all slider components with current sprite values
|
||||
func _update_all_slider_values():
|
||||
if not Global.heldSprite:
|
||||
return
|
||||
|
||||
# Update drag slider
|
||||
var drag_slider = $VBoxContainer/PhysicsSection/DragSlider
|
||||
if drag_slider:
|
||||
drag_slider.set_value(Global.heldSprite.dragSpeed)
|
||||
|
||||
# Update wobble sliders
|
||||
var wobble_sliders = {
|
||||
$VBoxContainer/WobbleSection/xFrqSlider: Global.heldSprite.xFrq,
|
||||
$VBoxContainer/WobbleSection/xAmpSlider: Global.heldSprite.xAmp,
|
||||
$VBoxContainer/WobbleSection/yFrqSlider: Global.heldSprite.yFrq,
|
||||
$VBoxContainer/WobbleSection/yAmpSlider: Global.heldSprite.yAmp,
|
||||
$VBoxContainer/WobbleSection/rFrqSlider: Global.heldSprite.rFrq,
|
||||
$VBoxContainer/WobbleSection/rAmpSlider: Global.heldSprite.rAmp
|
||||
}
|
||||
|
||||
for slider in wobble_sliders:
|
||||
if slider:
|
||||
slider.set_value(wobble_sliders[slider])
|
||||
|
||||
# Update rotation sliders
|
||||
var rotation_sliders = {
|
||||
$VBoxContainer/PhysicsSection/rDragSlider: Global.heldSprite.rdragStr,
|
||||
$VBoxContainer/PhysicsSection/squashSlider: Global.heldSprite.stretchAmount
|
||||
}
|
||||
|
||||
for slider in rotation_sliders:
|
||||
if slider:
|
||||
slider.set_value(rotation_sliders[slider])
|
||||
|
||||
# Update limit sliders
|
||||
var limit_sliders = {
|
||||
$VBoxContainer/RotationalLimitsSection/rotLimitMinSlider: Global.heldSprite.rLimitMin,
|
||||
$VBoxContainer/RotationalLimitsSection/rotLimitMaxSlider: Global.heldSprite.rLimitMax
|
||||
}
|
||||
|
||||
for slider in limit_sliders:
|
||||
if slider:
|
||||
slider.set_value(limit_sliders[slider])
|
||||
|
||||
# Update animation sliders
|
||||
var animation_sliders = {
|
||||
$VBoxContainer/AnimationSection/animFramesSlider: Global.heldSprite.frames,
|
||||
$VBoxContainer/AnimationSection/animSpeedSlider: Global.heldSprite.animSpeed
|
||||
}
|
||||
|
||||
for slider in animation_sliders:
|
||||
if slider:
|
||||
slider.set_value(animation_sliders[slider])
|
||||
|
||||
# Update opacity slider
|
||||
var opacity_slider = $VBoxContainer/RenderingSection/opacitySlider
|
||||
if opacity_slider:
|
||||
opacity_slider.set_value(Global.heldSprite.spriteOpacity)
|
||||
|
||||
## Update wobble control states based on sync status
|
||||
func updateWobbleControlStates():
|
||||
if Global.heldSprite == null:
|
||||
return
|
||||
|
||||
# Use slider manager if available
|
||||
if slider_manager:
|
||||
slider_manager.update_wobble_control_states(Global.heldSprite)
|
||||
|
||||
# Also update individual wobble sliders for visual feedback
|
||||
var is_synced = Global.heldSprite.isSynced()
|
||||
var tint = Color(1.0, 1.0, 0.9) if is_synced else Color.WHITE
|
||||
|
||||
var wobble_sliders = [
|
||||
$VBoxContainer/WobbleSection/xFrqSlider,
|
||||
$VBoxContainer/WobbleSection/xAmpSlider,
|
||||
$VBoxContainer/WobbleSection/yFrqSlider,
|
||||
$VBoxContainer/WobbleSection/yAmpSlider,
|
||||
$VBoxContainer/WobbleSection/rFrqSlider,
|
||||
$VBoxContainer/WobbleSection/rAmpSlider
|
||||
]
|
||||
|
||||
for slider in wobble_sliders:
|
||||
if slider and slider.has_method("set_modulate_color"):
|
||||
slider.set_modulate_color(tint)
|
||||
slider.set_editable(true) # Keep enabled even when synced
|
||||
|
||||
func _process(delta):
|
||||
|
||||
@@ -111,9 +301,9 @@ func _process(delta):
|
||||
spriteSpin.rotate_y(delta*4.0)
|
||||
parentSpin.rotate_y(delta*4.0)
|
||||
|
||||
$Position/Label.text = "position X : "+str(obj.position.x)+" Y: " + str(obj.position.y)
|
||||
$Position/Label2.text = "offset X : "+str(obj.offset.x)+" Y: " + str(obj.offset.y)
|
||||
$Position/Label3.text = "layer : "+str(obj.z)
|
||||
$VBoxContainer/InfoSection/PositionLabel.text = "position X : "+str(obj.position.x)+" Y: " + str(obj.position.y)
|
||||
$VBoxContainer/InfoSection/OffsetLabel.text = "offset X : "+str(obj.offset.x)+" Y: " + str(obj.offset.y)
|
||||
$VBoxContainer/InfoSection/LayerLabel.text = "layer : "+str(obj.z)
|
||||
|
||||
#Sprite Rotational Limit Display
|
||||
|
||||
@@ -121,65 +311,30 @@ func _process(delta):
|
||||
var minimum = Global.heldSprite.rLimitMin
|
||||
|
||||
spriteRotDisplay.rotation_degrees = sin(Global.animationTick*0.05)*(size/2.0)+(minimum+(size/2.0))
|
||||
$RotationalLimits/RotBack/RotLineDisplay3.rotation_degrees = spriteRotDisplay.rotation_degrees
|
||||
|
||||
|
||||
func _on_drag_slider_value_changed(value):
|
||||
if Global.heldSprite != null:
|
||||
$Slider/Label.text = "drag: " + str(value)
|
||||
Global.heldSprite.dragSpeed = value
|
||||
|
||||
|
||||
func _on_x_frq_value_changed(value):
|
||||
$WobbleControl/xFrqLabel.text = "x frequency: " + str(value)
|
||||
Global.heldSprite.xFrq = value
|
||||
|
||||
|
||||
func _on_x_amp_value_changed(value):
|
||||
$WobbleControl/xAmpLabel.text = "x amplitude: " + str(value)
|
||||
Global.heldSprite.xAmp = value
|
||||
|
||||
|
||||
func _on_y_frq_value_changed(value):
|
||||
$WobbleControl/yFrqLabel.text = "y frequency: " + str(value)
|
||||
Global.heldSprite.yFrq = value
|
||||
|
||||
func _on_y_amp_value_changed(value):
|
||||
$WobbleControl/yAmpLabel.text = "y amplitude: " + str(value)
|
||||
Global.heldSprite.yAmp = value
|
||||
|
||||
|
||||
func _on_r_frq_value_changed(value):
|
||||
$WobbleControl/rFrqLabel.text = "rotation frequency: " + str(value)
|
||||
Global.heldSprite.rFrq = value
|
||||
|
||||
func _on_r_amp_value_changed(value):
|
||||
$WobbleControl/rAmpLabel.text = "rotation amplitude: " + str(value) + "%"
|
||||
Global.heldSprite.rAmp = value
|
||||
|
||||
|
||||
func _on_r_drag_value_changed(value):
|
||||
$Rotation/rDragLabel.text = "rotational drag: " + str(value)
|
||||
Global.heldSprite.rdragStr = value
|
||||
$VBoxContainer/RotationalLimitsSection/RotBack/RotLineDisplay3.rotation_degrees = spriteRotDisplay.rotation_degrees
|
||||
|
||||
|
||||
func _on_speaking_pressed():
|
||||
var f = $Buttons/Speaking.frame
|
||||
var f = $VBoxContainer/Buttons/SpeakingWrapper/Speaking.frame
|
||||
f = (f+1) % 3
|
||||
|
||||
$Buttons/Speaking.frame = f
|
||||
$VBoxContainer/Buttons/SpeakingWrapper/Speakingaking.frame = f
|
||||
Global.heldSprite.showOnTalk = f
|
||||
|
||||
|
||||
func _on_blinking_pressed():
|
||||
var f = $Buttons/Blinking.frame
|
||||
var f = $VBoxContainer/Buttons/BlinkingWrapper/Blinking.frame
|
||||
f = (f+1) % 3
|
||||
|
||||
$Buttons/Blinking.frame = f
|
||||
$VBoxContainer/Buttons/BlinkingWrapper/Blinking.frame = f
|
||||
Global.heldSprite.showOnBlink = f
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -192,39 +347,34 @@ func _on_unlink_pressed():
|
||||
setImage()
|
||||
|
||||
|
||||
func _on_rot_limit_min_value_changed(value):
|
||||
$RotationalLimits/RotLimitMin.text = "rotational limit min: " + str(value)
|
||||
Global.heldSprite.rLimitMin = value
|
||||
|
||||
changeRotLimit()
|
||||
|
||||
func _on_rot_limit_max_value_changed(value):
|
||||
$RotationalLimits/RotLimitMax.text = "rotational limit max: " + str(value)
|
||||
Global.heldSprite.rLimitMax = value
|
||||
|
||||
changeRotLimit()
|
||||
|
||||
func changeRotLimit():
|
||||
$RotationalLimits/RotBack/rotLimitBar.value = Global.heldSprite.rLimitMax - Global.heldSprite.rLimitMin
|
||||
$RotationalLimits/RotBack/rotLimitBar.rotation_degrees = Global.heldSprite.rLimitMin + 90
|
||||
$VBoxContainer/RotationalLimitsSection/RotBack/rotLimitBar.value = Global.heldSprite.rLimitMax - Global.heldSprite.rLimitMin
|
||||
$VBoxContainer/RotationalLimitsSection/RotBack/rotLimitBar.rotation_degrees = Global.heldSprite.rLimitMin + 90
|
||||
|
||||
$VBoxContainer/RotationalLimitsSection/RotBack/RotLineDisplay.rotation_degrees = Global.heldSprite.rLimitMin
|
||||
$VBoxContainer/RotationalLimitsSection/RotBack/RotLineDisplay2.rotation_degrees = Global.heldSprite.rLimitMax
|
||||
|
||||
$RotationalLimits/RotBack/RotLineDisplay.rotation_degrees = Global.heldSprite.rLimitMin
|
||||
$RotationalLimits/RotBack/RotLineDisplay2.rotation_degrees = Global.heldSprite.rLimitMax
|
||||
|
||||
func setLayerButtons():
|
||||
if Global.heldSprite == null:
|
||||
return
|
||||
|
||||
var a = Global.heldSprite.costumeLayers.duplicate()
|
||||
|
||||
$Layers/Layer1.frame = 1-a[0]
|
||||
$Layers/Layer2.frame = 1-a[1]
|
||||
$Layers/Layer3.frame = 1-a[2]
|
||||
$Layers/Layer4.frame = 1-a[3]
|
||||
$Layers/Layer5.frame = 1-a[4]
|
||||
$Layers/Layer6.frame = 1-a[5]
|
||||
$Layers/Layer7.frame = 1-a[6]
|
||||
$Layers/Layer8.frame = 1-a[7]
|
||||
$Layers/Layer9.frame = 1-a[8]
|
||||
$Layers/Layer10.frame = 1-a[9]
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer1Container/Layer1.frame = 1-a[0]
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer2Container/Layer2.frame = 1-a[1]
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer3Container/Layer3.frame = 1-a[2]
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer4Container/Layer4.frame = 1-a[3]
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer5Container/Layer5.frame = 1-a[4]
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer6Container/Layer6.frame = 1-a[5]
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer7Container/Layer7.frame = 1-a[6]
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer8Container/Layer8.frame = 1-a[7]
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer9Container/Layer9.frame = 1-a[8]
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer10Container/Layer10.frame = 1-a[9]
|
||||
|
||||
# Update checkmarks to show current costume layer
|
||||
updateLayerCheckmarks()
|
||||
|
||||
# Update sprite visibility for all sprites based on current costume
|
||||
var nodes = get_tree().get_nodes_in_group("saved")
|
||||
for sprite in nodes:
|
||||
if sprite.costumeLayers[Global.main.costume - 1] == 1:
|
||||
@@ -233,7 +383,45 @@ func setLayerButtons():
|
||||
else:
|
||||
sprite.visible = false
|
||||
sprite.changeCollision(false)
|
||||
|
||||
|
||||
# Update the sprite list to reflect visibility changes
|
||||
Global.spriteList.updateAllVisible()
|
||||
|
||||
func updateLayerCheckmarks():
|
||||
# Hide all checkmarks first
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer1Container/Layer1/Checkmark1.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer2Container/Layer2/Checkmark2.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer3Container/Layer3/Checkmark3.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer4Container/Layer4/Checkmark4.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer5Container/Layer5/Checkmark5.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer6Container/Layer6/Checkmark6.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer7Container/Layer7/Checkmark7.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer8Container/Layer8/Checkmark8.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer9Container/Layer9/Checkmark9.visible = false
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer10Container/Layer10/Checkmark10.visible = false
|
||||
|
||||
# Show checkmark for current costume layer
|
||||
match Global.main.costume:
|
||||
1:
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer1Container/Layer1/Checkmark1.visible = true
|
||||
2:
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer2Container/Layer2/Checkmark2.visible = true
|
||||
3:
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer3Container/Layer3/Checkmark3.visible = true
|
||||
4:
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer4Container/Layer4/Checkmark4.visible = true
|
||||
5:
|
||||
$VBoxContainer/LayersSection/HBoxContainer/Layer5Container/Layer5/Checkmark5.visible = true
|
||||
6:
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer6Container/Layer6/Checkmark6.visible = true
|
||||
7:
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer7Container/Layer7/Checkmark7.visible = true
|
||||
8:
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer8Container/Layer8/Checkmark8.visible = true
|
||||
9:
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer9Container/Layer9/Checkmark9.visible = true
|
||||
10:
|
||||
$VBoxContainer/LayersSection/HBoxContainer2/Layer10Container/Layer10/Checkmark10.visible = true
|
||||
|
||||
|
||||
func _on_layer_button_1_pressed():
|
||||
@@ -314,71 +502,67 @@ func layerSelected():
|
||||
var newPos = Vector2.ZERO
|
||||
match Global.main.costume:
|
||||
1:
|
||||
newPos = $Layers/Layer1.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer/Layer1Container/Layer1.position
|
||||
2:
|
||||
newPos = $Layers/Layer2.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer/Layer2Container/Layer2.position
|
||||
3:
|
||||
newPos = $Layers/Layer3.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer/Layer3Container/Layer3.position
|
||||
4:
|
||||
newPos = $Layers/Layer4.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer/Layer4Container/Layer4.position
|
||||
5:
|
||||
newPos = $Layers/Layer5.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer/Layer5Container/Layer5.position
|
||||
6:
|
||||
newPos = $Layers/Layer6.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer2/Layer6Container/Layer6.position
|
||||
7:
|
||||
newPos = $Layers/Layer7.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer2/Layer7Container/Layer7.position
|
||||
8:
|
||||
newPos = $Layers/Layer8.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer2/Layer8Container/Layer8.position
|
||||
9:
|
||||
newPos = $Layers/Layer9.position
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer2/Layer9Container/Layer9.position
|
||||
10:
|
||||
newPos = $Layers/Layer10.position
|
||||
$Layers/Select.position = newPos
|
||||
|
||||
|
||||
func _on_squash_value_changed(value):
|
||||
$Rotation/squashlabel.text = "squash: " + str(value)
|
||||
Global.heldSprite.stretchAmount = value
|
||||
|
||||
|
||||
func _on_check_box_toggled(button_pressed):
|
||||
Global.heldSprite.ignoreBounce = button_pressed
|
||||
|
||||
|
||||
func _on_anim_speed_value_changed(value):
|
||||
$Animation/animSpeedLabel.text = "animation speed: " + str(value)
|
||||
Global.heldSprite.animSpeed = value
|
||||
|
||||
func _on_anim_frames_value_changed(value):
|
||||
$Animation/animFramesLabel.text = "sprite frames: " + str(value)
|
||||
Global.heldSprite.frames = value
|
||||
spriteSpin.hframes = Global.heldSprite.frames
|
||||
Global.heldSprite.changeFrames()
|
||||
newPos = $VBoxContainer/LayersSection/HBoxContainer2/Layer10Container/Layer10.position
|
||||
|
||||
# Update selection indicator position if it exists
|
||||
var select_node = get_node_or_null("VBoxContainer/LayersSection/Select")
|
||||
if select_node:
|
||||
select_node.position = newPos
|
||||
|
||||
|
||||
func _on_clip_linked_toggled(button_pressed):
|
||||
Global.heldSprite.setClip(button_pressed)
|
||||
|
||||
|
||||
func _on_check_box_toggled(button_pressed):
|
||||
Global.heldSprite.ignoreBounce = button_pressed
|
||||
|
||||
|
||||
func _on_delete_pressed():
|
||||
Global.heldSprite.toggle = "null"
|
||||
$VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||
$VBoxContainer/BoxContainer/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||
Global.heldSprite.makeVis()
|
||||
|
||||
func _on_set_toggle_pressed():
|
||||
$VisToggle/setToggle/Label.text = "toggle: AWAITING INPUT"
|
||||
$VBoxContainer/BoxContainer/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 + "\""
|
||||
|
||||
func _on_opacity_slider_value_changed(value):
|
||||
$Opacity/opacityLabel.text = "opacity: " + str(int(value * 100)) + "%"
|
||||
Global.heldSprite.spriteOpacity = value
|
||||
Global.heldSprite.updateOpacity()
|
||||
$VBoxContainer/BoxContainer/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\""
|
||||
|
||||
func _on_affect_children_check_toggled(button_pressed):
|
||||
Global.heldSprite.affectChildrenOpacity = button_pressed
|
||||
Global.heldSprite.updateOpacity()
|
||||
|
||||
## Set up the blend mode dropdown with all available blend modes
|
||||
func setupBlendModeDropdown():
|
||||
blendModeDropdown.clear()
|
||||
var blendModeNames = BlendModeManager.get_blend_mode_names()
|
||||
for mode_name in blendModeNames:
|
||||
blendModeDropdown.add_item(mode_name)
|
||||
|
||||
## Handle blend mode dropdown selection
|
||||
func _on_blend_mode_dropdown_item_selected(index: int):
|
||||
if Global.heldSprite != null:
|
||||
Global.heldSprite.updateBlendMode(index)
|
||||
Global.pushUpdate("Blend mode set to: " + BlendModeManager.get_blend_mode_name(index))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
15017
ui_scenes/spriteEditMenu/sprite_viewer.tscn19865666400.tmp
Normal file
15017
ui_scenes/spriteEditMenu/sprite_viewer.tscn19865666400.tmp
Normal file
File diff suppressed because one or more lines are too long
190
ui_scenes/spriteEditMenu/wobble_sync_control.gd
Normal file
190
ui_scenes/spriteEditMenu/wobble_sync_control.gd
Normal file
@@ -0,0 +1,190 @@
|
||||
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()
|
||||
for group_name in groups:
|
||||
groupDropdown.add_item(group_name)
|
||||
else:
|
||||
pass # 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 != "":
|
||||
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:
|
||||
groupDropdown.selected = group_index
|
||||
else:
|
||||
groupDropdown.selected = 0 # Select "None"
|
||||
else:
|
||||
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):
|
||||
currentSprite = sprite
|
||||
if currentSprite:
|
||||
pass # Sprite is valid
|
||||
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)
|
||||
|
||||
match selected_text:
|
||||
"None":
|
||||
# Remove sprite from any group
|
||||
if currentSprite.wobbleSyncGroup != "" and WobbleSyncManager:
|
||||
WobbleSyncManager.removeSpriteFromAllGroups(currentSprite.id)
|
||||
Global.pushUpdate("Removed sprite from wobble sync group")
|
||||
|
||||
"Create New...":
|
||||
# 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):
|
||||
WobbleSyncManager.addSpriteToGroup(currentSprite.id, selected_text)
|
||||
# Refresh sprite editor to show updated wobble values
|
||||
if Global.spriteEdit:
|
||||
Global.spriteEdit.setImage()
|
||||
else:
|
||||
pass # Group doesn't exist
|
||||
|
||||
updateSyncIndicator()
|
||||
|
||||
## Handle create group dialog confirmation
|
||||
func _on_create_group_confirmed():
|
||||
var group_name = groupNameInput.text.strip_edges()
|
||||
|
||||
if group_name == "":
|
||||
Global.pushUpdate("Group name cannot be empty")
|
||||
return
|
||||
|
||||
if WobbleSyncManager and WobbleSyncManager.hasGroup(group_name):
|
||||
Global.pushUpdate("Group \"" + group_name + "\" already exists")
|
||||
return
|
||||
|
||||
# Create group with current sprite
|
||||
if currentSprite != null and WobbleSyncManager:
|
||||
if WobbleSyncManager.createGroup(group_name, currentSprite.id):
|
||||
createGroupDialog.hide()
|
||||
updateUI()
|
||||
# Refresh sprite editor to show sync status
|
||||
if Global.spriteEdit:
|
||||
Global.spriteEdit.setImage()
|
||||
else:
|
||||
pass # Failed to create group
|
||||
else:
|
||||
pass # No sprite or WobbleSyncManager not available
|
||||
|
||||
## 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():
|
||||
updateUI()
|
||||
1
ui_scenes/spriteEditMenu/wobble_sync_control.gd.uid
Normal file
1
ui_scenes/spriteEditMenu/wobble_sync_control.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ykov43vxml0k
|
||||
59
ui_scenes/spriteEditMenu/wobble_sync_control.tscn
Normal file
59
ui_scenes/spriteEditMenu/wobble_sync_control.tscn
Normal 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..."
|
||||
@@ -2,9 +2,9 @@ extends NinePatchRect
|
||||
|
||||
@onready var spritePreview = $SpritePreview/Sprite2D
|
||||
@onready var outline = $Selected
|
||||
|
||||
|
||||
@onready var fade = $Fade
|
||||
@onready var button = $Button
|
||||
@onready var label = $Label
|
||||
|
||||
var sprite = null
|
||||
var parent = null
|
||||
@@ -14,9 +14,23 @@ var indent = 0
|
||||
var childrenTags = []
|
||||
var parentTag = null
|
||||
|
||||
## Context menu for right-click actions
|
||||
var context_menu: PopupMenu = null
|
||||
## Line edit for renaming
|
||||
var rename_edit: LineEdit = null
|
||||
## Track if we're currently in rename mode
|
||||
var is_renaming: bool = false
|
||||
|
||||
func _ready():
|
||||
var count = spritePath.get_slice_count("/") - 1
|
||||
$Label.text = spritePath.get_slice("/",count)
|
||||
# Use displayName if available, otherwise fallback to path
|
||||
var display_text = ""
|
||||
if sprite.displayName != "":
|
||||
display_text = sprite.displayName
|
||||
else:
|
||||
var count = spritePath.get_slice_count("/") - 1
|
||||
display_text = spritePath.get_slice("/",count)
|
||||
|
||||
label.text = display_text
|
||||
$Line2D.visible = false
|
||||
|
||||
spritePreview.texture = sprite.sprite.texture
|
||||
@@ -25,6 +39,115 @@ func _ready():
|
||||
spritePreview.scale = Vector2(1,1) * (60.0/displaySize)
|
||||
spritePreview.offset = sprite.sprite.offset
|
||||
|
||||
# Set up right-click detection
|
||||
button.gui_input.connect(_on_button_gui_input)
|
||||
|
||||
# Create context menu
|
||||
_setup_context_menu()
|
||||
|
||||
## Set up the right-click context menu
|
||||
func _setup_context_menu():
|
||||
context_menu = PopupMenu.new()
|
||||
add_child(context_menu)
|
||||
|
||||
# Add rename option
|
||||
context_menu.add_item("Rename", 0)
|
||||
context_menu.id_pressed.connect(_on_context_menu_selected)
|
||||
|
||||
# Wait for menu to be ready, then adjust size
|
||||
context_menu.ready.connect(_adjust_menu_size)
|
||||
|
||||
## Adjust the context menu size to be more compact
|
||||
func _adjust_menu_size():
|
||||
if context_menu:
|
||||
# Let the menu calculate its natural size first
|
||||
await get_tree().process_frame
|
||||
context_menu.reset_size() # Reset to content-based size
|
||||
|
||||
## Handle GUI input events on the button
|
||||
func _on_button_gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||
# Show context menu at cursor position
|
||||
var global_pos = get_global_mouse_position()
|
||||
context_menu.position = Vector2i(global_pos)
|
||||
context_menu.popup()
|
||||
|
||||
## Handle context menu selections
|
||||
func _on_context_menu_selected(id: int):
|
||||
match id:
|
||||
0: # Rename
|
||||
_start_rename()
|
||||
|
||||
## Start the rename process
|
||||
func _start_rename():
|
||||
if is_renaming:
|
||||
return
|
||||
|
||||
is_renaming = true
|
||||
|
||||
# Create line edit for renaming
|
||||
rename_edit = LineEdit.new()
|
||||
add_child(rename_edit)
|
||||
|
||||
# Position and size the line edit over the label
|
||||
rename_edit.position = label.position
|
||||
rename_edit.size = label.size
|
||||
rename_edit.text = label.text
|
||||
|
||||
# Hide the original label
|
||||
label.visible = false
|
||||
|
||||
# Focus the line edit and select all text
|
||||
rename_edit.grab_focus()
|
||||
rename_edit.select_all()
|
||||
|
||||
# Connect signals
|
||||
rename_edit.text_submitted.connect(_on_rename_submitted)
|
||||
rename_edit.focus_exited.connect(_on_rename_focus_lost)
|
||||
|
||||
## Handle rename submission (Enter key)
|
||||
func _on_rename_submitted(new_name: String):
|
||||
if rename_edit != null:
|
||||
_finish_rename(new_name)
|
||||
|
||||
## Handle losing focus (clicking elsewhere)
|
||||
func _on_rename_focus_lost():
|
||||
if rename_edit != null:
|
||||
_finish_rename(rename_edit.text)
|
||||
|
||||
## Finish the rename process
|
||||
func _finish_rename(new_name: String):
|
||||
if not is_renaming:
|
||||
return
|
||||
|
||||
is_renaming = false
|
||||
|
||||
# Clean up the line edit
|
||||
if rename_edit:
|
||||
rename_edit.queue_free()
|
||||
rename_edit = null
|
||||
|
||||
# Show the label again
|
||||
label.visible = true
|
||||
|
||||
# Validate and apply the new name
|
||||
new_name = new_name.strip_edges()
|
||||
if new_name != "" and new_name != label.text:
|
||||
_apply_rename(new_name)
|
||||
|
||||
## Apply the rename to the sprite
|
||||
func _apply_rename(new_name: String):
|
||||
# Update the display name (without file extension)
|
||||
label.text = new_name
|
||||
sprite.displayName = new_name # Store display name separately
|
||||
|
||||
# Update the global sprite list
|
||||
Global.spriteList.updateData()
|
||||
|
||||
# Show feedback to user
|
||||
Global.pushUpdate("Renamed sprite to: " + new_name)
|
||||
|
||||
|
||||
func updateChildren():
|
||||
for child in childrenTags:
|
||||
|
||||
Reference in New Issue
Block a user