Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc7e8c5c4b | |||
| c0e002f783 | |||
| e8850ca7c5 | |||
| 69cb63ad89 | |||
| 11790434f5 | |||
| 8bd2b09232 | |||
| 480d149ede | |||
| c6fc400994 | |||
| 16251db248 | |||
| 4752002caf | |||
| 0e028a2cb9 | |||
| 01a281c2aa | |||
| 2a46d363e9 | |||
| 92c3f5e054 | |||
| 6727339e9a | |||
| e3d67722de | |||
| b6fd6292e3 | |||
| e8c24e18be | |||
| 199d0873c8 | |||
| e84ef716f6 | |||
| 135ba0d198 | |||
| 2940475062 | |||
| 202f8809aa | |||
| 28bb81c0b0 | |||
| b9c5de6dc3 | |||
| 8c18688a69 | |||
| 2429958741 | |||
| 6b5a8ee126 | |||
| 8d40529349 | |||
| 9e97cc3b80 | |||
| ebc3ee9cf9 | |||
| a1bbe44105 | |||
| 3ce10c8ba9 | |||
| 7e6163ed68 | |||
| 55d6222698 | |||
| d7ad2ba072 | |||
| 9c50a63dc3 | |||
| 746ac58329 | |||
| a9cb20c8bc | |||
| 32249fde86 | |||
| 27e137bc02 | |||
| 01daddee3b | |||
| ba512508f8 | |||
| ec653aa847 | |||
| 9acea54f28 | |||
| 2f5296e05a | |||
| e8a58655ad | |||
| aa3789566c | |||
| 9c7ae20c18 | |||
| 9f04c8819f | |||
| 6cd7be196b | |||
| ab2cf3f497 | |||
| bf096aa716 | |||
| 09ef508f49 | |||
| 6de8368414 | |||
| d20085a67c | |||
| a54003c658 | |||
| 648ae8395b | |||
| fbad33213c | |||
| b68eb0953e | |||
| 35c7e9a25e | |||
| 57896e37df | |||
| 242879159a | |||
| 7ef9fe53b2 | |||
| 600d9cfca1 | |||
| 48796de1ba | |||
| 4a0b2cd550 | |||
| ba429d7b84 | |||
| e33776daeb | |||
| 9cfd3bd6f4 | |||
| 7eb18caaaf | |||
| e65e82f421 | |||
| c36d0b5866 | |||
| bcdba7b812 | |||
| 7467ec56d9 | |||
| 990494e8b0 | |||
| c6e0cfe4d9 | |||
| 691e613a5c | |||
| 4e010220bf | |||
| ccc4eb759e | |||
| 250f0d07ca | |||
| f481f5bea7 | |||
| 58c65fc928 | |||
| 937e54e47a | |||
| 83c8a15e5a | |||
| 1fcddb7349 | |||
| 26cae9b0f8 | |||
| 0ebd0c46a0 | |||
| 2226b859df | |||
| 07bcab4454 | |||
| 4c6d5350b2 | |||
| f2c492de54 | |||
| e269b3ab44 | |||
| 89ae347a91 | |||
| ddb5547b42 | |||
| afa06d3ee2 | |||
| dd656d5f5f | |||
| cdb62f3c2f | |||
| b0f4592fdd | |||
| 406c11b581 | |||
| 9812006234 | |||
| b8e456f7ef | |||
| 97e5485ef1 | |||
| c77563d38e | |||
| c322f7667a | |||
| 68be479a09 | |||
| dc52d80a92 | |||
| a84ca9c32c | |||
| 6b1dd04dd5 | |||
| 523624756e | |||
| 72593ff7c7 | |||
| d7dfd03241 | |||
| eac135508c | |||
| 90cadbea07 | |||
| 72c3454728 | |||
| 83e25f44cd | |||
| 31f19ad139 | |||
| c6ec99e87a | |||
| b8cb60aec9 | |||
| 55a3e56791 | |||
| 1f17cca62d | |||
| 1629862759 | |||
| a53b4c4010 | |||
| 3e135afecb | |||
| 25d8f217ff | |||
| a477f9ef4f | |||
| b917fb5fbd | |||
| 47a1326292 | |||
| 708aa8cca4 | |||
| 83f4144b43 | |||
| 2c677a8cda | |||
| ec5d42068e | |||
| 8fdb395ae8 | |||
| 60928c520c | |||
| f3ed5e0ad0 | |||
| ee897db52b | |||
| 41201d7406 | |||
| 267172d026 | |||
| 917e6a5f7e | |||
| 23ef4e9a63 | |||
| fa7cc66748 | |||
| 7b006d19ef | |||
| ad070514d7 | |||
| 80c48ea3df | |||
| d76218f88f | |||
| 7276bdda38 | |||
| 73dc3063c0 | |||
| ae4f7d9c58 | |||
| 9a5caef52f | |||
| 15c2a84ecd | |||
| 1094fb2142 | |||
| 2ab1c38de9 | |||
| 4261789f13 | |||
| ce18c89c09 | |||
| 7643cccebf | |||
| 7e4d733d7c | |||
| 1dcd900737 | |||
| 74c3540c96 | |||
| 6d80253d9c | |||
| 1b7c35879c | |||
| ff426b0494 | |||
| 90616537bc | |||
| 0f8eb244e9 | |||
| 38fdad7690 | |||
| 0de6bc0c04 | |||
| fc2d6592a2 | |||
| 222c10fe0a | |||
| 8af825bc18 | |||
| f7684b6c2a | |||
| f9a3a8e389 | |||
| 6ad8fb00bd | |||
| c4dbc104d4 | |||
| 77c0babdca | |||
| aa5a3483bb | |||
| 68b6031da1 | |||
| 4afb02440c | |||
| de5657a9cd | |||
| ff85b67087 | |||
| 6f3d843818 | |||
| 20f8eaaebd | |||
| 69848e77c9 | |||
| 366923699c | |||
| 2a7f39e3fe | |||
| 296fed6e08 | |||
| 1af04631c5 | |||
| 9ec1bcab97 | |||
| d11fc9f1f2 | |||
| 16a65f7155 | |||
| 0fcc425ace | |||
| 21685d60d5 | |||
| 752cdb1502 | |||
| 8299998a0f | |||
| 5ea6431216 | |||
| 7cccd40290 | |||
| 762a694be3 | |||
| 339428c312 | |||
| 7e19268847 | |||
| 20b37de108 | |||
| 7d1187d218 | |||
| 54b07cccad | |||
| fb0c14c3f1 | |||
| fb362e10ad | |||
| c73d3ff370 | |||
| e99b574d1f | |||
| 1062193785 | |||
| 9190b2a16a | |||
| c200098f0c | |||
| 0e775309fb | |||
| 5db71adf03 | |||
| ce5d2b65d3 | |||
| 6016f1cf38 | |||
| b5dcad5614 | |||
| 64f039ba19 | |||
| 8c1822557c | |||
| dacf48b9fd | |||
| daa53e4aef | |||
| 77591425cb | |||
| 667ef6fb9b | |||
| c90687939c | |||
| 8622f2d38d | |||
| bd4f7edc20 | |||
| 45a58f701b | |||
| f08f3a7978 | |||
| 26502f2d37 | |||
| a9b1c179cd | |||
| d144f5c0c4 | |||
| 45d09cd618 | |||
| f2902ad7c1 | |||
| 7f8b444270 | |||
| d4b7c0bf45 | |||
| 959ab68b4d | |||
| 4f312209b3 | |||
| e0b5a26ee6 | |||
| 78471268db | |||
| 84fcec8227 | |||
| dc322d8943 | |||
| 4ce0367f15 | |||
| 421a90ec17 | |||
| ec10f0e54b | |||
| cbcbcbbcee | |||
| dde4f18305 | |||
| 267a0b9556 | |||
| 5ea5e5547c | |||
| d231bb0773 | |||
| ea20332b4f | |||
| 6090e7b17b |
@@ -1,3 +1,14 @@
|
|||||||
# Godot 4+ specific ignores
|
# Godot 4+ specific ignores
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
/builds/
|
||||||
|
/_builds/
|
||||||
|
/_clips/
|
||||||
|
|
||||||
|
# temporary files
|
||||||
|
**/*.tmp
|
||||||
|
**/*.translation
|
||||||
|
**/*~lock~
|
||||||
|
|
||||||
|
# override config can be used by developers to override the settings without pushing changes to the repository
|
||||||
|
override.cfg
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.Babushka.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/.idea.Babushka.iml
|
||||||
|
/modules.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="RiderAndroidProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="XmlDuplicatedId" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="GdSdk Master" type="GdScript">
|
||||||
|
<properties path="$USER_HOME$/.cache/JetBrains/Rider2025.1/projects/.idea.babushka.a4de4632/sdk/GdSdk Master" version="Master" date="2024-06-01T15:14:16.000+02:00" />
|
||||||
|
<CLASSES />
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://$USER_HOME$/.cache/JetBrains/Rider2025.1/projects/.idea.babushka.a4de4632/sdk/GdSdk Master" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="GdSdk Master" type="GdScript">
|
||||||
|
<properties path="$USER_HOME$/.cache/JetBrains/Rider2024.3/projects/babushka.51b917cd/sdk/GdSdk Master" version="Master" date="2024-06-01T15:14:16.000+02:00" />
|
||||||
|
<CLASSES />
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://$USER_HOME$/.cache/JetBrains/Rider2024.3/projects/babushka.51b917cd/sdk/GdSdk Master" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Godot.NET.Sdk/4.4.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="prefabs\UI\Inventory\" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<Project Sdk="Godot.NET.Sdk/4.2.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<Project Sdk="Godot.NET.Sdk/4.2.2">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 2012
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Babushka", "Babushka.csproj", "{349E0430-96B6-4673-8609-74416BA9B1F6}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||||
|
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||||
|
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Description/@EntryValue">Godot Signal</s:String>
|
||||||
|
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Text/@EntryValue">[Signal]
|
||||||
|
public delegate void $SignalName$EventHandler($END$);</s:String>
|
||||||
|
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Field/=SignalName/Expression/@EntryValue">suggestVariableName()</s:String></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArea3D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8a54226fa2e1c9371a8091f24cfd744aef11fe6869527dc23b9b837623a29b9_003FArea3D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAudioStreamPlayer2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F848324b1c23114c3f5e8bbb5a42c4ade394c59a7a7a133a66b76581ca571_003FAudioStreamPlayer2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd111abf504bf42b5968a609b168fd093b2e200_003Fbb_003F1c116fcd_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACharacterBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbba0bbd7a98ee58286e9484fbe86e01afff6232283f6efd3556eb7116453_003FCharacterBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff1d69ec2da76ccf9bc8a75c8e0fdca9a7ba1adf8c8c9d5047e2fa5991c02eca_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a1 1 0 0 0 -1 1v5h-2c-1.108 0-2 .892-2 2v1h10v-1c0-1.108-.892-2-2-2h-2v-5a1 1 0 0 0 -1-1zm-5 10v4l10-1v-3z" fill="#e0e0e0"/></svg>
|
||||||
|
After Width: | Height: | Size: 227 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bmnff63evbdhv"
|
||||||
|
path="res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/SignalVisualizer/Clear.svg"
|
||||||
|
dest_files=["res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.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
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
class_name SignalConnection extends Object
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var signal_id: int
|
||||||
|
var source_node_name: String
|
||||||
|
var destination_node_name: String
|
||||||
|
var method_signature: String
|
||||||
|
|
||||||
|
var description: String :
|
||||||
|
get:
|
||||||
|
return "ID: {signal_id} Source: {source_node_name} Destination: {destination_node_name} Method: {method_signature}".format({
|
||||||
|
"signal_id": signal_id,
|
||||||
|
"source_node_name": source_node_name,
|
||||||
|
"destination_node_name": destination_node_name,
|
||||||
|
"method_signature": method_signature,
|
||||||
|
})
|
||||||
|
|
||||||
|
var dictionary_key: String :
|
||||||
|
get:
|
||||||
|
return "{signal_id}__{source_node_name}__{destination_node_name}__{method_signature}".format({ "signal_id": signal_id, "source_node_name": source_node_name, "destination_node_name": destination_node_name, "method_signature": method_signature.replace("::", "_") })
|
||||||
|
|
||||||
|
var dictionary_representation: Dictionary :
|
||||||
|
get:
|
||||||
|
return {
|
||||||
|
"signal_id": signal_id,
|
||||||
|
"source_node_name": source_node_name,
|
||||||
|
"destination_node_name": destination_node_name,
|
||||||
|
"method_signature": method_signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _init(signal_id: int, source_node_name: String, destination_node_name: String, method_signature: String):
|
||||||
|
self.signal_id = signal_id
|
||||||
|
self.source_node_name = source_node_name
|
||||||
|
self.destination_node_name = destination_node_name
|
||||||
|
self.method_signature = method_signature
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dm613ct57qfwa
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
class_name SignalDescription extends Object
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var id: int:
|
||||||
|
get:
|
||||||
|
if _source_id != null:
|
||||||
|
return _source_id
|
||||||
|
return get_instance_id()
|
||||||
|
|
||||||
|
var node_name: String
|
||||||
|
var signal_name: String
|
||||||
|
|
||||||
|
var description: String :
|
||||||
|
get:
|
||||||
|
return "ID: {id} Node: {node_name} Signal: {signal_name}".format({
|
||||||
|
"id": id,
|
||||||
|
"node_name": node_name,
|
||||||
|
"signal_name": signal_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
var dictionary_representation: Dictionary :
|
||||||
|
get:
|
||||||
|
return {
|
||||||
|
"id": id,
|
||||||
|
"node_name": node_name,
|
||||||
|
"signal_name": signal_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
var _source_id = null
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _init(node_name: String, signal_name: String):
|
||||||
|
self.node_name = node_name
|
||||||
|
self.signal_name = signal_name
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dvgsocxisw3ae
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
class_name SignalGraph extends Object
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var name: String
|
||||||
|
var signals: Array[SignalDescription]
|
||||||
|
var edges: Array[SignalConnection]
|
||||||
|
|
||||||
|
var description: String :
|
||||||
|
get:
|
||||||
|
return "Signals: {signals}\nEdges: {edges}".format({
|
||||||
|
"signals": signals.map(func (item): return item.description),
|
||||||
|
"edges": edges.map(func (item): return item.description),
|
||||||
|
})
|
||||||
|
|
||||||
|
var dictionary_representation: Dictionary :
|
||||||
|
get:
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"signals": signals.map(func (element): return element.dictionary_representation),
|
||||||
|
"edges": edges.map(func (element): return element.dictionary_representation),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _init(name: String, signals: Array[SignalDescription] = [], edges: Array[SignalConnection] = []):
|
||||||
|
self.name = name
|
||||||
|
self.signals = signals
|
||||||
|
self.edges = edges
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func get_source_signal_for_edge(edge: SignalConnection) -> SignalDescription:
|
||||||
|
var result = signals.filter(func (item): return item.id == edge.signal_id)
|
||||||
|
if result.size() > 0:
|
||||||
|
return result[0]
|
||||||
|
return null
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://2qj81iy1le0a
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
@tool
|
||||||
|
class_name SignalGraphUtility
|
||||||
|
|
||||||
|
static var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
|
||||||
|
static var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
|
||||||
|
|
||||||
|
const SOURCE_COLOR: Color = Color.SKY_BLUE
|
||||||
|
const DESTINATION_COLOR: Color = Color.CORAL
|
||||||
|
const CONNECTION_TYPE: int = 0
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
static func create_signal_graph(name: String, signals: Array, edges: Array) -> SignalGraph:
|
||||||
|
var signal_graph = SignalGraph.new(name)
|
||||||
|
|
||||||
|
for signal_item in signals:
|
||||||
|
var new_signal_description = SignalDescription.new(signal_item.node_name, signal_item.signal_name)
|
||||||
|
new_signal_description._source_id = signal_item.id
|
||||||
|
signal_graph.signals.append(new_signal_description)
|
||||||
|
|
||||||
|
for connection in edges:
|
||||||
|
var new_edge = SignalConnection.new(connection.signal_id, connection.source_node_name, connection.destination_node_name, connection.method_signature)
|
||||||
|
signal_graph.edges.append(new_edge)
|
||||||
|
|
||||||
|
return signal_graph
|
||||||
|
|
||||||
|
static func create_signal_graph_from_node(root_node: Node, is_persistent_only: bool = false):
|
||||||
|
var signal_graph = SignalGraph.new(root_node.scene_file_path)
|
||||||
|
var all_nodes: Array[Node] = _gather_nodes_from_node(root_node)
|
||||||
|
var signals: Array[SignalDescription] = []
|
||||||
|
var edges: Array[SignalConnection] = []
|
||||||
|
|
||||||
|
for node in all_nodes:
|
||||||
|
for signal_item in node.get_signal_list():
|
||||||
|
var existing_signals = []
|
||||||
|
var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
|
||||||
|
if connection_list.size() > 0:
|
||||||
|
for connection in connection_list:
|
||||||
|
var enabled_flags = connection["flags"] == CONNECT_PERSIST if is_persistent_only else true
|
||||||
|
var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@") and enabled_flags
|
||||||
|
if should_display_connection:
|
||||||
|
var signal_description: SignalDescription
|
||||||
|
var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
|
||||||
|
if filtered_signals.size() == 1:
|
||||||
|
signal_description = filtered_signals[0]
|
||||||
|
else:
|
||||||
|
signal_description = SignalDescription.new(node.name, signal_item.name)
|
||||||
|
existing_signals.append(signal_description)
|
||||||
|
signals.append(signal_description)
|
||||||
|
|
||||||
|
var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
|
||||||
|
if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
|
||||||
|
edges.append(signal_edge)
|
||||||
|
|
||||||
|
var temp_signals = {}
|
||||||
|
for item in signals:
|
||||||
|
temp_signals[item.id] = item
|
||||||
|
|
||||||
|
var temp_edges = {}
|
||||||
|
for item in edges:
|
||||||
|
temp_edges[item.dictionary_key] = item
|
||||||
|
|
||||||
|
signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
|
||||||
|
signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
|
||||||
|
|
||||||
|
return signal_graph
|
||||||
|
|
||||||
|
static func generate_signal_graph_nodes(signal_graph: SignalGraph, graph_node: GraphEdit, open_script_callable: Callable):
|
||||||
|
var graph_nodes: Dictionary = {}
|
||||||
|
|
||||||
|
for signal_item in signal_graph.signals:
|
||||||
|
var current_graph_node: SignalGraphNode
|
||||||
|
if graph_nodes.has(signal_item.node_name):
|
||||||
|
current_graph_node = graph_nodes[signal_item.node_name]
|
||||||
|
if not current_graph_node:
|
||||||
|
current_graph_node = SignalGraphNode.instantiate()
|
||||||
|
current_graph_node.title = signal_item.node_name
|
||||||
|
current_graph_node.name = _get_graph_node_name(signal_item.node_name)
|
||||||
|
graph_node.add_child(current_graph_node)
|
||||||
|
graph_nodes[signal_item.node_name] = current_graph_node
|
||||||
|
|
||||||
|
for edge in signal_graph.edges:
|
||||||
|
var destination_graph_node: SignalGraphNode
|
||||||
|
if graph_nodes.has(edge.destination_node_name):
|
||||||
|
destination_graph_node = graph_nodes[edge.destination_node_name]
|
||||||
|
else:
|
||||||
|
destination_graph_node = SignalGraphNode.instantiate()
|
||||||
|
destination_graph_node.title = edge.destination_node_name
|
||||||
|
destination_graph_node.name = _get_graph_node_name(edge.destination_node_name)
|
||||||
|
graph_node.add_child(destination_graph_node)
|
||||||
|
graph_nodes[edge.destination_node_name] = destination_graph_node
|
||||||
|
|
||||||
|
var source_signal = signal_graph.get_source_signal_for_edge(edge)
|
||||||
|
if source_signal != null:
|
||||||
|
var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
|
||||||
|
|
||||||
|
if not source_graph_node.has_source_signal_description(source_signal.signal_name, edge.destination_node_name):
|
||||||
|
var source_signal_label = Label.new()
|
||||||
|
source_signal_label.text = source_signal.signal_name
|
||||||
|
source_signal_label.name = "source_" + source_signal.signal_name + "_" + edge.destination_node_name
|
||||||
|
source_graph_node.add_child(source_signal_label)
|
||||||
|
|
||||||
|
var destination_signal_name = "destination_" + source_signal.signal_name + "_" + edge.method_signature.replace("::", "__")
|
||||||
|
var has_destination = destination_graph_node.has_destination_signal_description(source_signal.signal_name, edge.method_signature)
|
||||||
|
if not has_destination:
|
||||||
|
var destination_signal_item = GraphNodeItem.instantiate()
|
||||||
|
destination_signal_item.signal_data = SignalGraphNodeItem.Metadata.new(source_signal.signal_name, edge.method_signature, edge.destination_node_name)
|
||||||
|
destination_signal_item.text = edge.method_signature
|
||||||
|
destination_signal_item.name = destination_signal_name
|
||||||
|
destination_signal_item.open_script.connect(open_script_callable)
|
||||||
|
destination_graph_node.add_child(destination_signal_item)
|
||||||
|
|
||||||
|
for edge in signal_graph.edges:
|
||||||
|
var source_signal = signal_graph.get_source_signal_for_edge(edge)
|
||||||
|
if source_signal != null:
|
||||||
|
var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
|
||||||
|
var destination_graph_node: SignalGraphNode = graph_nodes[edge.destination_node_name] as SignalGraphNode
|
||||||
|
|
||||||
|
var from_port = source_graph_node.get_source_slot(source_signal.signal_name, edge.destination_node_name)
|
||||||
|
var to_port = destination_graph_node.get_destination_slot(source_signal.signal_name, edge.method_signature)
|
||||||
|
|
||||||
|
source_graph_node.set_slot(from_port, false, CONNECTION_TYPE, Color.BLACK, true, CONNECTION_TYPE, SOURCE_COLOR)
|
||||||
|
destination_graph_node.set_slot(to_port, true, CONNECTION_TYPE, DESTINATION_COLOR, false, CONNECTION_TYPE, Color.BLACK)
|
||||||
|
|
||||||
|
var from_slot_index = source_graph_node.get_next_source_slot(source_signal.signal_name, edge.destination_node_name)
|
||||||
|
var to_slot_index = destination_graph_node.get_next_destination_slot(source_signal.signal_name, edge.method_signature)
|
||||||
|
|
||||||
|
if from_port >= 0 and to_port >= 0:
|
||||||
|
graph_node.connect_node(source_graph_node.name, from_slot_index, destination_graph_node.name, to_slot_index)
|
||||||
|
else:
|
||||||
|
print(">>> Invalid Connection Request")
|
||||||
|
|
||||||
|
static func generate_signal_graph_tree(signal_graph: SignalGraph, tree_node: Tree):
|
||||||
|
var root = tree_node.create_item()
|
||||||
|
root.set_text(0, signal_graph.name)
|
||||||
|
|
||||||
|
var tree_items: Dictionary = {}
|
||||||
|
|
||||||
|
for signal_item in signal_graph.signals:
|
||||||
|
var node_tree_item: TreeItem
|
||||||
|
if tree_items.has(signal_item.node_name):
|
||||||
|
node_tree_item = tree_items[signal_item.node_name] as TreeItem
|
||||||
|
else:
|
||||||
|
node_tree_item = tree_node.create_item(root)
|
||||||
|
node_tree_item.set_text(0, signal_item.node_name)
|
||||||
|
tree_items[signal_item.node_name] = node_tree_item
|
||||||
|
|
||||||
|
var signal_tree_item = tree_node.create_item(node_tree_item)
|
||||||
|
signal_tree_item.set_text(0, signal_item.signal_name)
|
||||||
|
|
||||||
|
for edge in signal_graph.edges.filter(func (item): return item.signal_id == signal_item.id):
|
||||||
|
var signal_connection_tree_item = tree_node.create_item(signal_tree_item)
|
||||||
|
signal_connection_tree_item.set_text(0, edge.destination_node_name + "::" + edge.method_signature)
|
||||||
|
|
||||||
|
static func _get_graph_node_name(name: String) -> String:
|
||||||
|
return "{node_name}_graph_node".format({ "node_name": name })
|
||||||
|
|
||||||
|
static func _gather_nodes_from_node(root_node: Node) -> Array[Node]:
|
||||||
|
var node_list: Array[Node] = [root_node]
|
||||||
|
return node_list + __gather_nodes_from_node(root_node)
|
||||||
|
|
||||||
|
static func __gather_nodes_from_node(node: Node) -> Array[Node]:
|
||||||
|
var nodes: Array[Node] = []
|
||||||
|
for child in node.get_children(false):
|
||||||
|
nodes.append(child)
|
||||||
|
nodes += __gather_nodes_from_node(child)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://csw8uccbs0vuk
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var _signal_graph: SignalGraph
|
||||||
|
var _lambda_map: Dictionary = {}
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if OS.is_debug_build():
|
||||||
|
EngineDebugger.register_message_capture("signal_debugger", _on_signal_debugger_message_capture)
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_signal_debugger_message_capture(message: String, data: Array) -> bool:
|
||||||
|
if message == "start":
|
||||||
|
_signal_graph = generate_signal_graph()
|
||||||
|
for signal_item in _signal_graph.signals:
|
||||||
|
_connect_to_signal(signal_item)
|
||||||
|
EngineDebugger.send_message(
|
||||||
|
"signal_debugger:generated_graph",
|
||||||
|
[[_signal_graph.signals.map(func (item): return item.dictionary_representation), _signal_graph.edges.map(func (item): return item.dictionary_representation)]]
|
||||||
|
)
|
||||||
|
if message == "stop" and _signal_graph:
|
||||||
|
for signal_item in _signal_graph.signals:
|
||||||
|
_disconnect_from_signal(signal_item)
|
||||||
|
|
||||||
|
if message == "invoke_signal" and data.size() == 2:
|
||||||
|
var node_name = data[0]
|
||||||
|
var signal_name = data[1]
|
||||||
|
|
||||||
|
var root_node = get_tree().current_scene
|
||||||
|
var node = root_node if root_node.name == node_name else root_node.find_child(node_name)
|
||||||
|
if node:
|
||||||
|
var connection_list = node.get_signal_connection_list(signal_name)
|
||||||
|
for connection in connection_list:
|
||||||
|
var callable = connection["callable"]
|
||||||
|
var bound_args = callable.get_bound_arguments()
|
||||||
|
var bound_args_count = callable.get_bound_arguments_count()
|
||||||
|
var method = callable.get_method()
|
||||||
|
callable.callv([node])
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _on_signal_execution(signal_name: String, node_name: String, args):
|
||||||
|
EngineDebugger.send_message(
|
||||||
|
"signal_debugger:signal_executed",
|
||||||
|
[Time.get_datetime_string_from_system(), node_name, signal_name]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func generate_signal_graph() -> SignalGraph:
|
||||||
|
var graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().current_scene)
|
||||||
|
return graph
|
||||||
|
#var signal_graph = SignalGraph.new(get_tree().current_scene.name)
|
||||||
|
#var all_nodes: Array[Node] = _gather_nodes_in_scene()
|
||||||
|
#var signals: Array[SignalDescription] = []
|
||||||
|
#var edges: Array[SignalConnection] = []
|
||||||
|
#
|
||||||
|
#for node in all_nodes:
|
||||||
|
#for signal_item in node.get_signal_list():
|
||||||
|
#var existing_signals = []
|
||||||
|
#var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
|
||||||
|
#if connection_list.size() > 0:
|
||||||
|
#for connection in connection_list:
|
||||||
|
#var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@")
|
||||||
|
#if should_display_connection:
|
||||||
|
#var signal_description: SignalDescription
|
||||||
|
#var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
|
||||||
|
#if filtered_signals.size() == 1:
|
||||||
|
#signal_description = filtered_signals[0]
|
||||||
|
#else:
|
||||||
|
#signal_description = SignalDescription.new(node.name, signal_item.name)
|
||||||
|
#existing_signals.append(signal_description)
|
||||||
|
#signals.append(signal_description)
|
||||||
|
#
|
||||||
|
#var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
|
||||||
|
#if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
|
||||||
|
#edges.append(signal_edge)
|
||||||
|
#
|
||||||
|
#var temp_signals = {}
|
||||||
|
#for item in signals:
|
||||||
|
#temp_signals[item.id] = item
|
||||||
|
#
|
||||||
|
#var temp_edges = {}
|
||||||
|
#for item in edges:
|
||||||
|
#temp_edges[item.dictionary_key] = item
|
||||||
|
#
|
||||||
|
#signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
|
||||||
|
#signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
|
||||||
|
#
|
||||||
|
#return signal_graph
|
||||||
|
|
||||||
|
#func _gather_nodes_in_scene() -> Array[Node]:
|
||||||
|
#var scene_root = get_tree().current_scene
|
||||||
|
#var node_list: Array[Node] = [scene_root]
|
||||||
|
#return node_list + _gather_nodes_from_node(scene_root)
|
||||||
|
#
|
||||||
|
#func _gather_nodes_from_node(node: Node) -> Array[Node]:
|
||||||
|
#var nodes: Array[Node] = []
|
||||||
|
#for child in node.get_children(false):
|
||||||
|
#nodes.append(child)
|
||||||
|
#nodes += _gather_nodes_from_node(child)
|
||||||
|
#
|
||||||
|
#return nodes
|
||||||
|
|
||||||
|
func _connect_to_signal(signal_item: SignalDescription):
|
||||||
|
var root_node = get_tree().current_scene
|
||||||
|
var _execute: Callable = func (args = []): _on_signal_execution(signal_item.signal_name, signal_item.node_name, args)
|
||||||
|
if root_node.name == signal_item.node_name:
|
||||||
|
root_node.connect(signal_item.signal_name, _execute)
|
||||||
|
_lambda_map[signal_item] = _execute
|
||||||
|
else:
|
||||||
|
var child = root_node.find_child(signal_item.node_name)
|
||||||
|
if child:
|
||||||
|
child.connect(signal_item.signal_name, _execute)
|
||||||
|
_lambda_map[signal_item] = _execute
|
||||||
|
|
||||||
|
func _disconnect_from_signal(signal_item: SignalDescription):
|
||||||
|
var root_node = get_tree().current_scene
|
||||||
|
if root_node.name == signal_item.node_name:
|
||||||
|
var callable = _lambda_map[signal_item]
|
||||||
|
if callable:
|
||||||
|
root_node.disconnect(signal_item.signal_name, callable)
|
||||||
|
_lambda_map.erase(signal_item)
|
||||||
|
else:
|
||||||
|
var child = root_node.find_child(signal_item.node_name)
|
||||||
|
if child:
|
||||||
|
var callable = _lambda_map[signal_item]
|
||||||
|
if callable:
|
||||||
|
child.disconnect(signal_item.signal_name, callable)
|
||||||
|
_lambda_map.erase(signal_item)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bmsqdh2cnmgw8
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://cbsmvov8u78q"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/SignalVisualizer/Debugger/signal_debugger_panel.gd" id="1_66cpc"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://be3nwoioa311t" path="res://addons/SignalVisualizer/Play.svg" id="2_2wkuv"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://oo1oq2colx5b" path="res://addons/SignalVisualizer/Stop.svg" id="3_bg5eu"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="4_vg63r"]
|
||||||
|
|
||||||
|
[node name="SignalDebugger" type="Control"]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_66cpc")
|
||||||
|
start_icon = ExtResource("2_2wkuv")
|
||||||
|
stop_icon = ExtResource("3_bg5eu")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(2.08165e-12, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="ActionButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
disabled = true
|
||||||
|
text = "Start"
|
||||||
|
icon = ExtResource("2_2wkuv")
|
||||||
|
|
||||||
|
[node name="ClearAllButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Clear All"
|
||||||
|
icon = ExtResource("4_vg63r")
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="ClearLogsButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Clear Logs"
|
||||||
|
icon = ExtResource("4_vg63r")
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(250, 2.08165e-12)
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
allow_reselect = true
|
||||||
|
allow_rmb_select = true
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="TabBar" type="TabBar" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tab_count = 2
|
||||||
|
tab_0/title = "Signal Log"
|
||||||
|
tab_1/title = "Signal Graph"
|
||||||
|
|
||||||
|
[node name="LogLabel" type="RichTextLabel" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_colors/default_color = Color(0.690196, 0.690196, 0.690196, 1)
|
||||||
|
bbcode_enabled = true
|
||||||
|
scroll_following = true
|
||||||
|
|
||||||
|
[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ActionButton" to="." method="_on_action_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearAllButton" to="." method="_on_clear_all_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearLogsButton" to="." method="_on_clear_logs_button_pressed"]
|
||||||
|
[connection signal="item_selected" from="VBoxContainer/HSplitContainer/SignalTree" to="." method="_on_signal_tree_item_selected"]
|
||||||
|
[connection signal="tab_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/TabBar" to="." method="_on_tab_bar_tab_changed"]
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
@tool
|
||||||
|
class_name SignalDebuggerPanel extends Control
|
||||||
|
|
||||||
|
signal open_script(node_name: String, method_signature: String)
|
||||||
|
|
||||||
|
signal start_signal_debugging
|
||||||
|
signal stop_signal_debugging
|
||||||
|
|
||||||
|
var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
|
||||||
|
var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
|
||||||
|
|
||||||
|
const SOURCE_COLOR: Color = Color.SKY_BLUE
|
||||||
|
const DESTINATION_COLOR: Color = Color.CORAL
|
||||||
|
const CONNECTION_TYPE: int = 0
|
||||||
|
|
||||||
|
enum Tabs {
|
||||||
|
LOG,
|
||||||
|
GRAPH
|
||||||
|
}
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
@export var start_icon: Texture2D
|
||||||
|
@export var stop_icon: Texture2D
|
||||||
|
|
||||||
|
@onready var action_button: Button = %ActionButton
|
||||||
|
@onready var clear_all_button: Button = %ClearAllButton
|
||||||
|
@onready var signal_tree: Tree = %SignalTree
|
||||||
|
@onready var log_label: RichTextLabel = %LogLabel
|
||||||
|
@onready var graph_node: GraphEdit = %Graph
|
||||||
|
|
||||||
|
var is_started: bool = false :
|
||||||
|
get: return is_started
|
||||||
|
set(new_value):
|
||||||
|
is_started = new_value
|
||||||
|
_update_action_button()
|
||||||
|
|
||||||
|
var _signals: Array = []
|
||||||
|
var _signal_filter: Array = []
|
||||||
|
var _is_stack_trace_enabled: bool = false
|
||||||
|
var _debugger_tab_state: Tabs = Tabs.LOG
|
||||||
|
var _graph: SignalGraph
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
disable()
|
||||||
|
_handle_tab_update(0)
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_action_button_pressed():
|
||||||
|
if is_started:
|
||||||
|
stop()
|
||||||
|
else:
|
||||||
|
start()
|
||||||
|
|
||||||
|
func _on_clear_all_button_pressed():
|
||||||
|
log_label.clear()
|
||||||
|
signal_tree.clear()
|
||||||
|
graph_node.clear_connections()
|
||||||
|
for child in graph_node.get_children():
|
||||||
|
if child is SignalGraphNode:
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
func _on_clear_logs_button_pressed():
|
||||||
|
log_label.clear()
|
||||||
|
|
||||||
|
func _on_signal_tree_item_selected():
|
||||||
|
# Updates the checkmark button
|
||||||
|
var selected_item = signal_tree.get_selected()
|
||||||
|
var is_checked = selected_item.is_checked(1)
|
||||||
|
selected_item.set_checked(1, (not is_checked))
|
||||||
|
|
||||||
|
# Add / Remove signal from filters
|
||||||
|
var selected_signal = _signals.filter(func (element): return element.signal_name == selected_item.get_text(0))[0]
|
||||||
|
if _signal_filter.has(selected_signal.signal_name):
|
||||||
|
var selected_index = _signal_filter.find(selected_signal.signal_name)
|
||||||
|
_signal_filter.remove_at(selected_index)
|
||||||
|
else:
|
||||||
|
_signal_filter.append(selected_signal.signal_name)
|
||||||
|
|
||||||
|
func _on_tab_bar_tab_changed(tab: int):
|
||||||
|
_handle_tab_update(tab)
|
||||||
|
|
||||||
|
func _on_stack_trace_button_pressed():
|
||||||
|
_is_stack_trace_enabled = not _is_stack_trace_enabled
|
||||||
|
|
||||||
|
func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
|
||||||
|
open_script.emit(data.node_name, data.method_signature)
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func enable():
|
||||||
|
action_button.disabled = false
|
||||||
|
|
||||||
|
func disable():
|
||||||
|
action_button.disabled = true
|
||||||
|
|
||||||
|
func start():
|
||||||
|
if not is_started:
|
||||||
|
is_started = true
|
||||||
|
action_button.icon = stop_icon
|
||||||
|
start_signal_debugging.emit()
|
||||||
|
log_label.append_text("[color=#B0B0B0]Signal Debugging Started...[/color]")
|
||||||
|
log_label.newline()
|
||||||
|
log_label.newline()
|
||||||
|
|
||||||
|
func stop():
|
||||||
|
if is_started:
|
||||||
|
is_started = false
|
||||||
|
action_button.icon = start_icon
|
||||||
|
stop_signal_debugging.emit()
|
||||||
|
log_label.newline()
|
||||||
|
log_label.append_text("[color=#B0B0B0]Signal Debugging Stopped[/color]")
|
||||||
|
log_label.newline()
|
||||||
|
log_label.newline()
|
||||||
|
|
||||||
|
func create_tree_from_signals(signals: Array):
|
||||||
|
_signals = signals
|
||||||
|
var root = signal_tree.create_item()
|
||||||
|
root.set_text(0, "Signals")
|
||||||
|
|
||||||
|
var tree_items: Dictionary = {}
|
||||||
|
|
||||||
|
for signal_item in signals:
|
||||||
|
var node_tree_item: TreeItem
|
||||||
|
if tree_items.has(signal_item.node_name):
|
||||||
|
node_tree_item = tree_items[signal_item.node_name] as TreeItem
|
||||||
|
else:
|
||||||
|
node_tree_item = signal_tree.create_item(root)
|
||||||
|
node_tree_item.set_text(0, signal_item.node_name)
|
||||||
|
node_tree_item.set_selectable(0, false)
|
||||||
|
node_tree_item.set_selectable(1, false)
|
||||||
|
tree_items[signal_item.node_name] = node_tree_item
|
||||||
|
|
||||||
|
var signal_tree_item = signal_tree.create_item(node_tree_item)
|
||||||
|
signal_tree_item.set_text(0, signal_item.signal_name)
|
||||||
|
signal_tree_item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
|
||||||
|
signal_tree_item.set_checked(1, true)
|
||||||
|
signal_tree_item.set_selectable(0, false)
|
||||||
|
signal_tree_item.set_selectable(1, true)
|
||||||
|
|
||||||
|
func create_signal_graph(signals: Array, edges: Array):
|
||||||
|
_graph = SignalGraphUtility.create_signal_graph(get_tree().edited_scene_root.scene_file_path, signals, edges)
|
||||||
|
SignalGraphUtility.generate_signal_graph_nodes(_graph, graph_node, _on_open_signal_in_script)
|
||||||
|
|
||||||
|
func log_signal_execution(time: String, node_name: String, signal_name: String):
|
||||||
|
if _signal_filter != null and _signal_filter.has(signal_name):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not log_label.text.is_empty():
|
||||||
|
log_label.newline()
|
||||||
|
log_label.append_text(
|
||||||
|
"[color=#FFCC00]{time}[/color]\t\t{node_name}\t\t{signal_name}".format({ "time": time, "node_name": node_name, "signal_name": signal_name })
|
||||||
|
)
|
||||||
|
log_label.newline()
|
||||||
|
|
||||||
|
func _handle_tab_update(selected_tab_index: int):
|
||||||
|
match selected_tab_index:
|
||||||
|
1:
|
||||||
|
_debugger_tab_state = Tabs.GRAPH
|
||||||
|
_:
|
||||||
|
_debugger_tab_state = Tabs.LOG
|
||||||
|
|
||||||
|
match _debugger_tab_state:
|
||||||
|
Tabs.LOG:
|
||||||
|
log_label.show()
|
||||||
|
graph_node.hide()
|
||||||
|
Tabs.GRAPH:
|
||||||
|
log_label.hide()
|
||||||
|
graph_node.show()
|
||||||
|
|
||||||
|
func _update_action_button():
|
||||||
|
if is_started:
|
||||||
|
action_button.text = "Stop"
|
||||||
|
action_button.modulate = Color("#ff3b30")
|
||||||
|
else:
|
||||||
|
action_button.text = "Start"
|
||||||
|
action_button.modulate = Color.WHITE
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://yg8cqm6f1prd
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="24" width="24" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M11 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM6.732 5A2 2 0 0 1 7 6v1.117L9.268 6A2 2 0 0 1 9 5V3.883zM2 5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zm5 3.883V10a2 2 0 0 1-.268 1L9 12.117V11a2 2 0 0 1 .268-1zM11 10a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1z" fill="#8eef97"/></svg>
|
||||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bxj8ep08wbnm6"
|
||||||
|
path="res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/SignalVisualizer/GraphEdit.svg"
|
||||||
|
dest_files=["res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.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
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z" fill="#e0e0e0"/></svg>
|
||||||
|
After Width: | Height: | Size: 184 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://be3nwoioa311t"
|
||||||
|
path="res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/SignalVisualizer/Play.svg"
|
||||||
|
dest_files=["res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.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
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
class SignalDebuggerPlugin extends EditorDebuggerPlugin:
|
||||||
|
var SignalDebuggerPanelScene = preload("res://addons/SignalVisualizer/Debugger/SignalDebugger.tscn")
|
||||||
|
|
||||||
|
signal open_script
|
||||||
|
signal start_signal_debugging
|
||||||
|
signal stop_signal_debugging
|
||||||
|
|
||||||
|
var debugger_panel
|
||||||
|
|
||||||
|
func _has_capture(prefix) -> bool:
|
||||||
|
return prefix == "signal_debugger"
|
||||||
|
|
||||||
|
func _capture(message, data, session_id) -> bool:
|
||||||
|
if message == "signal_debugger:signal_executed":
|
||||||
|
if data.size() == 3:
|
||||||
|
var time = data[0]
|
||||||
|
var node_name = data[1]
|
||||||
|
var signal_name = data[2]
|
||||||
|
debugger_panel.log_signal_execution(time, node_name, signal_name)
|
||||||
|
return true
|
||||||
|
|
||||||
|
if message == "signal_debugger:generated_graph":
|
||||||
|
if data.size() == 1:
|
||||||
|
var signals = data[0][0] as Array
|
||||||
|
var edges = data[0][1] as Array
|
||||||
|
debugger_panel.create_tree_from_signals(signals)
|
||||||
|
debugger_panel.create_signal_graph(signals, edges)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _setup_session(session_id):
|
||||||
|
debugger_panel = SignalDebuggerPanelScene.instantiate()
|
||||||
|
var session = get_session(session_id)
|
||||||
|
|
||||||
|
debugger_panel.name = "Signal Debugger"
|
||||||
|
debugger_panel.open_script.connect(func (arg1, arg2): open_script.emit(arg1, arg2))
|
||||||
|
debugger_panel.start_signal_debugging.connect(func (): start_signal_debugging.emit())
|
||||||
|
debugger_panel.stop_signal_debugging.connect(func (): stop_signal_debugging.emit())
|
||||||
|
|
||||||
|
session.started.connect(
|
||||||
|
func ():
|
||||||
|
debugger_panel.enable()
|
||||||
|
)
|
||||||
|
session.stopped.connect(
|
||||||
|
func ():
|
||||||
|
debugger_panel.stop()
|
||||||
|
debugger_panel.disable()
|
||||||
|
stop_signal_debugging.emit()
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add_session_tab(debugger_panel)
|
||||||
|
|
||||||
|
var SignalVisualizerDockScene = preload("res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn")
|
||||||
|
|
||||||
|
class ScriptMethodReference:
|
||||||
|
var script_reference: Script
|
||||||
|
var line_number: int
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var dock: Control
|
||||||
|
var debugger: SignalDebuggerPlugin
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
dock = SignalVisualizerDockScene.instantiate()
|
||||||
|
debugger = SignalDebuggerPlugin.new()
|
||||||
|
|
||||||
|
dock.open_script.connect(_on_open_signal_in_script)
|
||||||
|
add_control_to_bottom_panel(dock, "Signal Visualizer")
|
||||||
|
|
||||||
|
debugger.start_signal_debugging.connect(_on_debugger_start_signal_debugging)
|
||||||
|
debugger.stop_signal_debugging.connect(_on_debugger_stop_signal_debugging)
|
||||||
|
debugger.open_script.connect(_on_open_signal_in_script)
|
||||||
|
add_debugger_plugin(debugger)
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting("autoload/Signal_Debugger"):
|
||||||
|
add_autoload_singleton("Signal_Debugger", "res://addons/SignalVisualizer/Debugger/SignalDebugger.gd")
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
remove_control_from_bottom_panel(dock)
|
||||||
|
dock.free()
|
||||||
|
|
||||||
|
remove_debugger_plugin(debugger)
|
||||||
|
remove_autoload_singleton("Signal_Debugger")
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_open_signal_in_script(node_name: String, method_signature: String):
|
||||||
|
var node: Node
|
||||||
|
if get_tree().edited_scene_root.name == node_name:
|
||||||
|
node = get_tree().edited_scene_root
|
||||||
|
else:
|
||||||
|
node = get_tree().edited_scene_root.find_child(node_name)
|
||||||
|
|
||||||
|
if node != null:
|
||||||
|
var script: Script = node.get_script()
|
||||||
|
if script != null:
|
||||||
|
var editor = get_editor_interface()
|
||||||
|
var method_reference = _find_method_reference_in_script(script, method_signature)
|
||||||
|
|
||||||
|
if method_reference != null:
|
||||||
|
editor.edit_script(method_reference.script_reference, method_reference.line_number, 0)
|
||||||
|
editor.set_main_screen_editor("Script")
|
||||||
|
else:
|
||||||
|
push_warning("Requested method in script ({script}) for node ({name}) is not available.".format({ "name": node_name, "script": script.name }))
|
||||||
|
else:
|
||||||
|
push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
|
||||||
|
else:
|
||||||
|
push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
|
||||||
|
|
||||||
|
func _on_debugger_start_signal_debugging():
|
||||||
|
for session in debugger.get_sessions():
|
||||||
|
session.send_message("signal_debugger:start", [])
|
||||||
|
|
||||||
|
func _on_debugger_stop_signal_debugging():
|
||||||
|
for session in debugger.get_sessions():
|
||||||
|
session.send_message("signal_debugger:stop", [])
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _find_method_reference_in_script(script: Script, method_signature: String) -> ScriptMethodReference:
|
||||||
|
var line_number = __find_method_line_number_in_script(script, method_signature)
|
||||||
|
|
||||||
|
if line_number == -1:
|
||||||
|
var base_script = script.get_base_script()
|
||||||
|
if base_script:
|
||||||
|
return _find_method_reference_in_script(base_script, method_signature)
|
||||||
|
|
||||||
|
var reference = ScriptMethodReference.new()
|
||||||
|
reference.script_reference = script
|
||||||
|
reference.line_number = line_number
|
||||||
|
|
||||||
|
return reference
|
||||||
|
|
||||||
|
func __find_method_line_number_in_script(script: Script, method_signature: String) -> int:
|
||||||
|
var line_number = 0
|
||||||
|
var found = false
|
||||||
|
for line in script.source_code.split("\n", true):
|
||||||
|
line_number += 1
|
||||||
|
if line.contains(method_signature):
|
||||||
|
found = true
|
||||||
|
return line_number
|
||||||
|
|
||||||
|
return -1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://43lcsn3nt3ri
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="3" height="10" width="10" rx="1" fill="#e0e0e0"/></svg>
|
||||||
|
After Width: | Height: | Size: 154 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://oo1oq2colx5b"
|
||||||
|
path="res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/SignalVisualizer/Stop.svg"
|
||||||
|
dest_files=["res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.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
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@tool
|
||||||
|
extends Label
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func get_text_size() -> Vector2:
|
||||||
|
return get_theme_default_font().get_string_size(text)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d3lyqancfvwup
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
@tool
|
||||||
|
class_name SignalGraphNode extends GraphNode
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
var connections: Array = [] :
|
||||||
|
get: return connections
|
||||||
|
set(new_value):
|
||||||
|
connections = new_value
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
selectable = true
|
||||||
|
resizable = true
|
||||||
|
draggable = true
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_resize_request(new_minsize):
|
||||||
|
size = new_minsize
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func has_source_signal_description(signal_name: String, destination_node_name: String) -> bool:
|
||||||
|
for child in get_children():
|
||||||
|
if child.name == "source_" + signal_name + "_" + destination_node_name:
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func get_source_slot(signal_name: String, destination_node_name: String) -> int:
|
||||||
|
var index = 0
|
||||||
|
for child in get_children():
|
||||||
|
if child.name == "source_" + signal_name + "_" + destination_node_name:
|
||||||
|
return index
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
func get_next_source_slot(signal_name: String, destination_node_name: String) -> int:
|
||||||
|
var index = 0
|
||||||
|
for child in get_children():
|
||||||
|
if child.name.begins_with("source_"):
|
||||||
|
if child.name == "source_" + signal_name + "_" + destination_node_name:
|
||||||
|
return index
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
func has_destination_signal_description(signal_name: String, method_signature: String) -> bool:
|
||||||
|
for child in get_children():
|
||||||
|
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func get_destination_slot(signal_name: String, method_signature: String) -> int:
|
||||||
|
var index = 0
|
||||||
|
for child in get_children():
|
||||||
|
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
|
||||||
|
return index
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
func get_next_destination_slot(signal_name: String, method_signature: String) -> int:
|
||||||
|
var index = 0
|
||||||
|
for child in get_children():
|
||||||
|
if child.name.begins_with("destination_"):
|
||||||
|
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
|
||||||
|
return index
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
func _sanitize_method_signature(signature: String) -> String:
|
||||||
|
return signature.replace("::", "__")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bdwkkgkhgfrtk
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cq10iaub18e54"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node.gd" id="1_ovklj"]
|
||||||
|
|
||||||
|
[node name="SignalGraphNode" type="GraphNode"]
|
||||||
|
custom_minimum_size = Vector2(100, 50)
|
||||||
|
offset_right = 232.0
|
||||||
|
offset_bottom = 54.0
|
||||||
|
resizable = true
|
||||||
|
script = ExtResource("1_ovklj")
|
||||||
|
|
||||||
|
[connection signal="resize_request" from="." to="." method="_on_resize_request"]
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
@tool
|
||||||
|
class_name SignalGraphNodeItem extends Control
|
||||||
|
|
||||||
|
signal open_script(metadata: SignalGraphNodeItem.Metadata)
|
||||||
|
|
||||||
|
class Metadata:
|
||||||
|
var signal_name: String
|
||||||
|
var method_signature: String
|
||||||
|
var node_name: String
|
||||||
|
|
||||||
|
func _init(signal_name: String, method_signature: String, node_name: String):
|
||||||
|
self.signal_name = signal_name
|
||||||
|
self.method_signature = method_signature
|
||||||
|
self.node_name = node_name
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
@onready var label: Label = %DescriptionLabel
|
||||||
|
|
||||||
|
var signal_data: Metadata = null
|
||||||
|
|
||||||
|
var text: String = "" :
|
||||||
|
get: return text
|
||||||
|
set(new_value):
|
||||||
|
text = new_value
|
||||||
|
if label:
|
||||||
|
label.text = text
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
_update()
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_open_signal_in_script_button_pressed():
|
||||||
|
open_script.emit(signal_data)
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _update():
|
||||||
|
label.text = text
|
||||||
|
|
||||||
|
var text_size = label.get_text_size()
|
||||||
|
custom_minimum_size = Vector2((text_size.x * 2) + 50, text_size.y * 3)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c0n3sifmbiih0
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://b2lwtwp6kpwtb"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd" id="1_jrd34"]
|
||||||
|
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/resizable_label.gd" id="2_4wwd5"]
|
||||||
|
|
||||||
|
[node name="SignalItem" type="Control"]
|
||||||
|
clip_contents = true
|
||||||
|
custom_minimum_size = Vector2(51, 23)
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("1_jrd34")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||||
|
custom_minimum_size = Vector2(100, 50)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="DescriptionLabel" type="Label" parent="HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
clip_text = true
|
||||||
|
script = ExtResource("2_4wwd5")
|
||||||
|
|
||||||
|
[node name="OpenSignalInScriptButton" type="Button" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Open"
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[connection signal="pressed" from="HBoxContainer/OpenSignalInScriptButton" to="." method="_on_open_signal_in_script_button_pressed"]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal open_script(node_name: String, method_signature: String)
|
||||||
|
|
||||||
|
var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
|
||||||
|
var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
const SOURCE_COLOR: Color = Color.SKY_BLUE
|
||||||
|
const DESTINATION_COLOR: Color = Color.CORAL
|
||||||
|
const CONNECTION_TYPE: int = 0
|
||||||
|
|
||||||
|
@onready var arrange_nodes_checkbox: CheckBox = %ArrangeNodesCheckBox
|
||||||
|
@onready var signal_details_checkbox: CheckBox = %SignalDetailsCheckBox
|
||||||
|
@onready var signal_tree: Tree = %SignalTree
|
||||||
|
@onready var graph: GraphEdit = %Graph
|
||||||
|
|
||||||
|
# Lifecycle
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func _on_clear_graph_button_pressed():
|
||||||
|
clear()
|
||||||
|
|
||||||
|
func _on_generate_graph_button_pressed():
|
||||||
|
clear()
|
||||||
|
|
||||||
|
var scene_signal_graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().edited_scene_root, true)
|
||||||
|
SignalGraphUtility.generate_signal_graph_nodes(scene_signal_graph, graph, _on_open_signal_in_script)
|
||||||
|
SignalGraphUtility.generate_signal_graph_tree(scene_signal_graph, signal_tree)
|
||||||
|
|
||||||
|
if arrange_nodes_checkbox.button_pressed:
|
||||||
|
graph.arrange_nodes()
|
||||||
|
|
||||||
|
func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
|
||||||
|
open_script.emit(data.node_name, data.method_signature)
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
# |===================================|
|
||||||
|
|
||||||
|
func clear():
|
||||||
|
_clear_graph_nodes()
|
||||||
|
_clear_tree()
|
||||||
|
|
||||||
|
func _clear_graph_nodes():
|
||||||
|
graph.clear_connections()
|
||||||
|
for child in graph.get_children():
|
||||||
|
if child is SignalGraphNode:
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
func _clear_tree():
|
||||||
|
signal_tree.clear()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bbd48wbihmuos
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://dppfamjc0ji40"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd" id="1_akar5"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="2_m8bsv"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bxj8ep08wbnm6" path="res://addons/SignalVisualizer/GraphEdit.svg" id="3_dtmqs"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ae0jg"]
|
||||||
|
|
||||||
|
[node name="SignalVisualizerDock" type="Control"]
|
||||||
|
clip_contents = true
|
||||||
|
custom_minimum_size = Vector2(2.08165e-12, 200)
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_akar5")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
|
clip_contents = true
|
||||||
|
custom_minimum_size = Vector2(2.08165e-12, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="ArrangeNodesCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Arrange Nodes"
|
||||||
|
|
||||||
|
[node name="SignalDetailsCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Signal Details"
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxEmpty_ae0jg")
|
||||||
|
|
||||||
|
[node name="ClearGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Clear Graph"
|
||||||
|
icon = ExtResource("2_m8bsv")
|
||||||
|
|
||||||
|
[node name="GenerateGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Generate Graph"
|
||||||
|
icon = ExtResource("3_dtmqs")
|
||||||
|
|
||||||
|
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(200, 2.08165e-12)
|
||||||
|
layout_mode = 2
|
||||||
|
column_titles_visible = true
|
||||||
|
|
||||||
|
[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearGraphButton" to="." method="_on_clear_graph_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/GenerateGraphButton" to="." method="_on_generate_graph_button_pressed"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="SignalVisualizer"
|
||||||
|
description="Visual the current scene's signal connections as a graph. Debug the current running scene's signals with automatic logging in a new debugger panel."
|
||||||
|
author="MiniGameDev"
|
||||||
|
version="1.7.0"
|
||||||
|
script="SignalVisualizer.gd"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4 11V4h7" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity=".6"/></svg>
|
||||||
|
After Width: | Height: | Size: 221 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://btc01wc11tiid"
|
||||||
|
path="res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg"
|
||||||
|
dest_files=["res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.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
|
||||||
|
svg/scale=2.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M12 11V4h-7" fill="none" stroke="#fff" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://04l05jxuyt7k"
|
||||||
|
path="res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg"
|
||||||
|
dest_files=["res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.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
|
||||||
|
svg/scale=2.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 1v1l1 1v3h6v-3l1-1v-1zm1 6-2 3h10l-2-3zm2 4v2l1 2 1-2v-2z" fill="#e0e0e0"/></svg>
|
||||||
|
After Width: | Height: | Size: 177 B |
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://do6d60od41vmg"
|
||||||
|
path="res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/anthonyec.camera_preview/Pin.svg"
|
||||||
|
dest_files=["res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.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
|
||||||
|
svg/scale=2.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Little Camera Preview"
|
||||||
|
description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
|
||||||
|
author="Anthony Cossins"
|
||||||
|
version="1.3"
|
||||||
|
script="plugin.gd"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
|
||||||
|
|
||||||
|
var preview: CameraPreview
|
||||||
|
var current_main_screen_name: String
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
main_screen_changed.connect(_on_main_screen_changed)
|
||||||
|
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
|
||||||
|
|
||||||
|
# Initialise preview panel and add to main screen.
|
||||||
|
preview = preview_scene.instantiate() as CameraPreview
|
||||||
|
preview.request_hide()
|
||||||
|
|
||||||
|
var main_screen = EditorInterface.get_editor_main_screen()
|
||||||
|
main_screen.add_child(preview)
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if preview:
|
||||||
|
preview.queue_free()
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# TODO: Currently there is no API to get the main screen name without
|
||||||
|
# listening to the `EditorPlugin.main_screen_changed` signal:
|
||||||
|
# https://github.com/godotengine/godot-proposals/issues/2081
|
||||||
|
EditorInterface.set_main_screen_editor("Script")
|
||||||
|
EditorInterface.set_main_screen_editor("3D")
|
||||||
|
|
||||||
|
func _on_main_screen_changed(screen_name: String) -> void:
|
||||||
|
current_main_screen_name = screen_name
|
||||||
|
|
||||||
|
# TODO: Bit of a hack to prevent pinned staying between view changes on the same scene.
|
||||||
|
preview.unlink_camera()
|
||||||
|
_on_editor_selection_changed()
|
||||||
|
|
||||||
|
func _on_editor_selection_changed() -> void:
|
||||||
|
if not is_main_screen_viewport():
|
||||||
|
# This hides the preview "container" and not the preview itself, allowing
|
||||||
|
# any locked previews to remain visible once switching back to 3D tab.
|
||||||
|
preview.visible = false
|
||||||
|
return
|
||||||
|
|
||||||
|
preview.visible = true
|
||||||
|
|
||||||
|
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
|
||||||
|
|
||||||
|
var selected_camera_3d: Camera3D = find_camera_3d_or_null(selected_nodes)
|
||||||
|
var selected_camera_2d: Camera2D = find_camera_2d_or_null(selected_nodes)
|
||||||
|
|
||||||
|
if selected_camera_3d and current_main_screen_name == "3D":
|
||||||
|
preview.link_with_camera_3d(selected_camera_3d)
|
||||||
|
preview.request_show()
|
||||||
|
|
||||||
|
elif selected_camera_2d and current_main_screen_name == "2D":
|
||||||
|
preview.link_with_camera_2d(selected_camera_2d)
|
||||||
|
preview.request_show()
|
||||||
|
|
||||||
|
else:
|
||||||
|
preview.request_hide()
|
||||||
|
|
||||||
|
func is_main_screen_viewport() -> bool:
|
||||||
|
return current_main_screen_name == "3D" or current_main_screen_name == "2D"
|
||||||
|
|
||||||
|
func find_camera_3d_or_null(nodes: Array[Node]) -> Camera3D:
|
||||||
|
var camera: Camera3D
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if node is Camera3D:
|
||||||
|
camera = node as Camera3D
|
||||||
|
break
|
||||||
|
|
||||||
|
return camera
|
||||||
|
|
||||||
|
func find_camera_2d_or_null(nodes: Array[Node]) -> Camera2D:
|
||||||
|
var camera: Camera2D
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if node is Camera2D:
|
||||||
|
camera = node as Camera2D
|
||||||
|
break
|
||||||
|
|
||||||
|
return camera
|
||||||
|
|
||||||
|
func _on_selected_camera_3d_tree_exiting() -> void:
|
||||||
|
preview.unlink_camera()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dxnp5qcip84u7
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
@tool
|
||||||
|
|
||||||
|
class_name CameraPreview
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
enum CameraType {
|
||||||
|
CAMERA_2D,
|
||||||
|
CAMERA_3D
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PinnedPosition {
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InteractionState {
|
||||||
|
NONE,
|
||||||
|
RESIZE,
|
||||||
|
DRAG,
|
||||||
|
|
||||||
|
# Animation is split into 2 seperate states so that the tween is only
|
||||||
|
# invoked once in the "start" state.
|
||||||
|
START_ANIMATE_INTO_PLACE,
|
||||||
|
ANIMATE_INTO_PLACE,
|
||||||
|
}
|
||||||
|
|
||||||
|
const margin_3d: Vector2 = Vector2(10, 10)
|
||||||
|
const margin_2d: Vector2 = Vector2(20, 15)
|
||||||
|
const panel_margin: float = 2
|
||||||
|
const min_panel_size: float = 250
|
||||||
|
|
||||||
|
@onready var panel: Panel = %Panel
|
||||||
|
@onready var placeholder: Panel = %Placeholder
|
||||||
|
@onready var preview_camera_3d: Camera3D = %Camera3D
|
||||||
|
@onready var preview_camera_2d: Camera2D = %Camera2D
|
||||||
|
@onready var sub_viewport: SubViewport = %SubViewport
|
||||||
|
@onready var sub_viewport_text_rect: TextureRect = %TextureRect
|
||||||
|
@onready var resize_left_handle: Button = %ResizeLeftHandle
|
||||||
|
@onready var resize_right_handle: Button = %ResizeRightHandle
|
||||||
|
@onready var lock_button: Button = %LockButton
|
||||||
|
@onready var gradient: TextureRect = %Gradient
|
||||||
|
@onready var viewport_margin_container: MarginContainer = %ViewportMarginContainer
|
||||||
|
@onready var overlay_margin_container: MarginContainer = %OverlayMarginContainer
|
||||||
|
@onready var overlay_container: Control = %OverlayContainer
|
||||||
|
|
||||||
|
var camera_type: CameraType = CameraType.CAMERA_3D
|
||||||
|
var pinned_position: PinnedPosition = PinnedPosition.RIGHT
|
||||||
|
var viewport_ratio: float = 1
|
||||||
|
var editor_scale: float = EditorInterface.get_editor_scale()
|
||||||
|
var is_locked: bool
|
||||||
|
var show_controls: bool
|
||||||
|
var selected_camera_3d: Camera3D
|
||||||
|
var selected_camera_2d: Camera2D
|
||||||
|
|
||||||
|
var state: InteractionState = InteractionState.NONE
|
||||||
|
var initial_mouse_position: Vector2
|
||||||
|
var initial_panel_size: Vector2
|
||||||
|
var initial_panel_position: Vector2
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Set initial width.
|
||||||
|
panel.size.x = min_panel_size * editor_scale
|
||||||
|
|
||||||
|
# Setting texture to viewport in code instead of directly in the editor
|
||||||
|
# because otherwise an error "Path to node is invalid: Panel/SubViewport"
|
||||||
|
# on first load. This is harmless but doesn't look great.
|
||||||
|
#
|
||||||
|
# This is a known issue:
|
||||||
|
# https://github.com/godotengine/godot/issues/27790#issuecomment-499740220
|
||||||
|
sub_viewport_text_rect.texture = sub_viewport.get_texture()
|
||||||
|
|
||||||
|
# From what I can tell there's something wrong with how an editor theme
|
||||||
|
# scales when used within a plugin. It seems to ignore the screen scale.
|
||||||
|
# For instance, a 30x30px button will appear tiny on a retina display.
|
||||||
|
#
|
||||||
|
# Someone else had the issue with no luck:
|
||||||
|
# https://forum.godotengine.org/t/how-to-scale-plugin-controls-to-look-the-same-in-4k-as-1080p/36151
|
||||||
|
#
|
||||||
|
# And seems Dialogic also scales buttons manually:
|
||||||
|
# https://github.com/dialogic-godot/dialogic/blob/master/addons/dialogic/Editor/Common/sidebar.gd#L25C6-L38
|
||||||
|
#
|
||||||
|
# Maybe I don't know the correct way to do it, so for now the workaround is
|
||||||
|
# to set the correct size in code using screen scale.
|
||||||
|
var button_size = Vector2(30, 30) * editor_scale
|
||||||
|
var margin_size: float = panel_margin * editor_scale
|
||||||
|
|
||||||
|
resize_left_handle.size = button_size
|
||||||
|
resize_left_handle.pivot_offset = Vector2(0, 0) * editor_scale
|
||||||
|
|
||||||
|
resize_right_handle.size = button_size
|
||||||
|
resize_right_handle.pivot_offset = Vector2(30, 30) * editor_scale
|
||||||
|
|
||||||
|
lock_button.size = button_size
|
||||||
|
lock_button.pivot_offset = Vector2(0, 30) * editor_scale
|
||||||
|
|
||||||
|
viewport_margin_container.add_theme_constant_override("margin_left", margin_size)
|
||||||
|
viewport_margin_container.add_theme_constant_override("margin_top", margin_size)
|
||||||
|
viewport_margin_container.add_theme_constant_override("margin_right", margin_size)
|
||||||
|
viewport_margin_container.add_theme_constant_override("margin_bottom", margin_size)
|
||||||
|
|
||||||
|
overlay_margin_container.add_theme_constant_override("margin_left", margin_size)
|
||||||
|
overlay_margin_container.add_theme_constant_override("margin_top", margin_size)
|
||||||
|
overlay_margin_container.add_theme_constant_override("margin_right", margin_size)
|
||||||
|
overlay_margin_container.add_theme_constant_override("margin_bottom", margin_size)
|
||||||
|
|
||||||
|
# Parent node overlay size is not available on first ready, need to wait a
|
||||||
|
# frame for it to be drawn.
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
# Anchors are set in code because setting them in the editor UI doesn't take
|
||||||
|
# editor scale into account.
|
||||||
|
resize_left_handle.position = Vector2(0, 0)
|
||||||
|
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
|
||||||
|
resize_right_handle.position = Vector2(overlay_container.size.x - button_size.x, 0)
|
||||||
|
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||||
|
|
||||||
|
lock_button.position = Vector2(0, overlay_container.size.y - button_size.y)
|
||||||
|
lock_button.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not visible: return
|
||||||
|
|
||||||
|
match state:
|
||||||
|
InteractionState.NONE:
|
||||||
|
panel.size = get_clamped_size(panel.size)
|
||||||
|
panel.position = get_pinned_position(pinned_position)
|
||||||
|
|
||||||
|
InteractionState.RESIZE:
|
||||||
|
var delta_mouse_position = initial_mouse_position - get_global_mouse_position()
|
||||||
|
var resized_size = panel.size
|
||||||
|
|
||||||
|
if pinned_position == PinnedPosition.LEFT:
|
||||||
|
resized_size = initial_panel_size - delta_mouse_position
|
||||||
|
|
||||||
|
if pinned_position == PinnedPosition.RIGHT:
|
||||||
|
resized_size = initial_panel_size + delta_mouse_position
|
||||||
|
|
||||||
|
panel.size = get_clamped_size(resized_size)
|
||||||
|
panel.position = get_pinned_position(pinned_position)
|
||||||
|
|
||||||
|
InteractionState.DRAG:
|
||||||
|
placeholder.size = panel.size
|
||||||
|
|
||||||
|
var global_mouse_position = get_global_mouse_position()
|
||||||
|
var offset = initial_mouse_position - initial_panel_position
|
||||||
|
|
||||||
|
panel.global_position = global_mouse_position - offset
|
||||||
|
|
||||||
|
if global_mouse_position.x < global_position.x + size.x / 2:
|
||||||
|
pinned_position = PinnedPosition.LEFT
|
||||||
|
else:
|
||||||
|
pinned_position = PinnedPosition.RIGHT
|
||||||
|
|
||||||
|
placeholder.position = get_pinned_position(pinned_position)
|
||||||
|
|
||||||
|
InteractionState.START_ANIMATE_INTO_PLACE:
|
||||||
|
var final_position: Vector2 = get_pinned_position(pinned_position)
|
||||||
|
var tween = get_tree().create_tween()
|
||||||
|
|
||||||
|
tween.set_ease(Tween.EASE_OUT)
|
||||||
|
tween.set_trans(Tween.TRANS_CUBIC)
|
||||||
|
tween.tween_property(panel, "position", final_position, 0.3)
|
||||||
|
|
||||||
|
tween.finished.connect(func():
|
||||||
|
panel.position = final_position
|
||||||
|
state = InteractionState.NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
state = InteractionState.ANIMATE_INTO_PLACE
|
||||||
|
|
||||||
|
# I couldn't get `mouse_entered` and `mouse_exited` events to work
|
||||||
|
# nicely, so I use rect method instead. Plus using this method it's easy to
|
||||||
|
# grow the hit area size.
|
||||||
|
var panel_hover_rect = Rect2(panel.global_position, panel.size)
|
||||||
|
panel_hover_rect = panel_hover_rect.grow(40)
|
||||||
|
|
||||||
|
var mouse_position = get_global_mouse_position()
|
||||||
|
|
||||||
|
show_controls = state != InteractionState.NONE or panel_hover_rect.has_point(mouse_position)
|
||||||
|
|
||||||
|
# UI visibility.
|
||||||
|
resize_left_handle.visible = show_controls and pinned_position == PinnedPosition.RIGHT
|
||||||
|
resize_right_handle.visible = show_controls and pinned_position == PinnedPosition.LEFT
|
||||||
|
lock_button.visible = show_controls or is_locked
|
||||||
|
placeholder.visible = state == InteractionState.DRAG or state == InteractionState.ANIMATE_INTO_PLACE
|
||||||
|
gradient.visible = show_controls
|
||||||
|
|
||||||
|
# Sync camera settings.
|
||||||
|
if camera_type == CameraType.CAMERA_3D and selected_camera_3d:
|
||||||
|
sub_viewport.size = panel.size
|
||||||
|
|
||||||
|
# Sync position and rotation without using a `RemoteTransform` node
|
||||||
|
# because if you save a camera as a scene, the remote transform node will
|
||||||
|
# be stored within the scene. Also it's harder to keep the remote
|
||||||
|
# transform `remote_path` up-to-date with scene changes, which causes
|
||||||
|
# many errors.
|
||||||
|
preview_camera_3d.global_position = selected_camera_3d.global_position
|
||||||
|
preview_camera_3d.global_rotation = selected_camera_3d.global_rotation
|
||||||
|
|
||||||
|
preview_camera_3d.fov = selected_camera_3d.fov
|
||||||
|
preview_camera_3d.projection = selected_camera_3d.projection
|
||||||
|
preview_camera_3d.size = selected_camera_3d.size
|
||||||
|
preview_camera_3d.cull_mask = selected_camera_3d.cull_mask
|
||||||
|
preview_camera_3d.keep_aspect = selected_camera_3d.keep_aspect
|
||||||
|
preview_camera_3d.near = selected_camera_3d.near
|
||||||
|
preview_camera_3d.far = selected_camera_3d.far
|
||||||
|
preview_camera_3d.h_offset = selected_camera_3d.h_offset
|
||||||
|
preview_camera_3d.v_offset = selected_camera_3d.v_offset
|
||||||
|
preview_camera_3d.attributes = selected_camera_3d.attributes
|
||||||
|
preview_camera_3d.environment = selected_camera_3d.environment
|
||||||
|
|
||||||
|
if camera_type == CameraType.CAMERA_2D and selected_camera_2d:
|
||||||
|
var project_window_size = get_project_window_size()
|
||||||
|
var ratio = project_window_size.x / panel.size.x
|
||||||
|
|
||||||
|
# TODO: Is there a better way to fix this?
|
||||||
|
# The camera border is visible sometimes due to pixel rounding.
|
||||||
|
# Subtract 1px from right and bottom to hide this.
|
||||||
|
var hide_camera_border_fix = Vector2(1, 1)
|
||||||
|
|
||||||
|
sub_viewport.size = panel.size
|
||||||
|
sub_viewport.size_2d_override = (panel.size - hide_camera_border_fix) * ratio
|
||||||
|
sub_viewport.size_2d_override_stretch = true
|
||||||
|
|
||||||
|
preview_camera_2d.global_position = selected_camera_2d.global_position
|
||||||
|
preview_camera_2d.global_rotation = selected_camera_2d.global_rotation
|
||||||
|
|
||||||
|
preview_camera_2d.offset = selected_camera_2d.offset
|
||||||
|
preview_camera_2d.zoom = selected_camera_2d.zoom
|
||||||
|
preview_camera_2d.ignore_rotation = selected_camera_2d.ignore_rotation
|
||||||
|
preview_camera_2d.anchor_mode = selected_camera_2d.anchor_mode
|
||||||
|
preview_camera_2d.limit_left = selected_camera_2d.limit_left
|
||||||
|
preview_camera_2d.limit_right = selected_camera_2d.limit_right
|
||||||
|
preview_camera_2d.limit_top = selected_camera_2d.limit_top
|
||||||
|
preview_camera_2d.limit_bottom = selected_camera_2d.limit_bottom
|
||||||
|
|
||||||
|
func link_with_camera_3d(camera_3d: Camera3D) -> void:
|
||||||
|
# TODO: Camera may not be ready since this method is called in `_enter_tree`
|
||||||
|
# in the plugin because of a workaround for:
|
||||||
|
# https://github.com/godotengine/godot-proposals/issues/2081
|
||||||
|
if not preview_camera_3d:
|
||||||
|
return request_hide()
|
||||||
|
|
||||||
|
var is_different_camera = camera_3d != preview_camera_3d
|
||||||
|
|
||||||
|
# TODO: A bit messy.
|
||||||
|
if is_different_camera:
|
||||||
|
if preview_camera_3d.tree_exiting.is_connected(unlink_camera):
|
||||||
|
preview_camera_3d.tree_exiting.disconnect(unlink_camera)
|
||||||
|
|
||||||
|
if not camera_3d.tree_exiting.is_connected(unlink_camera):
|
||||||
|
camera_3d.tree_exiting.connect(unlink_camera)
|
||||||
|
|
||||||
|
sub_viewport.disable_3d = false
|
||||||
|
sub_viewport.world_3d = camera_3d.get_world_3d()
|
||||||
|
|
||||||
|
selected_camera_3d = camera_3d
|
||||||
|
camera_type = CameraType.CAMERA_3D
|
||||||
|
|
||||||
|
func link_with_camera_2d(camera_2d: Camera2D) -> void:
|
||||||
|
if not preview_camera_2d:
|
||||||
|
return request_hide()
|
||||||
|
|
||||||
|
var is_different_camera = camera_2d != preview_camera_2d
|
||||||
|
|
||||||
|
# TODO: A bit messy.
|
||||||
|
if is_different_camera:
|
||||||
|
if preview_camera_2d.tree_exiting.is_connected(unlink_camera):
|
||||||
|
preview_camera_2d.tree_exiting.disconnect(unlink_camera)
|
||||||
|
|
||||||
|
if not camera_2d.tree_exiting.is_connected(unlink_camera):
|
||||||
|
camera_2d.tree_exiting.connect(unlink_camera)
|
||||||
|
|
||||||
|
sub_viewport.disable_3d = true
|
||||||
|
sub_viewport.world_2d = camera_2d.get_world_2d()
|
||||||
|
|
||||||
|
selected_camera_2d = camera_2d
|
||||||
|
camera_type = CameraType.CAMERA_2D
|
||||||
|
|
||||||
|
func unlink_camera() -> void:
|
||||||
|
if selected_camera_3d:
|
||||||
|
selected_camera_3d = null
|
||||||
|
|
||||||
|
if selected_camera_2d:
|
||||||
|
selected_camera_2d = null
|
||||||
|
|
||||||
|
is_locked = false
|
||||||
|
lock_button.button_pressed = false
|
||||||
|
|
||||||
|
func request_hide() -> void:
|
||||||
|
if is_locked: return
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func request_show() -> void:
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
func get_pinned_position(pinned_position: PinnedPosition) -> Vector2:
|
||||||
|
var margin: Vector2 = margin_3d * editor_scale
|
||||||
|
|
||||||
|
if camera_type == CameraType.CAMERA_2D:
|
||||||
|
margin = margin_2d * editor_scale
|
||||||
|
|
||||||
|
match pinned_position:
|
||||||
|
PinnedPosition.LEFT:
|
||||||
|
return Vector2.ZERO - Vector2(0, panel.size.y) - Vector2(-margin.x, margin.y)
|
||||||
|
PinnedPosition.RIGHT:
|
||||||
|
return size - panel.size - margin
|
||||||
|
_:
|
||||||
|
assert(false, "Unknown pinned position %s" % str(pinned_position))
|
||||||
|
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
func get_clamped_size(desired_size: Vector2) -> Vector2:
|
||||||
|
var viewport_ratio = get_project_window_ratio()
|
||||||
|
var editor_viewport_size = get_editor_viewport_size()
|
||||||
|
|
||||||
|
var max_bounds = Vector2(
|
||||||
|
editor_viewport_size.x * 0.6,
|
||||||
|
editor_viewport_size.y * 0.8
|
||||||
|
)
|
||||||
|
|
||||||
|
var clamped_size = desired_size
|
||||||
|
|
||||||
|
# Apply aspect ratio.
|
||||||
|
clamped_size = Vector2(clamped_size.x, clamped_size.x * viewport_ratio)
|
||||||
|
|
||||||
|
# Clamp the max size while respecting the aspect ratio.
|
||||||
|
if clamped_size.y >= max_bounds.y:
|
||||||
|
clamped_size.x = max_bounds.y / viewport_ratio
|
||||||
|
clamped_size.y = max_bounds.y
|
||||||
|
|
||||||
|
if clamped_size.x >= max_bounds.x:
|
||||||
|
clamped_size.x = max_bounds.x
|
||||||
|
clamped_size.y = max_bounds.x * viewport_ratio
|
||||||
|
|
||||||
|
# Clamp the min size based on if it's portrait or landscape. Portrait min
|
||||||
|
# size should be based on it's height. Landscape min size is based on it's
|
||||||
|
# width instead. Applying min width to a portrait size would make it too big.
|
||||||
|
var is_portrait = viewport_ratio > 1
|
||||||
|
|
||||||
|
if is_portrait and clamped_size.y <= min_panel_size * editor_scale:
|
||||||
|
clamped_size.x = min_panel_size / viewport_ratio
|
||||||
|
clamped_size.y = min_panel_size
|
||||||
|
clamped_size = clamped_size * editor_scale
|
||||||
|
|
||||||
|
if not is_portrait and clamped_size.x <= min_panel_size * editor_scale:
|
||||||
|
clamped_size.x = min_panel_size
|
||||||
|
clamped_size.y = min_panel_size * viewport_ratio
|
||||||
|
clamped_size = clamped_size * editor_scale
|
||||||
|
|
||||||
|
# Round down to avoid sub-pixel artifacts, mainly seen around the margins.
|
||||||
|
return clamped_size.floor()
|
||||||
|
|
||||||
|
func get_project_window_size() -> Vector2:
|
||||||
|
var window_width = float(ProjectSettings.get_setting("display/window/size/viewport_width"))
|
||||||
|
var window_height = float(ProjectSettings.get_setting("display/window/size/viewport_height"))
|
||||||
|
|
||||||
|
return Vector2(window_width, window_height)
|
||||||
|
|
||||||
|
func get_project_window_ratio() -> float:
|
||||||
|
var project_window_size = get_project_window_size()
|
||||||
|
|
||||||
|
return project_window_size.y / project_window_size.x
|
||||||
|
|
||||||
|
func get_editor_viewport_size() -> Vector2:
|
||||||
|
var fallback_size = EditorInterface.get_editor_main_screen().size
|
||||||
|
|
||||||
|
# There isn't an API for getting the viewport node. Instead it has to be
|
||||||
|
# found by checking the parent's parent of the subviewport and find
|
||||||
|
# the correct node based on name and class.
|
||||||
|
var editor_sub_viewport_3d = EditorInterface.get_editor_viewport_3d(0)
|
||||||
|
var editor_viewport_container = editor_sub_viewport_3d.get_parent().get_parent().get_parent()
|
||||||
|
|
||||||
|
# Early return incase editor tree structure has changed.
|
||||||
|
if editor_viewport_container.get_class() != "Node3DEditorViewportContainer":
|
||||||
|
return fallback_size
|
||||||
|
|
||||||
|
return editor_viewport_container.size
|
||||||
|
|
||||||
|
func _on_resize_handle_button_down() -> void:
|
||||||
|
if state != InteractionState.NONE: return
|
||||||
|
|
||||||
|
state = InteractionState.RESIZE
|
||||||
|
initial_mouse_position = get_global_mouse_position()
|
||||||
|
initial_panel_size = panel.size
|
||||||
|
|
||||||
|
func _on_resize_handle_button_up() -> void:
|
||||||
|
state = InteractionState.NONE
|
||||||
|
|
||||||
|
func _on_drag_handle_button_down() -> void:
|
||||||
|
if state != InteractionState.NONE: return
|
||||||
|
|
||||||
|
state = InteractionState.DRAG
|
||||||
|
initial_mouse_position = get_global_mouse_position()
|
||||||
|
initial_panel_position = panel.global_position
|
||||||
|
|
||||||
|
func _on_drag_handle_button_up() -> void:
|
||||||
|
if state != InteractionState.DRAG: return
|
||||||
|
|
||||||
|
state = InteractionState.START_ANIMATE_INTO_PLACE
|
||||||
|
|
||||||
|
func _on_lock_button_pressed() -> void:
|
||||||
|
is_locked = !is_locked
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dcq364kglr71e
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dcq364kglr71e" path="res://addons/anthonyec.camera_preview/preview.gd" id="1_6b32r"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://do6d60od41vmg" path="res://addons/anthonyec.camera_preview/Pin.svg" id="2_p0pa8"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://btc01wc11tiid" path="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg" id="2_t64ej"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://04l05jxuyt7k" path="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg" id="3_6yuab"]
|
||||||
|
|
||||||
|
[sub_resource type="ViewportTexture" id="ViewportTexture_hchdq"]
|
||||||
|
viewport_path = NodePath("Panel/SubViewport")
|
||||||
|
|
||||||
|
[sub_resource type="Gradient" id="Gradient_11p6r"]
|
||||||
|
offsets = PackedFloat32Array(0, 0.3, 0.6, 1)
|
||||||
|
colors = PackedColorArray(0, 0, 0, 0.235294, 0, 0, 0, 0.0784314, 0, 0, 0, 0.0784314, 0, 0, 0, 0.235294)
|
||||||
|
|
||||||
|
[sub_resource type="GradientTexture2D" id="GradientTexture2D_4dkve"]
|
||||||
|
gradient = SubResource("Gradient_11p6r")
|
||||||
|
width = 256
|
||||||
|
height = 256
|
||||||
|
fill_to = Vector2(2.08165e-12, 1)
|
||||||
|
|
||||||
|
[node name="Preview" type="Control"]
|
||||||
|
z_index = 999
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_6b32r")
|
||||||
|
|
||||||
|
[node name="Placeholder" type="Panel" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
modulate = Color(1, 1, 1, 0.705882)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -40.0
|
||||||
|
offset_top = -40.0
|
||||||
|
offset_right = 410.0
|
||||||
|
offset_bottom = 410.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -520.0
|
||||||
|
offset_top = -908.889
|
||||||
|
offset_right = -20.0
|
||||||
|
offset_bottom = -20.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
pivot_offset = Vector2(450, 300)
|
||||||
|
|
||||||
|
[node name="SubViewport" type="SubViewport" parent="Panel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
handle_input_locally = false
|
||||||
|
gui_disable_input = true
|
||||||
|
size_2d_override_stretch = true
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="Panel/SubViewport"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
current = true
|
||||||
|
|
||||||
|
[node name="Camera2D" type="Camera2D" parent="Panel/SubViewport"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
ignore_rotation = false
|
||||||
|
|
||||||
|
[node name="ViewportMarginContainer" type="MarginContainer" parent="Panel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 4
|
||||||
|
theme_override_constants/margin_top = 4
|
||||||
|
theme_override_constants/margin_right = 4
|
||||||
|
theme_override_constants/margin_bottom = 4
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="Panel/ViewportMarginContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
texture = SubResource("ViewportTexture_hchdq")
|
||||||
|
expand_mode = 1
|
||||||
|
|
||||||
|
[node name="Gradient" type="TextureRect" parent="Panel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
texture = SubResource("GradientTexture2D_4dkve")
|
||||||
|
|
||||||
|
[node name="OverlayMarginContainer" type="MarginContainer" parent="Panel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/margin_left = 4
|
||||||
|
theme_override_constants/margin_top = 4
|
||||||
|
theme_override_constants/margin_right = 4
|
||||||
|
theme_override_constants/margin_bottom = 4
|
||||||
|
|
||||||
|
[node name="OverlayContainer" type="Control" parent="Panel/OverlayMarginContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="DragHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
focus_mode = 0
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="ResizeLeftHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
offset_right = 60.0
|
||||||
|
offset_bottom = 60.0
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_default_cursor_shape = 12
|
||||||
|
icon = ExtResource("2_t64ej")
|
||||||
|
flat = true
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="ResizeRightHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -60.0
|
||||||
|
offset_bottom = 60.0
|
||||||
|
pivot_offset = Vector2(60, 60)
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
size_flags_vertical = 0
|
||||||
|
mouse_default_cursor_shape = 11
|
||||||
|
icon = ExtResource("3_6yuab")
|
||||||
|
flat = true
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[node name="LockButton" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 2
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_top = -60.0
|
||||||
|
offset_right = 60.0
|
||||||
|
pivot_offset = Vector2(0, 60)
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
size_flags_vertical = 8
|
||||||
|
tooltip_text = "Always Show Preview"
|
||||||
|
toggle_mode = true
|
||||||
|
icon = ExtResource("2_p0pa8")
|
||||||
|
flat = true
|
||||||
|
icon_alignment = 1
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
|
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_down"]
|
||||||
|
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_up"]
|
||||||
|
[connection signal="renamed" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_renamed"]
|
||||||
|
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_down"]
|
||||||
|
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_up"]
|
||||||
|
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_down"]
|
||||||
|
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_up"]
|
||||||
|
[connection signal="pressed" from="Panel/OverlayMarginContainer/OverlayContainer/LockButton" to="." method="_on_lock_button_pressed"]
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
class_name DialogicGameHandler
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## Class that is used as the Dialogic autoload.
|
||||||
|
|
||||||
|
## Autoload script that allows you to interact with all of Dialogic's systems:[br]
|
||||||
|
## - Holds all important information about the current state of Dialogic.[br]
|
||||||
|
## - Provides access to all the subsystems.[br]
|
||||||
|
## - Has methods to start/end timelines.[br]
|
||||||
|
|
||||||
|
|
||||||
|
## States indicating different phases of dialog.
|
||||||
|
enum States {
|
||||||
|
IDLE, ## Dialogic is awaiting input to advance.
|
||||||
|
REVEALING_TEXT, ## Dialogic is currently revealing text.
|
||||||
|
ANIMATING, ## Some animation is happening.
|
||||||
|
AWAITING_CHOICE, ## Dialogic awaits the selection of a choice
|
||||||
|
WAITING ## Dialogic is currently awaiting something.
|
||||||
|
}
|
||||||
|
|
||||||
|
## Flags indicating what to clear when calling [method clear].
|
||||||
|
enum ClearFlags {
|
||||||
|
FULL_CLEAR = 0, ## Clears all subsystems
|
||||||
|
KEEP_VARIABLES = 1, ## Clears all subsystems and info except for variables
|
||||||
|
TIMELINE_INFO_ONLY = 2 ## Doesn't clear subsystems but current timeline and index
|
||||||
|
}
|
||||||
|
|
||||||
|
## Reference to the currently executed timeline.
|
||||||
|
var current_timeline: DialogicTimeline = null
|
||||||
|
## Copy of the [member current_timeline]'s events.
|
||||||
|
var current_timeline_events: Array = []
|
||||||
|
|
||||||
|
## Index of the event the timeline handling is currently at.
|
||||||
|
var current_event_idx: int = 0
|
||||||
|
## Contains all information that subsystems consider relevant for
|
||||||
|
## the current situation
|
||||||
|
var current_state_info: Dictionary = {}
|
||||||
|
|
||||||
|
## Current state (see [member States] enum).
|
||||||
|
var current_state := States.IDLE:
|
||||||
|
get:
|
||||||
|
return current_state
|
||||||
|
|
||||||
|
set(new_state):
|
||||||
|
current_state = new_state
|
||||||
|
state_changed.emit(new_state)
|
||||||
|
|
||||||
|
## Emitted when [member current_state] change.
|
||||||
|
signal state_changed(new_state:States)
|
||||||
|
|
||||||
|
## When `true`, many dialogic processes won't continue until it's `false` again.
|
||||||
|
var paused := false:
|
||||||
|
set(value):
|
||||||
|
paused = value
|
||||||
|
|
||||||
|
if paused:
|
||||||
|
|
||||||
|
for subsystem in get_children():
|
||||||
|
|
||||||
|
if subsystem is DialogicSubsystem:
|
||||||
|
(subsystem as DialogicSubsystem).pause()
|
||||||
|
|
||||||
|
dialogic_paused.emit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
for subsystem in get_children():
|
||||||
|
|
||||||
|
if subsystem is DialogicSubsystem:
|
||||||
|
(subsystem as DialogicSubsystem).resume()
|
||||||
|
|
||||||
|
dialogic_resumed.emit()
|
||||||
|
|
||||||
|
## Emitted when [member paused] changes to `true`.
|
||||||
|
signal dialogic_paused
|
||||||
|
## Emitted when [member paused] changes to `false`.
|
||||||
|
signal dialogic_resumed
|
||||||
|
|
||||||
|
|
||||||
|
## Emitted when the timeline ends.
|
||||||
|
## This can be a timeline ending or [method end_timeline] being called.
|
||||||
|
signal timeline_ended
|
||||||
|
## Emitted when a timeline starts by calling either [method start]
|
||||||
|
## or [method start_timeline].
|
||||||
|
signal timeline_started
|
||||||
|
## Emitted when an event starts being executed.
|
||||||
|
## The event may not have finished executing yet.
|
||||||
|
signal event_handled(resource: DialogicEvent)
|
||||||
|
|
||||||
|
## Emitted when a [class SignalEvent] event was reached.
|
||||||
|
signal signal_event(argument: Variant)
|
||||||
|
## Emitted when a signal event gets fired from a [class TextEvent] event.
|
||||||
|
signal text_signal(argument: String)
|
||||||
|
|
||||||
|
|
||||||
|
# Careful, this section is repopulated automatically at certain moments.
|
||||||
|
#region SUBSYSTEMS
|
||||||
|
|
||||||
|
var Audio := preload("res://addons/dialogic/Modules/Audio/subsystem_audio.gd").new():
|
||||||
|
get: return get_subsystem("Audio")
|
||||||
|
|
||||||
|
var Backgrounds := preload("res://addons/dialogic/Modules/Background/subsystem_backgrounds.gd").new():
|
||||||
|
get: return get_subsystem("Backgrounds")
|
||||||
|
|
||||||
|
var Portraits := preload("res://addons/dialogic/Modules/Character/subsystem_portraits.gd").new():
|
||||||
|
get: return get_subsystem("Portraits")
|
||||||
|
|
||||||
|
var PortraitContainers := preload("res://addons/dialogic/Modules/Character/subsystem_containers.gd").new():
|
||||||
|
get: return get_subsystem("PortraitContainers")
|
||||||
|
|
||||||
|
var Choices := preload("res://addons/dialogic/Modules/Choice/subsystem_choices.gd").new():
|
||||||
|
get: return get_subsystem("Choices")
|
||||||
|
|
||||||
|
var Expressions := preload("res://addons/dialogic/Modules/Core/subsystem_expression.gd").new():
|
||||||
|
get: return get_subsystem("Expressions")
|
||||||
|
|
||||||
|
var Animations := preload("res://addons/dialogic/Modules/Core/subsystem_animation.gd").new():
|
||||||
|
get: return get_subsystem("Animations")
|
||||||
|
|
||||||
|
var Inputs := preload("res://addons/dialogic/Modules/Core/subsystem_input.gd").new():
|
||||||
|
get: return get_subsystem("Inputs")
|
||||||
|
|
||||||
|
var Glossary := preload("res://addons/dialogic/Modules/Glossary/subsystem_glossary.gd").new():
|
||||||
|
get: return get_subsystem("Glossary")
|
||||||
|
|
||||||
|
var History := preload("res://addons/dialogic/Modules/History/subsystem_history.gd").new():
|
||||||
|
get: return get_subsystem("History")
|
||||||
|
|
||||||
|
var Jump := preload("res://addons/dialogic/Modules/Jump/subsystem_jump.gd").new():
|
||||||
|
get: return get_subsystem("Jump")
|
||||||
|
|
||||||
|
var Save := preload("res://addons/dialogic/Modules/Save/subsystem_save.gd").new():
|
||||||
|
get: return get_subsystem("Save")
|
||||||
|
|
||||||
|
var Settings := preload("res://addons/dialogic/Modules/Settings/subsystem_settings.gd").new():
|
||||||
|
get: return get_subsystem("Settings")
|
||||||
|
|
||||||
|
var Styles := preload("res://addons/dialogic/Modules/Style/subsystem_styles.gd").new():
|
||||||
|
get: return get_subsystem("Styles")
|
||||||
|
|
||||||
|
var Text := preload("res://addons/dialogic/Modules/Text/subsystem_text.gd").new():
|
||||||
|
get: return get_subsystem("Text")
|
||||||
|
|
||||||
|
var TextInput := preload("res://addons/dialogic/Modules/TextInput/subsystem_text_input.gd").new():
|
||||||
|
get: return get_subsystem("TextInput")
|
||||||
|
|
||||||
|
var VAR := preload("res://addons/dialogic/Modules/Variable/subsystem_variables.gd").new():
|
||||||
|
get: return get_subsystem("VAR")
|
||||||
|
|
||||||
|
var Voice := preload("res://addons/dialogic/Modules/Voice/subsystem_voice.gd").new():
|
||||||
|
get: return get_subsystem("Voice")
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
## Autoloads are added first, so this happens REALLY early on game startup.
|
||||||
|
func _ready() -> void:
|
||||||
|
_collect_subsystems()
|
||||||
|
|
||||||
|
clear()
|
||||||
|
|
||||||
|
|
||||||
|
#region TIMELINE & EVENT HANDLING
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
## Method to start a timeline AND ensure that a layout scene is present.
|
||||||
|
## For argument info, checkout [method start_timeline].
|
||||||
|
## -> returns the layout node
|
||||||
|
func start(timeline:Variant, label:Variant="") -> Node:
|
||||||
|
# If we don't have a style subsystem, default to just start_timeline()
|
||||||
|
if not has_subsystem('Styles'):
|
||||||
|
printerr("[Dialogic] You called Dialogic.start() but the Styles subsystem is missing!")
|
||||||
|
clear(ClearFlags.KEEP_VARIABLES)
|
||||||
|
start_timeline(timeline, label)
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Otherwise make sure there is a style active.
|
||||||
|
var scene: Node = null
|
||||||
|
if !self.Styles.has_active_layout_node():
|
||||||
|
scene = self.Styles.load_style()
|
||||||
|
else:
|
||||||
|
scene = self.Styles.get_layout_node()
|
||||||
|
scene.show()
|
||||||
|
|
||||||
|
if not scene.is_node_ready():
|
||||||
|
scene.ready.connect(clear.bind(ClearFlags.KEEP_VARIABLES))
|
||||||
|
scene.ready.connect(start_timeline.bind(timeline, label))
|
||||||
|
else:
|
||||||
|
start_timeline(timeline, label)
|
||||||
|
|
||||||
|
return scene
|
||||||
|
|
||||||
|
|
||||||
|
## Method to start a timeline without adding a layout scene.
|
||||||
|
## @timeline can be either a loaded timeline resource or a path to a timeline file.
|
||||||
|
## @label_or_idx can be a label (string) or index (int) to skip to immediatly.
|
||||||
|
func start_timeline(timeline:Variant, label_or_idx:Variant = "") -> void:
|
||||||
|
# load the resource if only the path is given
|
||||||
|
if typeof(timeline) == TYPE_STRING:
|
||||||
|
#check the lookup table if it's not a full file name
|
||||||
|
if (timeline as String).contains("res://"):
|
||||||
|
timeline = load((timeline as String))
|
||||||
|
else:
|
||||||
|
timeline = DialogicResourceUtil.get_timeline_resource((timeline as String))
|
||||||
|
|
||||||
|
if timeline == null:
|
||||||
|
printerr("[Dialogic] There was an error loading this timeline. Check the filename, and the timeline for errors")
|
||||||
|
return
|
||||||
|
|
||||||
|
(timeline as DialogicTimeline).process()
|
||||||
|
|
||||||
|
current_timeline = timeline
|
||||||
|
current_timeline_events = current_timeline.events
|
||||||
|
for event in current_timeline_events:
|
||||||
|
event.dialogic = self
|
||||||
|
current_event_idx = -1
|
||||||
|
|
||||||
|
if typeof(label_or_idx) == TYPE_STRING:
|
||||||
|
if label_or_idx:
|
||||||
|
if has_subsystem('Jump'):
|
||||||
|
Jump.jump_to_label((label_or_idx as String))
|
||||||
|
elif typeof(label_or_idx) == TYPE_INT:
|
||||||
|
if label_or_idx >-1:
|
||||||
|
current_event_idx = label_or_idx -1
|
||||||
|
|
||||||
|
timeline_started.emit()
|
||||||
|
handle_next_event()
|
||||||
|
|
||||||
|
|
||||||
|
## Preloader function, prepares a timeline and returns an object to hold for later
|
||||||
|
## [param timeline_resource] can be either a path (string) or a loaded timeline (resource)
|
||||||
|
func preload_timeline(timeline_resource:Variant) -> Variant:
|
||||||
|
# I think ideally this should be on a new thread, will test
|
||||||
|
if typeof(timeline_resource) == TYPE_STRING:
|
||||||
|
timeline_resource = load((timeline_resource as String))
|
||||||
|
if timeline_resource == null:
|
||||||
|
printerr("[Dialogic] There was an error preloading this timeline. Check the filename, and the timeline for errors")
|
||||||
|
return null
|
||||||
|
|
||||||
|
(timeline_resource as DialogicTimeline).process()
|
||||||
|
|
||||||
|
return timeline_resource
|
||||||
|
|
||||||
|
|
||||||
|
## Clears and stops the current timeline.
|
||||||
|
func end_timeline() -> void:
|
||||||
|
await clear(ClearFlags.TIMELINE_INFO_ONLY)
|
||||||
|
_on_timeline_ended()
|
||||||
|
timeline_ended.emit()
|
||||||
|
|
||||||
|
|
||||||
|
## Handles the next event.
|
||||||
|
func handle_next_event(_ignore_argument: Variant = "") -> void:
|
||||||
|
handle_event(current_event_idx+1)
|
||||||
|
|
||||||
|
|
||||||
|
## Handles the event at the given index [param event_index].
|
||||||
|
## You can call this manually, but if another event is still executing, it might have unexpected results.
|
||||||
|
func handle_event(event_index:int) -> void:
|
||||||
|
if not current_timeline:
|
||||||
|
return
|
||||||
|
|
||||||
|
_cleanup_previous_event()
|
||||||
|
|
||||||
|
if paused:
|
||||||
|
await dialogic_resumed
|
||||||
|
|
||||||
|
if event_index >= len(current_timeline_events):
|
||||||
|
end_timeline()
|
||||||
|
return
|
||||||
|
|
||||||
|
#actually process the event now, since we didnt earlier at runtime
|
||||||
|
#this needs to happen before we create the copy DialogicEvent variable, so it doesn't throw an error if not ready
|
||||||
|
if current_timeline_events[event_index].event_node_ready == false:
|
||||||
|
current_timeline_events[event_index]._load_from_string(current_timeline_events[event_index].event_node_as_text)
|
||||||
|
|
||||||
|
current_event_idx = event_index
|
||||||
|
|
||||||
|
if not current_timeline_events[event_index].event_finished.is_connected(handle_next_event):
|
||||||
|
current_timeline_events[event_index].event_finished.connect(handle_next_event)
|
||||||
|
|
||||||
|
set_meta('previous_event', current_timeline_events[event_index])
|
||||||
|
|
||||||
|
current_timeline_events[event_index].execute(self)
|
||||||
|
event_handled.emit(current_timeline_events[event_index])
|
||||||
|
|
||||||
|
|
||||||
|
## Resets Dialogic's state fully or partially.
|
||||||
|
## By using the clear flags from the [member ClearFlags] enum you can specify
|
||||||
|
## what info should be kept.
|
||||||
|
## For example, at timeline end usually it doesn't clear node or subsystem info.
|
||||||
|
func clear(clear_flags := ClearFlags.FULL_CLEAR) -> void:
|
||||||
|
_cleanup_previous_event()
|
||||||
|
|
||||||
|
if !clear_flags & ClearFlags.TIMELINE_INFO_ONLY:
|
||||||
|
for subsystem in get_children():
|
||||||
|
if subsystem is DialogicSubsystem:
|
||||||
|
(subsystem as DialogicSubsystem).clear_game_state(clear_flags)
|
||||||
|
|
||||||
|
var timeline := current_timeline
|
||||||
|
|
||||||
|
current_timeline = null
|
||||||
|
current_event_idx = -1
|
||||||
|
current_timeline_events = []
|
||||||
|
current_state = States.IDLE
|
||||||
|
|
||||||
|
# Resetting variables
|
||||||
|
if timeline:
|
||||||
|
await timeline.clean()
|
||||||
|
|
||||||
|
|
||||||
|
## Cleanup after previous event (if any).
|
||||||
|
func _cleanup_previous_event():
|
||||||
|
if has_meta('previous_event') and get_meta('previous_event') is DialogicEvent:
|
||||||
|
var event := get_meta('previous_event') as DialogicEvent
|
||||||
|
if event.event_finished.is_connected(handle_next_event):
|
||||||
|
event.event_finished.disconnect(handle_next_event)
|
||||||
|
event._clear_state()
|
||||||
|
remove_meta("previous_event")
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region SAVING & LOADING
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
## Returns a dictionary containing all necessary information to later recreate the same state with load_full_state.
|
||||||
|
## The [subsystem Save] subsystem might be more useful for you.
|
||||||
|
## However, this can be used to integrate the info into your own save system.
|
||||||
|
func get_full_state() -> Dictionary:
|
||||||
|
if current_timeline:
|
||||||
|
current_state_info['current_event_idx'] = current_event_idx
|
||||||
|
current_state_info['current_timeline'] = current_timeline.resource_path
|
||||||
|
else:
|
||||||
|
current_state_info['current_event_idx'] = -1
|
||||||
|
current_state_info['current_timeline'] = null
|
||||||
|
|
||||||
|
for subsystem in get_children():
|
||||||
|
(subsystem as DialogicSubsystem).save_game_state()
|
||||||
|
|
||||||
|
return current_state_info.duplicate(true)
|
||||||
|
|
||||||
|
|
||||||
|
## This method tries to load the state from the given [param state_info].
|
||||||
|
## Will automatically start a timeline and add a layout if a timeline was running when
|
||||||
|
## the dictionary was retrieved with [method get_full_state].
|
||||||
|
func load_full_state(state_info:Dictionary) -> void:
|
||||||
|
clear()
|
||||||
|
current_state_info = state_info
|
||||||
|
## The Style subsystem needs to run first for others to load correctly.
|
||||||
|
var scene: Node = null
|
||||||
|
if has_subsystem('Styles'):
|
||||||
|
get_subsystem('Styles').load_game_state()
|
||||||
|
scene = self.Styles.get_layout_node()
|
||||||
|
|
||||||
|
var load_subsystems := func() -> void:
|
||||||
|
for subsystem in get_children():
|
||||||
|
if subsystem.name == 'Styles':
|
||||||
|
continue
|
||||||
|
(subsystem as DialogicSubsystem).load_game_state()
|
||||||
|
|
||||||
|
if null != scene and not scene.is_node_ready():
|
||||||
|
scene.ready.connect(load_subsystems)
|
||||||
|
else:
|
||||||
|
await get_tree().process_frame
|
||||||
|
load_subsystems.call()
|
||||||
|
|
||||||
|
if current_state_info.get('current_timeline', null):
|
||||||
|
start_timeline(current_state_info.current_timeline, current_state_info.get('current_event_idx', 0))
|
||||||
|
else:
|
||||||
|
end_timeline.call_deferred()
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region SUB-SYTSEMS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
func _collect_subsystems() -> void:
|
||||||
|
var subsystem_nodes := [] as Array[DialogicSubsystem]
|
||||||
|
for indexer in DialogicUtil.get_indexers():
|
||||||
|
for subsystem in indexer._get_subsystems():
|
||||||
|
var subsystem_node := add_subsystem(str(subsystem.name), str(subsystem.script))
|
||||||
|
subsystem_nodes.push_back(subsystem_node)
|
||||||
|
|
||||||
|
for subsystem in subsystem_nodes:
|
||||||
|
subsystem.post_install()
|
||||||
|
|
||||||
|
|
||||||
|
## Returns `true` if a subystem with the given [param subsystem_name] exists.
|
||||||
|
func has_subsystem(subsystem_name:String) -> bool:
|
||||||
|
return has_node(subsystem_name)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the subsystem node of the given [param subsystem_name] or null if it doesn't exist.
|
||||||
|
func get_subsystem(subsystem_name:String) -> DialogicSubsystem:
|
||||||
|
return get_node(subsystem_name)
|
||||||
|
|
||||||
|
|
||||||
|
## Adds a subsystem node with the given [param subsystem_name] and [param script_path].
|
||||||
|
func add_subsystem(subsystem_name:String, script_path:String) -> DialogicSubsystem:
|
||||||
|
var node: Node = Node.new()
|
||||||
|
node.name = subsystem_name
|
||||||
|
node.set_script(load(script_path))
|
||||||
|
node = node as DialogicSubsystem
|
||||||
|
node.dialogic = self
|
||||||
|
add_child(node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region HELPERS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
## This handles the `Layout End Behaviour` setting that can be changed in the Dialogic settings.
|
||||||
|
func _on_timeline_ended() -> void:
|
||||||
|
if self.Styles.has_active_layout_node() and self.Styles.get_layout_node().is_inside_tree():
|
||||||
|
match ProjectSettings.get_setting('dialogic/layout/end_behaviour', 0):
|
||||||
|
0:
|
||||||
|
self.Styles.get_layout_node().get_parent().remove_child(self.Styles.get_layout_node())
|
||||||
|
self.Styles.get_layout_node().queue_free()
|
||||||
|
1:
|
||||||
|
@warning_ignore("unsafe_method_access")
|
||||||
|
self.Styles.get_layout_node().hide()
|
||||||
|
|
||||||
|
|
||||||
|
func print_debug_moment() -> void:
|
||||||
|
if not current_timeline:
|
||||||
|
return
|
||||||
|
|
||||||
|
printerr("\tAt event ", current_event_idx+1, " (",current_timeline_events[current_event_idx].event_name, ' Event) in timeline "', DialogicResourceUtil.get_unique_identifier(current_timeline.resource_path), '" (',current_timeline.resource_path,').')
|
||||||
|
print("\n")
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c0ucrkws16k4i
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
@tool
|
||||||
|
class_name DialogicResourceUtil
|
||||||
|
|
||||||
|
static var label_cache := {}
|
||||||
|
static var event_cache: Array[DialogicEvent] = []
|
||||||
|
|
||||||
|
static var special_resources := {}
|
||||||
|
|
||||||
|
|
||||||
|
static func update() -> void:
|
||||||
|
update_directory('.dch')
|
||||||
|
update_directory('.dtl')
|
||||||
|
update_label_cache()
|
||||||
|
|
||||||
|
|
||||||
|
#region RESOURCE DIRECTORIES
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func get_directory(extension:String) -> Dictionary:
|
||||||
|
extension = extension.trim_prefix('.')
|
||||||
|
if Engine.has_meta(extension+'_directory'):
|
||||||
|
return Engine.get_meta(extension+'_directory', {})
|
||||||
|
|
||||||
|
var directory: Dictionary = ProjectSettings.get_setting("dialogic/directories/"+extension+'_directory', {})
|
||||||
|
Engine.set_meta(extension+'_directory', directory)
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
static func set_directory(extension:String, directory:Dictionary) -> void:
|
||||||
|
extension = extension.trim_prefix('.')
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
ProjectSettings.set_setting("dialogic/directories/"+extension+'_directory', directory)
|
||||||
|
ProjectSettings.save()
|
||||||
|
Engine.set_meta(extension+'_directory', directory)
|
||||||
|
|
||||||
|
|
||||||
|
static func update_directory(extension:String) -> void:
|
||||||
|
var directory := get_directory(extension)
|
||||||
|
|
||||||
|
for resource in list_resources_of_type(extension):
|
||||||
|
if not resource in directory.values():
|
||||||
|
directory = add_resource_to_directory(resource, directory)
|
||||||
|
|
||||||
|
var keys_to_remove := []
|
||||||
|
for key in directory:
|
||||||
|
if not ResourceLoader.exists(directory[key]):
|
||||||
|
keys_to_remove.append(key)
|
||||||
|
for key in keys_to_remove:
|
||||||
|
directory.erase(key)
|
||||||
|
|
||||||
|
set_directory(extension, directory)
|
||||||
|
|
||||||
|
|
||||||
|
static func add_resource_to_directory(file_path:String, directory:Dictionary) -> Dictionary:
|
||||||
|
var suggested_name := file_path.get_file().trim_suffix("."+file_path.get_extension())
|
||||||
|
while suggested_name in directory:
|
||||||
|
suggested_name = file_path.trim_suffix("/"+suggested_name+"."+file_path.get_extension()).get_file().path_join(suggested_name)
|
||||||
|
directory[suggested_name] = file_path
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the unique identifier for the given resource path.
|
||||||
|
## Returns an empty string if no identifier was found.
|
||||||
|
static func get_unique_identifier(file_path:String) -> String:
|
||||||
|
var identifier: String = get_directory(file_path.get_extension()).find_key(file_path)
|
||||||
|
if typeof(identifier) == TYPE_STRING:
|
||||||
|
return identifier
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the resource associated with the given unique identifier.
|
||||||
|
## The expected extension is needed to use the right directory.
|
||||||
|
static func get_resource_from_identifier(identifier:String, extension:String) -> Resource:
|
||||||
|
var path: String = get_directory(extension).get(identifier, '')
|
||||||
|
if ResourceLoader.exists(path):
|
||||||
|
return load(path)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func change_unique_identifier(file_path:String, new_identifier:String) -> void:
|
||||||
|
var directory := get_directory(file_path.get_extension())
|
||||||
|
var key: String = directory.find_key(file_path)
|
||||||
|
while key != null:
|
||||||
|
if key == new_identifier:
|
||||||
|
break
|
||||||
|
directory.erase(key)
|
||||||
|
directory[new_identifier] = file_path
|
||||||
|
key = directory.find_key(file_path)
|
||||||
|
set_directory(file_path.get_extension(), directory)
|
||||||
|
|
||||||
|
|
||||||
|
static func change_resource_path(old_path:String, new_path:String) -> void:
|
||||||
|
var directory := get_directory(new_path.get_extension())
|
||||||
|
var key: String = directory.find_key(old_path)
|
||||||
|
while key != null:
|
||||||
|
directory[key] = new_path
|
||||||
|
key = directory.find_key(old_path)
|
||||||
|
set_directory(new_path.get_extension(), directory)
|
||||||
|
|
||||||
|
|
||||||
|
static func remove_resource(file_path:String) -> void:
|
||||||
|
var directory := get_directory(file_path.get_extension())
|
||||||
|
var key: String = directory.find_key(file_path)
|
||||||
|
while key != null:
|
||||||
|
directory.erase(key)
|
||||||
|
key = directory.find_key(file_path)
|
||||||
|
set_directory(file_path.get_extension(), directory)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_identifier_unused(extension:String, identifier:String) -> bool:
|
||||||
|
return not identifier in get_directory(extension)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region LABEL CACHE
|
||||||
|
################################################################################
|
||||||
|
# The label cache is only for the editor so we don't have to scan all timelines
|
||||||
|
# whenever we want to suggest labels. This has no use in game and is not always perfect.
|
||||||
|
|
||||||
|
static func get_label_cache() -> Dictionary:
|
||||||
|
if not label_cache.is_empty():
|
||||||
|
return label_cache
|
||||||
|
|
||||||
|
label_cache = DialogicUtil.get_editor_setting('label_ref', {})
|
||||||
|
return label_cache
|
||||||
|
|
||||||
|
|
||||||
|
static func set_label_cache(cache:Dictionary) -> void:
|
||||||
|
label_cache = cache
|
||||||
|
|
||||||
|
|
||||||
|
static func update_label_cache() -> void:
|
||||||
|
var cache := get_label_cache()
|
||||||
|
var timelines := get_timeline_directory().values()
|
||||||
|
for timeline in cache:
|
||||||
|
if !timeline in timelines:
|
||||||
|
cache.erase(timeline)
|
||||||
|
set_label_cache(cache)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region EVENT CACHE
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
## Dialogic keeps a list that has each event once. This allows retrieval of that list.
|
||||||
|
static func get_event_cache() -> Array:
|
||||||
|
if not event_cache.is_empty():
|
||||||
|
return event_cache
|
||||||
|
|
||||||
|
event_cache = update_event_cache()
|
||||||
|
return event_cache
|
||||||
|
|
||||||
|
|
||||||
|
static func update_event_cache() -> Array:
|
||||||
|
event_cache = []
|
||||||
|
for indexer in DialogicUtil.get_indexers():
|
||||||
|
# build event cache
|
||||||
|
for event in indexer._get_events():
|
||||||
|
if not ResourceLoader.exists(event):
|
||||||
|
continue
|
||||||
|
if not 'event_end_branch.gd' in event and not 'event_text.gd' in event:
|
||||||
|
event_cache.append(load(event).new())
|
||||||
|
|
||||||
|
# Events are checked in order while testing them. EndBranch needs to be first, Text needs to be last
|
||||||
|
event_cache.push_front(DialogicEndBranchEvent.new())
|
||||||
|
event_cache.push_back(DialogicTextEvent.new())
|
||||||
|
|
||||||
|
return event_cache
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SPECIAL RESOURCES
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func update_special_resources() -> void:
|
||||||
|
special_resources.clear()
|
||||||
|
for indexer in DialogicUtil.get_indexers():
|
||||||
|
var additions := indexer._get_special_resources()
|
||||||
|
for resource_type in additions:
|
||||||
|
if not resource_type in special_resources:
|
||||||
|
special_resources[resource_type] = {}
|
||||||
|
special_resources[resource_type].merge(additions[resource_type])
|
||||||
|
|
||||||
|
|
||||||
|
static func list_special_resources(type:String, filter := {}) -> Dictionary:
|
||||||
|
if special_resources.is_empty():
|
||||||
|
update_special_resources()
|
||||||
|
if type in special_resources:
|
||||||
|
if filter.is_empty():
|
||||||
|
return special_resources[type]
|
||||||
|
else:
|
||||||
|
var results := {}
|
||||||
|
for i in special_resources[type]:
|
||||||
|
if match_resource_filter(special_resources[type][i], filter):
|
||||||
|
results[i] = special_resources[type][i]
|
||||||
|
return results
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
static func match_resource_filter(dict:Dictionary, filter:Dictionary) -> bool:
|
||||||
|
for i in filter:
|
||||||
|
if not i in dict:
|
||||||
|
return false
|
||||||
|
if typeof(filter[i]) == TYPE_ARRAY:
|
||||||
|
if not dict[i] in filter[i]:
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
if not dict[i] == filter[i]:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
static func guess_special_resource(type: String, string: String, default := {}, filter := {}, ignores:PackedStringArray=[]) -> Dictionary:
|
||||||
|
if string.is_empty():
|
||||||
|
return default
|
||||||
|
|
||||||
|
if special_resources.is_empty():
|
||||||
|
update_special_resources()
|
||||||
|
var resources := list_special_resources(type, filter)
|
||||||
|
if resources.is_empty():
|
||||||
|
printerr("[Dialogic] No ", type, "s found, but attempted to use one.")
|
||||||
|
return default
|
||||||
|
|
||||||
|
if string.begins_with('res://'):
|
||||||
|
for i in resources.values():
|
||||||
|
if i.path == string:
|
||||||
|
return i
|
||||||
|
printerr("[Dialogic] Unable to find ", type, " at path '", string, "'.")
|
||||||
|
return default
|
||||||
|
|
||||||
|
string = string.to_lower()
|
||||||
|
|
||||||
|
if string in resources:
|
||||||
|
return resources[string]
|
||||||
|
|
||||||
|
if not ignores.is_empty():
|
||||||
|
var regex := RegEx.create_from_string(r" ?\b(" + "|".join(ignores) + r")\b")
|
||||||
|
for name in resources:
|
||||||
|
if regex.sub(name, "") == regex.sub(string, ""):
|
||||||
|
return resources[name]
|
||||||
|
|
||||||
|
## As a last effort check against the unfiltered list
|
||||||
|
if string in special_resources[type]:
|
||||||
|
push_warning("[Dialogic] Using ", type, " '", string,"' when not supposed to.")
|
||||||
|
return special_resources[type][string]
|
||||||
|
|
||||||
|
printerr("[Dialogic] Unable to identify ", type, " based on string '", string, "'.")
|
||||||
|
return default
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region HELPERS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func get_character_directory() -> Dictionary:
|
||||||
|
return get_directory('dch')
|
||||||
|
|
||||||
|
|
||||||
|
static func get_timeline_directory() -> Dictionary:
|
||||||
|
return get_directory('dtl')
|
||||||
|
|
||||||
|
|
||||||
|
static func get_timeline_resource(timeline_identifier:String) -> DialogicTimeline:
|
||||||
|
return get_resource_from_identifier(timeline_identifier, 'dtl')
|
||||||
|
|
||||||
|
|
||||||
|
static func get_character_resource(character_identifier:String) -> DialogicCharacter:
|
||||||
|
return get_resource_from_identifier(character_identifier, 'dch')
|
||||||
|
|
||||||
|
|
||||||
|
static func list_resources_of_type(extension:String) -> Array:
|
||||||
|
var all_resources := scan_folder('res://', extension)
|
||||||
|
return all_resources
|
||||||
|
|
||||||
|
|
||||||
|
static func scan_folder(path:String, extension:String) -> Array:
|
||||||
|
var list: Array = []
|
||||||
|
if DirAccess.dir_exists_absolute(path):
|
||||||
|
var dir := DirAccess.open(path)
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name := dir.get_next()
|
||||||
|
while file_name != "":
|
||||||
|
if dir.current_is_dir() and not file_name.begins_with("."):
|
||||||
|
list += scan_folder(path.path_join(file_name), extension)
|
||||||
|
else:
|
||||||
|
if file_name.ends_with(extension):
|
||||||
|
list.append(path.path_join(file_name))
|
||||||
|
file_name = dir.get_next()
|
||||||
|
return list
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cp8y6bxaigt7e
|
||||||
@@ -0,0 +1,676 @@
|
|||||||
|
@tool
|
||||||
|
class_name DialogicUtil
|
||||||
|
|
||||||
|
## Script that container helper methods for both editor and game execution.
|
||||||
|
## Used whenever the same thing is needed in different parts of the plugin.
|
||||||
|
|
||||||
|
#region EDITOR
|
||||||
|
|
||||||
|
## This method should be used instead of EditorInterface.get_editor_scale(), because if you use that
|
||||||
|
## it will run perfectly fine from the editor, but crash when the game is exported.
|
||||||
|
static func get_editor_scale() -> float:
|
||||||
|
return get_dialogic_plugin().get_editor_interface().get_editor_scale()
|
||||||
|
|
||||||
|
|
||||||
|
## Although this does in fact always return a EditorPlugin node,
|
||||||
|
## that class is apparently not present in export and referencing it here creates a crash.
|
||||||
|
static func get_dialogic_plugin() -> Node:
|
||||||
|
for child in Engine.get_main_loop().get_root().get_children():
|
||||||
|
if child.get_class() == "EditorNode":
|
||||||
|
return child.get_node('DialogicPlugin')
|
||||||
|
return null
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the autoload when in-game.
|
||||||
|
static func autoload() -> DialogicGameHandler:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return null
|
||||||
|
if not Engine.get_main_loop().root.has_node("Dialogic"):
|
||||||
|
return null
|
||||||
|
return Engine.get_main_loop().root.get_node("Dialogic")
|
||||||
|
|
||||||
|
|
||||||
|
#region FILE SYSTEM
|
||||||
|
################################################################################
|
||||||
|
static func listdir(path: String, files_only:= true, _throw_error:= true, full_file_path:= false, include_imports := false) -> Array:
|
||||||
|
var files: Array = []
|
||||||
|
if path.is_empty(): path = "res://"
|
||||||
|
if DirAccess.dir_exists_absolute(path):
|
||||||
|
var dir := DirAccess.open(path)
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name := dir.get_next()
|
||||||
|
while file_name != "":
|
||||||
|
if not file_name.begins_with("."):
|
||||||
|
if files_only:
|
||||||
|
if not dir.current_is_dir() and (not file_name.ends_with('.import') or include_imports):
|
||||||
|
if full_file_path:
|
||||||
|
files.append(path.path_join(file_name))
|
||||||
|
else:
|
||||||
|
files.append(file_name)
|
||||||
|
else:
|
||||||
|
if full_file_path:
|
||||||
|
files.append(path.path_join(file_name))
|
||||||
|
else:
|
||||||
|
files.append(file_name)
|
||||||
|
file_name = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
static func get_module_path(name:String, builtin:=true) -> String:
|
||||||
|
if builtin:
|
||||||
|
return "res://addons/dialogic/Modules".path_join(name)
|
||||||
|
else:
|
||||||
|
return ProjectSettings.get_setting('dialogic/extensions_folder', 'res://addons/dialogic_additions').path_join(name)
|
||||||
|
|
||||||
|
|
||||||
|
## This is a private and editor-only function.
|
||||||
|
##
|
||||||
|
## Populates the [class DialogicGameHandler] with new custom subsystems by
|
||||||
|
## directly manipulating the file's content and then importing the file.
|
||||||
|
static func _update_autoload_subsystem_access() -> void:
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
printerr("[Dialogic] This function is only available in the editor.")
|
||||||
|
return
|
||||||
|
|
||||||
|
var script: Script = load("res://addons/dialogic/Core/DialogicGameHandler.gd")
|
||||||
|
var new_subsystem_access_list := "#region SUBSYSTEMS\n"
|
||||||
|
|
||||||
|
for indexer: DialogicIndexer in get_indexers(true, true):
|
||||||
|
|
||||||
|
for subsystem: Dictionary in indexer._get_subsystems().duplicate(true):
|
||||||
|
new_subsystem_access_list += '\nvar {name} := preload("{script}").new():\n\tget: return get_subsystem("{name}")\n'.format(subsystem)
|
||||||
|
|
||||||
|
new_subsystem_access_list += "\n#endregion"
|
||||||
|
script.source_code = RegEx.create_from_string(r"#region SUBSYSTEMS\n#*\n((?!#endregion)(.*\n))*#endregion").sub(script.source_code, new_subsystem_access_list)
|
||||||
|
ResourceSaver.save(script)
|
||||||
|
Engine.get_singleton("EditorInterface").get_resource_filesystem().reimport_files(["res://addons/dialogic/Core/DialogicGameHandler.gd"])
|
||||||
|
|
||||||
|
|
||||||
|
static func get_indexers(include_custom := true, force_reload := false) -> Array[DialogicIndexer]:
|
||||||
|
if Engine.get_main_loop().has_meta('dialogic_indexers') and !force_reload:
|
||||||
|
return Engine.get_main_loop().get_meta('dialogic_indexers')
|
||||||
|
|
||||||
|
var indexers: Array[DialogicIndexer] = []
|
||||||
|
|
||||||
|
for file in listdir(DialogicUtil.get_module_path(''), false):
|
||||||
|
var possible_script: String = DialogicUtil.get_module_path(file).path_join("index.gd")
|
||||||
|
if ResourceLoader.exists(possible_script):
|
||||||
|
indexers.append(load(possible_script).new())
|
||||||
|
|
||||||
|
if include_custom:
|
||||||
|
var extensions_folder: String = ProjectSettings.get_setting('dialogic/extensions_folder', "res://addons/dialogic_additions/")
|
||||||
|
for file in listdir(extensions_folder, false, false):
|
||||||
|
var possible_script: String = extensions_folder.path_join(file + "/index.gd")
|
||||||
|
if ResourceLoader.exists(possible_script):
|
||||||
|
indexers.append(load(possible_script).new())
|
||||||
|
|
||||||
|
Engine.get_main_loop().set_meta('dialogic_indexers', indexers)
|
||||||
|
return indexers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Turns a [param file_path] from `some_file.png` to `Some File`.
|
||||||
|
static func pretty_name(file_path: String) -> String:
|
||||||
|
var _name := file_path.get_file().trim_suffix("." + file_path.get_extension())
|
||||||
|
_name = _name.replace('_', ' ')
|
||||||
|
_name = _name.capitalize()
|
||||||
|
|
||||||
|
return _name
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region EDITOR SETTINGS & COLORS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func set_editor_setting(setting:String, value:Variant) -> void:
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
if FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
|
||||||
|
cfg.load('user://dialogic/editor_settings.cfg')
|
||||||
|
|
||||||
|
cfg.set_value('DES', setting, value)
|
||||||
|
|
||||||
|
if !DirAccess.dir_exists_absolute('user://dialogic'):
|
||||||
|
DirAccess.make_dir_absolute('user://dialogic')
|
||||||
|
cfg.save('user://dialogic/editor_settings.cfg')
|
||||||
|
|
||||||
|
|
||||||
|
static func get_editor_setting(setting:String, default:Variant=null) -> Variant:
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
if !FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
|
||||||
|
return default
|
||||||
|
|
||||||
|
if !cfg.load('user://dialogic/editor_settings.cfg') == OK:
|
||||||
|
return default
|
||||||
|
|
||||||
|
return cfg.get_value('DES', setting, default)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_color_palette(default:bool = false) -> Dictionary:
|
||||||
|
var defaults := {
|
||||||
|
'Color1': Color('#3b8bf2'), # Blue
|
||||||
|
'Color2': Color('#00b15f'), # Green
|
||||||
|
'Color3': Color('#e868e2'), # Pink
|
||||||
|
'Color4': Color('#9468e8'), # Purple
|
||||||
|
'Color5': Color('#574fb0'), # DarkPurple
|
||||||
|
'Color6': Color('#1fa3a3'), # Aquamarine
|
||||||
|
'Color7': Color('#fa952a'), # Orange
|
||||||
|
'Color8': Color('#de5c5c'), # Red
|
||||||
|
'Color9': Color('#7c7c7c'), # Gray
|
||||||
|
}
|
||||||
|
if default:
|
||||||
|
return defaults
|
||||||
|
return get_editor_setting('color_palette', defaults)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_color(value:String) -> Color:
|
||||||
|
var colors := get_color_palette()
|
||||||
|
return colors[value]
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region TIMER PROCESS MODE
|
||||||
|
################################################################################
|
||||||
|
static func is_physics_timer() -> bool:
|
||||||
|
return ProjectSettings.get_setting('dialogic/timer/process_in_physics', false)
|
||||||
|
|
||||||
|
|
||||||
|
static func update_timer_process_callback(timer:Timer) -> void:
|
||||||
|
timer.process_callback = Timer.TIMER_PROCESS_PHYSICS if is_physics_timer() else Timer.TIMER_PROCESS_IDLE
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region MULTITWEEN
|
||||||
|
################################################################################
|
||||||
|
static func multitween(tweened_value:Variant, item:Node, property:String, part:String) -> void:
|
||||||
|
var parts: Dictionary = item.get_meta(property+'_parts', {})
|
||||||
|
parts[part] = tweened_value
|
||||||
|
|
||||||
|
if not item.has_meta(property+'_base_value') and not 'base' in parts:
|
||||||
|
item.set_meta(property+'_base_value', item.get(property))
|
||||||
|
|
||||||
|
var final_value: Variant = parts.get('base', item.get_meta(property+'_base_value', item.get(property)))
|
||||||
|
|
||||||
|
for key in parts:
|
||||||
|
if key == 'base':
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
final_value += parts[key]
|
||||||
|
|
||||||
|
item.set(property, final_value)
|
||||||
|
item.set_meta(property+'_parts', parts)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region TRANSLATIONS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func get_next_translation_id() -> String:
|
||||||
|
ProjectSettings.set_setting('dialogic/translation/id_counter', ProjectSettings.get_setting('dialogic/translation/id_counter', 16)+1)
|
||||||
|
return '%x' % ProjectSettings.get_setting('dialogic/translation/id_counter', 16)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region VARIABLES
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
enum VarTypes {ANY, STRING, FLOAT, INT, BOOL}
|
||||||
|
|
||||||
|
|
||||||
|
static func get_default_variables() -> Dictionary:
|
||||||
|
return ProjectSettings.get_setting('dialogic/variables', {})
|
||||||
|
|
||||||
|
|
||||||
|
# helper that converts a nested variable dictionary into an array with paths
|
||||||
|
static func list_variables(dict:Dictionary, path := "", type:=VarTypes.ANY) -> Array:
|
||||||
|
var array := []
|
||||||
|
for key in dict.keys():
|
||||||
|
if typeof(dict[key]) == TYPE_DICTIONARY:
|
||||||
|
array.append_array(list_variables(dict[key], path+key+".", type))
|
||||||
|
else:
|
||||||
|
if type == VarTypes.ANY or get_variable_value_type(dict[key]) == type:
|
||||||
|
array.append(path+key)
|
||||||
|
return array
|
||||||
|
|
||||||
|
|
||||||
|
static func get_variable_value_type(value:Variant) -> VarTypes:
|
||||||
|
match typeof(value):
|
||||||
|
TYPE_STRING:
|
||||||
|
return VarTypes.STRING
|
||||||
|
TYPE_FLOAT:
|
||||||
|
return VarTypes.FLOAT
|
||||||
|
TYPE_INT:
|
||||||
|
return VarTypes.INT
|
||||||
|
TYPE_BOOL:
|
||||||
|
return VarTypes.BOOL
|
||||||
|
return VarTypes.ANY
|
||||||
|
|
||||||
|
|
||||||
|
static func get_variable_type(path:String, dict:Dictionary={}) -> VarTypes:
|
||||||
|
if dict.is_empty():
|
||||||
|
dict = get_default_variables()
|
||||||
|
return get_variable_value_type(_get_value_in_dictionary(path, dict))
|
||||||
|
|
||||||
|
|
||||||
|
## This will set a value in a dictionary (or a sub-dictionary based on the path)
|
||||||
|
## e.g. it could set "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
|
||||||
|
static func _set_value_in_dictionary(path:String, dictionary:Dictionary, value):
|
||||||
|
if '.' in path:
|
||||||
|
var from := path.split('.')[0]
|
||||||
|
if from in dictionary.keys():
|
||||||
|
dictionary[from] = _set_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], value)
|
||||||
|
else:
|
||||||
|
if path in dictionary.keys():
|
||||||
|
dictionary[path] = value
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
|
||||||
|
## This will get a value in a dictionary (or a sub-dictionary based on the path)
|
||||||
|
## e.g. it could get "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
|
||||||
|
static func _get_value_in_dictionary(path:String, dictionary:Dictionary, default= null) -> Variant:
|
||||||
|
if '.' in path:
|
||||||
|
var from := path.split('.')[0]
|
||||||
|
if from in dictionary.keys():
|
||||||
|
return _get_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], default)
|
||||||
|
else:
|
||||||
|
if path in dictionary.keys():
|
||||||
|
return dictionary[path]
|
||||||
|
return default
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region STYLES
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func get_default_layout_base() -> PackedScene:
|
||||||
|
return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Base_Default/default_layout_base.tscn"))
|
||||||
|
|
||||||
|
|
||||||
|
static func get_fallback_style() -> DialogicStyle:
|
||||||
|
return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Style_VN_Default/default_vn_style.tres"))
|
||||||
|
|
||||||
|
|
||||||
|
static func get_default_style() -> DialogicStyle:
|
||||||
|
var default: String = ProjectSettings.get_setting('dialogic/layout/default_style', '')
|
||||||
|
if !ResourceLoader.exists(default):
|
||||||
|
return get_fallback_style()
|
||||||
|
return load(default)
|
||||||
|
|
||||||
|
|
||||||
|
static func get_style_by_name(name:String) -> DialogicStyle:
|
||||||
|
if name.is_empty():
|
||||||
|
return get_default_style()
|
||||||
|
|
||||||
|
var styles: Array = ProjectSettings.get_setting('dialogic/layout/style_list', [])
|
||||||
|
for style in styles:
|
||||||
|
if not ResourceLoader.exists(style):
|
||||||
|
continue
|
||||||
|
if load(style).name == name:
|
||||||
|
return load(style)
|
||||||
|
|
||||||
|
return get_default_style()
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region SCENE EXPORT OVERRIDES
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func apply_scene_export_overrides(node:Node, export_overrides:Dictionary, apply := true) -> void:
|
||||||
|
var default_info := get_scene_export_defaults(node)
|
||||||
|
if !node.script:
|
||||||
|
return
|
||||||
|
var property_info: Array[Dictionary] = node.script.get_script_property_list()
|
||||||
|
for i in property_info:
|
||||||
|
if i['usage'] & PROPERTY_USAGE_EDITOR:
|
||||||
|
if i['name'] in export_overrides:
|
||||||
|
if str_to_var(export_overrides[i['name']]) == null and typeof(node.get(i['name'])) == TYPE_STRING:
|
||||||
|
node.set(i['name'], export_overrides[i['name']])
|
||||||
|
else:
|
||||||
|
node.set(i['name'], str_to_var(export_overrides[i['name']]))
|
||||||
|
elif i['name'] in default_info:
|
||||||
|
node.set(i['name'], default_info.get(i['name']))
|
||||||
|
if apply:
|
||||||
|
if node.has_method('apply_export_overrides'):
|
||||||
|
node.apply_export_overrides()
|
||||||
|
|
||||||
|
|
||||||
|
static func get_scene_export_defaults(node:Node) -> Dictionary:
|
||||||
|
if !node.script:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if Engine.get_main_loop().has_meta('dialogic_scene_export_defaults') and \
|
||||||
|
node.script.resource_path in Engine.get_main_loop().get_meta('dialogic_scene_export_defaults'):
|
||||||
|
return Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path]
|
||||||
|
|
||||||
|
if !Engine.get_main_loop().has_meta('dialogic_scene_export_defaults'):
|
||||||
|
Engine.get_main_loop().set_meta('dialogic_scene_export_defaults', {})
|
||||||
|
var defaults := {}
|
||||||
|
var property_info: Array[Dictionary] = node.script.get_script_property_list()
|
||||||
|
for i in property_info:
|
||||||
|
if i['usage'] & PROPERTY_USAGE_EDITOR:
|
||||||
|
defaults[i['name']] = node.get(i['name'])
|
||||||
|
Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path] = defaults
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region MAKE CUSTOM
|
||||||
|
|
||||||
|
static func make_file_custom(original_file:String, target_folder:String, new_file_name := "", new_folder_name := "") -> String:
|
||||||
|
if not ResourceLoader.exists(original_file):
|
||||||
|
push_error("[Dialogic] Unable to make file with invalid path custom!")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if new_folder_name:
|
||||||
|
target_folder = target_folder.path_join(new_folder_name)
|
||||||
|
DirAccess.make_dir_absolute(target_folder)
|
||||||
|
|
||||||
|
if new_file_name.is_empty():
|
||||||
|
new_file_name = "custom_" + original_file.get_file()
|
||||||
|
|
||||||
|
if not new_file_name.ends_with(original_file.get_extension()):
|
||||||
|
new_file_name += "." + original_file.get_extension()
|
||||||
|
|
||||||
|
var target_file := target_folder.path_join(new_file_name)
|
||||||
|
|
||||||
|
customize_file(original_file, target_file)
|
||||||
|
|
||||||
|
get_dialogic_plugin().get_editor_interface().get_resource_filesystem().scan_sources()
|
||||||
|
|
||||||
|
return target_file
|
||||||
|
|
||||||
|
|
||||||
|
static func customize_file(original_file:String, target_file:String) -> String:
|
||||||
|
#print("\nCUSTOMIZE FILE")
|
||||||
|
#printt(original_file, "->", target_file)
|
||||||
|
|
||||||
|
DirAccess.copy_absolute(original_file, target_file)
|
||||||
|
|
||||||
|
var file := FileAccess.open(target_file, FileAccess.READ)
|
||||||
|
var file_text := file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
# If we are customizing a scene, we check for any resources used in that scene that are in the same folder.
|
||||||
|
# Those will be copied as well and the scene will be modified to point to them.
|
||||||
|
if file_text.begins_with('[gd_'):
|
||||||
|
var base_path: String = original_file.get_base_dir()
|
||||||
|
|
||||||
|
var remove_uuid_regex := r'\[gd_.* (?<uid>uid="uid:[^"]*")'
|
||||||
|
var result := RegEx.create_from_string(remove_uuid_regex).search(file_text)
|
||||||
|
if result:
|
||||||
|
file_text = file_text.replace(result.get_string("uid"), "")
|
||||||
|
|
||||||
|
# This regex also removes the UID referencing the original resource
|
||||||
|
var file_regex := r'(uid="[^"]*" )?\Qpath="'+base_path+r'\E(?<file>[^"]*)"'
|
||||||
|
result = RegEx.create_from_string(file_regex).search(file_text)
|
||||||
|
while result:
|
||||||
|
var found_file_name := result.get_string('file')
|
||||||
|
var found_file_path := base_path.path_join(found_file_name)
|
||||||
|
var target_file_path := target_file.get_base_dir().path_join(found_file_name)
|
||||||
|
|
||||||
|
# Files found in this file will ALSO be customized.
|
||||||
|
customize_file(found_file_path, target_file_path)
|
||||||
|
|
||||||
|
file_text = file_text.replace(found_file_path, target_file_path)
|
||||||
|
|
||||||
|
result = RegEx.create_from_string(file_regex).search(file_text)
|
||||||
|
|
||||||
|
file = FileAccess.open(target_file, FileAccess.WRITE)
|
||||||
|
file.store_string(file_text)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
return target_file
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region INSPECTOR FIELDS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func setup_script_property_edit_node(property_info: Dictionary, value:Variant, property_changed:Callable) -> Control:
|
||||||
|
var input: Control = null
|
||||||
|
match property_info['type']:
|
||||||
|
TYPE_BOOL:
|
||||||
|
input = CheckBox.new()
|
||||||
|
if value != null:
|
||||||
|
input.button_pressed = value
|
||||||
|
input.toggled.connect(DialogicUtil._on_export_bool_submitted.bind(property_info.name, property_changed))
|
||||||
|
TYPE_COLOR:
|
||||||
|
input = ColorPickerButton.new()
|
||||||
|
if value != null:
|
||||||
|
input.color = value
|
||||||
|
input.color_changed.connect(DialogicUtil._on_export_color_submitted.bind(property_info.name, property_changed))
|
||||||
|
input.custom_minimum_size.x = get_editor_scale() * 50
|
||||||
|
TYPE_INT:
|
||||||
|
if property_info['hint'] & PROPERTY_HINT_ENUM:
|
||||||
|
input = OptionButton.new()
|
||||||
|
for x in property_info['hint_string'].split(','):
|
||||||
|
input.add_item(x.split(':')[0])
|
||||||
|
if value != null:
|
||||||
|
input.select(value)
|
||||||
|
input.item_selected.connect(DialogicUtil._on_export_int_enum_submitted.bind(property_info.name, property_changed))
|
||||||
|
else:
|
||||||
|
input = SpinBox.new()
|
||||||
|
input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
|
||||||
|
if property_info.hint_string == 'int':
|
||||||
|
input.step = 1
|
||||||
|
input.allow_greater = true
|
||||||
|
input.allow_lesser = true
|
||||||
|
elif ',' in property_info.hint_string:
|
||||||
|
input.min_value = int(property_info.hint_string.get_slice(',', 0))
|
||||||
|
input.max_value = int(property_info.hint_string.get_slice(',', 1))
|
||||||
|
if property_info.hint_string.count(',') > 1:
|
||||||
|
input.step = int(property_info.hint_string.get_slice(',', 2))
|
||||||
|
if value != null:
|
||||||
|
input.value = value
|
||||||
|
TYPE_FLOAT:
|
||||||
|
input = SpinBox.new()
|
||||||
|
input.step = 0.01
|
||||||
|
if ',' in property_info.hint_string:
|
||||||
|
input.min_value = float(property_info.hint_string.get_slice(',', 0))
|
||||||
|
input.max_value = float(property_info.hint_string.get_slice(',', 1))
|
||||||
|
if property_info.hint_string.count(',') > 1:
|
||||||
|
input.step = float(property_info.hint_string.get_slice(',', 2))
|
||||||
|
input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
|
||||||
|
if value != null:
|
||||||
|
input.value = value
|
||||||
|
TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4:
|
||||||
|
var vectorSize: String = type_string(typeof(value))[-1]
|
||||||
|
input = load("res://addons/dialogic/Editor/Events/Fields/field_vector" + vectorSize + ".tscn").instantiate()
|
||||||
|
input.property_name = property_info['name']
|
||||||
|
input.set_value(value)
|
||||||
|
input.value_changed.connect(DialogicUtil._on_export_vector_submitted.bind(property_changed))
|
||||||
|
TYPE_STRING:
|
||||||
|
if property_info['hint'] & PROPERTY_HINT_FILE or property_info['hint'] & PROPERTY_HINT_DIR:
|
||||||
|
input = load("res://addons/dialogic/Editor/Events/Fields/field_file.tscn").instantiate()
|
||||||
|
input.file_filter = property_info['hint_string']
|
||||||
|
input.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||||
|
if property_info['hint'] == PROPERTY_HINT_DIR:
|
||||||
|
input.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||||
|
input.property_name = property_info['name']
|
||||||
|
input.placeholder = "Default"
|
||||||
|
input.hide_reset = true
|
||||||
|
if value != null:
|
||||||
|
input.set_value(value)
|
||||||
|
input.value_changed.connect(DialogicUtil._on_export_file_submitted.bind(property_changed))
|
||||||
|
elif property_info['hint'] & PROPERTY_HINT_ENUM:
|
||||||
|
input = OptionButton.new()
|
||||||
|
var options: PackedStringArray = []
|
||||||
|
for x in property_info['hint_string'].split(','):
|
||||||
|
options.append(x.split(':')[0].strip_edges())
|
||||||
|
input.add_item(options[-1])
|
||||||
|
if value != null:
|
||||||
|
input.select(options.find(value))
|
||||||
|
input.item_selected.connect(DialogicUtil._on_export_string_enum_submitted.bind(property_info.name, options, property_changed))
|
||||||
|
else:
|
||||||
|
input = LineEdit.new()
|
||||||
|
if value != null:
|
||||||
|
input.text = value
|
||||||
|
input.text_submitted.connect(DialogicUtil._on_export_input_text_submitted.bind(property_info.name, property_changed))
|
||||||
|
TYPE_DICTIONARY:
|
||||||
|
input = load("res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn").instantiate()
|
||||||
|
input.property_name = property_info["name"]
|
||||||
|
input.value_changed.connect(_on_export_dict_submitted.bind(property_changed))
|
||||||
|
TYPE_OBJECT:
|
||||||
|
input = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
|
||||||
|
input.hint_text = "Objects/Resources as settings are currently not supported. \nUse @export_file('*.extension') instead and load the resource once needed."
|
||||||
|
|
||||||
|
_:
|
||||||
|
input = LineEdit.new()
|
||||||
|
if value != null:
|
||||||
|
input.text = value
|
||||||
|
input.text_submitted.connect(_on_export_input_text_submitted.bind(property_info.name, property_changed))
|
||||||
|
return input
|
||||||
|
|
||||||
|
|
||||||
|
static func _on_export_input_text_submitted(text:String, property_name:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(text))
|
||||||
|
|
||||||
|
static func _on_export_bool_submitted(value:bool, property_name:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(value))
|
||||||
|
|
||||||
|
static func _on_export_color_submitted(color:Color, property_name:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(color))
|
||||||
|
|
||||||
|
static func _on_export_int_enum_submitted(item:int, property_name:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(item))
|
||||||
|
|
||||||
|
static func _on_export_number_submitted(value:float, property_name:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(value))
|
||||||
|
|
||||||
|
static func _on_export_file_submitted(property_name:String, value:String, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(value))
|
||||||
|
|
||||||
|
static func _on_export_string_enum_submitted(value:int, property_name:String, list:PackedStringArray, callable: Callable):
|
||||||
|
callable.call(property_name, var_to_str(list[value]))
|
||||||
|
|
||||||
|
static func _on_export_vector_submitted(property_name:String, value:Variant, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(value))
|
||||||
|
|
||||||
|
static func _on_export_dict_submitted(property_name:String, value:Variant, callable: Callable) -> void:
|
||||||
|
callable.call(property_name, var_to_str(value))
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT DEFAULTS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func get_custom_event_defaults(event_name:String) -> Dictionary:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return ProjectSettings.get_setting('dialogic/event_default_overrides', {}).get(event_name, {})
|
||||||
|
else:
|
||||||
|
if !Engine.get_main_loop().has_meta('dialogic_event_defaults'):
|
||||||
|
Engine.get_main_loop().set_meta('dialogic_event_defaults', ProjectSettings.get_setting('dialogic/event_default_overrides', {}))
|
||||||
|
return Engine.get_main_loop().get_meta('dialogic_event_defaults').get(event_name, {})
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region CONVERSION
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
static func str_to_bool(boolstring:String) -> bool:
|
||||||
|
return true if boolstring == "true" else false
|
||||||
|
|
||||||
|
|
||||||
|
static func logical_convert(value:Variant) -> Variant:
|
||||||
|
if typeof(value) == TYPE_STRING:
|
||||||
|
if value.is_valid_int():
|
||||||
|
return value.to_int()
|
||||||
|
if value.is_valid_float():
|
||||||
|
return value.to_float()
|
||||||
|
if value == 'true':
|
||||||
|
return true
|
||||||
|
if value == 'false':
|
||||||
|
return false
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
## Takes [param source] and builds a dictionary of keys only.
|
||||||
|
## The values are `null`.
|
||||||
|
static func str_to_hash_set(source: String) -> Dictionary:
|
||||||
|
var dictionary := Dictionary()
|
||||||
|
|
||||||
|
for character in source:
|
||||||
|
dictionary[character] = null
|
||||||
|
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
static func get_character_suggestions(_search_text:String, current_value:DialogicCharacter = null, allow_none := true, allow_all:= false, editor_node:Node = null) -> Dictionary:
|
||||||
|
var suggestions := {}
|
||||||
|
|
||||||
|
var icon := load("res://addons/dialogic/Editor/Images/Resources/character.svg")
|
||||||
|
|
||||||
|
if allow_none and current_value:
|
||||||
|
suggestions['(No one)'] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
|
||||||
|
|
||||||
|
if allow_all:
|
||||||
|
suggestions['ALL'] = {'value':'--All--', 'tooltip':'All currently joined characters leave', 'editor_icon':["GuiEllipsis", "EditorIcons"]}
|
||||||
|
|
||||||
|
# Get characters in the current timeline and place them at the top of suggestions.
|
||||||
|
if editor_node:
|
||||||
|
var recent_characters := []
|
||||||
|
var timeline_node := editor_node.get_parent().find_parent("Timeline") as DialogicEditor
|
||||||
|
for event_node in timeline_node.find_child("Timeline").get_children():
|
||||||
|
if event_node == editor_node:
|
||||||
|
break
|
||||||
|
if event_node.resource is DialogicCharacterEvent or event_node.resource is DialogicTextEvent:
|
||||||
|
recent_characters.append(event_node.resource.character)
|
||||||
|
|
||||||
|
recent_characters.reverse()
|
||||||
|
for character in recent_characters:
|
||||||
|
if character and not character.get_character_name() in suggestions:
|
||||||
|
suggestions[character.get_character_name()] = {'value': character.get_character_name(), 'tooltip': character.resource_path, 'icon': icon.duplicate()}
|
||||||
|
|
||||||
|
var character_directory := DialogicResourceUtil.get_character_directory()
|
||||||
|
for resource in character_directory.keys():
|
||||||
|
suggestions[resource] = {'value': resource, 'tooltip': character_directory[resource], 'icon': icon}
|
||||||
|
|
||||||
|
return suggestions
|
||||||
|
|
||||||
|
|
||||||
|
static func get_portrait_suggestions(search_text:String, character:DialogicCharacter, allow_empty := false, empty_text := "Don't Change") -> Dictionary:
|
||||||
|
var icon := load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
|
||||||
|
var suggestions := {}
|
||||||
|
|
||||||
|
if allow_empty:
|
||||||
|
suggestions[empty_text] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
|
||||||
|
|
||||||
|
if "{" in search_text:
|
||||||
|
suggestions[search_text] = {'value':search_text, 'editor_icon':["Variant", "EditorIcons"]}
|
||||||
|
|
||||||
|
if character != null:
|
||||||
|
for portrait in character.portraits:
|
||||||
|
suggestions[portrait] = {'value':portrait, 'icon':icon}
|
||||||
|
|
||||||
|
return suggestions
|
||||||
|
|
||||||
|
|
||||||
|
static func get_portrait_position_suggestions(search_text := "") -> Dictionary:
|
||||||
|
var icon := load(DialogicUtil.get_module_path("Character").path_join('portrait_position.svg'))
|
||||||
|
|
||||||
|
var setting: String = ProjectSettings.get_setting('dialogic/portraits/position_suggestion_names', 'leftmost, left, center, right, rightmost')
|
||||||
|
|
||||||
|
var suggestions := {}
|
||||||
|
|
||||||
|
if not search_text.is_empty():
|
||||||
|
suggestions[search_text] = {'value':search_text.strip_edges(), 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
|
||||||
|
|
||||||
|
for position_id in setting.split(','):
|
||||||
|
suggestions[position_id.strip_edges()] = {'value':position_id.strip_edges(), 'icon':icon}
|
||||||
|
if not search_text.is_empty() and position_id.strip_edges().begins_with(search_text):
|
||||||
|
suggestions.erase(search_text)
|
||||||
|
|
||||||
|
return suggestions
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bomsqbbajkvag
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
class_name DialogicSubsystem
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var dialogic: DialogicGameHandler = null
|
||||||
|
|
||||||
|
enum LoadFlags {FULL_LOAD, ONLY_DNODES}
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
# Called once after every subsystem has been added to the tree
|
||||||
|
func post_install() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
# Fill in everything that should be cleared (for example before loading a different state)
|
||||||
|
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
# Fill in everything that should be loaded using the dialogic_game_handler.current_state_info
|
||||||
|
# This is called when a save is loaded
|
||||||
|
func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
# Fill in everything that should be saved into the dialogic_game_handler.current_state_info
|
||||||
|
# This is called when a save is saved
|
||||||
|
func save_game_state() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
func pause() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# To be overriden by sub-classes
|
||||||
|
func resume() -> void:
|
||||||
|
pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://vqjpqkl5rmom
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
@tool
|
||||||
|
class_name DialogicIndexer
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Script that indexes events, subsystems, settings pages and more. [br]
|
||||||
|
## Place a script of this type in every folder in "addons/Events". [br]
|
||||||
|
## Overwrite the methods to return the contents of that folder.
|
||||||
|
|
||||||
|
|
||||||
|
var this_folder: String = get_script().resource_path.get_base_dir()
|
||||||
|
|
||||||
|
## Overwrite if this module contains any events. [br]
|
||||||
|
## Return an array with all the paths to the event scripts.[br]
|
||||||
|
## You can use the [property this_folder].path_join('my_event.gd')
|
||||||
|
func _get_events() -> Array:
|
||||||
|
if ResourceLoader.exists(this_folder.path_join('event.gd')):
|
||||||
|
return [this_folder.path_join('event.gd')]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite if this module contains any subsystems.
|
||||||
|
## Should return an array of dictionaries each with the following keys: [br]
|
||||||
|
## "name" -> name for this subsystem[br]
|
||||||
|
## "script" -> array of preview images[br]
|
||||||
|
func _get_subsystems() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
func _get_editors() -> Array[String]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
func _get_settings_pages() -> Array:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
func _get_character_editor_sections() -> Array:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
#region TEXT EFFECTS & MODIFIERS
|
||||||
|
|
||||||
|
## Should return array of dictionaries with the following keys:[br]
|
||||||
|
## "command" -> the text e.g. "speed"[br]
|
||||||
|
## "node_path" or "subsystem" -> whichever contains your effect method[br]
|
||||||
|
## "method" -> name of the effect method[br]
|
||||||
|
func _get_text_effects() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
## Should return array of dictionaries with the same arguments as _get_text_effects()
|
||||||
|
func _get_text_modifiers() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
## Return a list of resources, scripts, etc.
|
||||||
|
## These can later be retrieved with DialogicResourceUtil.
|
||||||
|
## Each dictionary should contain (at least "type" and "path").
|
||||||
|
## E.g. {"type":"Animation", "path": "res://..."}
|
||||||
|
func _get_special_resources() -> Dictionary:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
## Return a list of dictionaries, each
|
||||||
|
func _get_portrait_scene_presets() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
#region HELPERS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
func list_dir(subdir:='') -> Array:
|
||||||
|
return Array(DirAccess.get_files_at(this_folder.path_join(subdir))).map(func(file):return this_folder.path_join(subdir).path_join(file))
|
||||||
|
|
||||||
|
|
||||||
|
func list_special_resources(subdir:='', extension:="") -> Dictionary:
|
||||||
|
var dict := {}
|
||||||
|
for i in list_dir(subdir):
|
||||||
|
if extension.is_empty() or i.ends_with(extension):
|
||||||
|
dict[DialogicUtil.pretty_name(i).to_lower()] = {"path":i}
|
||||||
|
return dict
|
||||||
|
|
||||||
|
|
||||||
|
func list_animations(subdir := "") -> Dictionary:
|
||||||
|
var full_animation_list := {}
|
||||||
|
for path in list_dir(subdir):
|
||||||
|
if not path.ends_with(".gd") and not path.ends_with(".gdc"):
|
||||||
|
continue
|
||||||
|
var anim_object: DialogicAnimation = load(path).new()
|
||||||
|
var versions := anim_object._get_named_variations()
|
||||||
|
for version_name in versions:
|
||||||
|
full_animation_list[version_name] = versions[version_name]
|
||||||
|
full_animation_list[version_name]["path"] = path
|
||||||
|
anim_object.queue_free()
|
||||||
|
return full_animation_list
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region STYLES & LAYOUTS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
func _get_style_presets() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
## Should return an array of dictionaries with the following keys:[br]
|
||||||
|
## "path" -> the path to the scene[br]
|
||||||
|
## "name" -> name for this layout[br]
|
||||||
|
## "description"-> description of this layout. list what features/events are supported[br]
|
||||||
|
## "preview_image"-> array of preview images[br]
|
||||||
|
func _get_layout_parts() -> Array[Dictionary]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
## Helper that allows scanning sub directories that might be layout parts or styles
|
||||||
|
func scan_for_layout_parts() -> Array[Dictionary]:
|
||||||
|
var dir := DirAccess.open(this_folder)
|
||||||
|
var style_list: Array[Dictionary] = []
|
||||||
|
if !dir:
|
||||||
|
return style_list
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var dir_name := dir.get_next()
|
||||||
|
while dir_name != "":
|
||||||
|
if !dir.current_is_dir() or !dir.file_exists(dir_name.path_join('part_config.cfg')):
|
||||||
|
dir_name = dir.get_next()
|
||||||
|
continue
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
config.load(this_folder.path_join(dir_name).path_join('part_config.cfg'))
|
||||||
|
var default_image_path: String = this_folder.path_join(dir_name).path_join('preview.png')
|
||||||
|
style_list.append(
|
||||||
|
{
|
||||||
|
'type': config.get_value('style', 'type', 'Unknown type'),
|
||||||
|
'name': config.get_value('style', 'name', 'Unnamed Layout'),
|
||||||
|
'path': this_folder.path_join(dir_name).path_join(config.get_value('style', 'scene', '')),
|
||||||
|
'author': config.get_value('style', 'author', 'Anonymous'),
|
||||||
|
'description': config.get_value('style', 'description', 'No description'),
|
||||||
|
'preview_image': [config.get_value('style', 'image', default_image_path)],
|
||||||
|
'style_path':config.get_value('style', 'style_path', ''),
|
||||||
|
'icon':this_folder.path_join(dir_name).path_join(config.get_value('style', 'icon', '')),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not style_list[-1].style_path.begins_with('res://'):
|
||||||
|
style_list[-1].style_path = this_folder.path_join(dir_name).path_join(style_list[-1].style_path)
|
||||||
|
|
||||||
|
dir_name = dir.get_next()
|
||||||
|
|
||||||
|
return style_list
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://qn2wgcosejiw
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorPortraitSection
|
||||||
|
|
||||||
|
## Section that allows setting values of exported scene variables
|
||||||
|
## for custom portrait scenes
|
||||||
|
|
||||||
|
var current_portrait_data := {}
|
||||||
|
var last_scene := ""
|
||||||
|
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "Settings"
|
||||||
|
|
||||||
|
|
||||||
|
func _load_portrait_data(data:Dictionary) -> void:
|
||||||
|
_recheck(data, true)
|
||||||
|
|
||||||
|
|
||||||
|
## Recheck section visibility and reload export fields.
|
||||||
|
## This allows reacting to changes of the portrait_scene setting.
|
||||||
|
func _recheck(data: Dictionary, force:=false):
|
||||||
|
if last_scene == data.get("scene", "") and not force:
|
||||||
|
current_portrait_data = data
|
||||||
|
last_scene = data.get("scene", "")
|
||||||
|
return
|
||||||
|
|
||||||
|
last_scene = data.get("scene", "")
|
||||||
|
current_portrait_data = data
|
||||||
|
|
||||||
|
for child in $Grid.get_children():
|
||||||
|
child.get_parent().remove_child(child)
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
var scene: Variant = null
|
||||||
|
|
||||||
|
if current_portrait_data.get('scene', '').is_empty():
|
||||||
|
if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
|
||||||
|
scene = load(character_editor.def_portrait_path)
|
||||||
|
else:
|
||||||
|
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
|
||||||
|
else:
|
||||||
|
scene = load(current_portrait_data.get('scene'))
|
||||||
|
|
||||||
|
if not scene:
|
||||||
|
return
|
||||||
|
|
||||||
|
scene = scene.instantiate()
|
||||||
|
|
||||||
|
var skip := false
|
||||||
|
for i in scene.script.get_script_property_list():
|
||||||
|
if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
|
||||||
|
var label := Label.new()
|
||||||
|
label.text = i['name'].capitalize()
|
||||||
|
$Grid.add_child(label)
|
||||||
|
|
||||||
|
var current_value: Variant = scene.get(i['name'])
|
||||||
|
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
|
||||||
|
current_value = str_to_var(current_portrait_data.export_overrides[i['name']])
|
||||||
|
if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
|
||||||
|
current_value = current_portrait_data['export_overrides'][i['name']]
|
||||||
|
|
||||||
|
var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
|
||||||
|
input.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
$Grid.add_child(input)
|
||||||
|
|
||||||
|
if i['usage'] & PROPERTY_USAGE_GROUP:
|
||||||
|
if i['name'] == 'Main' or i["name"] == "Private":
|
||||||
|
skip = true
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
skip = false
|
||||||
|
|
||||||
|
if $Grid.get_child_count():
|
||||||
|
get_parent().get_child(get_index()-1).show()
|
||||||
|
show()
|
||||||
|
else:
|
||||||
|
hide()
|
||||||
|
get_parent().get_child(get_index()-1).hide()
|
||||||
|
get_parent().get_child(get_index()+1).hide()
|
||||||
|
|
||||||
|
|
||||||
|
## On any change, save the export override to the portrait items metadata.
|
||||||
|
func set_export_override(property_name:String, value:String = "") -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
if !data.has('export_overrides'):
|
||||||
|
data['export_overrides'] = {}
|
||||||
|
if !value.is_empty():
|
||||||
|
data.export_overrides[property_name] = value
|
||||||
|
else:
|
||||||
|
data.export_overrides.erase(property_name)
|
||||||
|
changed.emit()
|
||||||
|
update_preview.emit()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bipo5hdfv3epn
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cfcs7lb6gqnmd"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bipo5hdfv3epn" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd" id="1_isys8"]
|
||||||
|
|
||||||
|
[node name="Settings" type="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 35)
|
||||||
|
offset_right = 367.0
|
||||||
|
offset_bottom = 82.0
|
||||||
|
script = ExtResource("1_isys8")
|
||||||
|
|
||||||
|
[node name="Grid" type="GridContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/h_separation = 10
|
||||||
|
columns = 2
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorPortraitSection
|
||||||
|
|
||||||
|
## Tab that allows setting size, offset and mirror of a portrait.
|
||||||
|
|
||||||
|
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "Scale, Offset & Mirror"
|
||||||
|
|
||||||
|
|
||||||
|
func _load_portrait_data(data:Dictionary) -> void:
|
||||||
|
%IgnoreScale.set_pressed_no_signal(data.get('ignore_char_scale', false))
|
||||||
|
%PortraitScale.value = data.get('scale', 1.0)*100
|
||||||
|
%PortraitOffset.set_value(data.get('offset', Vector2()))
|
||||||
|
%PortraitOffset._load_display_info({'step':1})
|
||||||
|
%PortraitMirror.set_pressed_no_signal(data.get('mirror', false))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_portrait_scale_value_changed(value:float) -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
data['scale'] = value/100.0
|
||||||
|
update_preview.emit()
|
||||||
|
changed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_portrait_mirror_toggled(button_pressed:bool)-> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
data['mirror'] = button_pressed
|
||||||
|
update_preview.emit()
|
||||||
|
changed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ignore_scale_toggled(button_pressed:bool) -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
data['ignore_char_scale'] = button_pressed
|
||||||
|
update_preview.emit()
|
||||||
|
changed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_portrait_offset_value_changed(property:String, value:Vector2) -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
data['offset'] = value
|
||||||
|
update_preview.emit()
|
||||||
|
changed.emit()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://we5rp2oyvypl
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://crke8suvv52c6"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://we5rp2oyvypl" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd" id="1_76vf2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="2_c8kyi"]
|
||||||
|
|
||||||
|
[node name="Layout" type="HFlowContainer"]
|
||||||
|
offset_right = 428.0
|
||||||
|
offset_bottom = 128.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("1_76vf2")
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Ignore Main Scale: "
|
||||||
|
|
||||||
|
[node name="IgnoreScale" type="CheckBox" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "This portrait will ignore the main scale."
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Scale:"
|
||||||
|
|
||||||
|
[node name="PortraitScale" type="SpinBox" parent="HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "A scale to be applied on top of the main scale
|
||||||
|
(unless ignore main scale is pressed)."
|
||||||
|
value = 100.0
|
||||||
|
allow_greater = true
|
||||||
|
suffix = "%"
|
||||||
|
|
||||||
|
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HBoxContainer2"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Offset:"
|
||||||
|
|
||||||
|
[node name="PortraitOffset" parent="HBoxContainer2" instance=ExtResource("2_c8kyi")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Offset that is applied on top of the main portrait offset."
|
||||||
|
|
||||||
|
[node name="MirrorOption" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MirrorOption"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Mirror:"
|
||||||
|
|
||||||
|
[node name="PortraitMirror" type="CheckBox" parent="MirrorOption"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Mirroring that is applied on top of the main portrait mirror."
|
||||||
|
|
||||||
|
[connection signal="toggled" from="IgnoreScale" to="." method="_on_ignore_scale_toggled"]
|
||||||
|
[connection signal="value_changed" from="HBoxContainer/PortraitScale" to="." method="_on_portrait_scale_value_changed"]
|
||||||
|
[connection signal="value_changed" from="HBoxContainer2/PortraitOffset" to="." method="_on_portrait_offset_value_changed"]
|
||||||
|
[connection signal="toggled" from="MirrorOption/PortraitMirror" to="." method="_on_portrait_mirror_toggled"]
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorPortraitSection
|
||||||
|
|
||||||
|
## Tab that allows setting a custom scene for a portrait.
|
||||||
|
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "Scene"
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
hint_text = "You can use a custom scene for this portrait."
|
||||||
|
|
||||||
|
func _start_opened() -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
%ChangeSceneButton.icon = get_theme_icon("Loop", "EditorIcons")
|
||||||
|
%ScenePicker.file_filter = "*.tscn, *.scn; Scenes"
|
||||||
|
%ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons')
|
||||||
|
%ScenePicker.placeholder = 'Default scene'
|
||||||
|
|
||||||
|
%OpenSceneButton.icon = get_theme_icon("ExternalLink", "EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
func _load_portrait_data(data:Dictionary) -> void:
|
||||||
|
reload_ui(data)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_open_scene_button_pressed() -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
if ResourceLoader.exists(data.get("scene", "")):
|
||||||
|
DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(data.get("scene", ""))
|
||||||
|
await get_tree().process_frame
|
||||||
|
EditorInterface.set_main_screen_editor("2D")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_change_scene_button_pressed() -> void:
|
||||||
|
%PortraitSceneBrowserWindow.popup_centered_ratio(0.6)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_portrait_scene_browser_activate_part(part_info: Dictionary) -> void:
|
||||||
|
%PortraitSceneBrowserWindow.hide()
|
||||||
|
match part_info.type:
|
||||||
|
"General":
|
||||||
|
set_scene_path(part_info.path)
|
||||||
|
"Preset":
|
||||||
|
find_parent("EditorView").godot_file_dialog(
|
||||||
|
create_new_portrait_scene.bind(part_info),
|
||||||
|
'*.tscn,*.scn',
|
||||||
|
EditorFileDialog.FILE_MODE_SAVE_FILE,
|
||||||
|
"Select where to save the new scene",
|
||||||
|
part_info.path.get_file().trim_suffix("."+part_info.path.get_extension())+"_"+character_editor.current_resource.get_character_name().to_lower())
|
||||||
|
"Custom":
|
||||||
|
find_parent("EditorView").godot_file_dialog(
|
||||||
|
set_scene_path,
|
||||||
|
'*.tscn, *.scn',
|
||||||
|
EditorFileDialog.FILE_MODE_OPEN_FILE,
|
||||||
|
"Select custom portrait scene",)
|
||||||
|
"Default":
|
||||||
|
set_scene_path("")
|
||||||
|
|
||||||
|
|
||||||
|
func create_new_portrait_scene(target_file: String, info: Dictionary) -> void:
|
||||||
|
var path := make_portrait_preset_custom(target_file, info)
|
||||||
|
set_scene_path(path)
|
||||||
|
|
||||||
|
|
||||||
|
func make_portrait_preset_custom(target_file:String, info: Dictionary) -> String:
|
||||||
|
var previous_file: String = info.path
|
||||||
|
|
||||||
|
var result_path := DialogicUtil.make_file_custom(previous_file, target_file.get_base_dir(), target_file.get_file())
|
||||||
|
|
||||||
|
return result_path
|
||||||
|
|
||||||
|
|
||||||
|
func set_scene_path(path:String) -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
data['scene'] = path
|
||||||
|
update_preview.emit()
|
||||||
|
changed.emit()
|
||||||
|
reload_ui(data)
|
||||||
|
|
||||||
|
|
||||||
|
func reload_ui(data: Dictionary) -> void:
|
||||||
|
var path: String = data.get('scene', '')
|
||||||
|
%OpenSceneButton.hide()
|
||||||
|
|
||||||
|
if path.is_empty():
|
||||||
|
%SceneLabel.text = "Default Portrait Scene"
|
||||||
|
%SceneLabel.tooltip_text = "Can be changed in the settings."
|
||||||
|
%SceneLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
|
||||||
|
|
||||||
|
elif %PortraitSceneBrowser.is_premade_portrait_scene(path):
|
||||||
|
%SceneLabel.text = %PortraitSceneBrowser.portrait_scenes_info[path].name
|
||||||
|
%SceneLabel.tooltip_text = path
|
||||||
|
%SceneLabel.add_theme_color_override("font_color", get_theme_color("accent_color", "Editor"))
|
||||||
|
|
||||||
|
else:
|
||||||
|
%SceneLabel.text = path.get_file()
|
||||||
|
%SceneLabel.tooltip_text = path
|
||||||
|
%SceneLabel.add_theme_color_override("font_color", get_theme_color("property_color_x", "Editor"))
|
||||||
|
%OpenSceneButton.show()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cgaygwgc73gbp
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
[gd_scene load_steps=6 format=3 uid="uid://djq4aasoihexj"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cgaygwgc73gbp" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b1wn8r84uh11b" path="res://addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn" id="3_ngvgq"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_m6kd3"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_f5xt2"]
|
||||||
|
image = SubResource("Image_m6kd3")
|
||||||
|
|
||||||
|
[node name="Scene" type="GridContainer"]
|
||||||
|
offset_right = 298.0
|
||||||
|
offset_bottom = 86.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("1_ht8lu")
|
||||||
|
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="ChangeSceneButton" type="Button" parent="HBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Change Scene"
|
||||||
|
icon = SubResource("ImageTexture_f5xt2")
|
||||||
|
|
||||||
|
[node name="SceneLabel" type="Label" parent="HBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 0
|
||||||
|
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||||
|
text = "asdsdasdasd"
|
||||||
|
clip_text = true
|
||||||
|
|
||||||
|
[node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
file_filter = "*.tscn, *.scn; Scenes"
|
||||||
|
placeholder = "Default scene"
|
||||||
|
|
||||||
|
[node name="OpenSceneButton" type="Button" parent="HBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Open/Edit Scene"
|
||||||
|
icon = SubResource("ImageTexture_f5xt2")
|
||||||
|
|
||||||
|
[node name="PortraitSceneBrowserWindow" type="Window" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
title = "Portrait Scene Browser"
|
||||||
|
position = Vector2i(0, 36)
|
||||||
|
visible = false
|
||||||
|
wrap_controls = true
|
||||||
|
transient = true
|
||||||
|
popup_window = true
|
||||||
|
|
||||||
|
[node name="PortraitSceneBrowser" parent="PortraitSceneBrowserWindow" instance=ExtResource("3_ngvgq")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[connection signal="pressed" from="HBox/ChangeSceneButton" to="." method="_on_change_scene_button_pressed"]
|
||||||
|
[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"]
|
||||||
|
[connection signal="activate_part" from="PortraitSceneBrowserWindow/PortraitSceneBrowser" to="." method="_on_portrait_scene_browser_activate_part"]
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorPortraitSection
|
||||||
|
|
||||||
|
## Portrait Settings Section that only shows the MAIN settings of a portrait scene.
|
||||||
|
|
||||||
|
var current_portrait_data := {}
|
||||||
|
var last_scene := ""
|
||||||
|
|
||||||
|
func _show_title() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _load_portrait_data(data:Dictionary) -> void:
|
||||||
|
_recheck(data, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _recheck(data:Dictionary, force := false) -> void:
|
||||||
|
get_parent().get_child(get_index()+1).hide()
|
||||||
|
if last_scene == data.get("scene", "") and not force:
|
||||||
|
current_portrait_data = data
|
||||||
|
last_scene = data.get("scene", "")
|
||||||
|
return
|
||||||
|
|
||||||
|
last_scene = data.get("scene", "")
|
||||||
|
current_portrait_data = data
|
||||||
|
|
||||||
|
load_portrait_scene_export_variables()
|
||||||
|
|
||||||
|
|
||||||
|
func load_portrait_scene_export_variables() -> void:
|
||||||
|
for child in $Grid.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
var scene: Variant = null
|
||||||
|
if current_portrait_data.get('scene', '').is_empty():
|
||||||
|
if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
|
||||||
|
scene = load(character_editor.def_portrait_path)
|
||||||
|
else:
|
||||||
|
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
|
||||||
|
else:
|
||||||
|
scene = load(current_portrait_data.get('scene'))
|
||||||
|
|
||||||
|
if not scene:
|
||||||
|
return
|
||||||
|
|
||||||
|
scene = scene.instantiate()
|
||||||
|
var skip := true
|
||||||
|
for i in scene.script.get_script_property_list():
|
||||||
|
if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
|
||||||
|
var label := Label.new()
|
||||||
|
label.text = i['name'].capitalize()
|
||||||
|
$Grid.add_child(label)
|
||||||
|
|
||||||
|
var current_value: Variant = scene.get(i['name'])
|
||||||
|
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
|
||||||
|
current_value = str_to_var(current_portrait_data['export_overrides'][i['name']])
|
||||||
|
if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
|
||||||
|
current_value = current_portrait_data['export_overrides'][i['name']]
|
||||||
|
|
||||||
|
var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
|
||||||
|
input.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||||
|
$Grid.add_child(input)
|
||||||
|
|
||||||
|
if i['usage'] & PROPERTY_USAGE_GROUP:
|
||||||
|
if i['name'] == 'Main':
|
||||||
|
skip = false
|
||||||
|
else:
|
||||||
|
skip = true
|
||||||
|
continue
|
||||||
|
|
||||||
|
func set_export_override(property_name:String, value:String = "") -> void:
|
||||||
|
var data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
if !data.has('export_overrides'):
|
||||||
|
data['export_overrides'] = {}
|
||||||
|
if !value.is_empty():
|
||||||
|
data['export_overrides'][property_name] = value
|
||||||
|
else:
|
||||||
|
data['export_overrides'].erase(property_name)
|
||||||
|
changed.emit()
|
||||||
|
update_preview.emit()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bj2w8wy64e87i
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://ba5w02lm3ewkj"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bj2w8wy64e87i" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd" id="1_mttrr"]
|
||||||
|
|
||||||
|
[node name="MainExports" type="VBoxContainer"]
|
||||||
|
offset_right = 374.0
|
||||||
|
offset_bottom = 82.0
|
||||||
|
script = ExtResource("1_mttrr")
|
||||||
|
|
||||||
|
[node name="Grid" type="GridContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/h_separation = 10
|
||||||
|
columns = 2
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorMainSection
|
||||||
|
|
||||||
|
var min_width := 200
|
||||||
|
|
||||||
|
## The general character settings tab
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "General"
|
||||||
|
|
||||||
|
|
||||||
|
func _start_opened() -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Connecting all necessary signals
|
||||||
|
%ColorPickerButton.custom_minimum_size.x = DialogicUtil.get_editor_scale() * 30
|
||||||
|
%ColorPickerButton.color_changed.connect(character_editor.something_changed)
|
||||||
|
%DisplayNameLineEdit.text_changed.connect(character_editor.something_changed)
|
||||||
|
%NicknameLineEdit.text_changed.connect(character_editor.something_changed)
|
||||||
|
%DescriptionTextEdit.text_changed.connect(character_editor.something_changed)
|
||||||
|
min_width = get_minimum_size().x
|
||||||
|
resized.connect(_on_resized)
|
||||||
|
|
||||||
|
func _load_character(resource:DialogicCharacter) -> void:
|
||||||
|
%DisplayNameLineEdit.text = resource.display_name
|
||||||
|
%ColorPickerButton.color = resource.color
|
||||||
|
|
||||||
|
%NicknameLineEdit.text = ""
|
||||||
|
for nickname in resource.nicknames:
|
||||||
|
%NicknameLineEdit.text += nickname +", "
|
||||||
|
%NicknameLineEdit.text = %NicknameLineEdit.text.trim_suffix(', ')
|
||||||
|
|
||||||
|
%DescriptionTextEdit.text = resource.description
|
||||||
|
|
||||||
|
|
||||||
|
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
|
||||||
|
resource.display_name = %DisplayNameLineEdit.text
|
||||||
|
resource.color = %ColorPickerButton.color
|
||||||
|
var nicknames := []
|
||||||
|
for n_name in %NicknameLineEdit.text.split(','):
|
||||||
|
nicknames.append(n_name.strip_edges())
|
||||||
|
resource.nicknames = nicknames
|
||||||
|
resource.description = %DescriptionTextEdit.text
|
||||||
|
|
||||||
|
return resource
|
||||||
|
|
||||||
|
|
||||||
|
func _on_resized() -> void:
|
||||||
|
if size.x > min_width+20:
|
||||||
|
self.columns = 2
|
||||||
|
else:
|
||||||
|
self.columns = 1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cj21jlbij33mx
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://bnkck3hocbkk5"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cj21jlbij33mx" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd" id="1_3e1i1"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_cxfqm"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_ywoka"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_hx3oq"]
|
||||||
|
image = SubResource("Image_ywoka")
|
||||||
|
|
||||||
|
[node name="General" type="GridContainer"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 7.5
|
||||||
|
offset_top = 38.5
|
||||||
|
offset_right = -7.5
|
||||||
|
offset_bottom = -7.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/h_separation = 6
|
||||||
|
theme_override_constants/v_separation = 6
|
||||||
|
columns = 2
|
||||||
|
script = ExtResource("1_3e1i1")
|
||||||
|
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="HBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Display Name"
|
||||||
|
|
||||||
|
[node name="HintTooltip" parent="HBox" instance=ExtResource("2_cxfqm")]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
|
||||||
|
texture = SubResource("ImageTexture_hx3oq")
|
||||||
|
hint_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
|
||||||
|
|
||||||
|
[node name="DisplayName" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="DisplayNameLineEdit" type="LineEdit" parent="DisplayName"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
caret_blink = true
|
||||||
|
caret_blink_interval = 0.5
|
||||||
|
|
||||||
|
[node name="HintTooltip4" parent="DisplayName" instance=ExtResource("2_cxfqm")]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
|
||||||
|
texture = SubResource("ImageTexture_hx3oq")
|
||||||
|
hint_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
|
||||||
|
|
||||||
|
[node name="ColorPickerButton" type="ColorPickerButton" parent="DisplayName"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(30, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
color = Color(1, 1, 1, 1)
|
||||||
|
edit_alpha = false
|
||||||
|
|
||||||
|
[node name="HBox2" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="HBox2"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Nicknames"
|
||||||
|
|
||||||
|
[node name="HintTooltip2" parent="HBox2" instance=ExtResource("2_cxfqm")]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "If autocolor names is enabled, these will be colored in the characters color as well."
|
||||||
|
texture = SubResource("ImageTexture_hx3oq")
|
||||||
|
hint_text = "If autocolor names is enabled, these will be colored in the characters color as well."
|
||||||
|
|
||||||
|
[node name="NicknameLineEdit" type="LineEdit" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
caret_blink = true
|
||||||
|
caret_blink_interval = 0.5
|
||||||
|
|
||||||
|
[node name="HBox3" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
|
||||||
|
[node name="Label4" type="Label" parent="HBox3"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Description"
|
||||||
|
|
||||||
|
[node name="HintTooltip3" parent="HBox3" instance=ExtResource("2_cxfqm")]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "No effect, just for you."
|
||||||
|
texture = SubResource("ImageTexture_hx3oq")
|
||||||
|
hint_text = "No effect, just for you."
|
||||||
|
|
||||||
|
[node name="DescriptionTextEdit" type="TextEdit" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 65)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
wrap_mode = 1
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicCharacterEditorMainSection
|
||||||
|
|
||||||
|
## The general portrait settings section
|
||||||
|
|
||||||
|
var loading := false
|
||||||
|
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "Portraits"
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Connecting all necessary signals
|
||||||
|
%DefaultPortraitPicker.value_changed.connect(default_portrait_changed)
|
||||||
|
%MainScale.value_changed.connect(main_portrait_settings_update)
|
||||||
|
%MainOffset._load_display_info({'step':1})
|
||||||
|
%MainOffset.value_changed.connect(main_portrait_settings_update)
|
||||||
|
%MainMirror.toggled.connect(main_portrait_settings_update)
|
||||||
|
|
||||||
|
# Setting up Default Portrait Picker
|
||||||
|
%DefaultPortraitPicker.resource_icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
|
||||||
|
%DefaultPortraitPicker.get_suggestions_func = suggest_portraits
|
||||||
|
|
||||||
|
|
||||||
|
## Make sure preview get's updated when portrait settings change
|
||||||
|
func main_portrait_settings_update(_something=null, _value=null) -> void:
|
||||||
|
if loading:
|
||||||
|
return
|
||||||
|
character_editor.current_resource.scale = %MainScale.value/100.0
|
||||||
|
character_editor.current_resource.offset = %MainOffset.current_value
|
||||||
|
character_editor.current_resource.mirror = %MainMirror.button_pressed
|
||||||
|
character_editor.update_preview()
|
||||||
|
character_editor.something_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func default_portrait_changed(property:String, value:String) -> void:
|
||||||
|
character_editor.current_resource.default_portrait = value
|
||||||
|
character_editor.update_default_portrait_star(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_default_portrait(portrait_name:String) -> void:
|
||||||
|
%DefaultPortraitPicker.set_value(portrait_name)
|
||||||
|
default_portrait_changed("", portrait_name)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_character(resource:DialogicCharacter) -> void:
|
||||||
|
loading = true
|
||||||
|
%DefaultPortraitPicker.set_value(resource.default_portrait)
|
||||||
|
|
||||||
|
%MainScale.value = 100*resource.scale
|
||||||
|
%MainOffset.set_value(resource.offset)
|
||||||
|
%MainMirror.button_pressed = resource.mirror
|
||||||
|
loading = false
|
||||||
|
|
||||||
|
|
||||||
|
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
|
||||||
|
# Portrait settings
|
||||||
|
if %DefaultPortraitPicker.current_value in resource.portraits.keys():
|
||||||
|
resource.default_portrait = %DefaultPortraitPicker.current_value
|
||||||
|
elif !resource.portraits.is_empty():
|
||||||
|
resource.default_portrait = resource.portraits.keys()[0]
|
||||||
|
else:
|
||||||
|
resource.default_portrait = ""
|
||||||
|
|
||||||
|
resource.scale = %MainScale.value/100.0
|
||||||
|
resource.offset = %MainOffset.current_value
|
||||||
|
resource.mirror = %MainMirror.button_pressed
|
||||||
|
return resource
|
||||||
|
|
||||||
|
|
||||||
|
## Get suggestions for DefaultPortraitPicker
|
||||||
|
func suggest_portraits(search:String) -> Dictionary:
|
||||||
|
var suggestions := {}
|
||||||
|
for portrait in character_editor.get_updated_portrait_dict().keys():
|
||||||
|
suggestions[portrait] = {'value':portrait}
|
||||||
|
return suggestions
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://x5a7jcmpdwvn
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://cmrgbo8qi145o"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://x5a7jcmpdwvn" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd" id="1_6sxsl"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="2_birla"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="3_vcvin"]
|
||||||
|
|
||||||
|
[node name="Portraits" type="GridContainer"]
|
||||||
|
offset_right = 453.0
|
||||||
|
offset_bottom = 141.0
|
||||||
|
theme_override_constants/h_separation = 1
|
||||||
|
theme_override_constants/v_separation = 6
|
||||||
|
columns = 2
|
||||||
|
script = ExtResource("1_6sxsl")
|
||||||
|
|
||||||
|
[node name="Label5" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Default"
|
||||||
|
|
||||||
|
[node name="DefaultPortraitPicker" parent="." instance=ExtResource("2_birla")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
placeholder_text = "Select Default Portrait"
|
||||||
|
fit_text_length = false
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Main Scale"
|
||||||
|
|
||||||
|
[node name="MainScale" type="SpinBox" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
|
value = 100.0
|
||||||
|
allow_greater = true
|
||||||
|
alignment = 1
|
||||||
|
suffix = "%"
|
||||||
|
|
||||||
|
[node name="Label2" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Main Offset"
|
||||||
|
|
||||||
|
[node name="MainOffset" parent="." instance=ExtResource("3_vcvin")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Label3" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
text = "Main Mirror"
|
||||||
|
|
||||||
|
[node name="MainMirror" type="CheckBox" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 8
|
||||||
@@ -0,0 +1,688 @@
|
|||||||
|
@tool
|
||||||
|
extends DialogicEditor
|
||||||
|
|
||||||
|
## Editor for editing character resources.
|
||||||
|
|
||||||
|
signal character_loaded(resource_path:String)
|
||||||
|
signal portrait_selected()
|
||||||
|
|
||||||
|
|
||||||
|
# Current state
|
||||||
|
var loading := false
|
||||||
|
var current_previewed_scene: Variant = null
|
||||||
|
var current_scene_path: String = ""
|
||||||
|
|
||||||
|
# References
|
||||||
|
var selected_item: TreeItem
|
||||||
|
var def_portrait_path: String = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
|
||||||
|
|
||||||
|
|
||||||
|
######### EDITOR STUFF and LOADING/SAVING ######################################
|
||||||
|
|
||||||
|
#region Resource Logic
|
||||||
|
## Method is called once editors manager is ready to accept registers.
|
||||||
|
func _register() -> void:
|
||||||
|
## Makes the editor open this when a .dch file is selected.
|
||||||
|
## Then _open_resource() is called.
|
||||||
|
editors_manager.register_resource_editor("dch", self)
|
||||||
|
|
||||||
|
## Add an "add character" button
|
||||||
|
var add_character_button: Button = editors_manager.add_icon_button(
|
||||||
|
load("res://addons/dialogic/Editor/Images/Toolbar/add-character.svg"),
|
||||||
|
'Add Character',
|
||||||
|
self)
|
||||||
|
add_character_button.pressed.connect(_on_create_character_button_pressed)
|
||||||
|
add_character_button.shortcut = Shortcut.new()
|
||||||
|
add_character_button.shortcut.events.append(InputEventKey.new())
|
||||||
|
add_character_button.shortcut.events[0].keycode = KEY_2
|
||||||
|
add_character_button.shortcut.events[0].ctrl_pressed = true
|
||||||
|
|
||||||
|
## By default show the no character screen
|
||||||
|
$NoCharacterScreen.show()
|
||||||
|
|
||||||
|
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "Character"
|
||||||
|
|
||||||
|
|
||||||
|
func _get_icon() -> Texture:
|
||||||
|
return load("res://addons/dialogic/Editor/Images/Resources/character.svg")
|
||||||
|
|
||||||
|
|
||||||
|
## Called when a character is opened somehow
|
||||||
|
func _open_resource(resource:Resource) -> void:
|
||||||
|
if resource == null:
|
||||||
|
$NoCharacterScreen.show()
|
||||||
|
return
|
||||||
|
|
||||||
|
## Update resource
|
||||||
|
current_resource = (resource as DialogicCharacter)
|
||||||
|
|
||||||
|
## Make sure changes in the ui won't trigger saving
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
## Load other main tabs
|
||||||
|
for child in %MainSettingsSections.get_children():
|
||||||
|
if child is DialogicCharacterEditorMainSection:
|
||||||
|
child._load_character(current_resource)
|
||||||
|
|
||||||
|
## Clear and then load Portrait section
|
||||||
|
%PortraitSearch.text = ""
|
||||||
|
load_portrait_tree()
|
||||||
|
|
||||||
|
loading = false
|
||||||
|
character_loaded.emit(resource.resource_path)
|
||||||
|
|
||||||
|
%CharacterName.text = DialogicResourceUtil.get_unique_identifier(resource.resource_path)
|
||||||
|
|
||||||
|
$NoCharacterScreen.hide()
|
||||||
|
%PortraitChangeInfo.hide()
|
||||||
|
|
||||||
|
|
||||||
|
## Called when the character is opened.
|
||||||
|
func _open(extra_info:Variant="") -> void:
|
||||||
|
if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
|
||||||
|
def_portrait_path = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
|
||||||
|
else:
|
||||||
|
def_portrait_path = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
|
||||||
|
|
||||||
|
if current_resource == null:
|
||||||
|
$NoCharacterScreen.show()
|
||||||
|
return
|
||||||
|
|
||||||
|
update_preview(true)
|
||||||
|
%PortraitChangeInfo.hide()
|
||||||
|
|
||||||
|
|
||||||
|
func _clear() -> void:
|
||||||
|
current_resource = null
|
||||||
|
current_resource_state = ResourceStates.SAVED
|
||||||
|
$NoCharacterScreen.show()
|
||||||
|
|
||||||
|
|
||||||
|
func _save() -> void:
|
||||||
|
if ! visible or not current_resource:
|
||||||
|
return
|
||||||
|
|
||||||
|
## Portrait list
|
||||||
|
current_resource.portraits = get_updated_portrait_dict()
|
||||||
|
|
||||||
|
## Main tabs
|
||||||
|
for child in %MainSettingsSections.get_children():
|
||||||
|
if child is DialogicCharacterEditorMainSection:
|
||||||
|
current_resource = child._save_changes(current_resource)
|
||||||
|
|
||||||
|
ResourceSaver.save(current_resource, current_resource.resource_path)
|
||||||
|
current_resource_state = ResourceStates.SAVED
|
||||||
|
DialogicResourceUtil.update_directory('dch')
|
||||||
|
|
||||||
|
|
||||||
|
## Saves a new empty character to the given path
|
||||||
|
func new_character(path: String) -> void:
|
||||||
|
var resource := DialogicCharacter.new()
|
||||||
|
resource.resource_path = path
|
||||||
|
resource.display_name = path.get_file().trim_suffix("."+path.get_extension())
|
||||||
|
resource.color = Color(1,1,1,1)
|
||||||
|
resource.default_portrait = ""
|
||||||
|
resource.custom_info = {}
|
||||||
|
ResourceSaver.save(resource, path)
|
||||||
|
EditorInterface.get_resource_filesystem().update_file(path)
|
||||||
|
DialogicResourceUtil.update_directory('dch')
|
||||||
|
editors_manager.edit_resource(resource)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
######### INTERFACE ############################################################
|
||||||
|
|
||||||
|
#region Interface
|
||||||
|
func _ready() -> void:
|
||||||
|
if get_parent() is SubViewport:
|
||||||
|
return
|
||||||
|
|
||||||
|
DialogicUtil.get_dialogic_plugin().resource_saved.connect(_on_some_resource_saved)
|
||||||
|
# NOTE: This check is required because up to 4.2 this signal is not exposed.
|
||||||
|
if DialogicUtil.get_dialogic_plugin().has_signal("scene_saved"):
|
||||||
|
DialogicUtil.get_dialogic_plugin().scene_saved.connect(_on_some_resource_saved)
|
||||||
|
|
||||||
|
$NoCharacterScreen.color = get_theme_color("dark_color_2", "Editor")
|
||||||
|
$NoCharacterScreen.show()
|
||||||
|
setup_portrait_list_tab()
|
||||||
|
|
||||||
|
_on_fit_preview_toggle_toggled(DialogicUtil.get_editor_setting('character_preview_fit', true))
|
||||||
|
%PreviewLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
|
||||||
|
|
||||||
|
%PortraitChangeWarning.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
|
||||||
|
|
||||||
|
%RealPreviewPivot.texture = get_theme_icon("EditorPivot", "EditorIcons")
|
||||||
|
|
||||||
|
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
|
||||||
|
|
||||||
|
set_portrait_settings_position(DialogicUtil.get_editor_setting('portrait_settings_position', true))
|
||||||
|
|
||||||
|
await find_parent('EditorView').ready
|
||||||
|
|
||||||
|
## Add general tabs
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn").instantiate(), %MainSettingsSections)
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn").instantiate(), %MainSettingsSections)
|
||||||
|
|
||||||
|
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn").instantiate(), %PortraitSettingsSection)
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn").instantiate(), %PortraitSettingsSection)
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn").instantiate(), %PortraitSettingsSection)
|
||||||
|
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn").instantiate(), %PortraitSettingsSection)
|
||||||
|
|
||||||
|
## Load custom sections from modules
|
||||||
|
for indexer in DialogicUtil.get_indexers():
|
||||||
|
for path in indexer._get_character_editor_sections():
|
||||||
|
var scene: Control = load(path).instantiate()
|
||||||
|
if scene is DialogicCharacterEditorMainSection:
|
||||||
|
add_settings_section(scene, %MainSettingsSections)
|
||||||
|
elif scene is DialogicCharacterEditorPortraitSection:
|
||||||
|
add_settings_section(scene, %PortraitSettingsSection)
|
||||||
|
|
||||||
|
|
||||||
|
## Add a section (a control) either to the given settings section (Main or Portraits)
|
||||||
|
## - sets up the title of the section
|
||||||
|
## - connects to various signals
|
||||||
|
func add_settings_section(edit:Control, parent:Node) -> void:
|
||||||
|
edit.changed.connect(something_changed)
|
||||||
|
edit.character_editor = self
|
||||||
|
if edit.has_signal('update_preview'):
|
||||||
|
edit.update_preview.connect(update_preview)
|
||||||
|
|
||||||
|
var button: Button
|
||||||
|
if edit._show_title():
|
||||||
|
var hbox := HBoxContainer.new()
|
||||||
|
hbox.name = edit._get_title()+"BOX"
|
||||||
|
button = Button.new()
|
||||||
|
button.flat = true
|
||||||
|
button.theme_type_variation = "DialogicSection"
|
||||||
|
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
|
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||||
|
button.text = edit._get_title()
|
||||||
|
button.icon_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
button.pressed.connect(_on_section_button_pressed.bind(button))
|
||||||
|
button.focus_mode = Control.FOCUS_NONE
|
||||||
|
button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
|
||||||
|
button.add_theme_color_override('icon_normal_color', get_theme_color("font_color", "DialogicSection"))
|
||||||
|
|
||||||
|
hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
hbox.add_child(button)
|
||||||
|
|
||||||
|
if !edit.hint_text.is_empty():
|
||||||
|
var hint: Node = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
|
||||||
|
hint.hint_text = edit.hint_text
|
||||||
|
hbox.add_child(hint)
|
||||||
|
|
||||||
|
parent.add_child(hbox)
|
||||||
|
parent.add_child(edit)
|
||||||
|
parent.add_child(HSeparator.new())
|
||||||
|
if button and !edit._start_opened():
|
||||||
|
_on_section_button_pressed(button)
|
||||||
|
|
||||||
|
|
||||||
|
func get_settings_section_by_name(name:String, main:=true) -> Node:
|
||||||
|
var parent := %MainSettingsSections
|
||||||
|
if not main:
|
||||||
|
parent = %PortraitSettingsSection
|
||||||
|
|
||||||
|
if parent.has_node(name):
|
||||||
|
return parent.get_node(name)
|
||||||
|
elif parent.has_node(name+"BOX/"+name):
|
||||||
|
return parent.get_node(name+"BOX/"+name)
|
||||||
|
else:
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _on_section_button_pressed(button:Button) -> void:
|
||||||
|
var section_header := button.get_parent()
|
||||||
|
var section := section_header.get_parent().get_child(section_header.get_index()+1)
|
||||||
|
if section.visible:
|
||||||
|
button.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
|
||||||
|
section.visible = false
|
||||||
|
else:
|
||||||
|
button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
|
||||||
|
section.visible = true
|
||||||
|
|
||||||
|
if section_header.get_parent().get_child_count() > section_header.get_index()+2 and section_header.get_parent().get_child(section_header.get_index()+2) is Separator:
|
||||||
|
section_header.get_parent().get_child(section_header.get_index()+2).visible = section_header.get_parent().get_child(section_header.get_index()+1).visible
|
||||||
|
|
||||||
|
|
||||||
|
func something_changed(fake_argument = "", fake_arg2 = null) -> void:
|
||||||
|
if not loading:
|
||||||
|
current_resource_state = ResourceStates.UNSAVED
|
||||||
|
|
||||||
|
|
||||||
|
func _on_main_settings_collapse_toggled(button_pressed:bool) -> void:
|
||||||
|
%MainSettingsTitle.visible = !button_pressed
|
||||||
|
%MainSettingsScroll.visible = !button_pressed
|
||||||
|
if button_pressed:
|
||||||
|
%MainSettings.hide()
|
||||||
|
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
|
||||||
|
else:
|
||||||
|
%MainSettings.show()
|
||||||
|
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_switch_portrait_settings_position_pressed() -> void:
|
||||||
|
set_portrait_settings_position(!%RightSection.vertical)
|
||||||
|
|
||||||
|
|
||||||
|
func set_portrait_settings_position(is_below:bool) -> void:
|
||||||
|
%RightSection.vertical = is_below
|
||||||
|
DialogicUtil.set_editor_setting('portrait_settings_position', is_below)
|
||||||
|
if is_below:
|
||||||
|
%SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignRightWide", "EditorIcons")
|
||||||
|
else:
|
||||||
|
%SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignBottomWide", "EditorIcons")
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
########## PORTRAIT SECTION ####################################################
|
||||||
|
|
||||||
|
#region Portrait Section
|
||||||
|
func setup_portrait_list_tab() -> void:
|
||||||
|
%PortraitTree.editor = self
|
||||||
|
|
||||||
|
## Portrait section styling/connections
|
||||||
|
%AddPortraitButton.icon = get_theme_icon("Add", "EditorIcons")
|
||||||
|
%AddPortraitButton.pressed.connect(add_portrait)
|
||||||
|
%AddPortraitGroupButton.icon = load("res://addons/dialogic/Editor/Images/Pieces/add-folder.svg")
|
||||||
|
%AddPortraitGroupButton.pressed.connect(add_portrait_group)
|
||||||
|
%ImportPortraitsButton.icon = get_theme_icon("Load", "EditorIcons")
|
||||||
|
%ImportPortraitsButton.pressed.connect(open_portrait_folder_select)
|
||||||
|
%PortraitSearch.right_icon = get_theme_icon("Search", "EditorIcons")
|
||||||
|
%PortraitSearch.text_changed.connect(filter_portrait_list)
|
||||||
|
|
||||||
|
%PortraitTree.item_selected.connect(load_selected_portrait)
|
||||||
|
%PortraitTree.item_edited.connect(_on_item_edited)
|
||||||
|
%PortraitTree.item_activated.connect(_on_item_activated)
|
||||||
|
|
||||||
|
|
||||||
|
func open_portrait_folder_select() -> void:
|
||||||
|
find_parent("EditorView").godot_file_dialog(
|
||||||
|
import_portraits_from_folder, "*.svg, *.png",
|
||||||
|
EditorFileDialog.FILE_MODE_OPEN_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
func import_portraits_from_folder(path:String) -> void:
|
||||||
|
var parent: TreeItem = %PortraitTree.get_root()
|
||||||
|
|
||||||
|
if %PortraitTree.get_selected() and %PortraitTree.get_selected() != parent and %PortraitTree.get_selected().get_metadata(0).has('group'):
|
||||||
|
parent = %PortraitTree.get_selected()
|
||||||
|
|
||||||
|
var dir := DirAccess.open(path)
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name: String = dir.get_next()
|
||||||
|
var files := []
|
||||||
|
while file_name != "":
|
||||||
|
if not dir.current_is_dir():
|
||||||
|
var file_lower := file_name.to_lower()
|
||||||
|
if '.svg' in file_lower or '.png' in file_lower:
|
||||||
|
if not '.import' in file_lower:
|
||||||
|
files.append(file_name)
|
||||||
|
file_name = dir.get_next()
|
||||||
|
|
||||||
|
var prefix: String = files[0]
|
||||||
|
for file in files:
|
||||||
|
while true:
|
||||||
|
if file.begins_with(prefix):
|
||||||
|
break
|
||||||
|
if prefix.is_empty():
|
||||||
|
break
|
||||||
|
prefix = prefix.substr(0, len(prefix)-1)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
%PortraitTree.add_portrait_item(file.trim_prefix(prefix).trim_suffix('.'+file.get_extension()),
|
||||||
|
{'scene':"",'export_overrides':{'image':var_to_str(path.path_join(file))}, 'scale':1, 'offset':Vector2(), 'mirror':false}, parent)
|
||||||
|
|
||||||
|
## Handle selection
|
||||||
|
if parent.get_child_count():
|
||||||
|
parent.get_first_child().select(0)
|
||||||
|
else:
|
||||||
|
# Call anyways to clear preview and hide portrait settings section
|
||||||
|
load_selected_portrait()
|
||||||
|
|
||||||
|
something_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func add_portrait(portrait_name:String='New portrait', portrait_data:Dictionary={'scene':"", 'export_overrides':{'image':''}, 'scale':1, 'offset':Vector2(), 'mirror':false}) -> void:
|
||||||
|
var parent: TreeItem = %PortraitTree.get_root()
|
||||||
|
if %PortraitTree.get_selected():
|
||||||
|
if %PortraitTree.get_selected().get_metadata(0) and %PortraitTree.get_selected().get_metadata(0).has('group'):
|
||||||
|
parent = %PortraitTree.get_selected()
|
||||||
|
else:
|
||||||
|
parent = %PortraitTree.get_selected().get_parent()
|
||||||
|
var item: TreeItem = %PortraitTree.add_portrait_item(portrait_name, portrait_data, parent)
|
||||||
|
item.set_meta('new', true)
|
||||||
|
item.set_editable(0, true)
|
||||||
|
item.select(0)
|
||||||
|
%PortraitTree.call_deferred('edit_selected')
|
||||||
|
something_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func add_portrait_group() -> void:
|
||||||
|
var parent_item: TreeItem = %PortraitTree.get_root()
|
||||||
|
if %PortraitTree.get_selected() and %PortraitTree.get_selected().get_metadata(0).has('group'):
|
||||||
|
parent_item = %PortraitTree.get_selected()
|
||||||
|
var item: TreeItem = %PortraitTree.add_portrait_group("Group", parent_item)
|
||||||
|
item.set_meta('new', true)
|
||||||
|
item.set_editable(0, true)
|
||||||
|
item.select(0)
|
||||||
|
%PortraitTree.call_deferred('edit_selected')
|
||||||
|
|
||||||
|
|
||||||
|
func load_portrait_tree() -> void:
|
||||||
|
%PortraitTree.clear_tree()
|
||||||
|
var root: TreeItem = %PortraitTree.create_item()
|
||||||
|
|
||||||
|
for portrait in current_resource.portraits.keys():
|
||||||
|
var portrait_label: String = portrait
|
||||||
|
var parent: TreeItem = %PortraitTree.get_root()
|
||||||
|
if '/' in portrait:
|
||||||
|
parent = %PortraitTree.create_necessary_group_items(portrait)
|
||||||
|
portrait_label = portrait.split('/')[-1]
|
||||||
|
|
||||||
|
%PortraitTree.add_portrait_item(portrait_label, current_resource.portraits[portrait], parent)
|
||||||
|
|
||||||
|
update_default_portrait_star(current_resource.default_portrait)
|
||||||
|
|
||||||
|
if root.get_child_count():
|
||||||
|
root.get_first_child().select(0)
|
||||||
|
while %PortraitTree.get_selected().get_child_count():
|
||||||
|
%PortraitTree.get_selected().get_child(0).select(0)
|
||||||
|
else:
|
||||||
|
# Call anyways to clear preview and hide portrait settings section
|
||||||
|
load_selected_portrait()
|
||||||
|
|
||||||
|
|
||||||
|
func filter_portrait_list(filter_term := "") -> void:
|
||||||
|
filter_branch(%PortraitTree.get_root(), filter_term)
|
||||||
|
|
||||||
|
|
||||||
|
func filter_branch(parent: TreeItem, filter_term: String) -> bool:
|
||||||
|
var anything_visible := false
|
||||||
|
for item in parent.get_children():
|
||||||
|
if item.get_metadata(0).has('group'):
|
||||||
|
item.visible = filter_branch(item, filter_term)
|
||||||
|
anything_visible = item.visible
|
||||||
|
elif filter_term.is_empty() or filter_term.to_lower() in item.get_text(0).to_lower():
|
||||||
|
item.visible = true
|
||||||
|
anything_visible = true
|
||||||
|
else:
|
||||||
|
item.visible = false
|
||||||
|
return anything_visible
|
||||||
|
|
||||||
|
|
||||||
|
## This is used to save the portrait data
|
||||||
|
func get_updated_portrait_dict() -> Dictionary:
|
||||||
|
return list_portraits(%PortraitTree.get_root().get_children())
|
||||||
|
|
||||||
|
|
||||||
|
func list_portraits(tree_items: Array[TreeItem], dict := {}, path_prefix := "") -> Dictionary:
|
||||||
|
for item in tree_items:
|
||||||
|
if item.get_metadata(0).has('group'):
|
||||||
|
dict = list_portraits(item.get_children(), dict, path_prefix+item.get_text(0)+"/")
|
||||||
|
else:
|
||||||
|
dict[path_prefix +item.get_text(0)] = item.get_metadata(0)
|
||||||
|
return dict
|
||||||
|
|
||||||
|
|
||||||
|
func load_selected_portrait() -> void:
|
||||||
|
if selected_item and is_instance_valid(selected_item):
|
||||||
|
selected_item.set_editable(0, false)
|
||||||
|
|
||||||
|
selected_item = %PortraitTree.get_selected()
|
||||||
|
|
||||||
|
if selected_item and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
|
||||||
|
%PortraitSettingsSection.show()
|
||||||
|
var current_portrait_data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
portrait_selected.emit(%PortraitTree.get_full_item_name(selected_item), current_portrait_data)
|
||||||
|
|
||||||
|
update_preview()
|
||||||
|
|
||||||
|
for child in %PortraitSettingsSection.get_children():
|
||||||
|
if child is DialogicCharacterEditorPortraitSection:
|
||||||
|
child.selected_item = selected_item
|
||||||
|
child._load_portrait_data(current_portrait_data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
%PortraitSettingsSection.hide()
|
||||||
|
update_preview()
|
||||||
|
|
||||||
|
|
||||||
|
func delete_portrait_item(item: TreeItem) -> void:
|
||||||
|
if item.get_next_visible(true) and item.get_next_visible(true) != item:
|
||||||
|
item.get_next_visible(true).select(0)
|
||||||
|
else:
|
||||||
|
selected_item = null
|
||||||
|
load_selected_portrait()
|
||||||
|
item.free()
|
||||||
|
something_changed()
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_item(item: TreeItem) -> void:
|
||||||
|
var new_item: TreeItem = %PortraitTree.add_portrait_item(item.get_text(0)+'_duplicated', item.get_metadata(0).duplicate(true), item.get_parent())
|
||||||
|
new_item.set_meta('new', true)
|
||||||
|
new_item.select(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if !is_visible_in_tree() or (get_viewport().gui_get_focus_owner()!= null and !name+'/' in str(get_viewport().gui_get_focus_owner().get_path())):
|
||||||
|
return
|
||||||
|
if event is InputEventKey and event.pressed:
|
||||||
|
if event.keycode == KEY_F2 and %PortraitTree.get_selected():
|
||||||
|
%PortraitTree.get_selected().set_editable(0, true)
|
||||||
|
%PortraitTree.edit_selected()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.keycode == KEY_DELETE and get_viewport().gui_get_focus_owner() is Tree and %PortraitTree.get_selected():
|
||||||
|
delete_portrait_item(%PortraitTree.get_selected())
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_portrait_right_click_menu_index_pressed(id: int) -> void:
|
||||||
|
# RENAME BUTTON
|
||||||
|
if id == 0:
|
||||||
|
_on_item_activated()
|
||||||
|
# DELETE BUTTON
|
||||||
|
if id == 2:
|
||||||
|
delete_portrait_item(%PortraitTree.get_selected())
|
||||||
|
# DUPLICATE ITEM
|
||||||
|
elif id == 1:
|
||||||
|
duplicate_item(%PortraitTree.get_selected())
|
||||||
|
elif id == 4:
|
||||||
|
get_settings_section_by_name("Portraits").set_default_portrait(%PortraitTree.get_full_item_name(%PortraitTree.get_selected()))
|
||||||
|
|
||||||
|
|
||||||
|
## This removes/and adds the DEFAULT star on the portrait list
|
||||||
|
func update_default_portrait_star(default_portrait_name: String) -> void:
|
||||||
|
var item_list: Array = %PortraitTree.get_root().get_children()
|
||||||
|
if item_list.is_empty() == false:
|
||||||
|
while true:
|
||||||
|
var item: TreeItem = item_list.pop_back()
|
||||||
|
if item.get_button_by_id(0, 2) != -1:
|
||||||
|
item.erase_button(0, item.get_button_by_id(0, 2))
|
||||||
|
if %PortraitTree.get_full_item_name(item) == default_portrait_name:
|
||||||
|
item.add_button(0, get_theme_icon("Favorites", "EditorIcons"), 2, true, "Default")
|
||||||
|
item_list.append_array(item.get_children())
|
||||||
|
if item_list.is_empty():
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
func _on_item_edited() -> void:
|
||||||
|
selected_item = %PortraitTree.get_selected()
|
||||||
|
something_changed()
|
||||||
|
if selected_item:
|
||||||
|
if %PreviewLabel.text.trim_prefix('Preview of "').trim_suffix('"') == current_resource.default_portrait:
|
||||||
|
current_resource.default_portrait = %PortraitTree.get_full_item_name(selected_item)
|
||||||
|
selected_item.set_editable(0, false)
|
||||||
|
|
||||||
|
if !selected_item.has_meta('new') and %PortraitTree.get_full_item_name(selected_item) != selected_item.get_meta('previous_name'):
|
||||||
|
report_name_change(selected_item)
|
||||||
|
%PortraitChangeInfo.show()
|
||||||
|
update_preview()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_item_activated() -> void:
|
||||||
|
if %PortraitTree.get_selected() == null:
|
||||||
|
return
|
||||||
|
%PortraitTree.get_selected().set_editable(0, true)
|
||||||
|
%PortraitTree.edit_selected()
|
||||||
|
|
||||||
|
|
||||||
|
func report_name_change(item: TreeItem) -> void:
|
||||||
|
if item.get_metadata(0).has('group'):
|
||||||
|
for s_item in item.get_children():
|
||||||
|
if s_item.get_metadata(0).has('group') or !s_item.has_meta('new'):
|
||||||
|
report_name_change(s_item)
|
||||||
|
else:
|
||||||
|
if item.get_meta('previous_name') == %PortraitTree.get_full_item_name(item):
|
||||||
|
return
|
||||||
|
editors_manager.reference_manager.add_portrait_ref_change(
|
||||||
|
item.get_meta('previous_name'),
|
||||||
|
%PortraitTree.get_full_item_name(item),
|
||||||
|
[DialogicResourceUtil.get_unique_identifier(current_resource.resource_path)])
|
||||||
|
item.set_meta('previous_name', %PortraitTree.get_full_item_name(item))
|
||||||
|
%PortraitChangeInfo.show()
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
########### PREVIEW ############################################################
|
||||||
|
|
||||||
|
#region Preview
|
||||||
|
func update_preview(force := false, ignore_settings_reload := false) -> void:
|
||||||
|
%ScenePreviewWarning.hide()
|
||||||
|
|
||||||
|
if selected_item and is_instance_valid(selected_item) and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
|
||||||
|
%PreviewLabel.text = 'Preview of "'+%PortraitTree.get_full_item_name(selected_item)+'"'
|
||||||
|
|
||||||
|
var current_portrait_data: Dictionary = selected_item.get_metadata(0)
|
||||||
|
|
||||||
|
if not force and current_previewed_scene != null \
|
||||||
|
and scene_file_path == current_portrait_data.get('scene') \
|
||||||
|
and current_previewed_scene.has_method('_should_do_portrait_update') \
|
||||||
|
and is_instance_valid(current_previewed_scene.get_script()) \
|
||||||
|
and current_previewed_scene._should_do_portrait_update(current_resource, selected_item.get_text(0)):
|
||||||
|
# We keep the same scene.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
|
||||||
|
for node in %RealPreviewPivot.get_children():
|
||||||
|
node.queue_free()
|
||||||
|
|
||||||
|
current_previewed_scene = null
|
||||||
|
current_scene_path = ""
|
||||||
|
|
||||||
|
var scene_path := def_portrait_path
|
||||||
|
if not current_portrait_data.get('scene', '').is_empty():
|
||||||
|
scene_path = current_portrait_data.get('scene')
|
||||||
|
|
||||||
|
if ResourceLoader.exists(scene_path):
|
||||||
|
current_previewed_scene = load(scene_path).instantiate()
|
||||||
|
current_scene_path = scene_path
|
||||||
|
|
||||||
|
if not current_previewed_scene == null:
|
||||||
|
%RealPreviewPivot.add_child(current_previewed_scene)
|
||||||
|
|
||||||
|
if not current_previewed_scene == null:
|
||||||
|
var scene: Node = current_previewed_scene
|
||||||
|
|
||||||
|
scene.show_behind_parent = true
|
||||||
|
DialogicUtil.apply_scene_export_overrides(scene, current_portrait_data.get('export_overrides', {}))
|
||||||
|
|
||||||
|
var mirror: bool = current_portrait_data.get('mirror', false) != current_resource.mirror
|
||||||
|
var scale: float = current_portrait_data.get('scale', 1) * current_resource.scale
|
||||||
|
|
||||||
|
if current_portrait_data.get('ignore_char_scale', false):
|
||||||
|
scale = current_portrait_data.get('scale', 1)
|
||||||
|
|
||||||
|
var offset: Vector2 = current_portrait_data.get('offset', Vector2()) + current_resource.offset
|
||||||
|
|
||||||
|
if is_instance_valid(scene.get_script()) and scene.script.is_tool():
|
||||||
|
|
||||||
|
if scene.has_method('_update_portrait'):
|
||||||
|
## Create a fake duplicate resource that has all the portrait changes applied already
|
||||||
|
var preview_character := current_resource.duplicate()
|
||||||
|
preview_character.portraits = get_updated_portrait_dict()
|
||||||
|
scene._update_portrait(preview_character, %PortraitTree.get_full_item_name(selected_item))
|
||||||
|
|
||||||
|
if scene.has_method('_set_mirror'):
|
||||||
|
scene._set_mirror(mirror)
|
||||||
|
|
||||||
|
if !%FitPreview_Toggle.button_pressed:
|
||||||
|
scene.position = Vector2() + offset
|
||||||
|
scene.scale = Vector2(1,1)*scale
|
||||||
|
else:
|
||||||
|
|
||||||
|
if not scene.get_script() == null and scene.script.is_tool() and scene.has_method('_get_covered_rect'):
|
||||||
|
var rect: Rect2 = scene._get_covered_rect()
|
||||||
|
var available_rect: Rect2 = %FullPreviewAvailableRect.get_rect()
|
||||||
|
scene.scale = Vector2(1,1) * min(available_rect.size.x/rect.size.x, available_rect.size.y/rect.size.y)
|
||||||
|
%RealPreviewPivot.position = (rect.position)*-1*scene.scale
|
||||||
|
%RealPreviewPivot.position.x = %FullPreviewAvailableRect.size.x/2
|
||||||
|
scene.position = Vector2()
|
||||||
|
|
||||||
|
else:
|
||||||
|
%ScenePreviewWarning.show()
|
||||||
|
else:
|
||||||
|
%PreviewLabel.text = 'Nothing to preview'
|
||||||
|
|
||||||
|
if not ignore_settings_reload:
|
||||||
|
for child in %PortraitSettingsSection.get_children():
|
||||||
|
if child is DialogicCharacterEditorPortraitSection:
|
||||||
|
child._recheck(current_portrait_data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
%PreviewLabel.text = 'No portrait to preview.'
|
||||||
|
|
||||||
|
for node in %RealPreviewPivot.get_children():
|
||||||
|
node.queue_free()
|
||||||
|
|
||||||
|
current_previewed_scene = null
|
||||||
|
current_scene_path = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _on_some_resource_saved(file:Variant) -> void:
|
||||||
|
if current_previewed_scene == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
if file is Resource and file == current_previewed_scene.script:
|
||||||
|
update_preview(true)
|
||||||
|
|
||||||
|
if typeof(file) == TYPE_STRING and file == current_previewed_scene.get_meta("path", ""):
|
||||||
|
update_preview(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_full_preview_available_rect_resized() -> void:
|
||||||
|
if %FitPreview_Toggle.button_pressed:
|
||||||
|
update_preview(false, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_create_character_button_pressed() -> void:
|
||||||
|
editors_manager.show_add_resource_dialog(
|
||||||
|
new_character,
|
||||||
|
'*.dch; DialogicCharacter',
|
||||||
|
'Create new character',
|
||||||
|
'character',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_fit_preview_toggle_toggled(button_pressed):
|
||||||
|
%FitPreview_Toggle.set_pressed_no_signal(button_pressed)
|
||||||
|
if button_pressed:
|
||||||
|
%FitPreview_Toggle.icon = get_theme_icon("ScrollContainer", "EditorIcons")
|
||||||
|
%FitPreview_Toggle.tooltip_text = "Real scale"
|
||||||
|
else:
|
||||||
|
%FitPreview_Toggle.tooltip_text = "Fit into preview"
|
||||||
|
%FitPreview_Toggle.icon = get_theme_icon("CenterContainer", "EditorIcons")
|
||||||
|
DialogicUtil.set_editor_setting('character_preview_fit', button_pressed)
|
||||||
|
update_preview(false, true)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
## Open the reference manager
|
||||||
|
func _on_reference_manger_button_pressed() -> void:
|
||||||
|
editors_manager.reference_manager.open()
|
||||||
|
%PortraitChangeInfo.hide()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cp531dtjqotvl
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
[gd_scene load_steps=11 format=3 uid="uid://dlskc36c5hrwv"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cp531dtjqotvl" path="res://addons/dialogic/Editor/CharacterEditor/character_editor.gd" id="2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_uhhqs"]
|
||||||
|
[ext_resource type="Script" uid="uid://dr2wwtcytw5ew" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd" id="2_vad0i"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://babwe22dqjta" path="res://addons/dialogic/Editor/Images/Pieces/add-folder.svg" id="3_v1qnr"]
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_r5ayh"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_oab13"]
|
||||||
|
image = SubResource("Image_r5ayh")
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_2j4b6"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 16,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_u1a6g"]
|
||||||
|
image = SubResource("Image_2j4b6")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_es2rd"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4xgdx"]
|
||||||
|
|
||||||
|
[node name="CharacterEditor" type="Control"]
|
||||||
|
self_modulate = Color(0, 0, 0, 1)
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="VBox" type="VBoxContainer" parent="Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.3
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="TopSection" type="HBoxContainer" parent="Scroll/VBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="NameContainer" type="HBoxContainer" parent="Scroll/VBox/TopSection"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="CharacterName" type="Label" parent="Scroll/VBox/TopSection/NameContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"DialogicTitle"
|
||||||
|
text = "My Character"
|
||||||
|
|
||||||
|
[node name="NameTooltip" parent="Scroll/VBox/TopSection/NameContainer" instance=ExtResource("2_uhhqs")]
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||||
|
Use this name in timelines to reference this character."
|
||||||
|
texture = SubResource("ImageTexture_oab13")
|
||||||
|
hint_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||||
|
Use this name in timelines to reference this character."
|
||||||
|
|
||||||
|
[node name="MainSettingsCollapse" type="Button" parent="Scroll/VBox/TopSection"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 10
|
||||||
|
size_flags_vertical = 4
|
||||||
|
toggle_mode = true
|
||||||
|
text = "Main Settings"
|
||||||
|
icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
|
||||||
|
[node name="MainHSplit" type="HSplitContainer" parent="Scroll/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="MainSettings" type="VBoxContainer" parent="Scroll/VBox/MainHSplit"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 0.2
|
||||||
|
|
||||||
|
[node name="MainSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/MainSettings"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"DialogicSubTitle"
|
||||||
|
text = "Main Settings"
|
||||||
|
|
||||||
|
[node name="MainSettingsScroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/MainSettings"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxEmpty_es2rd")
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
|
||||||
|
[node name="MainSettingsSections" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/MainSettings/MainSettingsScroll"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Split" type="HSplitContainer" parent="Scroll/VBox/MainHSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 0.2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 0.2
|
||||||
|
theme_override_constants/margin_bottom = 10
|
||||||
|
|
||||||
|
[node name="PortraitListSection" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"DialogicPanelA"
|
||||||
|
|
||||||
|
[node name="Portraits" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PortraitsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"DialogicSubTitle"
|
||||||
|
text = "Portraits"
|
||||||
|
|
||||||
|
[node name="PortraitListTools" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="AddPortraitButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add portrait"
|
||||||
|
icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
|
||||||
|
[node name="AddPortraitGroupButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Add Group"
|
||||||
|
icon = ExtResource("3_v1qnr")
|
||||||
|
|
||||||
|
[node name="ImportPortraitsButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Import images from folder"
|
||||||
|
icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
|
||||||
|
[node name="PortraitSearch" type="LineEdit" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 4
|
||||||
|
placeholder_text = "Search"
|
||||||
|
expand_to_text_length = true
|
||||||
|
clear_button_enabled = true
|
||||||
|
right_icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
caret_blink = true
|
||||||
|
caret_blink_interval = 0.5
|
||||||
|
|
||||||
|
[node name="PortraitTreePanel" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxEmpty_4xgdx")
|
||||||
|
|
||||||
|
[node name="PortraitTree" type="Tree" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
allow_rmb_select = true
|
||||||
|
hide_root = true
|
||||||
|
drop_mode_flags = 3
|
||||||
|
script = ExtResource("2_vad0i")
|
||||||
|
|
||||||
|
[node name="PortraitRightClickMenu" type="PopupMenu" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree"]
|
||||||
|
size = Vector2i(118, 100)
|
||||||
|
item_count = 5
|
||||||
|
item_0/text = "Rename"
|
||||||
|
item_0/icon = SubResource("ImageTexture_oab13")
|
||||||
|
item_0/id = 2
|
||||||
|
item_1/text = "Duplicate"
|
||||||
|
item_1/icon = SubResource("ImageTexture_oab13")
|
||||||
|
item_1/id = 0
|
||||||
|
item_2/text = "Delete"
|
||||||
|
item_2/icon = SubResource("ImageTexture_oab13")
|
||||||
|
item_2/id = 1
|
||||||
|
item_3/text = ""
|
||||||
|
item_3/id = 3
|
||||||
|
item_3/separator = true
|
||||||
|
item_4/text = "Make Default"
|
||||||
|
item_4/icon = SubResource("ImageTexture_oab13")
|
||||||
|
item_4/id = 4
|
||||||
|
|
||||||
|
[node name="PortraitChangeInfo" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PortraitChangeWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||||
|
text = "Some portraits were renamed. Make sure no references broke!"
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="ReferenceMangerButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
text = "Reference
|
||||||
|
Manager"
|
||||||
|
|
||||||
|
[node name="RightSection2" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.5
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||||
|
custom_minimum_size = Vector2(0, 10)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="RightSection" type="SplitContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.5
|
||||||
|
vertical = true
|
||||||
|
|
||||||
|
[node name="PortraitPreviewSection" type="Panel" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
show_behind_parent = true
|
||||||
|
custom_minimum_size = Vector2(100, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_type_variation = &"DialogicPanelB"
|
||||||
|
|
||||||
|
[node name="ClipRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Node2D" type="Node2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect"]
|
||||||
|
position = Vector2(13, 17)
|
||||||
|
|
||||||
|
[node name="RealPreviewPivot" type="Sprite2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect/Node2D"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
position = Vector2(326.5, 267)
|
||||||
|
texture = SubResource("ImageTexture_u1a6g")
|
||||||
|
|
||||||
|
[node name="ScenePreviewWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -143.0
|
||||||
|
offset_top = -44.5
|
||||||
|
offset_right = 143.0
|
||||||
|
offset_bottom = 85.5
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
text = "Custom scenes can only be viewed in \"Full mode\" if they are in @tool mode and override _get_covered_rect"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
autowrap_mode = 3
|
||||||
|
metadata/_edit_layout_mode = 1
|
||||||
|
|
||||||
|
[node name="PreviewReal" type="CenterContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 7
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -302.0
|
||||||
|
offset_top = -80.0
|
||||||
|
offset_right = 302.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
metadata/_edit_layout_mode = 1
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="RealSizeRemotePivotTransform" type="RemoteTransform2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal/Control"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
remote_path = NodePath("../../../ClipRect/Node2D/RealPreviewPivot")
|
||||||
|
update_rotation = false
|
||||||
|
update_scale = false
|
||||||
|
|
||||||
|
[node name="FullPreviewAvailableRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 10.0
|
||||||
|
offset_top = 28.0
|
||||||
|
offset_right = -10.0
|
||||||
|
offset_bottom = -16.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
metadata/_edit_layout_mode = 1
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 10
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 6.0
|
||||||
|
offset_top = 7.0
|
||||||
|
offset_right = -6.0
|
||||||
|
offset_bottom = 43.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="PreviewLabel" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
show_behind_parent = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 0
|
||||||
|
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||||
|
text = "No portrait to preview."
|
||||||
|
text_overrun_behavior = 1
|
||||||
|
|
||||||
|
[node name="FitPreview_Toggle" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
tooltip_text = "Real scale"
|
||||||
|
focus_mode = 0
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = true
|
||||||
|
icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
flat = true
|
||||||
|
metadata/_edit_layout_mode = 1
|
||||||
|
|
||||||
|
[node name="VBox" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.75
|
||||||
|
|
||||||
|
[node name="Hbox" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PortraitSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"DialogicSubTitle"
|
||||||
|
text = "Portrait Settings"
|
||||||
|
|
||||||
|
[node name="SwitchPortraitSettingsPosition" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
modulate = Color(1, 1, 1, 0.647059)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Switch position"
|
||||||
|
focus_mode = 0
|
||||||
|
icon = SubResource("ImageTexture_u1a6g")
|
||||||
|
flat = true
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.4
|
||||||
|
|
||||||
|
[node name="PortraitSettingsSection" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Scroll"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
size_flags_stretch_ratio = 0.3
|
||||||
|
|
||||||
|
[node name="Spacer2" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||||
|
custom_minimum_size = Vector2(0, 20)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="NoCharacterScreen" type="ColorRect" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
color = Color(0, 0, 0, 1)
|
||||||
|
|
||||||
|
[node name="CenterContainer" type="CenterContainer" parent="NoCharacterScreen"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="NoCharacterScreen/CenterContainer"]
|
||||||
|
custom_minimum_size = Vector2(250, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "No character opened.
|
||||||
|
Create a character or double-click one in the file system dock."
|
||||||
|
horizontal_alignment = 1
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="CreateCharacterButton" type="Button" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Create New Character"
|
||||||
|
|
||||||
|
[connection signal="toggled" from="Scroll/VBox/TopSection/MainSettingsCollapse" to="." method="_on_main_settings_collapse_toggled"]
|
||||||
|
[connection signal="item_mouse_selected" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" to="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" method="_on_item_mouse_selected"]
|
||||||
|
[connection signal="index_pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree/PortraitRightClickMenu" to="." method="_on_portrait_right_click_menu_index_pressed"]
|
||||||
|
[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo/ReferenceMangerButton" to="." method="_on_reference_manger_button_pressed"]
|
||||||
|
[connection signal="resized" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/FullPreviewAvailableRect" to="." method="_on_full_preview_available_rect_resized"]
|
||||||
|
[connection signal="toggled" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer/FitPreview_Toggle" to="." method="_on_fit_preview_toggle_toggled"]
|
||||||
|
[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox/SwitchPortraitSettingsPosition" to="." method="_on_switch_portrait_settings_position_pressed"]
|
||||||
|
[connection signal="pressed" from="NoCharacterScreen/CenterContainer/VBoxContainer/CreateCharacterButton" to="." method="_on_create_character_button_pressed"]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
@tool
|
||||||
|
class_name DialogicCharacterEditorMainSection
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
## Base class for all character editor main sections. Methods should be overriden.
|
||||||
|
|
||||||
|
## Emit this, if something changed
|
||||||
|
signal changed
|
||||||
|
|
||||||
|
## Reference to the character editor, set when instantiated
|
||||||
|
var character_editor: Control
|
||||||
|
|
||||||
|
## If not empty, a hint icon is added to the section title.
|
||||||
|
var hint_text := ""
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set the title of this section
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "MainSection"
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set the visibility of the section title
|
||||||
|
func _show_title() -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set whether this should initially be opened.
|
||||||
|
func _start_opened() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to load all the information from the character into this section.
|
||||||
|
func _load_character(resource:DialogicCharacter) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to save all changes made in this section to the resource.
|
||||||
|
## In custom sections you will mostly likely save to the [resource.custom_info]
|
||||||
|
## dictionary.
|
||||||
|
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
|
||||||
|
return resource
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dkp1qmt58ftif
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
@tool
|
||||||
|
class_name DialogicCharacterEditorPortraitSection
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
## Base class for all portrait settings sections. Methods should be overriden.
|
||||||
|
## Changes made through fields in such a section should instantly be "saved"
|
||||||
|
## to the portrait_items metadata from where they will be saved to the resource.
|
||||||
|
|
||||||
|
## Emit this, if something changed
|
||||||
|
signal changed
|
||||||
|
## Emit this if the preview should reload
|
||||||
|
signal update_preview
|
||||||
|
|
||||||
|
## Reference to the character editor, set when instantiated
|
||||||
|
var character_editor: Control
|
||||||
|
## Reference to the selected portrait item.
|
||||||
|
## `selected_item.get_metadata(0)` can access the portraits data
|
||||||
|
var selected_item: TreeItem = null
|
||||||
|
|
||||||
|
## If not empty a hint icon is added to the section title
|
||||||
|
var hint_text := ""
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set the title of this section
|
||||||
|
func _get_title() -> String:
|
||||||
|
return "CustomSection"
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set the visibility of the section title
|
||||||
|
func _show_title() -> bool:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to set whether this should initially be opened.
|
||||||
|
func _start_opened() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to load all the information from the character into this section.
|
||||||
|
func _load_portrait_data(data:Dictionary) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Overwrite to recheck visibility of your section and the content of your fields.
|
||||||
|
## This is called whenever the preview is updated so it allows reacting to major
|
||||||
|
## changes in other portrait sections.
|
||||||
|
func _recheck(data:Dictionary) -> void:
|
||||||
|
pass
|
||||||