Initial commit, layout mockup
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
4527
Assets/DefaultTheme.tres
Normal file
BIN
Assets/Fonts/Inter-Regular.ttf
Normal file
40
Assets/Fonts/Inter-Regular.ttf.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://501bous51s3p"
|
||||
path="res://.godot/imported/Inter-Regular.ttf-2ec9900abaf32134c660163ed92ecaf1.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Fonts/Inter-Regular.ttf"
|
||||
dest_files=["res://.godot/imported/Inter-Regular.ttf-2ec9900abaf32134c660163ed92ecaf1.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=1
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[{
|
||||
"chars": [],
|
||||
"glyphs": [],
|
||||
"name": "New Configuration",
|
||||
"size": Vector2i(16, 0),
|
||||
"variation_embolden": 0.0
|
||||
}]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
1
Assets/Icons/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 182 B |
37
Assets/Icons/add.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b3mydiolrvqlr"
|
||||
path="res://.godot/imported/add.svg-807bb7fce18f604a33f75ba4589a0686.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/add.svg"
|
||||
dest_files=["res://.godot/imported/add.svg-807bb7fce18f604a33f75ba4589a0686.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
|
||||
1
Assets/Icons/audio_track.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M709-255H482L369-142q-23 23-56.5 23T256-142L143-255q-23-23-23-57t23-57l112-112v-227l454 453ZM289-785q107-68 231.5-54.5T735-736q90 90 103.5 214.5T784-290l-58-58q45-82 31.5-173.5T678-679q-66-66-157.5-79.5T347-727l-58-58Zm118 118q57-17 115-7t100 52q42 42 51.5 99.5T666-408l-68-68q0-25-7.5-48.5T566-565q-18-18-41.5-26t-49.5-8l-68-68Z"/></svg>
|
||||
|
After Width: | Height: | Size: 454 B |
37
Assets/Icons/audio_track.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://da3adi0s1qt85"
|
||||
path="res://.godot/imported/audio_track.svg-f7177dd1cfa1952efcda2b0fafbd5fe0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/audio_track.svg"
|
||||
dest_files=["res://.godot/imported/audio_track.svg-f7177dd1cfa1952efcda2b0fafbd5fe0.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=8.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
Assets/Icons/checked.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m424-312 282-282-56-56-226 226-114-114-56 56 170 170ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
37
Assets/Icons/checked.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://blqywcg2e22in"
|
||||
path="res://.godot/imported/checked.svg-a407a73e6fae12042ab7e03cfe289cf1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/checked.svg"
|
||||
dest_files=["res://.godot/imported/checked.svg-a407a73e6fae12042ab7e03cfe289cf1.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=0.75
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
Assets/Icons/label_track.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M280-320h80v-480h-80v480ZM240-80q-50 0-85-35t-35-85v-560q0-50 35-85t85-35h440v640H240q-17 0-28.5 11.5T200-200q0 17 11.5 28.5T240-160h520v-640h80v720H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 278 B |
37
Assets/Icons/label_track.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c1m8c0h70mpa5"
|
||||
path="res://.godot/imported/label_track.svg-163e679ece6be4343465835f131e9505.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/label_track.svg"
|
||||
dest_files=["res://.godot/imported/label_track.svg-163e679ece6be4343465835f131e9505.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=8.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
Assets/Icons/microphone_track.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M192-680q-15-17-23.5-37t-8.5-43q0-50 35-85t85-35q50 0 85 35t35 85q0 23-8.5 43T368-680H192ZM400-80q-66 0-113-47t-47-113h-40l-40-400h240l-40 400h-40q0 33 23.5 56.5T400-160q33 0 56.5-23.5T480-240v-480q0-66 47-113t113-47q66 0 113 47t47 113v640h-80v-640q0-33-23.5-56.5T640-800q-33 0-56.5 23.5T560-720v480q0 66-47 113T400-80Z"/></svg>
|
||||
|
After Width: | Height: | Size: 444 B |
37
Assets/Icons/microphone_track.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwfbs4jxbia3o"
|
||||
path="res://.godot/imported/microphone_track.svg-1ac8959b5c3b3d6b0f42611678ab4906.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/microphone_track.svg"
|
||||
dest_files=["res://.godot/imported/microphone_track.svg-1ac8959b5c3b3d6b0f42611678ab4906.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=8.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
Assets/Icons/mixer.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M200-160v-280h-80v-80h240v80h-80v280h-80Zm0-440v-200h80v200h-80Zm160 0v-80h80v-120h80v120h80v80H360Zm80 440v-360h80v360h-80Zm240 0v-120h-80v-80h240v80h-80v120h-80Zm0-280v-360h80v360h-80Z"/></svg>
|
||||
|
After Width: | Height: | Size: 311 B |
37
Assets/Icons/mixer.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dxl2qel0qbxrn"
|
||||
path="res://.godot/imported/mixer.svg-31a8bbab6edcb600067a9fb9df79889f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/mixer.svg"
|
||||
dest_files=["res://.godot/imported/mixer.svg-31a8bbab6edcb600067a9fb9df79889f.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
|
||||
1
Assets/Icons/mute.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M792-56 671-177q-25 16-53 27.5T560-131v-82q14-5 27.5-10t25.5-12L480-368v208L280-360H120v-240h128L56-792l56-56 736 736-56 56Zm-8-232-58-58q17-31 25.5-65t8.5-70q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 53-14.5 102T784-288ZM650-422l-90-90v-130q47 22 73.5 66t26.5 96q0 15-2.5 29.5T650-422ZM480-592 376-696l104-104v208Zm-80 238v-94l-72-72H200v80h114l86 86Zm-36-130Z"/></svg>
|
||||
|
After Width: | Height: | Size: 495 B |
37
Assets/Icons/mute.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d2yt5ppj82qr1"
|
||||
path="res://.godot/imported/mute.svg-9a4ef8a1492d59c860c1ab81fc8777ec.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/mute.svg"
|
||||
dest_files=["res://.godot/imported/mute.svg-9a4ef8a1492d59c860c1ab81fc8777ec.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
|
||||
1
Assets/Icons/pause.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M520-200v-560h240v560H520Zm-320 0v-560h240v560H200Zm400-80h80v-400h-80v400Zm-320 0h80v-400h-80v400Zm0-400v400-400Zm320 0v400-400Z"/></svg>
|
||||
|
After Width: | Height: | Size: 254 B |
37
Assets/Icons/pause.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bqy573te333lt"
|
||||
path="res://.godot/imported/pause.svg-b569a82cdc28c1abec0d4398e766838d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/pause.svg"
|
||||
dest_files=["res://.godot/imported/pause.svg-b569a82cdc28c1abec0d4398e766838d.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
|
||||
1
Assets/Icons/pause_fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 176 B |
37
Assets/Icons/pause_fill.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bqmcx807prwj0"
|
||||
path="res://.godot/imported/pause_fill.svg-03ac87b224a544d5ced160fd4fa709fa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/pause_fill.svg"
|
||||
dest_files=["res://.godot/imported/pause_fill.svg-03ac87b224a544d5ced160fd4fa709fa.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
|
||||
1
Assets/Icons/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z"/></svg>
|
||||
|
After Width: | Height: | Size: 190 B |
37
Assets/Icons/play.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://qh7l84v0jj73"
|
||||
path="res://.godot/imported/play.svg-b90f638081075dc471c4bc5e46375e06.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/play.svg"
|
||||
dest_files=["res://.godot/imported/play.svg-b90f638081075dc471c4bc5e46375e06.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
|
||||
1
Assets/Icons/play_fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M320-200v-560l440 280-440 280Z"/></svg>
|
||||
|
After Width: | Height: | Size: 155 B |
37
Assets/Icons/play_fill.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c7bolv16y04n"
|
||||
path="res://.godot/imported/play_fill.svg-452dbc9af399995874ab2cfd125b1e41.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/play_fill.svg"
|
||||
dest_files=["res://.godot/imported/play_fill.svg-452dbc9af399995874ab2cfd125b1e41.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
|
||||
1
Assets/Icons/record.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-280q83 0 141.5-58.5T680-480q0-83-58.5-141.5T480-680q-83 0-141.5 58.5T280-480q0 83 58.5 141.5T480-280Zm0 200q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 515 B |
37
Assets/Icons/record.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cc0dy71y8shcx"
|
||||
path="res://.godot/imported/record.svg-f7796b623cc0470b54858cbf04cbc209.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/record.svg"
|
||||
dest_files=["res://.godot/imported/record.svg-f7796b623cc0470b54858cbf04cbc209.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
|
||||
1
Assets/Icons/stop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M320-640v320-320Zm-80 400v-480h480v480H240Zm80-80h320v-320H320v320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 192 B |
37
Assets/Icons/stop.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dpqaikr0jp2yd"
|
||||
path="res://.godot/imported/stop.svg-dcc4cbafa165cd18017f97877eb3350b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/stop.svg"
|
||||
dest_files=["res://.godot/imported/stop.svg-dcc4cbafa165cd18017f97877eb3350b.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
|
||||
1
Assets/Icons/stop_fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M240-240v-480h480v480H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
37
Assets/Icons/stop_fill.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmw8ftyp0yfnq"
|
||||
path="res://.godot/imported/stop_fill.svg-ea601ce41d839bb8d5f76cdfccd0a2e7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/stop_fill.svg"
|
||||
dest_files=["res://.godot/imported/stop_fill.svg-ea601ce41d839bb8d5f76cdfccd0a2e7.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
|
||||
1
Assets/Icons/timeline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M240-280h240v-80H240v80Zm120-160h240v-80H360v80Zm120-160h240v-80H480v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>
|
||||
|
After Width: | Height: | Size: 354 B |
37
Assets/Icons/timeline.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cg6sewiokut5v"
|
||||
path="res://.godot/imported/timeline.svg-e7e71bb3d592cb6eb26b81e4f159f847.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/timeline.svg"
|
||||
dest_files=["res://.godot/imported/timeline.svg-e7e71bb3d592cb6eb26b81e4f159f847.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
|
||||
1
Assets/Icons/tracks.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M80-160v-160h160v160H80Zm240 0v-160h560v160H320ZM80-400v-160h160v160H80Zm240 0v-160h560v160H320ZM80-640v-160h160v160H80Zm240 0v-160h560v160H320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 269 B |
37
Assets/Icons/tracks.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vd53o7va0tni"
|
||||
path="res://.godot/imported/tracks.svg-84971df5232332b00b3794f9fc75ad3e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/tracks.svg"
|
||||
dest_files=["res://.godot/imported/tracks.svg-84971df5232332b00b3794f9fc75ad3e.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=0.75
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
Assets/Icons/unchecked.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Z"/></svg>
|
||||
|
After Width: | Height: | Size: 267 B |
37
Assets/Icons/unchecked.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wx4uqiy4qp5c"
|
||||
path="res://.godot/imported/unchecked.svg-d3ce4a27a0eafd6c66353b82b84d0acc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/unchecked.svg"
|
||||
dest_files=["res://.godot/imported/unchecked.svg-d3ce4a27a0eafd6c66353b82b84d0acc.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=0.75
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
8
AudioEditor.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.3.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>
|
||||
19
AudioEditor.sln
Normal file
@@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioEditor", "AudioEditor.csproj", "{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}"
|
||||
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
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{F3C7EA3D-DA7C-425C-A086-5D8E3E189BFF}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
36
Controls/AudioTrack.tscn
Normal file
@@ -0,0 +1,36 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dlb5xpe1t8rrk"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c3kajrpp2ux7" path="res://Controls/Track.tscn" id="1_envsv"]
|
||||
[ext_resource type="Texture2D" uid="uid://cc0dy71y8shcx" path="res://Assets/Icons/record.svg" id="2_iwhga"]
|
||||
|
||||
[node name="Track" instance=ExtResource("1_envsv")]
|
||||
|
||||
[node name="MuteSolo" type="VBoxContainer" parent="PanelContainer/HBoxContainer/Body/HBoxContainer" index="0"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="Mute" type="Button" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/MuteSolo" index="0"]
|
||||
modulate = Color(1, 0, 0, 1)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "M"
|
||||
|
||||
[node name="Solo" type="Button" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/MuteSolo" index="1"]
|
||||
modulate = Color(1, 1, 0, 1)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "S"
|
||||
|
||||
[node name="Record" type="Button" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName" index="2"]
|
||||
self_modulate = Color(1, 0.373852, 0.504338, 1)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
toggle_mode = true
|
||||
icon = ExtResource("2_iwhga")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="VolumeSlider" type="HSlider" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls" index="1"]
|
||||
layout_mode = 2
|
||||
min_value = -80.0
|
||||
max_value = 0.0
|
||||
106
Controls/AudioTrackOld.tscn
Normal file
@@ -0,0 +1,106 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://y1aeb1b7cgh3"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://b8rk41gjual2b" path="res://Assets/DefaultTheme.tres" id="1_7wbal"]
|
||||
[ext_resource type="Texture2D" uid="uid://bwfbs4jxbia3o" path="res://Assets/Icons/microphone_track.svg" id="2_ek3ni"]
|
||||
[ext_resource type="Texture2D" uid="uid://cc0dy71y8shcx" path="res://Assets/Icons/record.svg" id="3_2mwx3"]
|
||||
|
||||
[node name="Track" type="Control"]
|
||||
clip_children = 1
|
||||
custom_minimum_size = Vector2(128, 74)
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 382.0
|
||||
offset_bottom = 74.0
|
||||
theme = ExtResource("1_7wbal")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
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="PanelContainer"]
|
||||
clip_children = 1
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="PanelContainer/HBoxContainer"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(4, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="PanelContainer/HBoxContainer"]
|
||||
self_modulate = Color(0.699653, 0.699654, 0.699653, 1)
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(4, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
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="HBoxContainer" type="HBoxContainer" parent="PanelContainer/HBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MuteSolo" type="VBoxContainer" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="Mute" type="Button" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/MuteSolo"]
|
||||
modulate = Color(1, 0, 0, 1)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "M"
|
||||
|
||||
[node name="Solo" type="Button" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/MuteSolo"]
|
||||
modulate = Color(1, 1, 0, 1)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "S"
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer"]
|
||||
clip_children = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TrackIcon" type="TextureRect" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
|
||||
modulate = Color(0.43134, 0.43134, 0.43134, 1)
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
texture = ExtResource("2_ek3ni")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="TrackName" type="LineEdit" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Track Name"
|
||||
expand_to_text_length = true
|
||||
context_menu_enabled = false
|
||||
caret_blink = true
|
||||
|
||||
[node name="Record" type="Button" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer"]
|
||||
self_modulate = Color(1, 0.373852, 0.504338, 1)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
toggle_mode = true
|
||||
icon = ExtResource("3_2mwx3")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="VolumeSlider" type="HSlider" parent="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
min_value = -80.0
|
||||
max_value = 0.0
|
||||
|
||||
[connection signal="text_submitted" from="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/TrackName" to="PanelContainer/HBoxContainer/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/TrackName" method="release_focus" unbinds=1]
|
||||
9
Controls/LabelTrack.tscn
Normal file
@@ -0,0 +1,9 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bhe0fbxuxrgjk"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c3kajrpp2ux7" path="res://Controls/Track.tscn" id="1_oloar"]
|
||||
[ext_resource type="Texture2D" uid="uid://c1m8c0h70mpa5" path="res://Assets/Icons/label_track.svg" id="2_v5n7i"]
|
||||
|
||||
[node name="LabelTrack" instance=ExtResource("1_oloar")]
|
||||
|
||||
[node name="TrackIcon" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName" index="0"]
|
||||
texture = ExtResource("2_v5n7i")
|
||||
71
Controls/Track.tscn
Normal file
@@ -0,0 +1,71 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c3kajrpp2ux7"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://b8rk41gjual2b" path="res://Assets/DefaultTheme.tres" id="1_tbi88"]
|
||||
[ext_resource type="Texture2D" uid="uid://bwfbs4jxbia3o" path="res://Assets/Icons/microphone_track.svg" id="2_r2pin"]
|
||||
|
||||
[node name="Track" type="Control"]
|
||||
clip_children = 1
|
||||
custom_minimum_size = Vector2(128, 74)
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 382.0
|
||||
offset_bottom = 74.0
|
||||
theme = ExtResource("1_tbi88")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
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="PanelContainer"]
|
||||
clip_children = 1
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ColorStrip" type="ColorRect" parent="PanelContainer/HBoxContainer"]
|
||||
self_modulate = Color(0.699653, 0.699654, 0.699653, 1)
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(4, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Body" type="MarginContainer" parent="PanelContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
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="HBoxContainer" type="HBoxContainer" parent="PanelContainer/HBoxContainer/Body"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameIconControls" type="VBoxContainer" parent="PanelContainer/HBoxContainer/Body/HBoxContainer"]
|
||||
clip_children = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 4
|
||||
alignment = 1
|
||||
|
||||
[node name="IconAndName" type="HBoxContainer" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls"]
|
||||
custom_minimum_size = Vector2(0, 32)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TrackIcon" type="TextureRect" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName"]
|
||||
modulate = Color(0.43134, 0.43134, 0.43134, 1)
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
texture = ExtResource("2_r2pin")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="TrackName" type="LineEdit" parent="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Track Name"
|
||||
expand_to_text_length = true
|
||||
context_menu_enabled = false
|
||||
caret_blink = true
|
||||
|
||||
[connection signal="text_submitted" from="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName/TrackName" to="PanelContainer/HBoxContainer/Body/HBoxContainer/NameIconControls/IconAndName/TrackName" method="release_focus" unbinds=1]
|
||||
9
Scripts/NestablePopupMenu.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
extends PopupMenu
|
||||
|
||||
func _ready():
|
||||
for child in get_children():
|
||||
if child is PopupMenu:
|
||||
add_submenu_node_item(child.name, child)
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
238
Views/MainView.tscn
Normal file
@@ -0,0 +1,238 @@
|
||||
[gd_scene load_steps=18 format=3 uid="uid://cr2f68sbsegai"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://b8rk41gjual2b" path="res://Assets/DefaultTheme.tres" id="1_s6hk6"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/dockable_container.gd" id="2_7b0h5"]
|
||||
[ext_resource type="Script" path="res://Scripts/NestablePopupMenu.gd" id="2_lsrne"]
|
||||
[ext_resource type="Texture2D" uid="uid://vd53o7va0tni" path="res://Assets/Icons/tracks.svg" id="2_nidjn"]
|
||||
[ext_resource type="Texture2D" uid="uid://dxl2qel0qbxrn" path="res://Assets/Icons/mixer.svg" id="3_4tblp"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout_panel.gd" id="3_4twnq"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout.gd" id="4_ixaqo"]
|
||||
[ext_resource type="PackedScene" uid="uid://bpd6g2b3s7tqa" path="res://Views/TrackView.tscn" id="5_rgxdu"]
|
||||
[ext_resource type="PackedScene" uid="uid://v4oljx3qrk5q" path="res://Views/Timeline.tscn" id="7_xu70y"]
|
||||
[ext_resource type="Texture2D" uid="uid://c7bolv16y04n" path="res://Assets/Icons/play_fill.svg" id="9_5eeih"]
|
||||
[ext_resource type="Texture2D" uid="uid://bqmcx807prwj0" path="res://Assets/Icons/pause_fill.svg" id="10_a1xvk"]
|
||||
[ext_resource type="Texture2D" uid="uid://bmw8ftyp0yfnq" path="res://Assets/Icons/stop_fill.svg" id="11_6dqcn"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_pfnfv"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("3_4twnq")
|
||||
names = PackedStringArray("Tracks")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_20kxa"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource("4_ixaqo")
|
||||
root = SubResource("Resource_pfnfv")
|
||||
hidden_tabs = {}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_xnlko"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("3_4twnq")
|
||||
names = PackedStringArray("Mixer")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_h7r2s"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource("4_ixaqo")
|
||||
root = SubResource("Resource_xnlko")
|
||||
hidden_tabs = {}
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3f3qp"]
|
||||
bg_color = Color(0.180005, 0.180005, 0.180005, 1)
|
||||
|
||||
[node name="MainView" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_s6hk6")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="MenuBar" type="MenuBar" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
flat = true
|
||||
|
||||
[node name="File" type="PopupMenu" parent="VBoxContainer/MenuBar"]
|
||||
item_count = 3
|
||||
item_0/text = "New..."
|
||||
item_1/text = "Open..."
|
||||
item_1/id = 1
|
||||
item_2/id = 2
|
||||
item_2/separator = true
|
||||
script = ExtResource("2_lsrne")
|
||||
|
||||
[node name="Import" type="PopupMenu" parent="VBoxContainer/MenuBar/File"]
|
||||
auto_translate_mode = 1
|
||||
item_count = 2
|
||||
item_0/text = "Audio Clip"
|
||||
item_1/text = "Label Clip"
|
||||
item_1/id = 1
|
||||
|
||||
[node name="Export" type="PopupMenu" parent="VBoxContainer/MenuBar/File"]
|
||||
item_count = 2
|
||||
item_0/text = "Project"
|
||||
item_1/text = "Region"
|
||||
item_1/id = 1
|
||||
|
||||
[node name="Edit" type="PopupMenu" parent="VBoxContainer/MenuBar"]
|
||||
auto_translate_mode = 1
|
||||
item_count = 2
|
||||
item_0/text = "Add Audio Track"
|
||||
item_1/text = "Add Label Track"
|
||||
item_1/id = 1
|
||||
|
||||
[node name="View" type="PopupMenu" parent="VBoxContainer/MenuBar"]
|
||||
auto_translate_mode = 1
|
||||
hide_on_checkable_item_selection = false
|
||||
item_count = 2
|
||||
item_0/text = "Tracks"
|
||||
item_0/icon = ExtResource("2_nidjn")
|
||||
item_0/checkable = 1
|
||||
item_0/checked = true
|
||||
item_1/text = "Mixer"
|
||||
item_1/icon = ExtResource("3_4tblp")
|
||||
item_1/checkable = 1
|
||||
item_1/checked = true
|
||||
item_1/id = 1
|
||||
|
||||
[node name="Settings" type="PopupMenu" parent="VBoxContainer/MenuBar"]
|
||||
auto_translate_mode = 1
|
||||
hide_on_checkable_item_selection = false
|
||||
item_count = 2
|
||||
item_0/text = "Project Settings"
|
||||
item_1/text = "System Settings"
|
||||
item_1/id = 1
|
||||
|
||||
[node name="Panel" type="Panel" parent="VBoxContainer/MenuBar"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Transport" type="PanelContainer" parent="VBoxContainer/VSplitContainer"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VSplitContainer/Transport"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Controls" type="MarginContainer" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Controls"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="Play" type="Button" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Controls/HBoxContainer"]
|
||||
modulate = Color(0.253333, 1, 0.2, 1)
|
||||
layout_mode = 2
|
||||
icon = ExtResource("9_5eeih")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="Pause" type="Button" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Controls/HBoxContainer"]
|
||||
modulate = Color(1, 0.986667, 0.2, 1)
|
||||
layout_mode = 2
|
||||
icon = ExtResource("10_a1xvk")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="Stop" type="Button" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Controls/HBoxContainer"]
|
||||
self_modulate = Color(1, 0.2, 0.2, 1)
|
||||
layout_mode = 2
|
||||
icon = ExtResource("11_6dqcn")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="Time" type="VBoxContainer" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Time"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CurrentTime" type="Label" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Time/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "00:00.00"
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Time/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Length" type="Label" parent="VBoxContainer/VSplitContainer/Transport/HBoxContainer/Time/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "00:00.00"
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer/VSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LeftDock" type="Container" parent="VBoxContainer/VSplitContainer/HSplitContainer"]
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
script = ExtResource("2_7b0h5")
|
||||
tab_alignment = 0
|
||||
layout = SubResource("Resource_20kxa")
|
||||
|
||||
[node name="Tracks" parent="VBoxContainer/VSplitContainer/HSplitContainer/LeftDock" instance=ExtResource("5_rgxdu")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Timeline" parent="VBoxContainer/VSplitContainer/HSplitContainer" instance=ExtResource("7_xu70y")]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="BottomDock" type="Container" parent="VBoxContainer/VSplitContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
script = ExtResource("2_7b0h5")
|
||||
tab_alignment = 0
|
||||
layout = SubResource("Resource_h7r2s")
|
||||
|
||||
[node name="Mixer" type="Control" parent="VBoxContainer/VSplitContainer/BottomDock"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
show_behind_parent = 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_styles/panel = SubResource("StyleBoxFlat_3f3qp")
|
||||
15
Views/Timeline.tscn
Normal file
@@ -0,0 +1,15 @@
|
||||
[gd_scene format=3 uid="uid://v4oljx3qrk5q"]
|
||||
|
||||
[node name="Timeline" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
modulate = Color(0.329158, 0.329158, 0.329158, 1)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
38
Views/TrackView.tscn
Normal file
@@ -0,0 +1,38 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bpd6g2b3s7tqa"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://dlb5xpe1t8rrk" path="res://Controls/AudioTrack.tscn" id="1_ueogm"]
|
||||
[ext_resource type="Texture2D" uid="uid://b3mydiolrvqlr" path="res://Assets/Icons/add.svg" id="2_rekuu"]
|
||||
|
||||
[node name="Tracks" type="Control"]
|
||||
clip_children = 1
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 3
|
||||
anchors_preset = 9
|
||||
anchor_bottom = 1.0
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(128, 0)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TrackList" type="VBoxContainer" parent="VBoxContainer"]
|
||||
custom_minimum_size = Vector2(128, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Track" parent="VBoxContainer/TrackList" instance=ExtResource("1_ueogm")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddTrack" type="MenuButton" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
icon = ExtResource("2_rekuu")
|
||||
flat = false
|
||||
icon_alignment = 1
|
||||
item_count = 2
|
||||
popup/item_0/text = "Audio Track"
|
||||
popup/item_1/text = "Label Track"
|
||||
popup/item_1/id = 1
|
||||
121
addons/dockable_container/LICENSE
Normal file
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
448
addons/dockable_container/dockable_container.gd
Normal file
@@ -0,0 +1,448 @@
|
||||
@tool
|
||||
class_name DockableContainer
|
||||
extends Container
|
||||
|
||||
const SplitHandle := preload("split_handle.gd")
|
||||
const DockablePanel := preload("dockable_panel.gd")
|
||||
const DragNDropPanel := preload("drag_n_drop_panel.gd")
|
||||
|
||||
@export var tab_alignment := TabBar.ALIGNMENT_CENTER:
|
||||
get:
|
||||
return _tab_align
|
||||
set(value):
|
||||
_tab_align = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.tab_alignment = value
|
||||
@export var use_hidden_tabs_for_min_size := false:
|
||||
get:
|
||||
return _use_hidden_tabs_for_min_size
|
||||
set(value):
|
||||
_use_hidden_tabs_for_min_size = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.use_hidden_tabs_for_min_size = value
|
||||
@export var tabs_visible := true:
|
||||
get:
|
||||
return _tabs_visible
|
||||
set(value):
|
||||
_tabs_visible = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.show_tabs = _tabs_visible
|
||||
## If [code]true[/code] and a panel only has one tab, it keeps that tab hidden even if
|
||||
## [member tabs_visible] is [code]true[/code].
|
||||
## Only takes effect is [member tabs_visible] is [code]true[/code].
|
||||
@export var hide_single_tab := false:
|
||||
get:
|
||||
return _hide_single_tab
|
||||
set(value):
|
||||
_hide_single_tab = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.hide_single_tab = _hide_single_tab
|
||||
@export var rearrange_group := 0
|
||||
@export var layout := DockableLayout.new():
|
||||
get:
|
||||
return _layout
|
||||
set(value):
|
||||
set_layout(value)
|
||||
## If `clone_layout_on_ready` is true, `layout` will be cloned checked `_ready`.
|
||||
## This is useful for leaving layout Resources untouched in case you want to
|
||||
## restore layout to its default later.
|
||||
@export var clone_layout_on_ready := true
|
||||
|
||||
var _layout := DockableLayout.new()
|
||||
var _panel_container := Container.new()
|
||||
var _split_container := Container.new()
|
||||
var _drag_n_drop_panel := DragNDropPanel.new()
|
||||
var _drag_panel: DockablePanel
|
||||
var _tab_align := TabBar.ALIGNMENT_CENTER
|
||||
var _tabs_visible := true
|
||||
var _use_hidden_tabs_for_min_size := false
|
||||
var _hide_single_tab := false
|
||||
var _current_panel_index := 0
|
||||
var _current_split_index := 0
|
||||
var _children_names := {}
|
||||
var _layout_dirty := false
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
child_entered_tree.connect(_child_entered_tree)
|
||||
child_exiting_tree.connect(_child_exiting_tree)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_process_input(false)
|
||||
_panel_container.name = "_panel_container"
|
||||
add_child(_panel_container)
|
||||
move_child(_panel_container, 0)
|
||||
_split_container.name = "_split_container"
|
||||
_split_container.mouse_filter = MOUSE_FILTER_PASS
|
||||
_panel_container.add_child(_split_container)
|
||||
|
||||
_drag_n_drop_panel.name = "_drag_n_drop_panel"
|
||||
_drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS
|
||||
_drag_n_drop_panel.visible = false
|
||||
add_child(_drag_n_drop_panel)
|
||||
|
||||
if not _layout:
|
||||
set_layout(null)
|
||||
elif clone_layout_on_ready and not Engine.is_editor_hint():
|
||||
set_layout(_layout.clone())
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_SORT_CHILDREN:
|
||||
_resort()
|
||||
elif (
|
||||
what == NOTIFICATION_DRAG_BEGIN
|
||||
and _can_handle_drag_data(get_viewport().gui_get_drag_data())
|
||||
):
|
||||
_drag_n_drop_panel.set_enabled(true, not _layout.root.is_empty())
|
||||
set_process_input(true)
|
||||
elif what == NOTIFICATION_DRAG_END:
|
||||
_drag_n_drop_panel.set_enabled(false)
|
||||
set_process_input(false)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
assert(get_viewport().gui_is_dragging(), "FIXME: should only be called when dragging")
|
||||
if event is InputEventMouseMotion:
|
||||
var local_position := get_local_mouse_position()
|
||||
var panel: DockablePanel
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var p := _panel_container.get_child(i) as DockablePanel
|
||||
if p.get_rect().has_point(local_position):
|
||||
panel = p
|
||||
break
|
||||
_drag_panel = panel
|
||||
if not panel:
|
||||
return
|
||||
fit_child_in_rect(_drag_n_drop_panel, panel.get_child_rect())
|
||||
|
||||
|
||||
func _child_entered_tree(node: Node) -> void:
|
||||
if node == _panel_container or node == _drag_n_drop_panel:
|
||||
return
|
||||
_drag_n_drop_panel.move_to_front()
|
||||
_track_and_add_node(node)
|
||||
|
||||
|
||||
func _child_exiting_tree(node: Node) -> void:
|
||||
if node == _panel_container or node == _drag_n_drop_panel:
|
||||
return
|
||||
_untrack_node(node)
|
||||
|
||||
|
||||
func _can_drop_data(_position: Vector2, data) -> bool:
|
||||
return _can_handle_drag_data(data)
|
||||
|
||||
|
||||
func _drop_data(_position: Vector2, data) -> void:
|
||||
var from_node := get_node(data.from_path)
|
||||
if from_node is TabBar:
|
||||
from_node = from_node.get_parent()
|
||||
if from_node == _drag_panel and _drag_panel.get_child_count() == 1:
|
||||
return
|
||||
var tab_index = data.tabc_element if data.has("tabc_element") else data.tab_index
|
||||
var moved_tab = from_node.get_tab_control(tab_index)
|
||||
if moved_tab is DockableReferenceControl:
|
||||
moved_tab = moved_tab.reference_to
|
||||
if not _is_managed_node(moved_tab):
|
||||
moved_tab.get_parent().remove_child(moved_tab)
|
||||
add_child(moved_tab)
|
||||
|
||||
if _drag_panel != null:
|
||||
var margin := _drag_n_drop_panel.get_hover_margin()
|
||||
_layout.split_leaf_with_node(_drag_panel.leaf, moved_tab, margin)
|
||||
|
||||
_layout_dirty = true
|
||||
queue_sort()
|
||||
|
||||
|
||||
func set_control_as_current_tab(control: Control) -> void:
|
||||
assert(
|
||||
control.get_parent_control() == self,
|
||||
"Trying to focus a control not managed by this container"
|
||||
)
|
||||
if is_control_hidden(control):
|
||||
push_warning("Trying to focus a hidden control")
|
||||
return
|
||||
var leaf := _layout.get_leaf_for_node(control)
|
||||
if not leaf:
|
||||
return
|
||||
var position_in_leaf := leaf.find_child(control)
|
||||
if position_in_leaf < 0:
|
||||
return
|
||||
var panel: DockablePanel
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var p := _panel_container.get_child(i) as DockablePanel
|
||||
if p.leaf == leaf:
|
||||
panel = p
|
||||
break
|
||||
if not panel:
|
||||
return
|
||||
panel.current_tab = clampi(position_in_leaf, 0, panel.get_tab_count() - 1)
|
||||
|
||||
|
||||
func set_layout(value: DockableLayout) -> void:
|
||||
if value == null:
|
||||
value = DockableLayout.new()
|
||||
if value == _layout:
|
||||
return
|
||||
if _layout and _layout.changed.is_connected(queue_sort):
|
||||
_layout.changed.disconnect(queue_sort)
|
||||
_layout = value
|
||||
_layout.changed.connect(queue_sort)
|
||||
_layout_dirty = true
|
||||
queue_sort()
|
||||
|
||||
|
||||
func set_use_hidden_tabs_for_min_size(value: bool) -> void:
|
||||
_use_hidden_tabs_for_min_size = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel = _panel_container.get_child(i)
|
||||
panel.use_hidden_tabs_for_min_size = value
|
||||
|
||||
|
||||
func get_use_hidden_tabs_for_min_size() -> bool:
|
||||
return _use_hidden_tabs_for_min_size
|
||||
|
||||
|
||||
func set_control_hidden(child: Control, is_hidden: bool) -> void:
|
||||
_layout.set_node_hidden(child, is_hidden)
|
||||
|
||||
|
||||
func is_control_hidden(child: Control) -> bool:
|
||||
return _layout.is_node_hidden(child)
|
||||
|
||||
|
||||
func get_tabs() -> Array[Control]:
|
||||
var tabs: Array[Control] = []
|
||||
for i in get_child_count():
|
||||
var child := get_child(i)
|
||||
if _is_managed_node(child):
|
||||
tabs.append(child)
|
||||
return tabs
|
||||
|
||||
|
||||
func get_tab_count() -> int:
|
||||
var count := 0
|
||||
for i in get_child_count():
|
||||
var child := get_child(i)
|
||||
if _is_managed_node(child):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _can_handle_drag_data(data) -> bool:
|
||||
if data is Dictionary and data.get("type") in ["tab_container_tab", "tabc_element"]:
|
||||
var tabc := get_node_or_null(data.get("from_path"))
|
||||
return (
|
||||
tabc
|
||||
and tabc.has_method("get_tabs_rearrange_group")
|
||||
and tabc.get_tabs_rearrange_group() == rearrange_group
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
func _is_managed_node(node: Node) -> bool:
|
||||
return (
|
||||
node.get_parent() == self
|
||||
and node != _panel_container
|
||||
and node != _drag_n_drop_panel
|
||||
and node is Control
|
||||
and not node.top_level
|
||||
)
|
||||
|
||||
|
||||
func _update_layout_with_children() -> void:
|
||||
var names := PackedStringArray()
|
||||
_children_names.clear()
|
||||
for i in range(1, get_child_count() - 1):
|
||||
var c := get_child(i)
|
||||
if _track_node(c):
|
||||
names.append(c.name)
|
||||
_layout.update_nodes(names)
|
||||
_layout_dirty = false
|
||||
|
||||
|
||||
func _track_node(node: Node) -> bool:
|
||||
if not _is_managed_node(node):
|
||||
return false
|
||||
_children_names[node] = node.name
|
||||
_children_names[node.name] = node
|
||||
if not node.renamed.is_connected(_on_child_renamed):
|
||||
node.renamed.connect(_on_child_renamed.bind(node))
|
||||
if not node.tree_exiting.is_connected(_untrack_node):
|
||||
node.tree_exiting.connect(_untrack_node.bind(node))
|
||||
return true
|
||||
|
||||
|
||||
func _track_and_add_node(node: Node) -> void:
|
||||
var tracked_name = _children_names.get(node)
|
||||
if not _track_node(node):
|
||||
return
|
||||
if tracked_name and tracked_name != node.name:
|
||||
_layout.rename_node(tracked_name, node.name)
|
||||
_layout_dirty = true
|
||||
|
||||
|
||||
func _untrack_node(node: Node) -> void:
|
||||
_children_names.erase(node)
|
||||
_children_names.erase(node.name)
|
||||
if node.renamed.is_connected(_on_child_renamed):
|
||||
node.renamed.disconnect(_on_child_renamed)
|
||||
if node.tree_exiting.is_connected(_untrack_node):
|
||||
node.tree_exiting.disconnect(_untrack_node)
|
||||
_layout_dirty = true
|
||||
|
||||
|
||||
func _resort() -> void:
|
||||
assert(_panel_container, "FIXME: resorting without _panel_container")
|
||||
if _panel_container.get_index() != 0:
|
||||
move_child(_panel_container, 0)
|
||||
if _drag_n_drop_panel.get_index() < get_child_count() - 1:
|
||||
_drag_n_drop_panel.move_to_front()
|
||||
|
||||
if _layout_dirty:
|
||||
_update_layout_with_children()
|
||||
|
||||
var rect := Rect2(Vector2.ZERO, size)
|
||||
fit_child_in_rect(_panel_container, rect)
|
||||
_panel_container.fit_child_in_rect(_split_container, rect)
|
||||
|
||||
_current_panel_index = 1
|
||||
_current_split_index = 0
|
||||
|
||||
var children_list := []
|
||||
_calculate_panel_and_split_list(children_list, _layout.root)
|
||||
_fit_panel_and_split_list_to_rect(children_list, rect)
|
||||
|
||||
_untrack_children_after(_panel_container, _current_panel_index)
|
||||
_untrack_children_after(_split_container, _current_split_index)
|
||||
|
||||
|
||||
## Calculate DockablePanel and SplitHandle minimum sizes, skipping empty
|
||||
## branches.
|
||||
##
|
||||
## Returns a DockablePanel checked non-empty leaves, a SplitHandle checked non-empty
|
||||
## splits, `null` if the whole branch is empty and no space should be used.
|
||||
##
|
||||
## `result` will be filled with the non-empty nodes in this post-order tree
|
||||
## traversal.
|
||||
func _calculate_panel_and_split_list(result: Array, layout_node: DockableLayoutNode):
|
||||
if layout_node is DockableLayoutPanel:
|
||||
var nodes: Array[Control] = []
|
||||
for n in layout_node.names:
|
||||
var node: Control = _children_names.get(n)
|
||||
if node:
|
||||
assert(node is Control, "FIXME: node is not a control %s" % node)
|
||||
assert(
|
||||
node.get_parent_control() == self,
|
||||
"FIXME: node is not child of container %s" % node
|
||||
)
|
||||
if is_control_hidden(node):
|
||||
node.visible = false
|
||||
else:
|
||||
nodes.append(node)
|
||||
if nodes.is_empty():
|
||||
return null
|
||||
else:
|
||||
var panel := _get_panel(_current_panel_index)
|
||||
_current_panel_index += 1
|
||||
panel.track_nodes(nodes, layout_node)
|
||||
result.append(panel)
|
||||
return panel
|
||||
elif layout_node is DockableLayoutSplit:
|
||||
# by processing `second` before `first`, traversing `result` from back
|
||||
# to front yields a nice pre-order tree traversal
|
||||
var second_result = _calculate_panel_and_split_list(result, layout_node.second)
|
||||
var first_result = _calculate_panel_and_split_list(result, layout_node.first)
|
||||
if first_result and second_result:
|
||||
var split := _get_split(_current_split_index)
|
||||
_current_split_index += 1
|
||||
split.layout_split = layout_node
|
||||
split.first_minimum_size = first_result.get_layout_minimum_size()
|
||||
split.second_minimum_size = second_result.get_layout_minimum_size()
|
||||
result.append(split)
|
||||
return split
|
||||
elif first_result:
|
||||
return first_result
|
||||
else: # NOTE: this returns null if `second_result` is null
|
||||
return second_result
|
||||
else:
|
||||
push_warning("FIXME: invalid Resource, should be branch or leaf, found %s" % layout_node)
|
||||
|
||||
|
||||
## Traverse list from back to front fitting controls where they belong.
|
||||
##
|
||||
## Be sure to call this with the result from `_calculate_split_minimum_sizes`.
|
||||
func _fit_panel_and_split_list_to_rect(panel_and_split_list: Array, rect: Rect2) -> void:
|
||||
var control = panel_and_split_list.pop_back()
|
||||
if control is DockablePanel:
|
||||
_panel_container.fit_child_in_rect(control, rect)
|
||||
elif control is SplitHandle:
|
||||
var split_rects = control.get_split_rects(rect)
|
||||
_split_container.fit_child_in_rect(control, split_rects["self"])
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["first"])
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["second"])
|
||||
|
||||
|
||||
## Get the idx'th DockablePanel, reusing an instanced one if possible
|
||||
func _get_panel(idx: int) -> DockablePanel:
|
||||
assert(_panel_container, "FIXME: creating panel without _panel_container")
|
||||
if idx < _panel_container.get_child_count():
|
||||
return _panel_container.get_child(idx)
|
||||
var panel := DockablePanel.new()
|
||||
panel.tab_alignment = _tab_align
|
||||
panel.show_tabs = _tabs_visible
|
||||
panel.hide_single_tab = _hide_single_tab
|
||||
panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size
|
||||
panel.set_tabs_rearrange_group(maxi(0, rearrange_group))
|
||||
_panel_container.add_child(panel)
|
||||
panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel))
|
||||
return panel
|
||||
|
||||
|
||||
## Get the idx'th SplitHandle, reusing an instanced one if possible
|
||||
func _get_split(idx: int) -> SplitHandle:
|
||||
assert(_split_container, "FIXME: creating split without _split_container")
|
||||
if idx < _split_container.get_child_count():
|
||||
return _split_container.get_child(idx)
|
||||
var split := SplitHandle.new()
|
||||
_split_container.add_child(split)
|
||||
return split
|
||||
|
||||
|
||||
## Helper for removing and freeing all remaining children from node
|
||||
func _untrack_children_after(node: Control, idx: int) -> void:
|
||||
for i in range(idx, node.get_child_count()):
|
||||
var child := node.get_child(idx)
|
||||
node.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
## Handler for `DockablePanel.tab_layout_changed`, update its DockableLayoutPanel
|
||||
func _on_panel_tab_layout_changed(tab: int, panel: DockablePanel) -> void:
|
||||
_layout_dirty = true
|
||||
var control := panel.get_tab_control(tab)
|
||||
if control is DockableReferenceControl:
|
||||
control = control.reference_to
|
||||
if not _is_managed_node(control):
|
||||
control.get_parent().remove_child(control)
|
||||
add_child(control)
|
||||
_layout.move_node_to_leaf(control, panel.leaf, tab)
|
||||
queue_sort()
|
||||
|
||||
|
||||
## Handler for `Node.renamed` signal, updates tracked name for node
|
||||
func _on_child_renamed(child: Node) -> void:
|
||||
var old_name: String = _children_names.get(child)
|
||||
if old_name == str(child.name):
|
||||
return
|
||||
_children_names.erase(old_name)
|
||||
_children_names[child] = child.name
|
||||
_children_names[child.name] = child
|
||||
_layout.rename_node(old_name, child.name)
|
||||
108
addons/dockable_container/dockable_panel.gd
Normal file
@@ -0,0 +1,108 @@
|
||||
@tool
|
||||
extends TabContainer
|
||||
|
||||
signal tab_layout_changed(tab)
|
||||
|
||||
var leaf: DockableLayoutPanel:
|
||||
get:
|
||||
return get_leaf()
|
||||
set(value):
|
||||
set_leaf(value)
|
||||
var show_tabs := true:
|
||||
get:
|
||||
return _show_tabs
|
||||
set(value):
|
||||
_show_tabs = value
|
||||
_handle_tab_visibility()
|
||||
var hide_single_tab := false:
|
||||
get:
|
||||
return _hide_single_tab
|
||||
set(value):
|
||||
_hide_single_tab = value
|
||||
_handle_tab_visibility()
|
||||
|
||||
var _leaf: DockableLayoutPanel
|
||||
var _show_tabs := true
|
||||
var _hide_single_tab := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
drag_to_rearrange_enabled = true
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
active_tab_rearranged.connect(_on_tab_changed)
|
||||
tab_selected.connect(_on_tab_selected)
|
||||
tab_changed.connect(_on_tab_changed)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
active_tab_rearranged.disconnect(_on_tab_changed)
|
||||
tab_selected.disconnect(_on_tab_selected)
|
||||
tab_changed.disconnect(_on_tab_changed)
|
||||
|
||||
|
||||
func track_nodes(nodes: Array[Control], new_leaf: DockableLayoutPanel) -> void:
|
||||
_leaf = null # avoid using previous leaf in tab_changed signals
|
||||
var min_size := mini(nodes.size(), get_child_count())
|
||||
# remove spare children
|
||||
for i in range(min_size, get_child_count()):
|
||||
var child := get_child(min_size) as DockableReferenceControl
|
||||
child.reference_to = null
|
||||
remove_child(child)
|
||||
child.queue_free()
|
||||
# add missing children
|
||||
for i in range(min_size, nodes.size()):
|
||||
var ref_control := DockableReferenceControl.new()
|
||||
add_child(ref_control)
|
||||
assert(nodes.size() == get_child_count(), "FIXME")
|
||||
# setup children
|
||||
for i in nodes.size():
|
||||
var ref_control := get_child(i) as DockableReferenceControl
|
||||
ref_control.reference_to = nodes[i]
|
||||
set_tab_title(i, nodes[i].name)
|
||||
set_leaf(new_leaf)
|
||||
_handle_tab_visibility()
|
||||
|
||||
|
||||
func get_child_rect() -> Rect2:
|
||||
var control := get_current_tab_control()
|
||||
return Rect2(position + control.position, control.size)
|
||||
|
||||
|
||||
func set_leaf(value: DockableLayoutPanel) -> void:
|
||||
if get_tab_count() > 0 and value:
|
||||
current_tab = clampi(value.current_tab, 0, get_tab_count() - 1)
|
||||
_leaf = value
|
||||
|
||||
|
||||
func get_leaf() -> DockableLayoutPanel:
|
||||
return _leaf
|
||||
|
||||
|
||||
func get_layout_minimum_size() -> Vector2:
|
||||
return get_combined_minimum_size()
|
||||
|
||||
|
||||
func _on_tab_selected(tab: int) -> void:
|
||||
if _leaf:
|
||||
_leaf.current_tab = tab
|
||||
|
||||
|
||||
func _on_tab_changed(tab: int) -> void:
|
||||
if not _leaf:
|
||||
return
|
||||
var control := get_tab_control(tab)
|
||||
if not control:
|
||||
return
|
||||
var tab_name := control.name
|
||||
var name_index_in_leaf := _leaf.find_name(tab_name)
|
||||
if name_index_in_leaf != tab: # NOTE: this handles added tabs (index == -1)
|
||||
tab_layout_changed.emit(tab)
|
||||
|
||||
|
||||
func _handle_tab_visibility() -> void:
|
||||
if _hide_single_tab and get_tab_count() == 1:
|
||||
tabs_visible = false
|
||||
else:
|
||||
tabs_visible = _show_tabs
|
||||
@@ -0,0 +1,49 @@
|
||||
@tool
|
||||
class_name DockableReferenceControl
|
||||
extends Container
|
||||
## Control that mimics its own visibility and rect into another Control.
|
||||
|
||||
var reference_to: Control:
|
||||
get:
|
||||
return _reference_to
|
||||
set(control):
|
||||
if _reference_to != control:
|
||||
if is_instance_valid(_reference_to):
|
||||
_reference_to.renamed.disconnect(_on_reference_to_renamed)
|
||||
_reference_to.minimum_size_changed.disconnect(update_minimum_size)
|
||||
_reference_to = control
|
||||
|
||||
minimum_size_changed.emit()
|
||||
if not is_instance_valid(_reference_to):
|
||||
return
|
||||
_reference_to.renamed.connect(_on_reference_to_renamed)
|
||||
_reference_to.minimum_size_changed.connect(update_minimum_size)
|
||||
_reference_to.visible = visible
|
||||
_reposition_reference()
|
||||
|
||||
var _reference_to: Control = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = MOUSE_FILTER_IGNORE
|
||||
set_notify_transform(true)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED and _reference_to:
|
||||
_reference_to.visible = visible
|
||||
elif what == NOTIFICATION_TRANSFORM_CHANGED and _reference_to:
|
||||
_reposition_reference()
|
||||
|
||||
|
||||
func _get_minimum_size() -> Vector2:
|
||||
return _reference_to.get_combined_minimum_size() if _reference_to else Vector2.ZERO
|
||||
|
||||
|
||||
func _reposition_reference() -> void:
|
||||
_reference_to.global_position = global_position
|
||||
_reference_to.size = size
|
||||
|
||||
|
||||
func _on_reference_to_renamed() -> void:
|
||||
name = _reference_to.name
|
||||
82
addons/dockable_container/drag_n_drop_panel.gd
Normal file
@@ -0,0 +1,82 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
||||
|
||||
const DRAW_NOTHING := -1
|
||||
const DRAW_CENTERED := -2
|
||||
const MARGIN_NONE := -1
|
||||
|
||||
var _draw_margin := DRAW_NOTHING
|
||||
var _should_split := false
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_MOUSE_EXIT:
|
||||
_draw_margin = DRAW_NOTHING
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_MOUSE_ENTER and not _should_split:
|
||||
_draw_margin = DRAW_CENTERED
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if _should_split and event is InputEventMouseMotion:
|
||||
_draw_margin = _find_hover_margin(event.position)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var rect: Rect2
|
||||
if _draw_margin == DRAW_NOTHING:
|
||||
return
|
||||
elif _draw_margin == DRAW_CENTERED:
|
||||
rect = Rect2(Vector2.ZERO, size)
|
||||
elif _draw_margin == MARGIN_LEFT:
|
||||
rect = Rect2(0, 0, size.x * 0.5, size.y)
|
||||
elif _draw_margin == MARGIN_TOP:
|
||||
rect = Rect2(0, 0, size.x, size.y * 0.5)
|
||||
elif _draw_margin == MARGIN_RIGHT:
|
||||
var half_width = size.x * 0.5
|
||||
rect = Rect2(half_width, 0, half_width, size.y)
|
||||
elif _draw_margin == MARGIN_BOTTOM:
|
||||
var half_height = size.y * 0.5
|
||||
rect = Rect2(0, half_height, size.x, half_height)
|
||||
var stylebox := get_theme_stylebox("panel", "TooltipPanel")
|
||||
draw_style_box(stylebox, rect)
|
||||
|
||||
|
||||
func set_enabled(enabled: bool, should_split: bool = true) -> void:
|
||||
visible = enabled
|
||||
_should_split = should_split
|
||||
if enabled:
|
||||
_draw_margin = DRAW_NOTHING
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func get_hover_margin() -> int:
|
||||
return _draw_margin
|
||||
|
||||
|
||||
func _find_hover_margin(point: Vector2) -> int:
|
||||
var half_size := size * 0.5
|
||||
|
||||
var left := point.distance_squared_to(Vector2(0, half_size.y))
|
||||
var lesser := left
|
||||
var lesser_margin := MARGIN_LEFT
|
||||
|
||||
var top := point.distance_squared_to(Vector2(half_size.x, 0))
|
||||
if lesser > top:
|
||||
lesser = top
|
||||
lesser_margin = MARGIN_TOP
|
||||
|
||||
var right := point.distance_squared_to(Vector2(size.x, half_size.y))
|
||||
if lesser > right:
|
||||
lesser = right
|
||||
lesser_margin = MARGIN_RIGHT
|
||||
|
||||
var bottom := point.distance_squared_to(Vector2(half_size.x, size.y))
|
||||
if lesser > bottom:
|
||||
#lesser = bottom # unused result
|
||||
lesser_margin = MARGIN_BOTTOM
|
||||
return lesser_margin
|
||||
10
addons/dockable_container/icon.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="4.001" y="4.016" width="4.005" height="5.004" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="8.999" y="7.016" width="3.006" height="5.004" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="4.004" y="10.023" width="4.005" height="1.99" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="9" y="3.991" width="3.006" height="2.031" rx="1" ry="1"/>
|
||||
</g>
|
||||
<path d="M 13 1 C 14.097 1 15 1.903 15 3 L 15 13 C 15 14.097 14.097 15 13 15 L 3 15 C 1.903 15 1 14.097 1 13 L 1 3 C 1 1.903 1.903 1 3 1 L 13 1 Z M 3 13 L 13 13 L 13 3 L 3 3 L 3 13 Z" fill-rule="nonzero" style="fill-rule: nonzero; fill: rgb(142, 239, 152);"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 979 B |
37
addons/dockable_container/icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dy25danh2am23"
|
||||
path="res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dockable_container/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.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,22 @@
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
const LayoutEditorProperty := preload("layout_editor_property.gd")
|
||||
|
||||
|
||||
func _can_handle(object: Object) -> bool:
|
||||
return object is DockableContainer
|
||||
|
||||
|
||||
func _parse_property(
|
||||
_object: Object,
|
||||
_type: Variant.Type,
|
||||
name: String,
|
||||
_hint: PropertyHint,
|
||||
_hint_text: String,
|
||||
_usage: int,
|
||||
_wide: bool
|
||||
) -> bool:
|
||||
if name == "layout":
|
||||
var editor_property := LayoutEditorProperty.new()
|
||||
add_property_editor("layout", editor_property)
|
||||
return false
|
||||
@@ -0,0 +1,71 @@
|
||||
extends EditorProperty
|
||||
|
||||
var _container := DockableContainer.new()
|
||||
var _hidden_menu_button := MenuButton.new()
|
||||
var _hidden_menu_popup: PopupMenu
|
||||
var _hidden_menu_list: PackedStringArray
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(128, 256)
|
||||
|
||||
_hidden_menu_button.text = "Visible nodes"
|
||||
add_child(_hidden_menu_button)
|
||||
_hidden_menu_popup = _hidden_menu_button.get_popup()
|
||||
_hidden_menu_popup.hide_on_checkable_item_selection = false
|
||||
_hidden_menu_popup.about_to_popup.connect(_on_hidden_menu_popup_about_to_show)
|
||||
_hidden_menu_popup.id_pressed.connect(_on_hidden_menu_popup_id_pressed)
|
||||
|
||||
_container.clone_layout_on_ready = false
|
||||
_container.custom_minimum_size = custom_minimum_size
|
||||
|
||||
var value := _get_layout().clone() # The layout gets reset when selecting it without clone
|
||||
for n in value.get_names():
|
||||
var child := _create_child_control(n)
|
||||
_container.add_child(child)
|
||||
_container.set(get_edited_property(), value)
|
||||
add_child(_container)
|
||||
set_bottom_editor(_container)
|
||||
|
||||
|
||||
func _exit_tree() -> void: # Not sure if this is needed, but just to be sure
|
||||
queue_free()
|
||||
|
||||
|
||||
func _update_property() -> void:
|
||||
var value := _get_layout()
|
||||
_container.set(get_edited_property(), value)
|
||||
|
||||
|
||||
func _get_layout() -> DockableLayout:
|
||||
var original_container := get_edited_object() as DockableContainer
|
||||
return original_container.get(get_edited_property())
|
||||
|
||||
|
||||
func _create_child_control(named: String) -> Label:
|
||||
var new_control := Label.new()
|
||||
new_control.name = named
|
||||
new_control.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
new_control.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
new_control.clip_text = true
|
||||
new_control.text = named
|
||||
return new_control
|
||||
|
||||
|
||||
func _on_hidden_menu_popup_about_to_show() -> void:
|
||||
var layout := _get_layout().clone()
|
||||
_hidden_menu_popup.clear()
|
||||
_hidden_menu_list = layout.get_names()
|
||||
for i in _hidden_menu_list.size():
|
||||
var tab_name := _hidden_menu_list[i]
|
||||
_hidden_menu_popup.add_check_item(tab_name, i)
|
||||
_hidden_menu_popup.set_item_checked(i, not layout.is_tab_hidden(tab_name))
|
||||
|
||||
|
||||
func _on_hidden_menu_popup_id_pressed(id: int) -> void:
|
||||
var layout := _get_layout().clone()
|
||||
var tab_name := _hidden_menu_list[id]
|
||||
var new_hidden := not layout.is_tab_hidden(tab_name)
|
||||
_get_layout().set_tab_hidden(tab_name, new_hidden)
|
||||
_hidden_menu_popup.set_item_checked(id, not new_hidden)
|
||||
emit_changed(get_edited_property(), _get_layout()) # This line may not be needed
|
||||
242
addons/dockable_container/layout.gd
Normal file
@@ -0,0 +1,242 @@
|
||||
@tool
|
||||
class_name DockableLayout
|
||||
extends Resource
|
||||
## DockableLayout Resource definition, holding the root DockableLayoutNode and hidden tabs.
|
||||
##
|
||||
## DockableLayoutSplit are binary trees with nested DockableLayoutSplit subtrees
|
||||
## and DockableLayoutPanel leaves. Both of them inherit from DockableLayoutNode to help with
|
||||
## type annotation and define common functionality.
|
||||
##
|
||||
## Hidden tabs are marked in the `hidden_tabs` Dictionary by name.
|
||||
|
||||
enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
||||
|
||||
@export var root: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return _root
|
||||
set(value):
|
||||
set_root(value)
|
||||
@export var hidden_tabs := {}:
|
||||
get:
|
||||
return _hidden_tabs
|
||||
set(value):
|
||||
if value != _hidden_tabs:
|
||||
_hidden_tabs = value
|
||||
changed.emit()
|
||||
|
||||
var _changed_signal_queued := false
|
||||
var _first_leaf: DockableLayoutPanel
|
||||
var _hidden_tabs: Dictionary
|
||||
var _leaf_by_node_name: Dictionary
|
||||
var _root: DockableLayoutNode = DockableLayoutPanel.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
resource_name = "Layout"
|
||||
|
||||
|
||||
func set_root(value: DockableLayoutNode, should_emit_changed := true) -> void:
|
||||
if not value:
|
||||
value = DockableLayoutPanel.new()
|
||||
if _root == value:
|
||||
return
|
||||
if _root and _root.changed.is_connected(_on_root_changed):
|
||||
_root.changed.disconnect(_on_root_changed)
|
||||
_root = value
|
||||
_root.parent = null
|
||||
_root.changed.connect(_on_root_changed)
|
||||
if should_emit_changed:
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func get_root() -> DockableLayoutNode:
|
||||
return _root
|
||||
|
||||
|
||||
func clone() -> DockableLayout:
|
||||
return duplicate(true)
|
||||
|
||||
|
||||
func get_names() -> PackedStringArray:
|
||||
return _root.get_names()
|
||||
|
||||
|
||||
## Add missing nodes on first leaf and remove nodes outside indices from leaves.
|
||||
##
|
||||
## _leaf_by_node_name = {
|
||||
## (string keys) = respective Leaf that holds the node name,
|
||||
## }
|
||||
func update_nodes(names: PackedStringArray) -> void:
|
||||
_leaf_by_node_name.clear()
|
||||
_first_leaf = null
|
||||
var empty_leaves: Array[DockableLayoutPanel] = []
|
||||
_ensure_names_in_node(_root, names, empty_leaves) # Changes _leaf_by_node_name and empty_leaves
|
||||
for l in empty_leaves:
|
||||
_remove_leaf(l)
|
||||
if not _first_leaf:
|
||||
_first_leaf = DockableLayoutPanel.new()
|
||||
set_root(_first_leaf)
|
||||
for n in names:
|
||||
if not _leaf_by_node_name.has(n):
|
||||
_first_leaf.push_name(n)
|
||||
_leaf_by_node_name[n] = _first_leaf
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func move_node_to_leaf(node: Node, leaf: DockableLayoutPanel, relative_position: int) -> void:
|
||||
var node_name := node.name
|
||||
var previous_leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
||||
if previous_leaf:
|
||||
previous_leaf.remove_node(node)
|
||||
if previous_leaf.is_empty():
|
||||
_remove_leaf(previous_leaf)
|
||||
|
||||
leaf.insert_node(relative_position, node)
|
||||
_leaf_by_node_name[node_name] = leaf
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func get_leaf_for_node(node: Node) -> DockableLayoutPanel:
|
||||
return _leaf_by_node_name.get(node.name)
|
||||
|
||||
|
||||
func split_leaf_with_node(leaf: DockableLayoutPanel, node: Node, margin: int) -> void:
|
||||
var root_branch := leaf.parent
|
||||
var new_leaf := DockableLayoutPanel.new()
|
||||
var new_branch := DockableLayoutSplit.new()
|
||||
if margin == MARGIN_LEFT or margin == MARGIN_RIGHT:
|
||||
new_branch.direction = DockableLayoutSplit.Direction.HORIZONTAL
|
||||
else:
|
||||
new_branch.direction = DockableLayoutSplit.Direction.VERTICAL
|
||||
if margin == MARGIN_LEFT or margin == MARGIN_TOP:
|
||||
new_branch.first = new_leaf
|
||||
new_branch.second = leaf
|
||||
else:
|
||||
new_branch.first = leaf
|
||||
new_branch.second = new_leaf
|
||||
if _root == leaf:
|
||||
set_root(new_branch, false)
|
||||
elif root_branch:
|
||||
if leaf == root_branch.first:
|
||||
root_branch.first = new_branch
|
||||
else:
|
||||
root_branch.second = new_branch
|
||||
|
||||
move_node_to_leaf(node, new_leaf, 0)
|
||||
|
||||
|
||||
func add_node(node: Node) -> void:
|
||||
var node_name := node.name
|
||||
if _leaf_by_node_name.has(node_name):
|
||||
return
|
||||
_first_leaf.push_name(node_name)
|
||||
_leaf_by_node_name[node_name] = _first_leaf
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func remove_node(node: Node) -> void:
|
||||
var node_name := node.name
|
||||
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
||||
if not leaf:
|
||||
return
|
||||
leaf.remove_node(node)
|
||||
_leaf_by_node_name.erase(node_name)
|
||||
if leaf.is_empty():
|
||||
_remove_leaf(leaf)
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func rename_node(previous_name: String, new_name: String) -> void:
|
||||
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(previous_name)
|
||||
if not leaf:
|
||||
return
|
||||
leaf.rename_node(previous_name, new_name)
|
||||
_leaf_by_node_name.erase(previous_name)
|
||||
_leaf_by_node_name[new_name] = leaf
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func set_tab_hidden(name: String, hidden: bool) -> void:
|
||||
if not _leaf_by_node_name.has(name):
|
||||
return
|
||||
if hidden:
|
||||
_hidden_tabs[name] = true
|
||||
else:
|
||||
_hidden_tabs.erase(name)
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func is_tab_hidden(name: String) -> bool:
|
||||
return _hidden_tabs.get(name, false)
|
||||
|
||||
|
||||
func set_node_hidden(node: Node, hidden: bool) -> void:
|
||||
set_tab_hidden(node.name, hidden)
|
||||
|
||||
|
||||
func is_node_hidden(node: Node) -> bool:
|
||||
return is_tab_hidden(node.name)
|
||||
|
||||
|
||||
func _on_root_changed() -> void:
|
||||
if _changed_signal_queued:
|
||||
return
|
||||
_changed_signal_queued = true
|
||||
set_deferred("_changed_signal_queued", false)
|
||||
emit_changed.call_deferred()
|
||||
|
||||
|
||||
func _ensure_names_in_node(
|
||||
node: DockableLayoutNode, names: PackedStringArray, empty_leaves: Array[DockableLayoutPanel]
|
||||
) -> void:
|
||||
if node is DockableLayoutPanel:
|
||||
node.update_nodes(names, _leaf_by_node_name) # This changes _leaf_by_node_name
|
||||
if node.is_empty():
|
||||
empty_leaves.append(node)
|
||||
if not _first_leaf:
|
||||
_first_leaf = node
|
||||
elif node is DockableLayoutSplit:
|
||||
_ensure_names_in_node(node.first, names, empty_leaves)
|
||||
_ensure_names_in_node(node.second, names, empty_leaves)
|
||||
else:
|
||||
assert(false, "Invalid Resource, should be branch or leaf, found %s" % node)
|
||||
|
||||
|
||||
func _remove_leaf(leaf: DockableLayoutPanel) -> void:
|
||||
assert(leaf.is_empty(), "FIXME: trying to remove_at a leaf with nodes")
|
||||
if _root == leaf:
|
||||
return
|
||||
var collapsed_branch := leaf.parent
|
||||
assert(collapsed_branch is DockableLayoutSplit, "FIXME: leaf is not a child of branch")
|
||||
var kept_branch: DockableLayoutNode = (
|
||||
collapsed_branch.first if leaf == collapsed_branch.second else collapsed_branch.second
|
||||
)
|
||||
var root_branch := collapsed_branch.parent #HERE
|
||||
if collapsed_branch == _root:
|
||||
set_root(kept_branch, true)
|
||||
elif root_branch:
|
||||
if collapsed_branch == root_branch.first:
|
||||
root_branch.first = kept_branch
|
||||
else:
|
||||
root_branch.second = kept_branch
|
||||
|
||||
|
||||
func _print_tree() -> void:
|
||||
print("TREE")
|
||||
_print_tree_step(_root, 0, 0)
|
||||
print("")
|
||||
|
||||
|
||||
func _print_tree_step(tree_or_leaf: DockableLayoutNode, level: int, idx: int) -> void:
|
||||
if tree_or_leaf is DockableLayoutPanel:
|
||||
print(" |".repeat(level), "- (%d) = " % idx, tree_or_leaf.names)
|
||||
elif tree_or_leaf is DockableLayoutSplit:
|
||||
print(
|
||||
" |".repeat(level),
|
||||
"-+ (%d) = " % idx,
|
||||
tree_or_leaf.direction,
|
||||
" ",
|
||||
tree_or_leaf.percent
|
||||
)
|
||||
_print_tree_step(tree_or_leaf.first, level + 1, 1)
|
||||
_print_tree_step(tree_or_leaf.second, level + 1, 2)
|
||||
29
addons/dockable_container/layout_node.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
@tool
|
||||
class_name DockableLayoutNode
|
||||
extends Resource
|
||||
## Base class for DockableLayout tree nodes
|
||||
|
||||
var parent: DockableLayoutSplit:
|
||||
get:
|
||||
return _parent_ref.get_ref()
|
||||
set(value):
|
||||
_parent_ref = weakref(value)
|
||||
|
||||
var _parent_ref := WeakRef.new()
|
||||
|
||||
|
||||
func emit_tree_changed() -> void:
|
||||
var node := self
|
||||
while node:
|
||||
node.emit_changed()
|
||||
node = node.parent
|
||||
|
||||
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
## Returns all tab names in this node
|
||||
func get_names() -> PackedStringArray:
|
||||
return PackedStringArray()
|
||||
89
addons/dockable_container/layout_panel.gd
Normal file
@@ -0,0 +1,89 @@
|
||||
@tool
|
||||
class_name DockableLayoutPanel
|
||||
extends DockableLayoutNode
|
||||
## DockableLayout leaf nodes, defining tabs
|
||||
|
||||
@export var names: PackedStringArray:
|
||||
get:
|
||||
return get_names()
|
||||
set(value):
|
||||
_names = value
|
||||
emit_tree_changed()
|
||||
@export var current_tab: int:
|
||||
get:
|
||||
return int(clamp(_current_tab, 0, _names.size() - 1))
|
||||
set(value):
|
||||
if value != _current_tab:
|
||||
_current_tab = value
|
||||
emit_tree_changed()
|
||||
|
||||
var _names := PackedStringArray()
|
||||
var _current_tab := 0
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
resource_name = "Tabs"
|
||||
|
||||
|
||||
## Returns all tab names in this node
|
||||
func get_names() -> PackedStringArray:
|
||||
return _names
|
||||
|
||||
|
||||
func push_name(name: String) -> void:
|
||||
_names.append(name)
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func insert_node(position: int, node: Node) -> void:
|
||||
_names.insert(position, node.name)
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func find_name(node_name: String) -> int:
|
||||
for i in _names.size():
|
||||
if _names[i] == node_name:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
func find_child(node: Node) -> int:
|
||||
return find_name(node.name)
|
||||
|
||||
|
||||
func remove_node(node: Node) -> void:
|
||||
var i := find_child(node)
|
||||
if i >= 0:
|
||||
_names.remove_at(i)
|
||||
emit_tree_changed()
|
||||
else:
|
||||
push_warning("Remove failed, node '%s' was not found" % node)
|
||||
|
||||
|
||||
func rename_node(previous_name: String, new_name: String) -> void:
|
||||
var i := find_name(previous_name)
|
||||
if i >= 0:
|
||||
_names.set(i, new_name)
|
||||
emit_tree_changed()
|
||||
else:
|
||||
push_warning("Rename failed, name '%s' was not found" % previous_name)
|
||||
|
||||
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return _names.is_empty()
|
||||
|
||||
|
||||
func update_nodes(node_names: PackedStringArray, data: Dictionary) -> void:
|
||||
var i := 0
|
||||
var removed_any := false
|
||||
while i < _names.size():
|
||||
var current := _names[i]
|
||||
if not current in node_names or data.has(current):
|
||||
_names.remove_at(i)
|
||||
removed_any = true
|
||||
else:
|
||||
data[current] = self
|
||||
i += 1
|
||||
if removed_any:
|
||||
emit_tree_changed()
|
||||
100
addons/dockable_container/layout_split.gd
Normal file
@@ -0,0 +1,100 @@
|
||||
@tool
|
||||
class_name DockableLayoutSplit
|
||||
extends DockableLayoutNode
|
||||
## DockableLayout binary tree nodes, defining subtrees and leaf panels
|
||||
|
||||
enum Direction { HORIZONTAL, VERTICAL }
|
||||
|
||||
@export var direction := Direction.HORIZONTAL:
|
||||
get:
|
||||
return get_direction()
|
||||
set(value):
|
||||
set_direction(value)
|
||||
@export_range(0, 1) var percent := 0.5:
|
||||
get = get_percent,
|
||||
set = set_percent
|
||||
@export var first: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return get_first()
|
||||
set(value):
|
||||
set_first(value)
|
||||
@export var second: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return get_second()
|
||||
set(value):
|
||||
set_second(value)
|
||||
|
||||
var _direction := Direction.HORIZONTAL
|
||||
var _percent := 0.5
|
||||
var _first: DockableLayoutNode
|
||||
var _second: DockableLayoutNode
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
resource_name = "Split"
|
||||
|
||||
|
||||
func set_first(value: DockableLayoutNode) -> void:
|
||||
if value == null:
|
||||
_first = DockableLayoutPanel.new()
|
||||
else:
|
||||
_first = value
|
||||
_first.parent = self
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_first() -> DockableLayoutNode:
|
||||
return _first
|
||||
|
||||
|
||||
func set_second(value: DockableLayoutNode) -> void:
|
||||
if value == null:
|
||||
_second = DockableLayoutPanel.new()
|
||||
else:
|
||||
_second = value
|
||||
_second.parent = self
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_second() -> DockableLayoutNode:
|
||||
return _second
|
||||
|
||||
|
||||
func set_direction(value: Direction) -> void:
|
||||
if value != _direction:
|
||||
_direction = value
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_direction() -> Direction:
|
||||
return _direction
|
||||
|
||||
|
||||
func set_percent(value: float) -> void:
|
||||
var clamped_value := clampf(value, 0, 1)
|
||||
if not is_equal_approx(_percent, clamped_value):
|
||||
_percent = clamped_value
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_percent() -> float:
|
||||
return _percent
|
||||
|
||||
|
||||
func get_names() -> PackedStringArray:
|
||||
var names := _first.get_names()
|
||||
names.append_array(_second.get_names())
|
||||
return names
|
||||
|
||||
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return _first.is_empty() and _second.is_empty()
|
||||
|
||||
|
||||
func is_horizontal() -> bool:
|
||||
return _direction == Direction.HORIZONTAL
|
||||
|
||||
|
||||
func is_vertical() -> bool:
|
||||
return _direction == Direction.VERTICAL
|
||||
13
addons/dockable_container/plugin.cfg
Normal file
@@ -0,0 +1,13 @@
|
||||
[plugin]
|
||||
|
||||
name="Dockable Container"
|
||||
description="Container script that manages docking/tiling UI panels.
|
||||
|
||||
Panels are composed of tabs that can be dragged around and dropped to split another panel or compose its tabs.
|
||||
|
||||
Layout information is stored in Resource objects, so they can be saved/loaded from disk easily.
|
||||
|
||||
This plugin also offers a replica of the Container layout to be edited directly in the inspector."
|
||||
author="gilzoide"
|
||||
version="1.1.2"
|
||||
script="plugin.gd"
|
||||
19
addons/dockable_container/plugin.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const LayoutInspectorPlugin := preload("inspector_plugin/editor_inspector_plugin.gd")
|
||||
const Icon := preload("icon.svg")
|
||||
|
||||
var _layout_inspector_plugin: LayoutInspectorPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_layout_inspector_plugin = LayoutInspectorPlugin.new()
|
||||
add_custom_type("DockableContainer", "Container", DockableContainer, Icon)
|
||||
add_inspector_plugin(_layout_inspector_plugin)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_inspector_plugin(_layout_inspector_plugin)
|
||||
remove_custom_type("DockableContainer")
|
||||
_layout_inspector_plugin = null
|
||||
63
addons/dockable_container/samples/TestScene.gd
Normal file
@@ -0,0 +1,63 @@
|
||||
extends VBoxContainer
|
||||
|
||||
const SAVED_LAYOUT_PATH := "user://layout.tres"
|
||||
|
||||
@onready var _container := $DockableContainers/DockableContainer as DockableContainer
|
||||
@onready var _clone_control := $HBoxContainer/ControlPrefab as ColorRect
|
||||
@onready var _checkbox_container := $HBoxContainer as HBoxContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not OS.is_userfs_persistent():
|
||||
$HBoxContainer/SaveLayoutButton.visible = false
|
||||
$HBoxContainer/LoadLayoutButton.visible = false
|
||||
|
||||
var tabs := _container.get_tabs()
|
||||
for i in tabs.size():
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.text = str(i)
|
||||
checkbox.button_pressed = not _container.is_control_hidden(tabs[i])
|
||||
checkbox.toggled.connect(_on_CheckButton_toggled.bind(tabs[i]))
|
||||
_checkbox_container.add_child(checkbox)
|
||||
|
||||
|
||||
func _on_add_pressed() -> void:
|
||||
var control := _clone_control.duplicate()
|
||||
control.get_node("Buttons/Rename").pressed.connect(
|
||||
_on_control_rename_button_pressed.bind(control)
|
||||
)
|
||||
control.get_node("Buttons/Remove").pressed.connect(
|
||||
_on_control_remove_button_pressed.bind(control)
|
||||
)
|
||||
control.color = Color(randf(), randf(), randf())
|
||||
control.name = "Control0"
|
||||
|
||||
_container.add_child(control, true)
|
||||
await _container.sort_children
|
||||
_container.set_control_as_current_tab(control)
|
||||
|
||||
|
||||
func _on_save_pressed() -> void:
|
||||
if ResourceSaver.save(_container.layout, SAVED_LAYOUT_PATH) != OK:
|
||||
print("ERROR")
|
||||
|
||||
|
||||
func _on_load_pressed() -> void:
|
||||
var res = load(SAVED_LAYOUT_PATH)
|
||||
if res:
|
||||
_container.set_layout(res.clone())
|
||||
else:
|
||||
print("Error")
|
||||
|
||||
|
||||
func _on_control_rename_button_pressed(control: Control) -> void:
|
||||
control.name = StringName(str(control.name) + " =D")
|
||||
|
||||
|
||||
func _on_control_remove_button_pressed(control: Control) -> void:
|
||||
control.get_parent().remove_child(control)
|
||||
control.queue_free()
|
||||
|
||||
|
||||
func _on_CheckButton_toggled(button_pressed: bool, tab: Control) -> void:
|
||||
_container.set_control_hidden(tab, not button_pressed)
|
||||
177
addons/dockable_container/samples/TestScene.tscn
Normal file
@@ -0,0 +1,177 @@
|
||||
[gd_scene load_steps=16 format=3 uid="uid://drlvhuchtk6if"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/dockable_container.gd" id="1"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout.gd" id="2"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/samples/TestScene.gd" id="4"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout_split.gd" id="4_yhgfb"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout_panel.gd" id="5"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_8aoc2"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control0")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_6kjom"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control1", "Control2")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_hl8y1"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 1
|
||||
percent = 0.5
|
||||
first = SubResource("Resource_8aoc2")
|
||||
second = SubResource("Resource_6kjom")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ybwqe"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource("2")
|
||||
root = SubResource("Resource_hl8y1")
|
||||
hidden_tabs = {}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ntwfj"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control3")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_dmyvf"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control4")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_vag66"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 1
|
||||
percent = 0.281
|
||||
first = SubResource("Resource_ntwfj")
|
||||
second = SubResource("Resource_dmyvf")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_4q660"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control5")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_jhibs"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 0
|
||||
percent = 0.5
|
||||
first = SubResource("Resource_vag66")
|
||||
second = SubResource("Resource_4q660")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_xhxpg"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource("2")
|
||||
root = SubResource("Resource_jhibs")
|
||||
hidden_tabs = {}
|
||||
|
||||
[node name="SampleScene" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AddControlButton" type="Button" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "(+) ADD CONTROL"
|
||||
|
||||
[node name="SaveLayoutButton" type="Button" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "Save Layout"
|
||||
|
||||
[node name="LoadLayoutButton" type="Button" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "Load Layout"
|
||||
|
||||
[node name="ControlPrefab" type="ColorRect" parent="HBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
color = Color(0.129412, 0.121569, 0.121569, 1)
|
||||
|
||||
[node name="Buttons" type="VBoxContainer" parent="HBoxContainer/ControlPrefab"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -65.5
|
||||
offset_top = -22.0
|
||||
offset_right = 65.5
|
||||
offset_bottom = 22.0
|
||||
|
||||
[node name="Rename" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"]
|
||||
layout_mode = 2
|
||||
text = "Rename"
|
||||
|
||||
[node name="Remove" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"]
|
||||
layout_mode = 2
|
||||
text = "REMOVE"
|
||||
|
||||
[node name="DockableContainers" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="DockableContainer" type="Container" parent="DockableContainers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1")
|
||||
layout = SubResource("Resource_ybwqe")
|
||||
|
||||
[node name="Control0" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Control1" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
layout_mode = 2
|
||||
color = Color(0.141176, 0.0745098, 0.603922, 1)
|
||||
|
||||
[node name="Control2" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
color = Color(0.533333, 0.380392, 0.380392, 1)
|
||||
|
||||
[node name="Separator" type="ColorRect" parent="DockableContainers"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="DockableContainer2" type="Container" parent="DockableContainers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1")
|
||||
layout = SubResource("Resource_xhxpg")
|
||||
|
||||
[node name="Control3" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
layout_mode = 2
|
||||
color = Color(0, 1, 0.905882, 1)
|
||||
|
||||
[node name="Control4" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
layout_mode = 2
|
||||
color = Color(0, 0.698039, 0.0588235, 1)
|
||||
|
||||
[node name="Control5" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
layout_mode = 2
|
||||
color = Color(1, 0.937255, 0, 1)
|
||||
|
||||
[connection signal="pressed" from="HBoxContainer/AddControlButton" to="." method="_on_add_pressed"]
|
||||
[connection signal="pressed" from="HBoxContainer/SaveLayoutButton" to="." method="_on_save_pressed"]
|
||||
[connection signal="pressed" from="HBoxContainer/LoadLayoutButton" to="." method="_on_load_pressed"]
|
||||
120
addons/dockable_container/split_handle.gd
Normal file
@@ -0,0 +1,120 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const SPLIT_THEME_CLASS: PackedStringArray = [
|
||||
"HSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.HORIZONTAL]
|
||||
"VSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.VERTICAL]
|
||||
]
|
||||
|
||||
const SPLIT_MOUSE_CURSOR_SHAPE: Array[Control.CursorShape] = [
|
||||
Control.CURSOR_HSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.HORIZONTAL]
|
||||
Control.CURSOR_VSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.VERTICAL]
|
||||
]
|
||||
|
||||
var layout_split: DockableLayoutSplit
|
||||
var first_minimum_size: Vector2
|
||||
var second_minimum_size: Vector2
|
||||
|
||||
var _parent_rect: Rect2
|
||||
var _mouse_hovering := false
|
||||
var _dragging := false
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var theme_class := SPLIT_THEME_CLASS[layout_split.direction]
|
||||
var icon := get_theme_icon("grabber", theme_class)
|
||||
var autohide := bool(get_theme_constant("autohide", theme_class))
|
||||
if not icon or (autohide and not _mouse_hovering):
|
||||
return
|
||||
|
||||
draw_texture(icon, (size - icon.get_size()) * 0.5)
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_dragging = event.is_pressed()
|
||||
if event.double_click:
|
||||
layout_split.percent = 0.5
|
||||
elif _dragging and event is InputEventMouseMotion:
|
||||
var mouse_in_parent := get_parent_control().get_local_mouse_position()
|
||||
if layout_split.is_horizontal():
|
||||
layout_split.percent = (
|
||||
(mouse_in_parent.x - _parent_rect.position.x) / _parent_rect.size.x
|
||||
)
|
||||
else:
|
||||
layout_split.percent = (
|
||||
(mouse_in_parent.y - _parent_rect.position.y) / _parent_rect.size.y
|
||||
)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_MOUSE_ENTER:
|
||||
_mouse_hovering = true
|
||||
set_split_cursor(true)
|
||||
if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_MOUSE_EXIT:
|
||||
_mouse_hovering = false
|
||||
set_split_cursor(false)
|
||||
if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_FOCUS_EXIT:
|
||||
_dragging = false
|
||||
|
||||
|
||||
func get_layout_minimum_size() -> Vector2:
|
||||
if not layout_split:
|
||||
return Vector2.ZERO
|
||||
var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
if layout_split.is_horizontal():
|
||||
return Vector2(
|
||||
first_minimum_size.x + separation + second_minimum_size.x,
|
||||
maxf(first_minimum_size.y, second_minimum_size.y)
|
||||
)
|
||||
else:
|
||||
return Vector2(
|
||||
maxf(first_minimum_size.x, second_minimum_size.x),
|
||||
first_minimum_size.y + separation + second_minimum_size.y
|
||||
)
|
||||
|
||||
|
||||
func set_split_cursor(value: bool) -> void:
|
||||
if value:
|
||||
mouse_default_cursor_shape = SPLIT_MOUSE_CURSOR_SHAPE[layout_split.direction]
|
||||
else:
|
||||
mouse_default_cursor_shape = CURSOR_ARROW
|
||||
|
||||
|
||||
func get_split_rects(rect: Rect2) -> Dictionary:
|
||||
_parent_rect = rect
|
||||
var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
var origin := rect.position
|
||||
var percent := layout_split.percent
|
||||
if layout_split.is_horizontal():
|
||||
var split_offset := clampf(
|
||||
rect.size.x * percent - separation * 0.5,
|
||||
first_minimum_size.x,
|
||||
rect.size.x - second_minimum_size.x - separation
|
||||
)
|
||||
var second_width := rect.size.x - split_offset - separation
|
||||
|
||||
return {
|
||||
"first": Rect2(origin.x, origin.y, split_offset, rect.size.y),
|
||||
"self": Rect2(origin.x + split_offset, origin.y, separation, rect.size.y),
|
||||
"second":
|
||||
Rect2(origin.x + split_offset + separation, origin.y, second_width, rect.size.y),
|
||||
}
|
||||
else:
|
||||
var split_offset := clampf(
|
||||
rect.size.y * percent - separation * 0.5,
|
||||
first_minimum_size.y,
|
||||
rect.size.y - second_minimum_size.y - separation
|
||||
)
|
||||
var second_height := rect.size.y - split_offset - separation
|
||||
|
||||
return {
|
||||
"first": Rect2(origin.x, origin.y, rect.size.x, split_offset),
|
||||
"self": Rect2(origin.x, origin.y + split_offset, rect.size.x, separation),
|
||||
"second":
|
||||
Rect2(origin.x, origin.y + split_offset + separation, rect.size.x, second_height),
|
||||
}
|
||||
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 994 B |
37
icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cysf2s24sp30x"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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
|
||||
41
project.godot
Normal file
@@ -0,0 +1,41 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="AudioEditor"
|
||||
run/main_scene="res://Views/MainView.tscn"
|
||||
config/features=PackedStringArray("4.3", "C#", "GL Compatibility")
|
||||
run/low_processor_mode=true
|
||||
boot_splash/show_image=false
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
window/stretch/aspect="expand"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="AudioEditor"
|
||||
|
||||
[editor]
|
||||
|
||||
naming/scene_name_casing=1
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/dockable_container/plugin.cfg")
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||