playbar 추가, 모델 여러개 로드
This commit is contained in:
BIN
Assets/Resources/SHI/Images/playbar_bg.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
143
Assets/Resources/SHI/Images/playbar_bg.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_bg.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f019cda87b206f84098dd38575768a72
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 256
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/SHI/Images/playbar_icon_end_16x21.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_icon_end_16x21.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 237 B |
143
Assets/Resources/SHI/Images/playbar_icon_end_16x21.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_icon_end_16x21.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 25aaebf107892914cada8d94caeef0b2
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 32
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/SHI/Images/playbar_icon_pause_17.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_icon_pause_17.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 B |
143
Assets/Resources/SHI/Images/playbar_icon_pause_17.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_icon_pause_17.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9e72c11031ee853409b2890dfe74d9cb
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 32
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/SHI/Images/playbar_icon_play_17x21.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_icon_play_17x21.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 B |
143
Assets/Resources/SHI/Images/playbar_icon_play_17x21.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_icon_play_17x21.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 989509807ae628144bf9ec45a2dc4397
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 32
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/SHI/Images/playbar_icon_start_16x21.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_icon_start_16x21.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 B |
143
Assets/Resources/SHI/Images/playbar_icon_start_16x21.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_icon_start_16x21.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 67a53c30e0da69e4e899c7dddfaff16e
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 32
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/SHI/Images/playbar_icon_stop_17.png
Normal file
BIN
Assets/Resources/SHI/Images/playbar_icon_stop_17.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 B |
143
Assets/Resources/SHI/Images/playbar_icon_stop_17.png.meta
Normal file
143
Assets/Resources/SHI/Images/playbar_icon_stop_17.png.meta
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5a70228c7e2d0fa4fb56924dca7cf261
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 0
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 1
|
||||||
|
wrapV: 1
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 0
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 1
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 1
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 8
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 32
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WindowsStoreApps
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID: 5e97eb03825dee720800000000000000
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,68 +1,34 @@
|
|||||||
/*
|
|
||||||
* ISOPModal.uss - ISOP 모달 전역 스타일시트
|
|
||||||
*
|
|
||||||
* [개요]
|
|
||||||
* ISOP 모달 창 전체에 적용되는 스타일을 정의합니다.
|
|
||||||
* 주로 스크롤바 커스터마이징과 기본 텍스트 스타일을 포함합니다.
|
|
||||||
*
|
|
||||||
* [스타일 구조]
|
|
||||||
* 1. .unity-text-element : 기본 텍스트 스타일 (Pretendard 폰트, 10px)
|
|
||||||
* 2. .unity-scroller--vertical : 세로 스크롤바 커스터마이징
|
|
||||||
* 3. .unity-scroller--horizontal : 가로 스크롤바 커스터마이징
|
|
||||||
*
|
|
||||||
* [스크롤바 디자인]
|
|
||||||
* - 너비/높이: 6px (슬림한 디자인)
|
|
||||||
* - 트랙: 흰색 배경 (#FFFFFF)
|
|
||||||
* - 드래거: 밝은 회색 (#D8D8D8), 둥근 모서리 (3px)
|
|
||||||
* - 화살표 버튼: 숨김 처리
|
|
||||||
*
|
|
||||||
* [폰트 설정]
|
|
||||||
* - 기본 폰트: Pretendard-Regular
|
|
||||||
* - 기본 색상: rgb(34, 34, 34) - 진한 회색
|
|
||||||
* - 기본 크기: 10px
|
|
||||||
*
|
|
||||||
* [연관 파일]
|
|
||||||
* - ISOPModal.uxml : 메인 레이아웃
|
|
||||||
* - ISOPChart.uss : 간트 차트 스타일
|
|
||||||
* - ISOPModelView.uss : 모델 뷰 스타일
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ===================================
|
|
||||||
기본 텍스트 스타일
|
|
||||||
모달 내 모든 텍스트 요소에 적용
|
|
||||||
=================================== */
|
|
||||||
.unity-text-element {
|
.unity-text-element {
|
||||||
-unity-font: url('project://database/Assets/Fonts/Pretendard-Regular.otf');
|
-unity-font: url('project://database/Assets/Fonts/Pretendard-Regular.otf');
|
||||||
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
||||||
color: rgb(34, 34, 34); /* 진한 회색 텍스트 */
|
color: rgb(34, 34, 34);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===================================
|
|
||||||
세로 스크롤바 스타일
|
|
||||||
슬림한 6px 너비의 커스텀 스크롤바
|
|
||||||
=================================== */
|
|
||||||
.unity-scroller--vertical {
|
.unity-scroller--vertical {
|
||||||
width: 6px; /* 슬림한 너비 */
|
|
||||||
margin-right: 4px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 세로 스크롤바 트랙 (배경) */
|
|
||||||
.unity-scroller--vertical .unity-base-slider__tracker {
|
|
||||||
background-color: rgb(255, 255, 255); /* 흰색 배경 */
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 세로 스크롤바 드래거 (핸들) */
|
|
||||||
.unity-scroller--vertical .unity-base-slider__dragger {
|
|
||||||
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
|
||||||
border-width: 0;
|
|
||||||
border-radius: 3px; /* 둥근 모서리 */
|
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__tracker {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__drag-container {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 6px;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 세로 스크롤바 화살표 버튼 숨김 */
|
|
||||||
.unity-scroller--vertical .unity-repeat-button {
|
.unity-scroller--vertical .unity-repeat-button {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -71,36 +37,39 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 세로 스크롤바 슬라이더 마진 제거 */
|
|
||||||
.unity-scroller--vertical .unity-slider {
|
.unity-scroller--vertical .unity-slider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===================================
|
.unity-scroller--vertical .unity-base-field__input {
|
||||||
가로 스크롤바 스타일
|
width: 6px;
|
||||||
슬림한 6px 높이의 커스텀 스크롤바
|
min-width: 6px;
|
||||||
=================================== */
|
}
|
||||||
|
|
||||||
.unity-scroller--horizontal {
|
.unity-scroller--horizontal {
|
||||||
height: 6px; /* 슬림한 높이 */
|
|
||||||
margin-bottom: 4px;
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 가로 스크롤바 트랙 (배경) */
|
|
||||||
.unity-scroller--horizontal .unity-base-slider__tracker {
|
|
||||||
background-color: rgb(255, 255, 255); /* 흰색 배경 */
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 가로 스크롤바 드래거 (핸들) */
|
|
||||||
.unity-scroller--horizontal .unity-base-slider__dragger {
|
|
||||||
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
|
||||||
border-width: 0;
|
|
||||||
border-radius: 3px; /* 둥근 모서리 */
|
|
||||||
height: 6px;
|
height: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__tracker {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__drag-container {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 6px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 가로 스크롤바 화살표 버튼 숨김 */
|
|
||||||
.unity-scroller--horizontal .unity-repeat-button {
|
.unity-scroller--horizontal .unity-repeat-button {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -109,7 +78,48 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 가로 스크롤바 슬라이더 마진 제거 */
|
|
||||||
.unity-scroller--horizontal .unity-slider {
|
.unity-scroller--horizontal .unity-slider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-field__input {
|
||||||
|
height: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-top-width: 4px;
|
||||||
|
border-right-width: 4px;
|
||||||
|
border-bottom-width: 4px;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-right-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-left-color: rgb(255, 255, 255);
|
||||||
|
border-top-left-radius: 50%;
|
||||||
|
border-top-right-radius: 50%;
|
||||||
|
border-bottom-left-radius: 50%;
|
||||||
|
border-bottom-right-radius: 50%;
|
||||||
|
align-self: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,72 +1,17 @@
|
|||||||
<!--
|
|
||||||
ISOPModal.uxml - ISOP(조선소 공정 계획) 모달 메인 레이아웃
|
|
||||||
|
|
||||||
[개요]
|
|
||||||
ISOP 모달 창의 전체 레이아웃을 정의합니다.
|
|
||||||
TreeList(모델 계층), ISOPModelView(3D 뷰어), ISOPChart(간트 차트) 세 가지 뷰를
|
|
||||||
수평으로 배치합니다.
|
|
||||||
|
|
||||||
[UI 구조]
|
|
||||||
Root
|
|
||||||
├── topbar - 상단 헤더 바 (제목, 닫기 버튼)
|
|
||||||
│ ├── title - 모달 제목 라벨
|
|
||||||
│ └── closeButton - 모달 닫기 버튼
|
|
||||||
└── content - 메인 컨텐츠 영역 (수평 배치)
|
|
||||||
├── TreeList - 모델 계층 구조 (왼쪽 사이드바)
|
|
||||||
├── ISOPModelView - 3D 모델 뷰어 (중앙)
|
|
||||||
├── ISOPChart - 간트 차트 (오른쪽)
|
|
||||||
├── show-tree-btn - TreeList 표시 버튼 (숨겨진 상태일 때)
|
|
||||||
└── drag-btn - ModelView/Chart 크기 조절 드래그 버튼
|
|
||||||
|
|
||||||
[커스텀 컴포넌트]
|
|
||||||
- SHI.Modal.TreeList : 모델 계층 트리뷰
|
|
||||||
- SHI.Modal.ISOP.ISOPModelView : 3D 모델 렌더링 뷰
|
|
||||||
- SHI.Modal.ISOP.ISOPChart : 간트 차트 뷰
|
|
||||||
|
|
||||||
[레이아웃 특징]
|
|
||||||
- 수평(Horizontal) 레이아웃: TreeList | ModelView | Chart
|
|
||||||
- flex-basis: 0으로 ModelView와 Chart가 동일한 공간 차지
|
|
||||||
- drag-btn으로 ModelView/Chart 비율 조절 가능
|
|
||||||
|
|
||||||
[연관 파일]
|
|
||||||
- ISOPModal.uss : 스타일시트 (스크롤바 커스터마이징)
|
|
||||||
- ISOPModal.cs : C# 컨트롤러 (뷰 간 연동 처리)
|
|
||||||
|
|
||||||
[사용되는 리소스]
|
|
||||||
- Fonts/Pretendard/Pretendard-Bold : 제목 폰트
|
|
||||||
- SHI/Images/btn_close_24 : 닫기 버튼 이미지
|
|
||||||
- SHI/Images/btn_show_explorer_40x41 : TreeList 표시 버튼 이미지
|
|
||||||
- SHI/Images/btn_drag_40 : 드래그 버튼 이미지
|
|
||||||
-->
|
|
||||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||||
<Style src="project://database/Assets/Resources/SHI/Modal/ISOP/ISOPModal.uss?fileID=7433441132597879392&guid=b798684be75686146a13288df44a17a3&type=3#ISOPModal" />
|
<Style src="project://database/Assets/Resources/SHI/Modal/ISOP/ISOPModal.uss?fileID=7433441132597879392&guid=b798684be75686146a13288df44a17a3&type=3#ISOPModal" />
|
||||||
|
|
||||||
<!-- 상단 헤더 바: 제목과 닫기 버튼 -->
|
|
||||||
<ui:VisualElement name="topbar" style="flex-grow: 0; width: 100%; height: 60px; background-color: rgb(9, 24, 53); flex-shrink: 0; align-items: center; align-self: auto; align-content: auto; flex-direction: row; justify-content: center;">
|
<ui:VisualElement name="topbar" style="flex-grow: 0; width: 100%; height: 60px; background-color: rgb(9, 24, 53); flex-shrink: 0; align-items: center; align-self: auto; align-content: auto; flex-direction: row; justify-content: center;">
|
||||||
<!-- 모달 제목 (중앙 정렬) -->
|
|
||||||
<ui:Label text="블록2 상세" name="title" style="-unity-font-definition: url("project://database/Assets/Resources/Fonts/Pretendard/Pretendard-Bold.otf?fileID=12800000&guid=f9a95f3332ad9a9468b4eaa89f125b35&type=3#Pretendard-Bold"); font-size: 24px; color: rgb(255, 255, 255); flex-basis: auto; justify-content: center; align-items: center; -unity-text-align: middle-center; width: 500px;" />
|
<ui:Label text="블록2 상세" name="title" style="-unity-font-definition: url("project://database/Assets/Resources/Fonts/Pretendard/Pretendard-Bold.otf?fileID=12800000&guid=f9a95f3332ad9a9468b4eaa89f125b35&type=3#Pretendard-Bold"); font-size: 24px; color: rgb(255, 255, 255); flex-basis: auto; justify-content: center; align-items: center; -unity-text-align: middle-center; width: 500px;" />
|
||||||
<!-- 닫기 버튼 (오른쪽 상단) -->
|
|
||||||
<ui:Button name="closeButton" style="width: 24px; height: 24px; background-image: url("project://database/Assets/Resources/SHI/Images/btn_close_24.png?fileID=21300000&guid=461a84aa7f4a0104bb7a41e1f9d15f86&type=3#btn_close_24"); background-color: rgba(255, 255, 255, 0); border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; position: absolute; right: 18px;" />
|
<ui:Button name="closeButton" style="width: 24px; height: 24px; background-image: url("project://database/Assets/Resources/SHI/Images/btn_close_24.png?fileID=21300000&guid=461a84aa7f4a0104bb7a41e1f9d15f86&type=3#btn_close_24"); background-color: rgba(255, 255, 255, 0); border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; position: absolute; right: 18px;" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
<!-- 메인 컨텐츠 영역: TreeList, ModelView, Chart 수평 배치 -->
|
|
||||||
<ui:VisualElement name="content" style="flex-grow: 1; flex-shrink: 1; align-self: stretch; align-content: auto; align-items: stretch; width: 100%; flex-direction: row; overflow: hidden;">
|
<ui:VisualElement name="content" style="flex-grow: 1; flex-shrink: 1; align-self: stretch; align-content: auto; align-items: stretch; width: 100%; flex-direction: row; overflow: hidden;">
|
||||||
<!-- TreeList: 모델 계층 구조 트리뷰 (왼쪽 사이드바) -->
|
|
||||||
<SHI.Modal.TreeList name="treeList" style="bottom: 0; left: 0; flex-direction: column; flex-wrap: nowrap; height: 100%; flex-shrink: 0;" />
|
<SHI.Modal.TreeList name="treeList" style="bottom: 0; left: 0; flex-direction: column; flex-wrap: nowrap; height: 100%; flex-shrink: 0;" />
|
||||||
|
|
||||||
<!-- ISOPModelView: 3D 모델 렌더링 뷰어 (중앙)
|
|
||||||
flex-basis: 0으로 Chart와 동일한 공간 분할 -->
|
|
||||||
<SHI.Modal.ISOP.ISOPModelView style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; flex-direction: column; min-width: 0; overflow: hidden;" />
|
<SHI.Modal.ISOP.ISOPModelView style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; flex-direction: column; min-width: 0; overflow: hidden;" />
|
||||||
|
|
||||||
<!-- ISOPChart: 간트 차트 뷰 (오른쪽)
|
|
||||||
flex-basis: 0으로 ModelView와 동일한 공간 분할 -->
|
|
||||||
<SHI.Modal.ISOP.ISOPChart style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; height: 100%; min-width: 0; overflow: hidden;" />
|
<SHI.Modal.ISOP.ISOPChart style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; height: 100%; min-width: 0; overflow: hidden;" />
|
||||||
|
|
||||||
<!-- TreeList 표시 버튼: TreeList가 숨겨졌을 때 나타남 -->
|
|
||||||
<ui:Button text=" " name="show-tree-btn" style="position: absolute; left: 0; top: 26px; width: 40px; height: 41px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_show_explorer_40x41');" />
|
<ui:Button text=" " name="show-tree-btn" style="position: absolute; left: 0; top: 26px; width: 40px; height: 41px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_show_explorer_40x41');" />
|
||||||
|
|
||||||
<!-- 드래그 버튼: ModelView/Chart 크기 비율 조절
|
|
||||||
HorizontalDragManipulator에서 처리 -->
|
|
||||||
<ui:Button text=" " name="drag-btn" style="position: absolute; left: 60%; top: auto; width: 40px; height: 40px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_drag_40'); bottom: 40px; align-items: center; align-self: center;" />
|
<ui:Button text=" " name="drag-btn" style="position: absolute; left: 60%; top: auto; width: 40px; height: 40px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_drag_40'); bottom: 40px; align-items: center; align-self: center;" />
|
||||||
|
<ui:VisualElement name="loading-overlay" class="loading-overlay">
|
||||||
|
<ui:VisualElement name="loading-spinner" class="loading-spinner" />
|
||||||
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:UXML>
|
</ui:UXML>
|
||||||
|
|||||||
13
Assets/Resources/SHI/Modal/Modal.uss
Normal file
13
Assets/Resources/SHI/Modal/Modal.uss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
/* ===================================
|
||||||
|
드롭다운 팝업 아이템 스타일
|
||||||
|
=================================== */
|
||||||
|
.unity-base-dropdown__item {
|
||||||
|
padding: 0px;
|
||||||
|
background-color: rgb(40, 44, 52);
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-base-dropdown__container-inner {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
11
Assets/Resources/SHI/Modal/Modal.uss.meta
Normal file
11
Assets/Resources/SHI/Modal/Modal.uss.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 15f8f9b790a1042468c972e5ea4e9430
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
disableValidation: 0
|
||||||
@@ -1,69 +1,34 @@
|
|||||||
/*
|
|
||||||
* NWModal.uss - NW 모달 전역 스타일시트
|
|
||||||
*
|
|
||||||
* [개요]
|
|
||||||
* NW 모달 창 전체에 적용되는 스타일을 정의합니다.
|
|
||||||
* ISOPModal.uss와 동일한 스타일을 사용합니다.
|
|
||||||
* 주로 스크롤바 커스터마이징과 기본 텍스트 스타일을 포함합니다.
|
|
||||||
*
|
|
||||||
* [스타일 구조]
|
|
||||||
* 1. .unity-text-element : 기본 텍스트 스타일 (Pretendard 폰트, 10px)
|
|
||||||
* 2. .unity-scroller--vertical : 세로 스크롤바 커스터마이징
|
|
||||||
* 3. .unity-scroller--horizontal : 가로 스크롤바 커스터마이징
|
|
||||||
*
|
|
||||||
* [스크롤바 디자인]
|
|
||||||
* - 너비/높이: 6px (슬림한 디자인)
|
|
||||||
* - 트랙: 흰색 배경 (#FFFFFF)
|
|
||||||
* - 드래거: 밝은 회색 (#D8D8D8), 둥근 모서리 (3px)
|
|
||||||
* - 화살표 버튼: 숨김 처리
|
|
||||||
*
|
|
||||||
* [폰트 설정]
|
|
||||||
* - 기본 폰트: Pretendard-Regular
|
|
||||||
* - 기본 색상: rgb(34, 34, 34) - 진한 회색
|
|
||||||
* - 기본 크기: 10px
|
|
||||||
*
|
|
||||||
* [연관 파일]
|
|
||||||
* - NWModal.uxml : 메인 레이아웃
|
|
||||||
* - NWChart.uss : 네트워크 다이어그램 스타일
|
|
||||||
* - NWModalView.uss : 모델 뷰 스타일
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ===================================
|
|
||||||
기본 텍스트 스타일
|
|
||||||
모달 내 모든 텍스트 요소에 적용
|
|
||||||
=================================== */
|
|
||||||
.unity-text-element {
|
.unity-text-element {
|
||||||
-unity-font: url('project://database/Assets/Fonts/Pretendard-Regular.otf');
|
-unity-font: url('project://database/Assets/Fonts/Pretendard-Regular.otf');
|
||||||
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
||||||
color: rgb(34, 34, 34); /* 진한 회색 텍스트 */
|
color: rgb(34, 34, 34);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===================================
|
|
||||||
세로 스크롤바 스타일
|
|
||||||
슬림한 6px 너비의 커스텀 스크롤바
|
|
||||||
=================================== */
|
|
||||||
.unity-scroller--vertical {
|
.unity-scroller--vertical {
|
||||||
width: 6px; /* 슬림한 너비 */
|
|
||||||
margin-right: 4px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 세로 스크롤바 트랙 (배경) */
|
|
||||||
.unity-scroller--vertical .unity-base-slider__tracker {
|
|
||||||
background-color: rgb(255, 255, 255); /* 흰색 배경 */
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 세로 스크롤바 드래거 (핸들) */
|
|
||||||
.unity-scroller--vertical .unity-base-slider__dragger {
|
|
||||||
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
|
||||||
border-width: 0;
|
|
||||||
border-radius: 3px; /* 둥근 모서리 */
|
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__tracker {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__drag-container {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--vertical .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 6px;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 세로 스크롤바 화살표 버튼 숨김 */
|
|
||||||
.unity-scroller--vertical .unity-repeat-button {
|
.unity-scroller--vertical .unity-repeat-button {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -72,36 +37,39 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 세로 스크롤바 슬라이더 마진 제거 */
|
|
||||||
.unity-scroller--vertical .unity-slider {
|
.unity-scroller--vertical .unity-slider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===================================
|
.unity-scroller--vertical .unity-base-field__input {
|
||||||
가로 스크롤바 스타일
|
width: 6px;
|
||||||
슬림한 6px 높이의 커스텀 스크롤바
|
min-width: 6px;
|
||||||
=================================== */
|
}
|
||||||
|
|
||||||
.unity-scroller--horizontal {
|
.unity-scroller--horizontal {
|
||||||
height: 6px; /* 슬림한 높이 */
|
|
||||||
margin-bottom: 4px;
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 가로 스크롤바 트랙 (배경) */
|
|
||||||
.unity-scroller--horizontal .unity-base-slider__tracker {
|
|
||||||
background-color: rgb(255, 255, 255); /* 흰색 배경 */
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 가로 스크롤바 드래거 (핸들) */
|
|
||||||
.unity-scroller--horizontal .unity-base-slider__dragger {
|
|
||||||
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
|
||||||
border-width: 0;
|
|
||||||
border-radius: 3px; /* 둥근 모서리 */
|
|
||||||
height: 6px;
|
height: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__tracker {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__drag-container {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216);
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 6px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 가로 스크롤바 화살표 버튼 숨김 */
|
|
||||||
.unity-scroller--horizontal .unity-repeat-button {
|
.unity-scroller--horizontal .unity-repeat-button {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -110,7 +78,48 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 가로 스크롤바 슬라이더 마진 제거 */
|
|
||||||
.unity-scroller--horizontal .unity-slider {
|
.unity-scroller--horizontal .unity-slider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unity-scroller--horizontal .unity-base-field__input {
|
||||||
|
height: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-top-width: 4px;
|
||||||
|
border-right-width: 4px;
|
||||||
|
border-bottom-width: 4px;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-right-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.3);
|
||||||
|
border-left-color: rgb(255, 255, 255);
|
||||||
|
border-top-left-radius: 50%;
|
||||||
|
border-top-right-radius: 50%;
|
||||||
|
border-bottom-left-radius: 50%;
|
||||||
|
border-bottom-right-radius: 50%;
|
||||||
|
align-self: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,76 +1,22 @@
|
|||||||
<!--
|
|
||||||
NWModal.uxml - NW(네트워크 다이어그램) 모달 메인 레이아웃
|
|
||||||
|
|
||||||
[개요]
|
|
||||||
NW 모달 창의 전체 레이아웃을 정의합니다.
|
|
||||||
TreeList(모델 계층), NWModelView(3D 뷰어), NWChart(네트워크 다이어그램)를
|
|
||||||
수직으로 배치합니다. (ISOP과 달리 ModelView가 위, Chart가 아래)
|
|
||||||
|
|
||||||
[UI 구조]
|
|
||||||
Root
|
|
||||||
├── topbar - 상단 헤더 바 (제목, 닫기 버튼)
|
|
||||||
│ ├── title - 모달 제목 라벨
|
|
||||||
│ └── closeButton - 모달 닫기 버튼
|
|
||||||
└── content - 메인 컨텐츠 영역 (수평 배치)
|
|
||||||
├── TreeList - 모델 계층 구조 (왼쪽 사이드바)
|
|
||||||
├── right-panel - 오른쪽 패널 (수직 배치)
|
|
||||||
│ ├── NWModelView - 3D 모델 뷰어 (상단)
|
|
||||||
│ ├── NWChart - 네트워크 다이어그램 (하단)
|
|
||||||
│ └── drag-btn - ModelView/Chart 크기 조절 드래그 버튼
|
|
||||||
└── show-tree-btn - TreeList 표시 버튼 (숨겨진 상태일 때)
|
|
||||||
|
|
||||||
[ISOP과의 차이점]
|
|
||||||
- ISOP: TreeList | ModelView | Chart (수평 3분할)
|
|
||||||
- NW: TreeList | (ModelView / Chart) (수평 2분할 + 수직 2분할)
|
|
||||||
- 드래그 버튼: ISOP=수평, NW=수직 (90도 회전)
|
|
||||||
|
|
||||||
[커스텀 컴포넌트]
|
|
||||||
- SHI.Modal.TreeList : 모델 계층 트리뷰
|
|
||||||
- SHI.Modal.NW.NWModelView : 3D 모델 렌더링 뷰
|
|
||||||
- SHI.Modal.NW.NWChart : 네트워크 다이어그램 뷰
|
|
||||||
|
|
||||||
[연관 파일]
|
|
||||||
- NWModal.uss : 스타일시트 (스크롤바 커스터마이징)
|
|
||||||
- NWModal.cs : C# 컨트롤러 (뷰 간 연동 처리)
|
|
||||||
|
|
||||||
[사용되는 리소스]
|
|
||||||
- Fonts/Pretendard/Pretendard-Bold : 제목 폰트
|
|
||||||
- SHI/Images/btn_close_24 : 닫기 버튼 이미지
|
|
||||||
- SHI/Images/btn_show_explorer_40x41 : TreeList 표시 버튼 이미지
|
|
||||||
- SHI/Images/btn_drag_40 : 드래그 버튼 이미지 (90도 회전)
|
|
||||||
-->
|
|
||||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||||
<Style src="project://database/Assets/Resources/SHI/Modal/NW/NWModal.uss?fileID=7433441132597879392&guid=067929ea94907c248a545d139eb77664&type=3#NWModal" />
|
<Style src="project://database/Assets/Resources/SHI/Modal/NW/NWModal.uss?fileID=7433441132597879392&guid=067929ea94907c248a545d139eb77664&type=3#NWModal" />
|
||||||
|
|
||||||
<!-- 상단 헤더 바: 제목과 닫기 버튼 -->
|
|
||||||
<ui:VisualElement name="topbar" style="flex-grow: 0; width: 100%; height: 60px; background-color: rgb(9, 24, 53); flex-shrink: 0; align-items: center; align-self: auto; align-content: auto; flex-direction: row; justify-content: center;">
|
<ui:VisualElement name="topbar" style="flex-grow: 0; width: 100%; height: 60px; background-color: rgb(9, 24, 53); flex-shrink: 0; align-items: center; align-self: auto; align-content: auto; flex-direction: row; justify-content: center;">
|
||||||
<!-- 모달 제목 (중앙 정렬) -->
|
|
||||||
<ui:Label text="블록2 상세" name="title" style="-unity-font-definition: url("project://database/Assets/Resources/Fonts/Pretendard/Pretendard-Bold.otf?fileID=12800000&guid=f9a95f3332ad9a9468b4eaa89f125b35&type=3#Pretendard-Bold"); font-size: 24px; color: rgb(255, 255, 255); flex-basis: auto; justify-content: center; align-items: center; -unity-text-align: middle-center; width: 500px;" />
|
<ui:Label text="블록2 상세" name="title" style="-unity-font-definition: url("project://database/Assets/Resources/Fonts/Pretendard/Pretendard-Bold.otf?fileID=12800000&guid=f9a95f3332ad9a9468b4eaa89f125b35&type=3#Pretendard-Bold"); font-size: 24px; color: rgb(255, 255, 255); flex-basis: auto; justify-content: center; align-items: center; -unity-text-align: middle-center; width: 500px;" />
|
||||||
<!-- 닫기 버튼 (오른쪽 상단) -->
|
|
||||||
<ui:Button name="closeButton" style="width: 24px; height: 24px; background-image: url("project://database/Assets/Resources/SHI/Images/btn_close_24.png?fileID=21300000&guid=461a84aa7f4a0104bb7a41e1f9d15f86&type=3#btn_close_24"); background-color: rgba(255, 255, 255, 0); border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; position: absolute; right: 18px;" />
|
<ui:Button name="closeButton" style="width: 24px; height: 24px; background-image: url("project://database/Assets/Resources/SHI/Images/btn_close_24.png?fileID=21300000&guid=461a84aa7f4a0104bb7a41e1f9d15f86&type=3#btn_close_24"); background-color: rgba(255, 255, 255, 0); border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; position: absolute; right: 18px;" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
<!-- 메인 컨텐츠 영역: TreeList + 오른쪽 패널 -->
|
|
||||||
<ui:VisualElement name="content" style="flex-grow: 1; flex-shrink: 1; align-self: stretch; align-content: auto; align-items: stretch; width: 100%; flex-direction: row; overflow: hidden;">
|
<ui:VisualElement name="content" style="flex-grow: 1; flex-shrink: 1; align-self: stretch; align-content: auto; align-items: stretch; width: 100%; flex-direction: row; overflow: hidden;">
|
||||||
<!-- TreeList: 모델 계층 구조 트리뷰 (왼쪽 사이드바) -->
|
|
||||||
<SHI.Modal.TreeList name="treeList" style="bottom: 0; left: 0; flex-direction: column; flex-wrap: nowrap; height: 100%; flex-shrink: 0;" />
|
<SHI.Modal.TreeList name="treeList" style="bottom: 0; left: 0; flex-direction: column; flex-wrap: nowrap; height: 100%; flex-shrink: 0;" />
|
||||||
|
|
||||||
<!-- 오른쪽 패널: ModelView(상단) + Chart(하단) 수직 배치 -->
|
|
||||||
<ui:VisualElement name="right-panel" style="flex-grow: 1; flex-shrink: 1; flex-direction: column; min-width: 0; overflow: hidden;">
|
<ui:VisualElement name="right-panel" style="flex-grow: 1; flex-shrink: 1; flex-direction: column; min-width: 0; overflow: hidden;">
|
||||||
<!-- NWModelView: 3D 모델 렌더링 뷰어 (상단)
|
|
||||||
flex-basis: 0으로 Chart와 동일한 공간 분할 -->
|
|
||||||
<SHI.Modal.NW.NWModelView style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden;" />
|
<SHI.Modal.NW.NWModelView style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden;" />
|
||||||
|
|
||||||
<!-- NWChart: 네트워크 다이어그램 뷰 (하단)
|
|
||||||
flex-basis: 0으로 ModelView와 동일한 공간 분할 -->
|
|
||||||
<SHI.Modal.NW.NWChart style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; min-width: 0; min-height: 0; overflow: hidden;" />
|
<SHI.Modal.NW.NWChart style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; min-width: 0; min-height: 0; overflow: hidden;" />
|
||||||
|
|
||||||
<!-- 드래그 버튼: ModelView/Chart 크기 비율 조절 (수직)
|
|
||||||
rotate: 90deg로 수직 드래그용으로 회전 -->
|
|
||||||
<ui:Button text=" " name="drag-btn" style="position: absolute; width: 40px; height: 40px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: -20px; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_drag_40'); align-items: center; align-self: center; right: 10px; top: 47%; rotate: 90deg;" />
|
<ui:Button text=" " name="drag-btn" style="position: absolute; width: 40px; height: 40px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: -20px; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_drag_40'); align-items: center; align-self: center; right: 10px; top: 47%; rotate: 90deg;" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
<!-- TreeList 표시 버튼: TreeList가 숨겨졌을 때 나타남 -->
|
|
||||||
<ui:Button text=" " name="show-tree-btn" style="position: absolute; left: 0; top: 26px; width: 40px; height: 41px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_show_explorer_40x41');" />
|
<ui:Button text=" " name="show-tree-btn" style="position: absolute; left: 0; top: 26px; width: 40px; height: 41px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_show_explorer_40x41');" />
|
||||||
|
<SHI.Modal.PlayBar name="play-bar" style="position: absolute; bottom: 54px; right: 62px; left: 302px;" />
|
||||||
|
|
||||||
|
<!-- 로딩 오버레이: 데이터 로딩 중 표시 -->
|
||||||
|
<ui:VisualElement name="loading-overlay" class="loading-overlay">
|
||||||
|
<ui:VisualElement name="loading-spinner" class="loading-spinner" />
|
||||||
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:UXML>
|
</ui:UXML>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<!-- 3D 렌더링 컨테이너: RenderTexture가 배경으로 표시됨
|
<!-- 3D 렌더링 컨테이너: RenderTexture가 배경으로 표시됨
|
||||||
- flex-grow: 1로 부모 영역 전체 채움
|
- flex-grow: 1로 부모 영역 전체 채움
|
||||||
- overflow: hidden으로 내용 잘림 방지 -->
|
- overflow: hidden으로 내용 잘림 방지 -->
|
||||||
<ui:VisualElement name="render-container" style="flex-grow: 1; height: 100%; min-width: 0; overflow: hidden;" />
|
<ui:VisualElement name="render-container" style="flex-grow: 1; flex-shrink: 1; flex-basis: 0; min-width: 0; min-height: 0; overflow: hidden;" />
|
||||||
|
|
||||||
<!-- 확장 버튼: 모델 뷰를 전체 화면으로 확장
|
<!-- 확장 버튼: 모델 뷰를 전체 화면으로 확장
|
||||||
- 왼쪽 하단에 위치 (ISOPModelView는 오른쪽 상단) -->
|
- 왼쪽 하단에 위치 (ISOPModelView는 오른쪽 상단) -->
|
||||||
|
|||||||
152
Assets/Resources/SHI/Modal/PlayBar.uss
Normal file
152
Assets/Resources/SHI/Modal/PlayBar.uss
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
.playbar-container {
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
padding-left: 20px;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 130px;
|
||||||
|
background-image: resource('SHI/Images/playbar_bg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-section {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
font-size: 14px;
|
||||||
|
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-label {
|
||||||
|
-unity-text-align: middle-left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-label {
|
||||||
|
-unity-text-align: middle-right;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-track {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 4px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgb(0, 70, 140);
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-section {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-left-width: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
-unity-background-scale-mode: scale-to-fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:active {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
background-image: resource('SHI/Images/playbar_icon_play_17x21');
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn.playing {
|
||||||
|
background-image: resource('SHI/Images/playbar_icon_pause_17');
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-btn {
|
||||||
|
background-image: resource('SHI/Images/playbar_icon_stop_17');
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-btn {
|
||||||
|
background-image: resource('SHI/Images/playbar_icon_start_16x21');
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-btn {
|
||||||
|
background-image: resource('SHI/Images/playbar_icon_end_16x21');
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time-label {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
font-size: 14px;
|
||||||
|
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-SemiBold');
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interval-dropdown {
|
||||||
|
width: 60px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-color: rgb(180, 180, 180);
|
||||||
|
color: rgb(50, 50, 50);
|
||||||
|
font-size: 12px;
|
||||||
|
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
|
||||||
|
}
|
||||||
|
|
||||||
|
.interval-dropdown .unity-base-popup-field__input {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-width: 0;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interval-dropdown .unity-base-popup-field__text {
|
||||||
|
color: rgb(50, 50, 50);
|
||||||
|
-unity-text-align: middle-left;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interval-dropdown .unity-base-popup-field__arrow {
|
||||||
|
-unity-background-image-tint-color: rgb(100, 100, 100);
|
||||||
|
}
|
||||||
11
Assets/Resources/SHI/Modal/PlayBar.uss.meta
Normal file
11
Assets/Resources/SHI/Modal/PlayBar.uss.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21091244fd5f49d42a6694b75fe2c395
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
disableValidation: 0
|
||||||
22
Assets/Resources/SHI/Modal/PlayBar.uxml
Normal file
22
Assets/Resources/SHI/Modal/PlayBar.uxml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||||
|
<Style src="project://database/Assets/Resources/SHI/Modal/PlayBar.uss?fileID=7433441132597879392&guid=21091244fd5f49d42a6694b75fe2c395&type=3#PlayBar" />
|
||||||
|
<ui:VisualElement name="playbar-container" class="playbar-container">
|
||||||
|
<ui:VisualElement name="progress-section" class="progress-section">
|
||||||
|
<ui:Label name="start-label" text="2025-09-01" class="date-label start-label" />
|
||||||
|
<ui:VisualElement name="progress-track" class="progress-track">
|
||||||
|
<ui:VisualElement name="progress-fill" class="progress-fill" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:Label name="end-label" text="2025-09-01" class="date-label end-label" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="controls-section" class="controls-section">
|
||||||
|
<ui:VisualElement name="button-group" class="button-group">
|
||||||
|
<ui:Button name="play-btn" class="control-btn play-btn" style="width: 17px; height: 21px;" />
|
||||||
|
<ui:Button name="first-btn" class="control-btn first-btn" style="width: 16px; height: 21px;" />
|
||||||
|
<ui:Button name="last-btn" class="control-btn last-btn" style="width: 16px; height: 21px;" />
|
||||||
|
<ui:Button name="stop-btn" class="control-btn stop-btn" style="width: 17px; height: 17px;" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:Label name="current-time-label" text="2025-09-01 13:56" class="current-time-label" />
|
||||||
|
<ui:DropdownField name="interval-dropdown" index="-1" class="interval-dropdown" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
</ui:VisualElement>
|
||||||
|
</ui:UXML>
|
||||||
10
Assets/Resources/SHI/Modal/PlayBar.uxml.meta
Normal file
10
Assets/Resources/SHI/Modal/PlayBar.uxml.meta
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 92fab9ded97bb07428895b8a3bd766dc
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
@@ -150,3 +150,105 @@
|
|||||||
.visibility-off {
|
.visibility-off {
|
||||||
background-image: resource('SHI/Images/icon_visibility_off_64');
|
background-image: resource('SHI/Images/icon_visibility_off_64');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
세로 스크롤바 스타일
|
||||||
|
슬림한 6px 너비의 커스텀 스크롤바
|
||||||
|
=================================== */
|
||||||
|
.unity-scroller--vertical {
|
||||||
|
width: 6px; /* 슬림한 너비 */
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 트랙 (배경) */
|
||||||
|
.unity-scroller--vertical .unity-base-slider__tracker {
|
||||||
|
background-color: rgba(255, 255, 255, 0); /* 흰색 배경 */
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 드래거 컨테이너 위치 조정 */
|
||||||
|
.unity-scroller--vertical .unity-base-slider__drag-container {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 드래거 (핸들) */
|
||||||
|
.unity-scroller--vertical .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px; /* 둥근 모서리 */
|
||||||
|
width: 6px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 화살표 버튼 숨김 */
|
||||||
|
.unity-scroller--vertical .unity-repeat-button {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 슬라이더 마진 제거 */
|
||||||
|
.unity-scroller--vertical .unity-slider {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 세로 스크롤바 입력 필드 크기 조정 */
|
||||||
|
.unity-scroller--vertical .unity-base-field__input {
|
||||||
|
width: 6px;
|
||||||
|
min-width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
가로 스크롤바 스타일
|
||||||
|
슬림한 6px 높이의 커스텀 스크롤바
|
||||||
|
=================================== */
|
||||||
|
.unity-scroller--horizontal {
|
||||||
|
height: 6px; /* 슬림한 높이 */
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 트랙 (배경) */
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__tracker {
|
||||||
|
background-color: rgba(255, 255, 255, 0); /* 흰색 배경 */
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 드래거 컨테이너 위치 조정 */
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__drag-container {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 드래거 (핸들) */
|
||||||
|
.unity-scroller--horizontal .unity-base-slider__dragger {
|
||||||
|
background-color: rgb(216, 216, 216); /* 밝은 회색 */
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 3px; /* 둥근 모서리 */
|
||||||
|
height: 6px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 화살표 버튼 숨김 */
|
||||||
|
.unity-scroller--horizontal .unity-repeat-button {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 슬라이더 마진 제거 */
|
||||||
|
.unity-scroller--horizontal .unity-slider {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 가로 스크롤바 입력 필드 크기 조정 */
|
||||||
|
.unity-scroller--horizontal .unity-base-field__input {
|
||||||
|
height: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,57 +1,14 @@
|
|||||||
<!--
|
|
||||||
TreeList.uxml - 계층 구조 트리 뷰 메인 레이아웃
|
|
||||||
|
|
||||||
[개요]
|
|
||||||
3D 모델의 계층 구조를 표시하는 TreeList 컴포넌트의 UI 레이아웃입니다.
|
|
||||||
검색 기능과 가시성 토글을 지원합니다.
|
|
||||||
|
|
||||||
[UI 구조]
|
|
||||||
container (tree-menu-container)
|
|
||||||
├── header - 상단 헤더 영역
|
|
||||||
│ ├── Label "모델 검색" - 제목
|
|
||||||
│ └── hide-btn - TreeList 패널 숨기기 버튼
|
|
||||||
├── search-field - 검색 입력 필드
|
|
||||||
│ ├── clear-btn - 검색어 지우기 버튼
|
|
||||||
│ └── VisualElement - 검색 아이콘
|
|
||||||
└── main-tree-view - Unity TreeView (항목들이 여기에 렌더링됨)
|
|
||||||
|
|
||||||
[연관 파일]
|
|
||||||
- TreeList.uss : 스타일시트
|
|
||||||
- TreeListItem.uxml : 개별 항목 템플릿
|
|
||||||
- TreeList.cs : C# 컨트롤러 (SHI.Modal 네임스페이스)
|
|
||||||
|
|
||||||
[사용되는 리소스]
|
|
||||||
- Fonts/Pretendard/Pretendard-Bold : 제목 폰트
|
|
||||||
- SHI/Images/btn_close_explorer_22 : 닫기 버튼 이미지
|
|
||||||
- SHI/Images/btn_cancel_64 : 검색어 지우기 버튼 이미지
|
|
||||||
- SHI/Images/icon_search_16x17 : 검색 아이콘
|
|
||||||
-->
|
|
||||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||||
<Style src="project://database/Assets/Resources/SHI/Modal/TreeList.uss?fileID=7433441132597879392&guid=a1b1f50d423b463408e1f540fb4acfe9&type=3#TreeList" />
|
<Style src="project://database/Assets/Resources/SHI/Modal/TreeList.uss?fileID=7433441132597879392&guid=a1b1f50d423b463408e1f540fb4acfe9&type=3#TreeList" />
|
||||||
|
|
||||||
<!-- 메인 컨테이너: 어두운 배경의 사이드 패널 -->
|
|
||||||
<ui:VisualElement name="container" class="tree-menu-container">
|
<ui:VisualElement name="container" class="tree-menu-container">
|
||||||
|
|
||||||
<!-- 헤더 영역: 제목과 숨기기 버튼 -->
|
|
||||||
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between;">
|
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between;">
|
||||||
<!-- 제목 라벨 -->
|
|
||||||
<ui:Label text="모델 검색" style="color: white; -unity-font-style: normal; font-size: 20px; -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Bold');" />
|
<ui:Label text="모델 검색" style="color: white; -unity-font-style: normal; font-size: 20px; -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Bold');" />
|
||||||
<!-- TreeList 패널 숨기기 버튼 (X 아이콘) -->
|
|
||||||
<ui:Button name="hide-btn" style="width: 22px; height: 22px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; background-color: rgba(188, 188, 188, 0); background-image: resource('SHI/Images/btn_close_explorer_22'); align-self: center; align-items: auto; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0;" />
|
<ui:Button name="hide-btn" style="width: 22px; height: 22px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; background-color: rgba(188, 188, 188, 0); background-image: resource('SHI/Images/btn_close_explorer_22'); align-self: center; align-items: auto; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0;" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
<!-- 검색 입력 필드 -->
|
|
||||||
<ui:TextField name="search-field" placeholder-text="검색어를 입력하세요." class="search-field">
|
<ui:TextField name="search-field" placeholder-text="검색어를 입력하세요." class="search-field">
|
||||||
<!-- 검색어 지우기 버튼 (X 아이콘, 입력 필드 오른쪽) -->
|
|
||||||
<ui:Button name="clear-btn" style="width: 18px; height: 18px; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_cancel_64'); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; align-self: center; position: absolute; right: 26px; -unity-background-image-tint-color: rgb(180, 180, 180);" />
|
<ui:Button name="clear-btn" style="width: 18px; height: 18px; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource('SHI/Images/btn_cancel_64'); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; align-self: center; position: absolute; right: 26px; -unity-background-image-tint-color: rgb(180, 180, 180);" />
|
||||||
<!-- 검색 아이콘 (돋보기, 입력 필드 맨 오른쪽) -->
|
|
||||||
<ui:VisualElement style="flex-grow: 1; position: absolute; right: 6px; width: 16px; height: 17px; justify-content: center; align-items: auto; align-self: center; background-image: resource('SHI/Images/icon_search_16x17');" />
|
<ui:VisualElement style="flex-grow: 1; position: absolute; right: 6px; width: 16px; height: 17px; justify-content: center; align-items: auto; align-self: center; background-image: resource('SHI/Images/icon_search_16x17');" />
|
||||||
</ui:TextField>
|
</ui:TextField>
|
||||||
|
<ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="32" auto-expand="true" item-template="project://database/Assets/Resources/SHI/Modal/TreeListItem.uxml?fileID=9197481963319205126&guid=c5b3acae6c4669946b6e6b8b36d82e88&type=3#TreeListItem" horizontal-scrolling="true" style="flex-grow: 1;" />
|
||||||
<!-- 메인 트리뷰: 모델 계층 구조를 표시
|
|
||||||
- view-data-key: 상태 저장용 키
|
|
||||||
- fixed-item-height: 항목 높이 32px
|
|
||||||
- auto-expand: 자동 펼치기 활성화 -->
|
|
||||||
<ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="32" auto-expand="true" style="flex-grow: 1;" />
|
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:UXML>
|
</ui:UXML>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using SHI.Modal.ISOP;
|
using SHI.Modal.ISOP;
|
||||||
using SHI.Modal.NW;
|
using SHI.Modal.NW;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -80,12 +81,18 @@ public class ShiPopupSample : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
string sa = Application.streamingAssetsPath;
|
string sa = Application.streamingAssetsPath;
|
||||||
string glbPath = Path.Combine(sa, "block.glb");
|
string glbPath = Path.Combine(sa, "B16VC.glb");
|
||||||
string jsonPath = Path.Combine(sa, "isop_chart_short.json");
|
string jsonPath = Path.Combine(sa, "isop_chart_short.json");
|
||||||
|
|
||||||
|
|
||||||
Debug.Log($"Loaded blockDetailModal:{isopModal}");
|
Debug.Log($"Loaded blockDetailModal:{isopModal}");
|
||||||
await isopModal.LoadData(glbPath, jsonPath);
|
await isopModal.LoadData(new List<string> {
|
||||||
|
Path.Combine(sa, "B11TC.glb"),
|
||||||
|
Path.Combine(sa, "B16VC.glb"),
|
||||||
|
Path.Combine(sa, "E11VC.glb"),
|
||||||
|
Path.Combine(sa, "E41UC.glb"),
|
||||||
|
Path.Combine(sa, "M11UC.glb"),
|
||||||
|
}, jsonPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTaskVoid SetupDataNW()
|
private async UniTaskVoid SetupDataNW()
|
||||||
@@ -96,12 +103,17 @@ public class ShiPopupSample : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string sa = Application.streamingAssetsPath;
|
string sa = Application.streamingAssetsPath;
|
||||||
string glbPath = Path.Combine(sa, "block.glb");
|
|
||||||
string jsonPath = Path.Combine(sa, "nw_chart.json");
|
string jsonPath = Path.Combine(sa, "nw_chart.json");
|
||||||
|
|
||||||
|
|
||||||
Debug.Log($"Loaded blockDetailModal:{nwModal}");
|
Debug.Log($"Loaded blockDetailModal:{nwModal}");
|
||||||
await nwModal.LoadData(glbPath, jsonPath);
|
await nwModal.LoadData(new List<string> {
|
||||||
|
Path.Combine(sa, "B11TC.glb"),
|
||||||
|
Path.Combine(sa, "B16VC.glb"),
|
||||||
|
Path.Combine(sa, "E11VC.glb"),
|
||||||
|
Path.Combine(sa, "E41UC.glb"),
|
||||||
|
Path.Combine(sa, "M11UC.glb"),
|
||||||
|
}, jsonPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,12 @@ namespace SHI.Modal.ISOP
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UxmlElement]
|
[UxmlElement]
|
||||||
public partial class ISOPChart : VisualElement
|
public partial class ISOPChart : VisualElement, IDisposable
|
||||||
{
|
{
|
||||||
|
#region IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 상수 (Constants)
|
#region 상수 (Constants)
|
||||||
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
||||||
private const string UXML_PATH = "SHI/Modal/ISOP/ISOPChart";
|
private const string UXML_PATH = "SHI/Modal/ISOP/ISOPChart";
|
||||||
@@ -525,5 +529,47 @@ namespace SHI.Modal.ISOP
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
/// <summary>
|
||||||
|
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// 스크롤 이벤트 해제
|
||||||
|
if (contentScroll != null)
|
||||||
|
{
|
||||||
|
contentScroll.horizontalScroller.valueChanged -= OnHorizontalScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 외부 이벤트 정리
|
||||||
|
OnExpand = null;
|
||||||
|
|
||||||
|
// 동적으로 생성된 UI 요소 정리
|
||||||
|
monthsLayer?.Clear();
|
||||||
|
weeksLayer?.Clear();
|
||||||
|
daysLayer?.Clear();
|
||||||
|
timelineContent?.Clear();
|
||||||
|
|
||||||
|
// 데이터 정리
|
||||||
|
tasks?.Clear();
|
||||||
|
tasks = null;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
_expandBtn = null;
|
||||||
|
taskRowTemplate = null;
|
||||||
|
root = null;
|
||||||
|
headerTitle = null;
|
||||||
|
headerTimeline = null;
|
||||||
|
monthsLayer = null;
|
||||||
|
weeksLayer = null;
|
||||||
|
daysLayer = null;
|
||||||
|
timelineContent = null;
|
||||||
|
contentScroll = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ namespace SHI.Modal.ISOP
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
public UIDocument uiDocument;
|
public UIDocument uiDocument;
|
||||||
|
|
||||||
private VisualElement content;
|
private VisualElement? content;
|
||||||
|
|
||||||
private TreeList listView;
|
private TreeList? listView;
|
||||||
private ISOPModelView modelView;
|
private ISOPModelView? modelView;
|
||||||
private ISOPChart chartView;
|
private ISOPChart? chartView;
|
||||||
|
|
||||||
private Button closeBtn;
|
private Button? closeBtn;
|
||||||
private Button showTreeBtn;
|
private Button? showTreeBtn;
|
||||||
private Button dragBtn;
|
private Button? dragBtn;
|
||||||
|
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
private bool _suppressSelection = false;
|
private bool _suppressSelection = false;
|
||||||
@@ -79,6 +79,14 @@ namespace SHI.Modal.ISOP
|
|||||||
private float _lastModelFlexGrow = 1f;
|
private float _lastModelFlexGrow = 1f;
|
||||||
private float _lastChartFlexGrow = 1f;
|
private float _lastChartFlexGrow = 1f;
|
||||||
|
|
||||||
|
// GeometryChangedEvent 콜백 (해제용)
|
||||||
|
private EventCallback<GeometryChangedEvent>? _contentGeometryChangedCallback;
|
||||||
|
|
||||||
|
// 로딩 UI
|
||||||
|
private VisualElement? _loadingOverlay;
|
||||||
|
private VisualElement? _loadingSpinner;
|
||||||
|
private IVisualElementScheduledItem? _spinnerAnimation;
|
||||||
|
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -140,8 +148,12 @@ namespace SHI.Modal.ISOP
|
|||||||
}
|
}
|
||||||
|
|
||||||
initDrag(root);
|
initDrag(root);
|
||||||
|
|
||||||
_expanded = ExpandedSide.None;
|
_expanded = ExpandedSide.None;
|
||||||
|
|
||||||
|
// 로딩 UI 참조
|
||||||
|
_loadingOverlay = root.Q<VisualElement>("loading-overlay");
|
||||||
|
_loadingSpinner = root.Q<VisualElement>("loading-spinner");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDrag(VisualElement root)
|
private void initDrag(VisualElement root)
|
||||||
@@ -158,103 +170,107 @@ namespace SHI.Modal.ISOP
|
|||||||
|
|
||||||
//dragBtn.AddManipulator(new HorizontalDragManipulator());
|
//dragBtn.AddManipulator(new HorizontalDragManipulator());
|
||||||
|
|
||||||
// 드래그 시작
|
// 드래그 이벤트 등록
|
||||||
dragBtn.RegisterCallback<PointerDownEvent>((evt) =>
|
dragBtn.RegisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
||||||
{
|
dragBtn.RegisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
||||||
// 좌클릭만 처리 (0)
|
dragBtn.RegisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
||||||
if (evt.button != 0) return;
|
dragBtn.RegisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
||||||
|
|
||||||
Debug.Log("Drag Started (PointerDown) - captured");
|
|
||||||
|
|
||||||
// 포인터 캡처
|
|
||||||
_isDragging = true;
|
|
||||||
_activePointerId = evt.pointerId;
|
|
||||||
dragBtn.CapturePointer(_activePointerId);
|
|
||||||
|
|
||||||
// 포인터가 drag 버튼의 어느 위치를 눌렀는지 계산 (center 기준)
|
|
||||||
// evt.position은 content 기준 좌표
|
|
||||||
var dragCenterX = dragBtn.layout.x + dragBtn.layout.width * 0.5f;
|
|
||||||
_dragOffset = evt.position.x - dragCenterX;
|
|
||||||
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
// 전역 포인터 무브로 위치 추적 (root에 등록)
|
|
||||||
dragBtn.RegisterCallback<PointerMoveEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
//Debug.Log($"Dragging... evt.pointerId:{evt.pointerId}, _activePointerId:{_activePointerId}");
|
|
||||||
|
|
||||||
|
|
||||||
// evt.position은 content 기준 좌표
|
|
||||||
float pointerX = evt.position.x;
|
|
||||||
float centerX = pointerX - _dragOffset;
|
|
||||||
ApplyDragPosition(content, dragBtn, centerX);
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
// 드래그 종료 (마우스 업 또는 포인터 캔슬)
|
|
||||||
dragBtn.RegisterCallback<PointerUpEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
|
|
||||||
Debug.Log("Drag Ended");
|
|
||||||
|
|
||||||
_isDragging = false;
|
|
||||||
if (_activePointerId != -1)
|
|
||||||
{
|
|
||||||
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
|
||||||
}
|
|
||||||
_activePointerId = -1;
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
dragBtn.RegisterCallback<PointerCancelEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
|
|
||||||
_isDragging = false;
|
|
||||||
if (_activePointerId != -1)
|
|
||||||
{
|
|
||||||
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
|
||||||
}
|
|
||||||
_activePointerId = -1;
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
|
|
||||||
// 초기화 및 레이아웃 변경 시 재계산
|
// 초기화 및 레이아웃 변경 시 재계산
|
||||||
bool initialized = false;
|
_contentGeometryChangedCallback = OnContentGeometryChanged;
|
||||||
content.RegisterCallback<GeometryChangedEvent>((evt) =>
|
content.RegisterCallback(_contentGeometryChangedCallback);
|
||||||
{
|
|
||||||
// 드래그 중에는 GeometryChanged 이벤트 무시
|
|
||||||
if (_isDragging) return;
|
|
||||||
|
|
||||||
// 초기화: treeList의 레이아웃이 계산될 때까지 대기
|
|
||||||
if (!initialized)
|
|
||||||
{
|
|
||||||
if (listView == null || listView.layout.width <= 0)
|
|
||||||
{
|
|
||||||
return; // 아직 레이아웃이 계산되지 않음
|
|
||||||
}
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
UpdateDragAndPanels(content, dragBtn);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerDown(PointerDownEvent evt)
|
||||||
|
{
|
||||||
|
// 좌클릭만 처리 (0)
|
||||||
|
if (evt.button != 0) return;
|
||||||
|
|
||||||
|
Debug.Log("Drag Started (PointerDown) - captured");
|
||||||
|
|
||||||
|
// 포인터 캡처
|
||||||
|
_isDragging = true;
|
||||||
|
_activePointerId = evt.pointerId;
|
||||||
|
dragBtn.CapturePointer(_activePointerId);
|
||||||
|
|
||||||
|
// 포인터가 drag 버튼의 어느 위치를 눌렀는지 계산 (center 기준)
|
||||||
|
// evt.position은 content 기준 좌표
|
||||||
|
var dragCenterX = dragBtn.layout.x + dragBtn.layout.width * 0.5f;
|
||||||
|
_dragOffset = evt.position.x - dragCenterX;
|
||||||
|
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerMove(PointerMoveEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
// evt.position은 content 기준 좌표
|
||||||
|
float pointerX = evt.position.x;
|
||||||
|
float centerX = pointerX - _dragOffset;
|
||||||
|
ApplyDragPosition(content, dragBtn, centerX);
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerUp(PointerUpEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
Debug.Log("Drag Ended");
|
||||||
|
|
||||||
|
_isDragging = false;
|
||||||
|
if (_activePointerId != -1)
|
||||||
|
{
|
||||||
|
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
||||||
|
}
|
||||||
|
_activePointerId = -1;
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerCancel(PointerCancelEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
_isDragging = false;
|
||||||
|
if (_activePointerId != -1)
|
||||||
|
{
|
||||||
|
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
||||||
|
}
|
||||||
|
_activePointerId = -1;
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _geometryInitialized = false;
|
||||||
|
private void OnContentGeometryChanged(GeometryChangedEvent evt)
|
||||||
|
{
|
||||||
|
// 드래그 중에는 GeometryChanged 이벤트 무시
|
||||||
|
if (_isDragging) return;
|
||||||
|
|
||||||
|
// 초기화: treeList의 레이아웃이 계산될 때까지 대기
|
||||||
|
if (!_geometryInitialized)
|
||||||
|
{
|
||||||
|
if (listView == null || listView.layout.width <= 0)
|
||||||
|
{
|
||||||
|
return; // 아직 레이아웃이 계산되지 않음
|
||||||
|
}
|
||||||
|
_geometryInitialized = true;
|
||||||
|
}
|
||||||
|
UpdateDragAndPanels(content, dragBtn);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// glTF 모델과 간트 데이터를 하위 뷰에 로드하고, 선택 동기화를 위한 매핑을 구축합니다.
|
/// glTF 모델과 간트 데이터를 하위 뷰에 로드하고, 선택 동기화를 위한 매핑을 구축합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gltfPath">glTF/glb 파일 경로.</param>
|
/// <param name="gltfPath">glTF/glb 파일 경로.</param>
|
||||||
/// <param name="ganttPath">간트 데이터셋 경로.</param>
|
/// <param name="ganttPath">간트 데이터셋 경로.</param>
|
||||||
/// <param name="externalCt">외부 취소 토큰.</param>
|
/// <param name="externalCt">외부 취소 토큰.</param>
|
||||||
public async UniTask LoadData(string gltfPath, string ganttPath, CancellationToken externalCt = default)
|
public async UniTask LoadData(List<string> gltfPaths, string ganttPath, CancellationToken externalCt = default)
|
||||||
{
|
{
|
||||||
if(modelView == null)
|
if(modelView == null)
|
||||||
{
|
{
|
||||||
@@ -263,38 +279,103 @@ namespace SHI.Modal.ISOP
|
|||||||
{
|
{
|
||||||
await UniTask.Yield();
|
await UniTask.Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Debug.Log($"ISOPModal: LoadData {gltfPath}");
|
Debug.Log($"ISOPModal: LoadData {string.Join(", ", gltfPaths)}, {ganttPath}");
|
||||||
|
|
||||||
|
|
||||||
// 이전 작업 취소
|
// 로딩 표시
|
||||||
if (_cts != null)
|
ShowLoading(true);
|
||||||
{
|
|
||||||
try { _cts.Cancel(); } catch { }
|
|
||||||
_cts.Dispose();
|
|
||||||
}
|
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
|
||||||
var ct = _cts.Token;
|
|
||||||
|
|
||||||
// 모델/리스트 로드
|
try
|
||||||
IEnumerable<TreeListItemData> items = Array.Empty<TreeListItemData>();
|
|
||||||
if (modelView != null)
|
|
||||||
{
|
{
|
||||||
try
|
// 이전 작업 취소
|
||||||
|
if (_cts != null)
|
||||||
{
|
{
|
||||||
items = await modelView.LoadModelAsync(gltfPath, ct);
|
try { _cts.Cancel(); } catch { }
|
||||||
|
_cts.Dispose();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
||||||
|
var ct = _cts.Token;
|
||||||
|
|
||||||
|
// 모델/리스트 로드
|
||||||
|
IEnumerable<TreeListItemData> items = Array.Empty<TreeListItemData>();
|
||||||
|
if (modelView != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
items = await modelView.LoadModelAsync(gltfPaths, ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildKeyMaps(items);
|
||||||
|
|
||||||
|
if (listView != null) listView.SetData(items.ToList());
|
||||||
|
if (chartView != null) chartView.Load(ganttPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 로딩 숨기기
|
||||||
|
ShowLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 로딩 UI (Loading UI)
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 오버레이와 스피너 애니메이션을 표시하거나 숨깁니다.
|
||||||
|
/// </summary>
|
||||||
|
private void ShowLoading(bool show)
|
||||||
|
{
|
||||||
|
if (_loadingOverlay == null) return;
|
||||||
|
|
||||||
|
if (show)
|
||||||
|
{
|
||||||
|
_loadingOverlay.AddToClassList("visible");
|
||||||
|
StartSpinnerAnimation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_loadingOverlay.RemoveFromClassList("visible");
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스피너 회전 애니메이션 시작
|
||||||
|
/// </summary>
|
||||||
|
private void StartSpinnerAnimation()
|
||||||
|
{
|
||||||
|
if (_loadingSpinner == null) return;
|
||||||
|
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
|
||||||
|
float angle = 0f;
|
||||||
|
_spinnerAnimation = _loadingSpinner.schedule.Execute(() =>
|
||||||
|
{
|
||||||
|
angle = (angle + 10f) % 360f;
|
||||||
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(angle));
|
||||||
|
}).Every(16); // ~60fps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스피너 회전 애니메이션 중지
|
||||||
|
/// </summary>
|
||||||
|
private void StopSpinnerAnimation()
|
||||||
|
{
|
||||||
|
if (_spinnerAnimation != null)
|
||||||
|
{
|
||||||
|
_spinnerAnimation.Pause();
|
||||||
|
_spinnerAnimation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildKeyMaps(items);
|
if (_loadingSpinner != null)
|
||||||
|
{
|
||||||
if (listView != null) listView.SetData(items.ToList());
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(0));
|
||||||
if (chartView != null) chartView.Load(ganttPath);
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
private void BuildKeyMaps(IEnumerable<TreeListItemData> items)
|
private void BuildKeyMaps(IEnumerable<TreeListItemData> items)
|
||||||
{
|
{
|
||||||
@@ -595,13 +676,24 @@ namespace SHI.Modal.ISOP
|
|||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
// CancellationTokenSource 정리
|
||||||
|
if (_cts != null)
|
||||||
|
{
|
||||||
|
try { _cts.Cancel(); } catch { }
|
||||||
|
_cts.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listView 이벤트 해제 및 Dispose
|
||||||
if (listView != null)
|
if (listView != null)
|
||||||
{
|
{
|
||||||
listView.OnSelectionChanged -= OnListItemSelectionChanged;
|
listView.OnSelectionChanged -= OnListItemSelectionChanged;
|
||||||
listView.OnClosed -= OnListClosed;
|
listView.OnClosed -= OnListClosed;
|
||||||
listView.OnVisibilityChanged -= OnListVisibilityChanged;
|
listView.OnVisibilityChanged -= OnListVisibilityChanged;
|
||||||
|
listView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modelView 이벤트 해제 및 Dispose
|
||||||
if (modelView != null)
|
if (modelView != null)
|
||||||
{
|
{
|
||||||
modelView.OnItemSelected -= OnModelItemSelected;
|
modelView.OnItemSelected -= OnModelItemSelected;
|
||||||
@@ -609,13 +701,56 @@ namespace SHI.Modal.ISOP
|
|||||||
modelView.Dispose();
|
modelView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chartView 이벤트 해제 및 Dispose
|
||||||
if (chartView != null)
|
if (chartView != null)
|
||||||
{
|
{
|
||||||
chartView.OnExpand -= ToggleExpandChart;
|
chartView.OnExpand -= ToggleExpandChart;
|
||||||
|
chartView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 버튼 이벤트 해제
|
||||||
if (showTreeBtn != null) showTreeBtn.clicked -= OnClickShowTree;
|
if (showTreeBtn != null) showTreeBtn.clicked -= OnClickShowTree;
|
||||||
if (closeBtn != null) closeBtn.clicked -= OnClickClose;
|
if (closeBtn != null) closeBtn.clicked -= OnClickClose;
|
||||||
|
|
||||||
|
// dragBtn 포인터 이벤트 해제
|
||||||
|
if (dragBtn != null)
|
||||||
|
{
|
||||||
|
dragBtn.UnregisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// content GeometryChangedEvent 해제
|
||||||
|
if (content != null && _contentGeometryChangedCallback != null)
|
||||||
|
{
|
||||||
|
content.UnregisterCallback(_contentGeometryChangedCallback);
|
||||||
|
_contentGeometryChangedCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 딕셔너리 정리
|
||||||
|
_keyToId.Clear();
|
||||||
|
_idToKey.Clear();
|
||||||
|
|
||||||
|
// 드래그 상태 초기화
|
||||||
|
_isDragging = false;
|
||||||
|
_activePointerId = -1;
|
||||||
|
_dragOffset = 0f;
|
||||||
|
_geometryInitialized = false;
|
||||||
|
|
||||||
|
// 로딩 UI 정리
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
_loadingOverlay = null;
|
||||||
|
_loadingSpinner = null;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
content = null;
|
||||||
|
listView = null;
|
||||||
|
modelView = null;
|
||||||
|
chartView = null;
|
||||||
|
closeBtn = null;
|
||||||
|
showTreeBtn = null;
|
||||||
|
dragBtn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,12 @@ namespace SHI.Modal.ISOP
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UxmlElement]
|
[UxmlElement]
|
||||||
public partial class ISOPModelView : VisualElement
|
public partial class ISOPModelView : VisualElement, IDisposable
|
||||||
{
|
{
|
||||||
|
#region IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 외부 이벤트 (Public Events)
|
#region 외부 이벤트 (Public Events)
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 뷰 내부에서 항목이 선택될 때 발생합니다.
|
/// 뷰 내부에서 항목이 선택될 때 발생합니다.
|
||||||
@@ -156,35 +160,55 @@ namespace SHI.Modal.ISOP
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 주어진 경로의 glTF 모델을 비동기로 로드하고, UI 트리에 사용할 계층 항목을 생성합니다.
|
/// 주어진 경로들의 glTF 모델을 비동기로 로드하고, UI 트리에 사용할 계층 항목을 생성합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async UniTask<IEnumerable<TreeListItemData>> LoadModelAsync(string path, CancellationToken ct)
|
/// <param name="paths">로드할 glTF/glb 파일 경로 목록</param>
|
||||||
|
/// <param name="ct">취소 토큰</param>
|
||||||
|
/// <returns>로드된 모델들의 계층 항목 목록</returns>
|
||||||
|
public async UniTask<IEnumerable<TreeListItemData>> LoadModelAsync(List<string> paths, CancellationToken ct)
|
||||||
{
|
{
|
||||||
Debug.Log($"ISOPModelView.LoadModelAsync: {path}");
|
Debug.Log($"ISOPModelView.LoadModelAsync: {paths?.Count ?? 0} files");
|
||||||
Dispose();
|
|
||||||
|
CleanupForReload();
|
||||||
await UniTask.DelayFrame(1);
|
await UniTask.DelayFrame(1);
|
||||||
EnsureCameraAndTargetTexture();
|
EnsureCameraAndTargetTexture();
|
||||||
|
|
||||||
var items = new List<TreeListItemData>();
|
var items = new List<TreeListItemData>();
|
||||||
var gltf = new GltfImport();
|
|
||||||
var success = await gltf.Load(path, new ImportSettings(), ct);
|
if (paths == null || paths.Count == 0)
|
||||||
if (!success)
|
|
||||||
{
|
{
|
||||||
Debug.LogError($"glTFast Load failed: {path}");
|
Debug.LogWarning("ISOPModelView.LoadModelAsync: No paths provided");
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_root == null) _root = new GameObject("ISOPModelViewRoot");
|
if (_root == null) _root = new GameObject("ISOPModelViewRoot");
|
||||||
_root.layer = modelLayer;
|
_root.layer = modelLayer;
|
||||||
var sceneOk = await gltf.InstantiateMainSceneAsync(_root.transform);
|
|
||||||
if (!sceneOk)
|
// 각 파일을 순차적으로 로드
|
||||||
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
Debug.LogError("InstantiateMainSceneAsync failed");
|
if (string.IsNullOrEmpty(path)) continue;
|
||||||
return items;
|
|
||||||
|
Debug.Log($"ISOPModelView.LoadModelAsync: Loading {path}");
|
||||||
|
var gltf = new GltfImport();
|
||||||
|
var success = await gltf.Load(path, new ImportSettings(), ct);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
Debug.LogError($"glTFast Load failed: {path}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sceneOk = await gltf.InstantiateMainSceneAsync(_root.transform);
|
||||||
|
if (!sceneOk)
|
||||||
|
{
|
||||||
|
Debug.LogError($"InstantiateMainSceneAsync failed: {path}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetLayerRecursive(_root, modelLayer);
|
SetLayerRecursive(_root, modelLayer);
|
||||||
|
|
||||||
|
// 로드된 모든 자식에서 TreeListItemData 생성
|
||||||
if (_root != null)
|
if (_root != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _root.transform.childCount; i++)
|
for (int i = 0; i < _root.transform.childCount; i++)
|
||||||
@@ -600,8 +624,13 @@ namespace SHI.Modal.ISOP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
/// <summary>
|
||||||
|
/// 모델 재로드를 위한 리소스 정리.
|
||||||
|
/// Dispose()와 달리 UI 참조(_renderContainer, _expandBtn)는 유지합니다.
|
||||||
|
/// </summary>
|
||||||
|
private void CleanupForReload()
|
||||||
{
|
{
|
||||||
|
// 머티리얼 정리 (인스턴스 머티리얼 삭제)
|
||||||
foreach (var kv in _originalSharedByRenderer)
|
foreach (var kv in _originalSharedByRenderer)
|
||||||
{
|
{
|
||||||
var r = kv.Key;
|
var r = kv.Key;
|
||||||
@@ -616,11 +645,14 @@ namespace SHI.Modal.ISOP
|
|||||||
}
|
}
|
||||||
_originalSharedByRenderer.Clear();
|
_originalSharedByRenderer.Clear();
|
||||||
_idToObject.Clear();
|
_idToObject.Clear();
|
||||||
|
|
||||||
|
// 모델 루트 삭제
|
||||||
if (_root != null) UnityEngine.Object.Destroy(_root);
|
if (_root != null) UnityEngine.Object.Destroy(_root);
|
||||||
_root = null;
|
_root = null;
|
||||||
_focusedId = null;
|
_focusedId = null;
|
||||||
_wireframeApplied = false;
|
_wireframeApplied = false;
|
||||||
|
|
||||||
|
// 카메라 및 렌더텍스처 정리
|
||||||
if (_viewCamera != null)
|
if (_viewCamera != null)
|
||||||
{
|
{
|
||||||
if (_rt != null && _viewCamera.targetTexture == _rt)
|
if (_rt != null && _viewCamera.targetTexture == _rt)
|
||||||
@@ -629,6 +661,7 @@ namespace SHI.Modal.ISOP
|
|||||||
}
|
}
|
||||||
_viewCamera.enabled = false;
|
_viewCamera.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_rt != null)
|
if (_rt != null)
|
||||||
{
|
{
|
||||||
_rt.Release();
|
_rt.Release();
|
||||||
@@ -636,13 +669,95 @@ namespace SHI.Modal.ISOP
|
|||||||
_rt = null;
|
_rt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//ISOPModelViewRig란 이름의 게임오브젝트도 같이 삭제
|
// 드래그 상태 초기화
|
||||||
|
_mmbDragging = false;
|
||||||
|
_rmbDragging = false;
|
||||||
|
|
||||||
|
// ID 시드 초기화
|
||||||
|
itemIdSeed = 1;
|
||||||
|
|
||||||
|
// 게임오브젝트 정리
|
||||||
|
var rigGo = GameObject.Find("ISOPModelViewRig");
|
||||||
|
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
||||||
|
_viewCamera = null;
|
||||||
|
|
||||||
|
var rootGo = GameObject.Find("ISOPModelViewRoot");
|
||||||
|
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// 마우스 이벤트 해제
|
||||||
|
UnregisterCallback<MouseDownEvent>(OnMouseDown);
|
||||||
|
UnregisterCallback<MouseUpEvent>(OnMouseUp);
|
||||||
|
UnregisterCallback<MouseMoveEvent>(OnMouseMove);
|
||||||
|
UnregisterCallback<WheelEvent>(OnWheel);
|
||||||
|
UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
||||||
|
|
||||||
|
// 외부 이벤트 정리
|
||||||
|
OnItemSelected = null;
|
||||||
|
OnExpand = null;
|
||||||
|
|
||||||
|
// 머티리얼 정리 (인스턴스 머티리얼 삭제)
|
||||||
|
foreach (var kv in _originalSharedByRenderer)
|
||||||
|
{
|
||||||
|
var r = kv.Key;
|
||||||
|
if (r == null) continue;
|
||||||
|
var originals = kv.Value;
|
||||||
|
var mats = r.materials;
|
||||||
|
for (int m = 0; m < mats.Length; m++)
|
||||||
|
{
|
||||||
|
if (mats[m] != null) UnityEngine.Object.Destroy(mats[m]);
|
||||||
|
}
|
||||||
|
r.materials = originals;
|
||||||
|
}
|
||||||
|
_originalSharedByRenderer.Clear();
|
||||||
|
_idToObject.Clear();
|
||||||
|
|
||||||
|
// 모델 루트 삭제
|
||||||
|
if (_root != null) UnityEngine.Object.Destroy(_root);
|
||||||
|
_root = null;
|
||||||
|
_focusedId = null;
|
||||||
|
_wireframeApplied = false;
|
||||||
|
_wireframeMat = null; // Resources에서 로드한 것은 Destroy하지 않음
|
||||||
|
|
||||||
|
// 카메라 및 렌더텍스처 정리
|
||||||
|
if (_viewCamera != null)
|
||||||
|
{
|
||||||
|
if (_rt != null && _viewCamera.targetTexture == _rt)
|
||||||
|
{
|
||||||
|
_viewCamera.targetTexture = null;
|
||||||
|
}
|
||||||
|
_viewCamera.enabled = false;
|
||||||
|
}
|
||||||
|
_viewCamera = null;
|
||||||
|
|
||||||
|
if (_rt != null)
|
||||||
|
{
|
||||||
|
_rt.Release();
|
||||||
|
UnityEngine.Object.Destroy(_rt);
|
||||||
|
_rt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 드래그 상태 초기화
|
||||||
|
_mmbDragging = false;
|
||||||
|
_rmbDragging = false;
|
||||||
|
|
||||||
|
// ID 시드 초기화
|
||||||
|
itemIdSeed = 1;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
_renderContainer = null;
|
||||||
|
_expandBtn = null;
|
||||||
|
|
||||||
|
// 게임오브젝트 정리
|
||||||
var rigGo = GameObject.Find("ISOPModelViewRig");
|
var rigGo = GameObject.Find("ISOPModelViewRig");
|
||||||
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
||||||
var rootGo = GameObject.Find("ISOPModelViewRoot");
|
var rootGo = GameObject.Find("ISOPModelViewRoot");
|
||||||
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 유틸리티 메서드
|
// 유틸리티 메서드
|
||||||
|
|||||||
@@ -64,8 +64,12 @@ namespace SHI.Modal.NW
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UxmlElement]
|
[UxmlElement]
|
||||||
public partial class NWChart : VisualElement
|
public partial class NWChart : VisualElement, IDisposable
|
||||||
{
|
{
|
||||||
|
#region IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
#endregion
|
||||||
|
|
||||||
private const string UXML_PATH = "SHI/Modal/NW/NWChart";
|
private const string UXML_PATH = "SHI/Modal/NW/NWChart";
|
||||||
|
|
||||||
public Action? OnExpand;
|
public Action? OnExpand;
|
||||||
@@ -123,7 +127,9 @@ namespace SHI.Modal.NW
|
|||||||
private bool isDragging;
|
private bool isDragging;
|
||||||
|
|
||||||
private DateTime projectStartDate;
|
private DateTime projectStartDate;
|
||||||
|
public DateTime ProjectStartDate => projectStartDate;
|
||||||
private DateTime projectEndDate;
|
private DateTime projectEndDate;
|
||||||
|
public DateTime ProjectEndDate => projectEndDate;
|
||||||
private int totalDays;
|
private int totalDays;
|
||||||
private float canvasHeight;
|
private float canvasHeight;
|
||||||
|
|
||||||
@@ -142,10 +148,15 @@ namespace SHI.Modal.NW
|
|||||||
_expandBtn = this.Q<Button>("expand-btn");
|
_expandBtn = this.Q<Button>("expand-btn");
|
||||||
if (_expandBtn != null)
|
if (_expandBtn != null)
|
||||||
{
|
{
|
||||||
_expandBtn.clicked += () => OnExpand?.Invoke();
|
_expandBtn.clicked += OnExpandBtnClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnExpandBtnClicked()
|
||||||
|
{
|
||||||
|
OnExpand?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public void Load(string jsonFileName)
|
public void Load(string jsonFileName)
|
||||||
{
|
{
|
||||||
root = this;
|
root = this;
|
||||||
@@ -157,6 +168,45 @@ namespace SHI.Modal.NW
|
|||||||
RenderNetwork();
|
RenderNetwork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지정한 날짜가 STDT~FNDT 범위에 포함되는 작업들의 BLK_NO 목록을 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="yyyymmdd">조회할 날짜 (yyyyMMdd 형식, 예: "20250818")</param>
|
||||||
|
/// <returns>해당 날짜에 진행 중인 블록 번호 목록</returns>
|
||||||
|
public List<string> GetModelNamesByDate(string yyyymmdd)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
if (tasks == null || string.IsNullOrEmpty(yyyymmdd)) return result;
|
||||||
|
|
||||||
|
foreach (var task in tasks)
|
||||||
|
{
|
||||||
|
// BLK_NO가 없으면 스킵
|
||||||
|
if (string.IsNullOrEmpty(task.BLK_NO)) continue;
|
||||||
|
|
||||||
|
// STDT, FNDT 가져오기
|
||||||
|
string stdt = task.STDT;
|
||||||
|
string fndt = task.FNDT;
|
||||||
|
|
||||||
|
// STDT가 없으면 스킵
|
||||||
|
if (string.IsNullOrEmpty(stdt) || stdt == "null") continue;
|
||||||
|
|
||||||
|
// FNDT가 없으면 STDT와 동일하게 처리
|
||||||
|
if (string.IsNullOrEmpty(fndt) || fndt == "null") fndt = stdt;
|
||||||
|
|
||||||
|
// 문자열 비교로 범위 체크 (yyyyMMdd 형식은 문자열 비교로 날짜 비교 가능)
|
||||||
|
if (string.CompareOrdinal(stdt, yyyymmdd) <= 0 && string.CompareOrdinal(yyyymmdd, fndt) <= 0)
|
||||||
|
{
|
||||||
|
// 중복 방지
|
||||||
|
if (!result.Contains(task.BLK_NO))
|
||||||
|
{
|
||||||
|
result.Add(task.BLK_NO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void LoadData(string jsonFileName)
|
void LoadData(string jsonFileName)
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(jsonFileName);
|
var json = File.ReadAllText(jsonFileName);
|
||||||
@@ -428,7 +478,7 @@ namespace SHI.Modal.NW
|
|||||||
var task = tasks!.Find( (item) => item.STDT == date.ToString("yyyyMMdd"));
|
var task = tasks!.Find( (item) => item.STDT == date.ToString("yyyyMMdd"));
|
||||||
if(task != null)
|
if(task != null)
|
||||||
{
|
{
|
||||||
Debug.Log($"Found task for date {date:yyyyMMdd}: REL_TP={task.REL_TP}, PROJ_TP={task.PROJ_TP}");
|
// Debug.Log($"Found task for date {date:yyyyMMdd}: REL_TP={task.REL_TP}, PROJ_TP={task.PROJ_TP}");
|
||||||
var lab = new Label($"{task.REL_TP}\n{task.PROJ_TP}");
|
var lab = new Label($"{task.REL_TP}\n{task.PROJ_TP}");
|
||||||
lab.style.unityTextAlign = TextAnchor.MiddleRight;
|
lab.style.unityTextAlign = TextAnchor.MiddleRight;
|
||||||
lab.style.width = Length.Percent(100);
|
lab.style.width = Length.Percent(100);
|
||||||
@@ -940,5 +990,69 @@ namespace SHI.Modal.NW
|
|||||||
if (draggingActivityCode == null) return;
|
if (draggingActivityCode == null) return;
|
||||||
UpdateConnectionsForNode(draggingActivityCode);
|
UpdateConnectionsForNode(draggingActivityCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
/// <summary>
|
||||||
|
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// 버튼 이벤트 해제
|
||||||
|
if (_expandBtn != null)
|
||||||
|
{
|
||||||
|
_expandBtn.clicked -= OnExpandBtnClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스크롤 이벤트 해제
|
||||||
|
if (contentScroll != null)
|
||||||
|
{
|
||||||
|
contentScroll.horizontalScroller.valueChanged -= OnHorizontalScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 노드 드래그 이벤트 해제
|
||||||
|
foreach (var node in nodeElements.Values)
|
||||||
|
{
|
||||||
|
node.UnregisterCallback<PointerDownEvent>(OnNodePointerDown);
|
||||||
|
node.UnregisterCallback<PointerMoveEvent>(OnNodePointerMove);
|
||||||
|
node.UnregisterCallback<PointerUpEvent>(OnNodePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 외부 이벤트 정리
|
||||||
|
OnExpand = null;
|
||||||
|
|
||||||
|
// 동적으로 생성된 UI 요소 정리
|
||||||
|
monthsLayer?.Clear();
|
||||||
|
daysLayer?.Clear();
|
||||||
|
nodesLayer?.Clear();
|
||||||
|
linesLayer?.Clear();
|
||||||
|
|
||||||
|
// 데이터 정리
|
||||||
|
tasks?.Clear();
|
||||||
|
tasks = null;
|
||||||
|
tasksByActivityCode.Clear();
|
||||||
|
nodeElements.Clear();
|
||||||
|
nodePositions.Clear();
|
||||||
|
connectionCache.Clear();
|
||||||
|
|
||||||
|
// 드래그 상태 정리
|
||||||
|
draggingNode = null;
|
||||||
|
draggingActivityCode = null;
|
||||||
|
isDragging = false;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
_expandBtn = null;
|
||||||
|
root = null;
|
||||||
|
headerTimeline = null;
|
||||||
|
monthsLayer = null;
|
||||||
|
daysLayer = null;
|
||||||
|
networkCanvas = null;
|
||||||
|
nodesLayer = null;
|
||||||
|
linesLayer = null;
|
||||||
|
contentScroll = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,13 +50,14 @@ namespace SHI.Modal.NW
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
public UIDocument uiDocument;
|
public UIDocument uiDocument;
|
||||||
|
|
||||||
private TreeList listView;
|
private TreeList? listView;
|
||||||
private NWModelView modelView;
|
private NWModelView? modelView;
|
||||||
private NWChart chartView;
|
private NWChart? chartView;
|
||||||
|
private PlayBar? _playBar;
|
||||||
|
|
||||||
private Button closeBtn;
|
private Button? closeBtn;
|
||||||
private Button showTreeBtn;
|
private Button? showTreeBtn;
|
||||||
private Button dragBtn;
|
private Button? dragBtn;
|
||||||
|
|
||||||
private VisualElement? rightPanel;
|
private VisualElement? rightPanel;
|
||||||
|
|
||||||
@@ -81,6 +82,13 @@ namespace SHI.Modal.NW
|
|||||||
private float _lastModelFlexGrow = 1f;
|
private float _lastModelFlexGrow = 1f;
|
||||||
private float _lastChartFlexGrow = 1f;
|
private float _lastChartFlexGrow = 1f;
|
||||||
|
|
||||||
|
// GeometryChangedEvent 콜백 (해제용)
|
||||||
|
private EventCallback<GeometryChangedEvent>? _rightPanelGeometryChangedCallback;
|
||||||
|
|
||||||
|
// 로딩 UI
|
||||||
|
private VisualElement? _loadingOverlay;
|
||||||
|
private VisualElement? _loadingSpinner;
|
||||||
|
private IVisualElementScheduledItem? _spinnerAnimation;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -141,11 +149,45 @@ namespace SHI.Modal.NW
|
|||||||
closeBtn.clicked += OnClickClose;
|
closeBtn.clicked += OnClickClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_playBar = root.Q<PlayBar>("play-bar");
|
||||||
|
if (_playBar != null)
|
||||||
|
{
|
||||||
|
_playBar.SetTimeRange(DateTime.Now, DateTime.Now.AddHours(1));
|
||||||
|
_playBar.OnPlayProgress += OnPlayProgressHandler;
|
||||||
|
_playBar.OnPositionChanged += OnPlayPositionChangedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
initDrag(root);
|
initDrag(root);
|
||||||
|
|
||||||
_expanded = ExpandedSide.None;
|
_expanded = ExpandedSide.None;
|
||||||
|
|
||||||
|
// 로딩 UI 참조
|
||||||
|
_loadingOverlay = root.Q<VisualElement>("loading-overlay");
|
||||||
|
_loadingSpinner = root.Q<VisualElement>("loading-spinner");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPlayProgressHandler(DateTime time)
|
||||||
|
{
|
||||||
|
if(chartView != null && listView != null)
|
||||||
|
{
|
||||||
|
List<string> models = chartView.GetModelNamesByDate(time.ToString("yyyyMMdd"));
|
||||||
|
Debug.Log($"Models at {time:yyyyMMdd}: {string.Join(", ", models)}");
|
||||||
|
if(models.Count > 0) listView.ShowItems(models);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayPositionChangedHandler(DateTime time)
|
||||||
|
{
|
||||||
|
if(chartView != null && listView != null)
|
||||||
|
{
|
||||||
|
List<string> models = chartView.GetModelNamesByDate(time.ToString("yyyyMMdd"));
|
||||||
|
Debug.Log($"Models at {time:yyyyMMdd}: {string.Join(", ", models)}");
|
||||||
|
if(models.Count > 0) listView.ShowItems(models);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void initDrag(VisualElement root)
|
private void initDrag(VisualElement root)
|
||||||
{
|
{
|
||||||
dragBtn = root.Q<Button>("drag-btn");
|
dragBtn = root.Q<Button>("drag-btn");
|
||||||
@@ -157,100 +199,114 @@ namespace SHI.Modal.NW
|
|||||||
dragBtn.style.position = Position.Absolute;
|
dragBtn.style.position = Position.Absolute;
|
||||||
dragBtn.pickingMode = PickingMode.Position;
|
dragBtn.pickingMode = PickingMode.Position;
|
||||||
|
|
||||||
// 드래그 시작
|
// 드래그 이벤트 등록
|
||||||
dragBtn.RegisterCallback<PointerDownEvent>((evt) =>
|
dragBtn.RegisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
||||||
{
|
dragBtn.RegisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
||||||
// 좌클릭만 처리 (0)
|
dragBtn.RegisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
||||||
if (evt.button != 0) return;
|
dragBtn.RegisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
||||||
|
|
||||||
Debug.Log("Drag Started (PointerDown) - captured");
|
|
||||||
|
|
||||||
// 포인터 캡처
|
|
||||||
_isDragging = true;
|
|
||||||
_activePointerId = evt.pointerId;
|
|
||||||
dragBtn.CapturePointer(_activePointerId);
|
|
||||||
|
|
||||||
// 포인터가 drag 버튼의 어느 위치를 눌렀는지 계산 (center 기준, Y축)
|
|
||||||
var dragCenterY = dragBtn.layout.y + dragBtn.layout.height * 0.5f;
|
|
||||||
_dragOffset = evt.position.y - dragCenterY;
|
|
||||||
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
// 전역 포인터 무브로 위치 추적
|
|
||||||
dragBtn.RegisterCallback<PointerMoveEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
|
|
||||||
// evt.position은 rightPanel 기준 좌표
|
|
||||||
float pointerY = evt.position.y;
|
|
||||||
float centerY = pointerY - _dragOffset;
|
|
||||||
ApplyDragPosition(rightPanel, dragBtn, centerY);
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
// 드래그 종료 (마우스 업 또는 포인터 캔슬)
|
|
||||||
dragBtn.RegisterCallback<PointerUpEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
|
|
||||||
Debug.Log("Drag Ended");
|
|
||||||
|
|
||||||
_isDragging = false;
|
|
||||||
if (_activePointerId != -1)
|
|
||||||
{
|
|
||||||
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
|
||||||
}
|
|
||||||
_activePointerId = -1;
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
dragBtn.RegisterCallback<PointerCancelEvent>((evt) =>
|
|
||||||
{
|
|
||||||
if (!_isDragging) return;
|
|
||||||
if (evt.pointerId != _activePointerId) return;
|
|
||||||
|
|
||||||
_isDragging = false;
|
|
||||||
if (_activePointerId != -1)
|
|
||||||
{
|
|
||||||
try { dragBtn.ReleasePointer(_activePointerId); } catch { }
|
|
||||||
}
|
|
||||||
_activePointerId = -1;
|
|
||||||
evt.StopImmediatePropagation();
|
|
||||||
}, TrickleDown.TrickleDown);
|
|
||||||
|
|
||||||
|
|
||||||
// 초기화 및 레이아웃 변경 시 재계산
|
// 초기화 및 레이아웃 변경 시 재계산
|
||||||
bool initialized = false;
|
_rightPanelGeometryChangedCallback = OnRightPanelGeometryChanged;
|
||||||
rightPanel.RegisterCallback<GeometryChangedEvent>((evt) =>
|
rightPanel.RegisterCallback(_rightPanelGeometryChangedCallback);
|
||||||
{
|
|
||||||
// 드래그 중에는 GeometryChanged 이벤트 무시
|
|
||||||
if (_isDragging) return;
|
|
||||||
|
|
||||||
// 초기화: rightPanel의 레이아웃이 계산될 때까지 대기
|
|
||||||
if (!initialized)
|
|
||||||
{
|
|
||||||
if (rightPanel.layout.height <= 0)
|
|
||||||
{
|
|
||||||
return; // 아직 레이아웃이 계산되지 않음
|
|
||||||
}
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
UpdateDragAndPanels(rightPanel, dragBtn);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerDown(PointerDownEvent evt)
|
||||||
|
{
|
||||||
|
// 좌클릭만 처리 (0)
|
||||||
|
if (evt.button != 0) return;
|
||||||
|
|
||||||
|
Debug.Log("Drag Started (PointerDown) - captured");
|
||||||
|
|
||||||
|
// 포인터 캡처
|
||||||
|
_isDragging = true;
|
||||||
|
_activePointerId = evt.pointerId;
|
||||||
|
dragBtn?.CapturePointer(_activePointerId);
|
||||||
|
|
||||||
|
// 포인터가 drag 버튼의 어느 위치를 눌렀는지 계산 (center 기준, Y축)
|
||||||
|
if (dragBtn != null)
|
||||||
|
{
|
||||||
|
var dragCenterY = dragBtn.layout.y + dragBtn.layout.height * 0.5f;
|
||||||
|
_dragOffset = evt.position.y - dragCenterY;
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerMove(PointerMoveEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
// evt.position은 rightPanel 기준 좌표
|
||||||
|
float pointerY = evt.position.y;
|
||||||
|
float centerY = pointerY - _dragOffset;
|
||||||
|
if (rightPanel != null && dragBtn != null)
|
||||||
|
{
|
||||||
|
ApplyDragPosition(rightPanel, dragBtn, centerY);
|
||||||
|
}
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerUp(PointerUpEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
Debug.Log("Drag Ended");
|
||||||
|
|
||||||
|
_isDragging = false;
|
||||||
|
if (_activePointerId != -1)
|
||||||
|
{
|
||||||
|
try { dragBtn?.ReleasePointer(_activePointerId); } catch { }
|
||||||
|
}
|
||||||
|
_activePointerId = -1;
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragPointerCancel(PointerCancelEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isDragging) return;
|
||||||
|
if (evt.pointerId != _activePointerId) return;
|
||||||
|
|
||||||
|
_isDragging = false;
|
||||||
|
if (_activePointerId != -1)
|
||||||
|
{
|
||||||
|
try { dragBtn?.ReleasePointer(_activePointerId); } catch { }
|
||||||
|
}
|
||||||
|
_activePointerId = -1;
|
||||||
|
evt.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _geometryInitialized = false;
|
||||||
|
private void OnRightPanelGeometryChanged(GeometryChangedEvent evt)
|
||||||
|
{
|
||||||
|
// 드래그 중에는 GeometryChanged 이벤트 무시
|
||||||
|
if (_isDragging) return;
|
||||||
|
|
||||||
|
// 초기화: rightPanel의 레이아웃이 계산될 때까지 대기
|
||||||
|
if (!_geometryInitialized)
|
||||||
|
{
|
||||||
|
if (rightPanel == null || rightPanel.layout.height <= 0)
|
||||||
|
{
|
||||||
|
return; // 아직 레이아웃이 계산되지 않음
|
||||||
|
}
|
||||||
|
_geometryInitialized = true;
|
||||||
|
}
|
||||||
|
if (rightPanel != null && dragBtn != null)
|
||||||
|
{
|
||||||
|
UpdateDragAndPanels(rightPanel, dragBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// glTF 모델과 간트 데이터를 하위 뷰에 로드하고, 선택 동기화를 위한 매핑을 구축합니다.
|
/// glTF 모델과 간트 데이터를 하위 뷰에 로드하고, 선택 동기화를 위한 매핑을 구축합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gltfPath">glTF/glb 파일 경로.</param>
|
/// <param name="gltfPaths">glTF/glb 파일 경로 목록.</param>
|
||||||
/// <param name="ganttPath">간트 데이터셋 경로.</param>
|
/// <param name="ganttPath">간트 데이터셋 경로.</param>
|
||||||
/// <param name="externalCt">외부 취소 토큰.</param>
|
/// <param name="externalCt">외부 취소 토큰.</param>
|
||||||
public async UniTask LoadData(string gltfPath, string ganttPath, CancellationToken externalCt = default)
|
public async UniTask LoadData(List<string> gltfPaths, string ganttPath, CancellationToken externalCt = default)
|
||||||
{
|
{
|
||||||
if(modelView == null)
|
if(modelView == null)
|
||||||
{
|
{
|
||||||
@@ -259,38 +315,107 @@ namespace SHI.Modal.NW
|
|||||||
{
|
{
|
||||||
await UniTask.Yield();
|
await UniTask.Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Debug.Log($"NWModal: LoadData {gltfPath}");
|
Debug.Log($"NWModal.LoadData: gltfPath:{string.Join(", ", gltfPaths)}, ganttPath={ganttPath}");
|
||||||
|
|
||||||
|
|
||||||
// 이전 작업 취소
|
// 로딩 표시
|
||||||
if (_cts != null)
|
ShowLoading(true);
|
||||||
{
|
|
||||||
try { _cts.Cancel(); } catch { }
|
|
||||||
_cts.Dispose();
|
|
||||||
}
|
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
|
||||||
var ct = _cts.Token;
|
|
||||||
|
|
||||||
// 모델/리스트 로드
|
try
|
||||||
IEnumerable<TreeListItemData> items = Array.Empty<TreeListItemData>();
|
|
||||||
if (modelView != null)
|
|
||||||
{
|
{
|
||||||
try
|
// 이전 작업 취소
|
||||||
|
if (_cts != null)
|
||||||
{
|
{
|
||||||
items = await modelView.LoadModelAsync(gltfPath, ct);
|
try { _cts.Cancel(); } catch { }
|
||||||
|
_cts.Dispose();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
||||||
|
var ct = _cts.Token;
|
||||||
|
|
||||||
|
// 모델/리스트 로드
|
||||||
|
IEnumerable<TreeListItemData> items = Array.Empty<TreeListItemData>();
|
||||||
|
if (modelView != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
items = await modelView.LoadModelAsync(gltfPaths, ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildKeyMaps(items);
|
||||||
|
|
||||||
|
if (listView != null) listView.SetData(items.ToList());
|
||||||
|
if (chartView != null) chartView.Load(ganttPath);
|
||||||
|
if (_playBar != null && chartView != null)
|
||||||
|
{
|
||||||
|
_playBar.SetTimeRange(chartView.ProjectStartDate, chartView.ProjectEndDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 로딩 숨기기
|
||||||
|
ShowLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 로딩 UI (Loading UI)
|
||||||
|
/// <summary>
|
||||||
|
/// 로딩 오버레이와 스피너 애니메이션을 표시하거나 숨깁니다.
|
||||||
|
/// </summary>
|
||||||
|
private void ShowLoading(bool show)
|
||||||
|
{
|
||||||
|
if (_loadingOverlay == null) return;
|
||||||
|
|
||||||
|
if (show)
|
||||||
|
{
|
||||||
|
_loadingOverlay.AddToClassList("visible");
|
||||||
|
StartSpinnerAnimation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_loadingOverlay.RemoveFromClassList("visible");
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스피너 회전 애니메이션 시작
|
||||||
|
/// </summary>
|
||||||
|
private void StartSpinnerAnimation()
|
||||||
|
{
|
||||||
|
if (_loadingSpinner == null) return;
|
||||||
|
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
|
||||||
|
float angle = 0f;
|
||||||
|
_spinnerAnimation = _loadingSpinner.schedule.Execute(() =>
|
||||||
|
{
|
||||||
|
angle = (angle + 10f) % 360f;
|
||||||
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(angle));
|
||||||
|
}).Every(16); // ~60fps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스피너 회전 애니메이션 중지
|
||||||
|
/// </summary>
|
||||||
|
private void StopSpinnerAnimation()
|
||||||
|
{
|
||||||
|
if (_spinnerAnimation != null)
|
||||||
|
{
|
||||||
|
_spinnerAnimation.Pause();
|
||||||
|
_spinnerAnimation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildKeyMaps(items);
|
if (_loadingSpinner != null)
|
||||||
|
{
|
||||||
if (listView != null) listView.SetData(items.ToList());
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(0));
|
||||||
if (chartView != null) chartView.Load(ganttPath);
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
private void BuildKeyMaps(IEnumerable<TreeListItemData> items)
|
private void BuildKeyMaps(IEnumerable<TreeListItemData> items)
|
||||||
{
|
{
|
||||||
@@ -566,13 +691,24 @@ namespace SHI.Modal.NW
|
|||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
// CancellationTokenSource 정리
|
||||||
|
if (_cts != null)
|
||||||
|
{
|
||||||
|
try { _cts.Cancel(); } catch { }
|
||||||
|
_cts.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listView 이벤트 해제 및 Dispose
|
||||||
if (listView != null)
|
if (listView != null)
|
||||||
{
|
{
|
||||||
listView.OnSelectionChanged -= OnListItemSelectionChanged;
|
listView.OnSelectionChanged -= OnListItemSelectionChanged;
|
||||||
listView.OnClosed -= OnListClosed;
|
listView.OnClosed -= OnListClosed;
|
||||||
listView.OnVisibilityChanged -= OnListVisibilityChanged;
|
listView.OnVisibilityChanged -= OnListVisibilityChanged;
|
||||||
|
listView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modelView 이벤트 해제 및 Dispose
|
||||||
if (modelView != null)
|
if (modelView != null)
|
||||||
{
|
{
|
||||||
modelView.OnItemSelected -= OnModelItemSelected;
|
modelView.OnItemSelected -= OnModelItemSelected;
|
||||||
@@ -580,13 +716,63 @@ namespace SHI.Modal.NW
|
|||||||
modelView.Dispose();
|
modelView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chartView 이벤트 해제 및 Dispose
|
||||||
if (chartView != null)
|
if (chartView != null)
|
||||||
{
|
{
|
||||||
chartView.OnExpand -= ToggleExpandChart;
|
chartView.OnExpand -= ToggleExpandChart;
|
||||||
|
chartView.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 버튼 이벤트 해제
|
||||||
if (showTreeBtn != null) showTreeBtn.clicked -= OnClickShowTree;
|
if (showTreeBtn != null) showTreeBtn.clicked -= OnClickShowTree;
|
||||||
if (closeBtn != null) closeBtn.clicked -= OnClickClose;
|
if (closeBtn != null) closeBtn.clicked -= OnClickClose;
|
||||||
|
|
||||||
|
// dragBtn 포인터 이벤트 해제
|
||||||
|
if (dragBtn != null)
|
||||||
|
{
|
||||||
|
dragBtn.UnregisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
||||||
|
dragBtn.UnregisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rightPanel GeometryChangedEvent 해제
|
||||||
|
if (rightPanel != null && _rightPanelGeometryChangedCallback != null)
|
||||||
|
{
|
||||||
|
rightPanel.UnregisterCallback(_rightPanelGeometryChangedCallback);
|
||||||
|
_rightPanelGeometryChangedCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _playBar != null)
|
||||||
|
{
|
||||||
|
_playBar.OnPlayProgress -= OnPlayProgressHandler;
|
||||||
|
_playBar.OnPositionChanged -= OnPlayPositionChangedHandler;
|
||||||
|
_playBar.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 딕셔너리 정리
|
||||||
|
_keyToId.Clear();
|
||||||
|
_idToKey.Clear();
|
||||||
|
|
||||||
|
// 드래그 상태 초기화
|
||||||
|
_isDragging = false;
|
||||||
|
_activePointerId = -1;
|
||||||
|
_dragOffset = 0f;
|
||||||
|
_geometryInitialized = false;
|
||||||
|
|
||||||
|
// 로딩 UI 정리
|
||||||
|
StopSpinnerAnimation();
|
||||||
|
_loadingOverlay = null;
|
||||||
|
_loadingSpinner = null;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
rightPanel = null;
|
||||||
|
listView = null;
|
||||||
|
modelView = null;
|
||||||
|
chartView = null;
|
||||||
|
closeBtn = null;
|
||||||
|
showTreeBtn = null;
|
||||||
|
dragBtn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,12 @@ namespace SHI.Modal.NW
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UxmlElement]
|
[UxmlElement]
|
||||||
public partial class NWModelView : VisualElement
|
public partial class NWModelView : VisualElement, IDisposable
|
||||||
{
|
{
|
||||||
|
#region IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 외부 이벤트 (Public Events)
|
#region 외부 이벤트 (Public Events)
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 뷰 내부에서 항목이 선택될 때 발생합니다.
|
/// 뷰 내부에서 항목이 선택될 때 발생합니다.
|
||||||
@@ -141,10 +145,7 @@ namespace SHI.Modal.NW
|
|||||||
_expandBtn = this.Q<Button>("expand-btn");
|
_expandBtn = this.Q<Button>("expand-btn");
|
||||||
if(_expandBtn != null)
|
if(_expandBtn != null)
|
||||||
{
|
{
|
||||||
_expandBtn.clicked += () =>
|
_expandBtn.clicked += OnExpandBtnClicked;
|
||||||
{
|
|
||||||
OnExpand?.Invoke();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 마우스 이벤트 등록
|
// 마우스 이벤트 등록
|
||||||
@@ -155,36 +156,60 @@ namespace SHI.Modal.NW
|
|||||||
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void OnExpandBtnClicked()
|
||||||
/// 주어진 경로의 glTF 모델을 비동기로 로드하고, UI 트리에 사용할 계층 항목을 생성합니다.
|
|
||||||
/// </summary>
|
|
||||||
public async UniTask<IEnumerable<TreeListItemData>> LoadModelAsync(string path, CancellationToken ct)
|
|
||||||
{
|
{
|
||||||
Debug.Log($"NWModelView.LoadModelAsync: {path}");
|
OnExpand?.Invoke();
|
||||||
Dispose();
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 주어진 경로들의 glTF 모델을 비동기로 로드하고, UI 트리에 사용할 계층 항목을 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paths">로드할 glTF/glb 파일 경로 목록</param>
|
||||||
|
/// <param name="ct">취소 토큰</param>
|
||||||
|
/// <returns>로드된 모델들의 계층 항목 목록</returns>
|
||||||
|
public async UniTask<IEnumerable<TreeListItemData>> LoadModelAsync(List<string> paths, CancellationToken ct)
|
||||||
|
{
|
||||||
|
Debug.Log($"NWModelView.LoadModelAsync: {paths?.Count ?? 0} files");
|
||||||
|
CleanupForReload();
|
||||||
await UniTask.DelayFrame(1);
|
await UniTask.DelayFrame(1);
|
||||||
EnsureCameraAndTargetTexture();
|
EnsureCameraAndTargetTexture();
|
||||||
|
|
||||||
var items = new List<TreeListItemData>();
|
var items = new List<TreeListItemData>();
|
||||||
var gltf = new GltfImport();
|
|
||||||
var success = await gltf.Load(path, new ImportSettings(), ct);
|
if (paths == null || paths.Count == 0)
|
||||||
if (!success)
|
|
||||||
{
|
{
|
||||||
Debug.LogError($"glTFast Load failed: {path}");
|
Debug.LogWarning("NWModelView.LoadModelAsync: No paths provided");
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_root == null) _root = new GameObject("NWModelViewRoot");
|
if (_root == null) _root = new GameObject("NWModelViewRoot");
|
||||||
_root.layer = modelLayer;
|
_root.layer = modelLayer;
|
||||||
var sceneOk = await gltf.InstantiateMainSceneAsync(_root.transform);
|
|
||||||
if (!sceneOk)
|
// 각 파일을 순차적으로 로드
|
||||||
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
Debug.LogError("InstantiateMainSceneAsync failed");
|
if (string.IsNullOrEmpty(path)) continue;
|
||||||
return items;
|
|
||||||
|
Debug.Log($"NWModelView.LoadModelAsync: Loading {path}");
|
||||||
|
var gltf = new GltfImport();
|
||||||
|
var success = await gltf.Load(path, new ImportSettings(), ct);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
Debug.LogError($"glTFast Load failed: {path}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sceneOk = await gltf.InstantiateMainSceneAsync(_root.transform);
|
||||||
|
if (!sceneOk)
|
||||||
|
{
|
||||||
|
Debug.LogError($"InstantiateMainSceneAsync failed: {path}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetLayerRecursive(_root, modelLayer);
|
SetLayerRecursive(_root, modelLayer);
|
||||||
|
|
||||||
|
// 로드된 모든 자식에서 TreeListItemData 생성
|
||||||
if (_root != null)
|
if (_root != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _root.transform.childCount; i++)
|
for (int i = 0; i < _root.transform.childCount; i++)
|
||||||
@@ -313,7 +338,11 @@ namespace SHI.Modal.NW
|
|||||||
|
|
||||||
private void EnsureRenderTargetSize()
|
private void EnsureRenderTargetSize()
|
||||||
{
|
{
|
||||||
if (_viewCamera == null || _renderContainer == null) return;
|
if (_viewCamera == null || _renderContainer == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NWModelView] EnsureRenderTargetSize: camera={_viewCamera != null}, container={_renderContainer != null}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 고정 크기 사용
|
// 고정 크기 사용
|
||||||
int w = FIXED_RT_WIDTH;
|
int w = FIXED_RT_WIDTH;
|
||||||
@@ -377,12 +406,10 @@ namespace SHI.Modal.NW
|
|||||||
|
|
||||||
public void FocusItemById(int id)
|
public void FocusItemById(int id)
|
||||||
{
|
{
|
||||||
Debug.Log($"NWModelView.FocusItemById: id={id}");
|
|
||||||
_focusedId = id;
|
_focusedId = id;
|
||||||
if (_idToObject.TryGetValue(id, out var go))
|
if (_idToObject.TryGetValue(id, out var go))
|
||||||
{
|
{
|
||||||
Highlight(go, true);
|
Highlight(go, true);
|
||||||
Debug.Log($"NWModelView.FocusItemById: {go.name}");
|
|
||||||
_orbitTarget = go.transform.position;
|
_orbitTarget = go.transform.position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,17 +425,14 @@ namespace SHI.Modal.NW
|
|||||||
|
|
||||||
public void Export(int id)
|
public void Export(int id)
|
||||||
{
|
{
|
||||||
Debug.Log($"NWModelView.Export: id={id}");
|
|
||||||
if (_idToObject.TryGetValue(id, out var go))
|
if (_idToObject.TryGetValue(id, out var go))
|
||||||
{
|
{
|
||||||
Debug.Log($"Exporting object: {go.name}");
|
|
||||||
UVC.GLTF.GLTFExporter.ExportNodeByExplorer(go);
|
UVC.GLTF.GLTFExporter.ExportNodeByExplorer(go);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetVisibility(int id, bool on)
|
public void SetVisibility(int id, bool on)
|
||||||
{
|
{
|
||||||
Debug.Log($"NWModelView.SetVisibility: id={id} on={on}");
|
|
||||||
if (_idToObject.TryGetValue(id, out var go))
|
if (_idToObject.TryGetValue(id, out var go))
|
||||||
{
|
{
|
||||||
go.SetActive(on);
|
go.SetActive(on);
|
||||||
@@ -601,8 +625,12 @@ namespace SHI.Modal.NW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
/// <summary>
|
||||||
|
/// 모델 재로드를 위한 정리 (UI 참조는 유지)
|
||||||
|
/// </summary>
|
||||||
|
private void CleanupForReload()
|
||||||
{
|
{
|
||||||
|
// 머티리얼 정리 (인스턴스 머티리얼 삭제)
|
||||||
foreach (var kv in _originalSharedByRenderer)
|
foreach (var kv in _originalSharedByRenderer)
|
||||||
{
|
{
|
||||||
var r = kv.Key;
|
var r = kv.Key;
|
||||||
@@ -617,11 +645,14 @@ namespace SHI.Modal.NW
|
|||||||
}
|
}
|
||||||
_originalSharedByRenderer.Clear();
|
_originalSharedByRenderer.Clear();
|
||||||
_idToObject.Clear();
|
_idToObject.Clear();
|
||||||
|
|
||||||
|
// 모델 루트 삭제
|
||||||
if (_root != null) UnityEngine.Object.Destroy(_root);
|
if (_root != null) UnityEngine.Object.Destroy(_root);
|
||||||
_root = null;
|
_root = null;
|
||||||
_focusedId = null;
|
_focusedId = null;
|
||||||
_wireframeApplied = false;
|
_wireframeApplied = false;
|
||||||
|
|
||||||
|
// 카메라 및 렌더텍스처 정리
|
||||||
if (_viewCamera != null)
|
if (_viewCamera != null)
|
||||||
{
|
{
|
||||||
if (_rt != null && _viewCamera.targetTexture == _rt)
|
if (_rt != null && _viewCamera.targetTexture == _rt)
|
||||||
@@ -630,6 +661,7 @@ namespace SHI.Modal.NW
|
|||||||
}
|
}
|
||||||
_viewCamera.enabled = false;
|
_viewCamera.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_rt != null)
|
if (_rt != null)
|
||||||
{
|
{
|
||||||
_rt.Release();
|
_rt.Release();
|
||||||
@@ -637,13 +669,101 @@ namespace SHI.Modal.NW
|
|||||||
_rt = null;
|
_rt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//NWModelViewRig란 이름의 게임오브젝트도 같이 삭제
|
// 드래그 상태 초기화
|
||||||
|
_mmbDragging = false;
|
||||||
|
_rmbDragging = false;
|
||||||
|
|
||||||
|
// ID 시드 초기화
|
||||||
|
itemIdSeed = 1;
|
||||||
|
|
||||||
|
// 게임오브젝트 정리
|
||||||
|
var rigGo = GameObject.Find("NWModelViewRig");
|
||||||
|
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
||||||
|
_viewCamera = null;
|
||||||
|
|
||||||
|
var rootGo = GameObject.Find("NWModelViewRoot");
|
||||||
|
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// 버튼 이벤트 해제
|
||||||
|
if (_expandBtn != null)
|
||||||
|
{
|
||||||
|
_expandBtn.clicked -= OnExpandBtnClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마우스 이벤트 해제
|
||||||
|
UnregisterCallback<MouseDownEvent>(OnMouseDown);
|
||||||
|
UnregisterCallback<MouseUpEvent>(OnMouseUp);
|
||||||
|
UnregisterCallback<MouseMoveEvent>(OnMouseMove);
|
||||||
|
UnregisterCallback<WheelEvent>(OnWheel);
|
||||||
|
UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
||||||
|
|
||||||
|
// 외부 이벤트 정리
|
||||||
|
OnItemSelected = null;
|
||||||
|
OnExpand = null;
|
||||||
|
|
||||||
|
// 머티리얼 정리 (인스턴스 머티리얼 삭제)
|
||||||
|
foreach (var kv in _originalSharedByRenderer)
|
||||||
|
{
|
||||||
|
var r = kv.Key;
|
||||||
|
if (r == null) continue;
|
||||||
|
var originals = kv.Value;
|
||||||
|
var mats = r.materials;
|
||||||
|
for (int m = 0; m < mats.Length; m++)
|
||||||
|
{
|
||||||
|
if (mats[m] != null) UnityEngine.Object.Destroy(mats[m]);
|
||||||
|
}
|
||||||
|
r.materials = originals;
|
||||||
|
}
|
||||||
|
_originalSharedByRenderer.Clear();
|
||||||
|
_idToObject.Clear();
|
||||||
|
|
||||||
|
// 모델 루트 삭제
|
||||||
|
if (_root != null) UnityEngine.Object.Destroy(_root);
|
||||||
|
_root = null;
|
||||||
|
_focusedId = null;
|
||||||
|
_wireframeApplied = false;
|
||||||
|
_wireframeMat = null; // Resources에서 로드한 것은 Destroy하지 않음
|
||||||
|
|
||||||
|
// 카메라 및 렌더텍스처 정리
|
||||||
|
if (_viewCamera != null)
|
||||||
|
{
|
||||||
|
if (_rt != null && _viewCamera.targetTexture == _rt)
|
||||||
|
{
|
||||||
|
_viewCamera.targetTexture = null;
|
||||||
|
}
|
||||||
|
_viewCamera.enabled = false;
|
||||||
|
}
|
||||||
|
_viewCamera = null;
|
||||||
|
|
||||||
|
if (_rt != null)
|
||||||
|
{
|
||||||
|
_rt.Release();
|
||||||
|
UnityEngine.Object.Destroy(_rt);
|
||||||
|
_rt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 드래그 상태 초기화
|
||||||
|
_mmbDragging = false;
|
||||||
|
_rmbDragging = false;
|
||||||
|
|
||||||
|
// ID 시드 초기화
|
||||||
|
itemIdSeed = 1;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
_renderContainer = null;
|
||||||
|
_expandBtn = null;
|
||||||
|
|
||||||
|
// 게임오브젝트 정리
|
||||||
var rigGo = GameObject.Find("NWModelViewRig");
|
var rigGo = GameObject.Find("NWModelViewRig");
|
||||||
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
if (rigGo != null) UnityEngine.Object.Destroy(rigGo);
|
||||||
var rootGo = GameObject.Find("NWModelViewRoot");
|
var rootGo = GameObject.Find("NWModelViewRoot");
|
||||||
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
if (rootGo != null) UnityEngine.Object.Destroy(rootGo);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 유틸리티 메서드
|
// 유틸리티 메서드
|
||||||
|
|||||||
472
Assets/Scripts/SHI/modal/PlayBar.cs
Normal file
472
Assets/Scripts/SHI/modal/PlayBar.cs
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace SHI.Modal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 시간 기반 재생을 제어하는 UI Toolkit 컴포넌트입니다.
|
||||||
|
/// </summary>
|
||||||
|
[UxmlElement]
|
||||||
|
public partial class PlayBar : VisualElement, IDisposable
|
||||||
|
{
|
||||||
|
#region 상수 (Constants)
|
||||||
|
private const string UXML_PATH = "SHI/Modal/PlayBar";
|
||||||
|
private const int DEFAULT_INTERVAL_SECONDS = 2;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI 컴포넌트 참조
|
||||||
|
private Label? _startLabel;
|
||||||
|
private Label? _endLabel;
|
||||||
|
private Label? _currentTimeLabel;
|
||||||
|
private VisualElement? _progressTrack;
|
||||||
|
private VisualElement? _progressFill;
|
||||||
|
private Button? _playBtn;
|
||||||
|
private Button? _firstBtn;
|
||||||
|
private Button? _lastBtn;
|
||||||
|
private Button? _stopBtn;
|
||||||
|
private DropdownField? _intervalDropdown;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 상태 (State)
|
||||||
|
private DateTime _startTime;
|
||||||
|
private DateTime _endTime;
|
||||||
|
private DateTime _currentTime;
|
||||||
|
private bool _isPlaying;
|
||||||
|
private int _intervalSeconds = DEFAULT_INTERVAL_SECONDS;
|
||||||
|
private IVisualElementScheduledItem? _playSchedule;
|
||||||
|
private ProgressDragManipulator? _dragManipulator;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 외부 이벤트 (Public Events)
|
||||||
|
/// <summary>재생이 시작될 때 발생</summary>
|
||||||
|
public event Action? OnPlayStarted;
|
||||||
|
|
||||||
|
/// <summary>재생이 정지될 때 발생</summary>
|
||||||
|
public event Action? OnPlayStopped;
|
||||||
|
|
||||||
|
/// <summary>재생 중 시간이 변경될 때 발생 (자동 재생 시)</summary>
|
||||||
|
public event Action<DateTime>? OnPlayProgress;
|
||||||
|
|
||||||
|
/// <summary>사용자가 진행바를 드래그/클릭하여 위치를 변경할 때 발생</summary>
|
||||||
|
public event Action<DateTime>? OnPositionChanged;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UxmlAttribute
|
||||||
|
[UxmlAttribute]
|
||||||
|
public bool IsVisible
|
||||||
|
{
|
||||||
|
get => style.display == DisplayStyle.Flex;
|
||||||
|
set => style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 생성자 (Constructor)
|
||||||
|
public PlayBar()
|
||||||
|
{
|
||||||
|
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||||
|
Debug.Log("[PlayBar] UXML loaded and cloned." + (visualTree == null));
|
||||||
|
if (visualTree == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[PlayBar] UXML not found at: {UXML_PATH}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visualTree.CloneTree(this);
|
||||||
|
InitializeUIReferences();
|
||||||
|
InitializeEventHandlers();
|
||||||
|
|
||||||
|
// 패널에 연결된 후 드롭다운 초기화
|
||||||
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||||
|
{
|
||||||
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||||
|
InitializeDropdown();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 초기화 (Initialization)
|
||||||
|
private void InitializeUIReferences()
|
||||||
|
{
|
||||||
|
_startLabel = this.Q<Label>("start-label");
|
||||||
|
_endLabel = this.Q<Label>("end-label");
|
||||||
|
_currentTimeLabel = this.Q<Label>("current-time-label");
|
||||||
|
_progressTrack = this.Q<VisualElement>("progress-track");
|
||||||
|
_progressFill = this.Q<VisualElement>("progress-fill");
|
||||||
|
_playBtn = this.Q<Button>("play-btn");
|
||||||
|
_firstBtn = this.Q<Button>("first-btn");
|
||||||
|
_lastBtn = this.Q<Button>("last-btn");
|
||||||
|
_stopBtn = this.Q<Button>("stop-btn");
|
||||||
|
_intervalDropdown = this.Q<DropdownField>("interval-dropdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeEventHandlers()
|
||||||
|
{
|
||||||
|
_playBtn?.RegisterCallback<ClickEvent>(OnPlayClicked);
|
||||||
|
_firstBtn?.RegisterCallback<ClickEvent>(OnFirstClicked);
|
||||||
|
_lastBtn?.RegisterCallback<ClickEvent>(OnLastClicked);
|
||||||
|
_stopBtn?.RegisterCallback<ClickEvent>(OnStopClicked);
|
||||||
|
|
||||||
|
// 진행바 드래그/클릭 설정
|
||||||
|
if (_progressTrack != null)
|
||||||
|
{
|
||||||
|
_dragManipulator = new ProgressDragManipulator(this);
|
||||||
|
_progressTrack.AddManipulator(_dragManipulator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeDropdown()
|
||||||
|
{
|
||||||
|
if (_intervalDropdown == null) return;
|
||||||
|
|
||||||
|
var choices = new List<string>();
|
||||||
|
for (int i = 0; i <= 10; i++)
|
||||||
|
{
|
||||||
|
choices.Add($"{i}초");
|
||||||
|
}
|
||||||
|
_intervalDropdown.choices = choices;
|
||||||
|
_intervalDropdown.value = $"{DEFAULT_INTERVAL_SECONDS}초";
|
||||||
|
_intervalDropdown.RegisterValueChangedCallback(OnIntervalChanged);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 공개 메서드 (Public Methods)
|
||||||
|
/// <summary>
|
||||||
|
/// 시간 범위를 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetTimeRange(DateTime start, DateTime end)
|
||||||
|
{
|
||||||
|
_startTime = start;
|
||||||
|
_endTime = end;
|
||||||
|
_currentTime = start;
|
||||||
|
|
||||||
|
UpdateLabels();
|
||||||
|
UpdateProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 현재 시간을 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetCurrentTime(DateTime time)
|
||||||
|
{
|
||||||
|
_currentTime = ClampTime(time);
|
||||||
|
UpdateLabels();
|
||||||
|
UpdateProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 간격 선택 옵션을 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void SetIntervalChoices(List<int> seconds)
|
||||||
|
{
|
||||||
|
if (_intervalDropdown == null) return;
|
||||||
|
|
||||||
|
var choices = new List<string>();
|
||||||
|
foreach (var sec in seconds)
|
||||||
|
{
|
||||||
|
choices.Add($"{sec}초");
|
||||||
|
}
|
||||||
|
_intervalDropdown.choices = choices;
|
||||||
|
_intervalDropdown.index = 0;
|
||||||
|
_intervalSeconds = seconds.Count > 0 ? seconds[0] : DEFAULT_INTERVAL_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 시작합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Play()
|
||||||
|
{
|
||||||
|
if (_isPlaying) return;
|
||||||
|
_isPlaying = true;
|
||||||
|
|
||||||
|
UpdatePlayButtonState();
|
||||||
|
StartPlayTimer();
|
||||||
|
OnPlayStarted?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 일시정지합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (!_isPlaying) return;
|
||||||
|
_isPlaying = false;
|
||||||
|
|
||||||
|
StopPlayTimer();
|
||||||
|
UpdatePlayButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재생을 정지하고 처음으로 돌아갑니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_isPlaying = false;
|
||||||
|
_currentTime = _startTime;
|
||||||
|
|
||||||
|
StopPlayTimer();
|
||||||
|
UpdatePlayButtonState();
|
||||||
|
UpdateLabels();
|
||||||
|
UpdateProgressBar();
|
||||||
|
OnPlayStopped?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 리소스를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StopPlayTimer();
|
||||||
|
UnregisterEventHandlers();
|
||||||
|
|
||||||
|
OnPlayStarted = null;
|
||||||
|
OnPlayStopped = null;
|
||||||
|
OnPlayProgress = null;
|
||||||
|
OnPositionChanged = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 이벤트 핸들러 (Event Handlers)
|
||||||
|
private void OnPlayClicked(ClickEvent evt)
|
||||||
|
{
|
||||||
|
if (_isPlaying)
|
||||||
|
Pause();
|
||||||
|
else
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFirstClicked(ClickEvent evt)
|
||||||
|
{
|
||||||
|
SetCurrentTime(_startTime);
|
||||||
|
OnPositionChanged?.Invoke(_currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLastClicked(ClickEvent evt)
|
||||||
|
{
|
||||||
|
SetCurrentTime(_endTime);
|
||||||
|
OnPositionChanged?.Invoke(_currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStopClicked(ClickEvent evt)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIntervalChanged(ChangeEvent<string> evt)
|
||||||
|
{
|
||||||
|
// "2초" -> 2 파싱
|
||||||
|
if (int.TryParse(evt.newValue.Replace("초", ""), out int seconds))
|
||||||
|
{
|
||||||
|
_intervalSeconds = seconds;
|
||||||
|
|
||||||
|
// 재생 중이면 타이머 재시작
|
||||||
|
if (_isPlaying)
|
||||||
|
{
|
||||||
|
StopPlayTimer();
|
||||||
|
StartPlayTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 타이머 (Timer)
|
||||||
|
private void StartPlayTimer()
|
||||||
|
{
|
||||||
|
_playSchedule = schedule.Execute(OnPlayTick).Every(_intervalSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopPlayTimer()
|
||||||
|
{
|
||||||
|
_playSchedule?.Pause();
|
||||||
|
_playSchedule = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayTick()
|
||||||
|
{
|
||||||
|
if (!_isPlaying) return;
|
||||||
|
|
||||||
|
_currentTime = _currentTime.AddDays(1);
|
||||||
|
|
||||||
|
if (_currentTime >= _endTime)
|
||||||
|
{
|
||||||
|
_currentTime = _endTime;
|
||||||
|
Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateLabels();
|
||||||
|
UpdateProgressBar();
|
||||||
|
OnPlayProgress?.Invoke(_currentTime);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI 업데이트 (UI Updates)
|
||||||
|
private void UpdateLabels()
|
||||||
|
{
|
||||||
|
if (_startLabel != null)
|
||||||
|
_startLabel.text = _startTime.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
if (_endLabel != null)
|
||||||
|
_endLabel.text = _endTime.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
if (_currentTimeLabel != null)
|
||||||
|
_currentTimeLabel.text = _currentTime.ToString("yyyy-MM-dd HH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateProgressBar()
|
||||||
|
{
|
||||||
|
if (_progressFill == null) return;
|
||||||
|
|
||||||
|
float progress = CalculateProgress();
|
||||||
|
_progressFill.style.width = Length.Percent(progress * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePlayButtonState()
|
||||||
|
{
|
||||||
|
if (_playBtn == null) return;
|
||||||
|
|
||||||
|
if (_isPlaying)
|
||||||
|
_playBtn.AddToClassList("playing");
|
||||||
|
else
|
||||||
|
_playBtn.RemoveFromClassList("playing");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 유틸리티 (Utilities)
|
||||||
|
private float CalculateProgress()
|
||||||
|
{
|
||||||
|
double total = (_endTime - _startTime).TotalDays;
|
||||||
|
if (total <= 0) return 0;
|
||||||
|
|
||||||
|
double current = (_currentTime - _startTime).TotalDays;
|
||||||
|
return Mathf.Clamp01((float)(current / total));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime ClampTime(DateTime time)
|
||||||
|
{
|
||||||
|
if (time < _startTime) return _startTime;
|
||||||
|
if (time > _endTime) return _endTime;
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 정규화된 위치(0~1)로부터 시간을 설정합니다. (드래그용)
|
||||||
|
/// </summary>
|
||||||
|
internal void SetProgressFromPosition(float normalizedPosition)
|
||||||
|
{
|
||||||
|
double totalDays = (_endTime - _startTime).TotalDays;
|
||||||
|
double targetDays = totalDays * normalizedPosition;
|
||||||
|
|
||||||
|
_currentTime = _startTime.AddDays(targetDays);
|
||||||
|
_currentTime = ClampTime(_currentTime);
|
||||||
|
|
||||||
|
UpdateLabels();
|
||||||
|
UpdateProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 드래그 완료 시 이벤트를 발생시킵니다.
|
||||||
|
/// </summary>
|
||||||
|
internal void NotifyPositionChanged()
|
||||||
|
{
|
||||||
|
OnPositionChanged?.Invoke(_currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal float GetTrackWidth()
|
||||||
|
{
|
||||||
|
return _progressTrack?.layout.width ?? 0;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 이벤트 해제 (Unregister)
|
||||||
|
private void UnregisterEventHandlers()
|
||||||
|
{
|
||||||
|
_playBtn?.UnregisterCallback<ClickEvent>(OnPlayClicked);
|
||||||
|
_firstBtn?.UnregisterCallback<ClickEvent>(OnFirstClicked);
|
||||||
|
_lastBtn?.UnregisterCallback<ClickEvent>(OnLastClicked);
|
||||||
|
_stopBtn?.UnregisterCallback<ClickEvent>(OnStopClicked);
|
||||||
|
|
||||||
|
if (_progressTrack != null && _dragManipulator != null)
|
||||||
|
{
|
||||||
|
_progressTrack.RemoveManipulator(_dragManipulator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 진행바의 드래그/클릭을 처리하는 Manipulator입니다.
|
||||||
|
/// </summary>
|
||||||
|
public class ProgressDragManipulator : PointerManipulator
|
||||||
|
{
|
||||||
|
private readonly PlayBar _playBar;
|
||||||
|
private bool _isActive;
|
||||||
|
|
||||||
|
public ProgressDragManipulator(PlayBar playBar)
|
||||||
|
{
|
||||||
|
_playBar = playBar;
|
||||||
|
activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RegisterCallbacksOnTarget()
|
||||||
|
{
|
||||||
|
target.RegisterCallback<PointerDownEvent>(OnPointerDown);
|
||||||
|
target.RegisterCallback<PointerMoveEvent>(OnPointerMove);
|
||||||
|
target.RegisterCallback<PointerUpEvent>(OnPointerUp);
|
||||||
|
target.RegisterCallback<PointerCaptureOutEvent>(OnPointerCaptureOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UnregisterCallbacksFromTarget()
|
||||||
|
{
|
||||||
|
target.UnregisterCallback<PointerDownEvent>(OnPointerDown);
|
||||||
|
target.UnregisterCallback<PointerMoveEvent>(OnPointerMove);
|
||||||
|
target.UnregisterCallback<PointerUpEvent>(OnPointerUp);
|
||||||
|
target.UnregisterCallback<PointerCaptureOutEvent>(OnPointerCaptureOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerDown(PointerDownEvent evt)
|
||||||
|
{
|
||||||
|
if (!CanStartManipulation(evt)) return;
|
||||||
|
|
||||||
|
_isActive = true;
|
||||||
|
target.CapturePointer(evt.pointerId);
|
||||||
|
UpdatePosition(evt.localPosition);
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerMove(PointerMoveEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isActive || !target.HasPointerCapture(evt.pointerId)) return;
|
||||||
|
|
||||||
|
UpdatePosition(evt.localPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerUp(PointerUpEvent evt)
|
||||||
|
{
|
||||||
|
if (!_isActive) return;
|
||||||
|
|
||||||
|
_isActive = false;
|
||||||
|
target.ReleasePointer(evt.pointerId);
|
||||||
|
|
||||||
|
// 드래그 종료 시에만 이벤트 발생
|
||||||
|
_playBar.NotifyPositionChanged();
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPointerCaptureOut(PointerCaptureOutEvent evt)
|
||||||
|
{
|
||||||
|
_isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePosition(Vector2 localPosition)
|
||||||
|
{
|
||||||
|
float trackWidth = _playBar.GetTrackWidth();
|
||||||
|
if (trackWidth <= 0) return;
|
||||||
|
|
||||||
|
float normalizedPosition = Mathf.Clamp01(localPosition.x / trackWidth);
|
||||||
|
_playBar.SetProgressFromPosition(normalizedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/SHI/modal/PlayBar.cs.meta
Normal file
2
Assets/Scripts/SHI/modal/PlayBar.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea6077d0bdca20a4e954f6ab8a93e349
|
||||||
@@ -45,14 +45,15 @@ namespace SHI.Modal
|
|||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UxmlElement]
|
[UxmlElement]
|
||||||
public partial class TreeList : VisualElement
|
public partial class TreeList : VisualElement, IDisposable
|
||||||
{
|
{
|
||||||
|
#region IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
#endregion
|
||||||
#region 상수 (Constants)
|
#region 상수 (Constants)
|
||||||
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
||||||
private const string UXML_PATH = "SHI/Modal/TreeList";
|
private const string UXML_PATH = "SHI/Modal/TreeList";
|
||||||
|
|
||||||
/// <summary>개별 항목 템플릿 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
|
||||||
private const string ITEM_UXML_PATH = "SHI/Modal/TreeListItem";
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region UI 컴포넌트 참조 (UI Component References)
|
#region UI 컴포넌트 참조 (UI Component References)
|
||||||
@@ -70,11 +71,7 @@ namespace SHI.Modal
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 내부 데이터 (Internal Data)
|
#region 내부 데이터 (Internal Data)
|
||||||
/// <summary>
|
|
||||||
/// 개별 항목 UI를 생성할 때 사용하는 UXML 템플릿입니다.
|
|
||||||
/// 생성자에서 한 번만 로드하여 재사용합니다.
|
|
||||||
/// </summary>
|
|
||||||
private VisualTreeAsset _itemTemplate;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 원본 루트 데이터입니다.
|
/// 원본 루트 데이터입니다.
|
||||||
@@ -106,19 +103,19 @@ namespace SHI.Modal
|
|||||||
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
|
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
|
||||||
/// 3D 모델의 GameObject 활성화/비활성화에 연동합니다.
|
/// 3D 모델의 GameObject 활성화/비활성화에 연동합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TreeListItemData> OnVisibilityChanged;
|
public event Action<TreeListItemData>? OnVisibilityChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 항목 선택 상태가 변경될 때 발생합니다.
|
/// 항목 선택 상태가 변경될 때 발생합니다.
|
||||||
/// 선택 및 선택 해제 모두에서 발생하며, item.isSelected로 구분합니다.
|
/// 선택 및 선택 해제 모두에서 발생하며, item.isSelected로 구분합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TreeListItemData> OnSelectionChanged;
|
public event Action<TreeListItemData>? OnSelectionChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 트리 리스트가 닫힐 때(숨겨질 때) 발생합니다.
|
/// 트리 리스트가 닫힐 때(숨겨질 때) 발생합니다.
|
||||||
/// 닫기 버튼 클릭 시 트리거됩니다.
|
/// 닫기 버튼 클릭 시 트리거됩니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action OnClosed;
|
public event Action? OnClosed;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 생성자 (Constructor)
|
#region 생성자 (Constructor)
|
||||||
@@ -138,21 +135,14 @@ namespace SHI.Modal
|
|||||||
}
|
}
|
||||||
visualTree!.CloneTree(this);
|
visualTree!.CloneTree(this);
|
||||||
|
|
||||||
// 2. 항목 템플릿 UXML 로드 (성능을 위해 미리 로드)
|
|
||||||
_itemTemplate = Resources.Load<VisualTreeAsset>(ITEM_UXML_PATH);
|
|
||||||
if (_itemTemplate == null)
|
|
||||||
{
|
|
||||||
Debug.LogError($"[TreeMenu] Item UXML not found at: {ITEM_UXML_PATH}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
|
// 2. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
|
||||||
_searchField = this.Q<TextField>("search-field");
|
_searchField = this.Q<TextField>("search-field");
|
||||||
_treeView = this.Q<TreeView>("main-tree-view");
|
_treeView = this.Q<TreeView>("main-tree-view");
|
||||||
_closeButton = this.Q<Button>("hide-btn");
|
_closeButton = this.Q<Button>("hide-btn");
|
||||||
_clearButton = this.Q<Button>("clear-btn");
|
_clearButton = this.Q<Button>("clear-btn");
|
||||||
|
|
||||||
// 4. 이벤트 연결 및 로직 초기화
|
// 3. 이벤트 연결 및 로직 초기화
|
||||||
InitializeLogic();
|
InitializeLogic();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -166,7 +156,7 @@ namespace SHI.Modal
|
|||||||
// 검색창 이벤트: 입력 값이 변경될 때마다 필터링 실행
|
// 검색창 이벤트: 입력 값이 변경될 때마다 필터링 실행
|
||||||
if (_searchField != null)
|
if (_searchField != null)
|
||||||
{
|
{
|
||||||
_searchField.RegisterValueChangedCallback(evt => OnSearch(evt.newValue));
|
_searchField.RegisterValueChangedCallback(OnSearchValueChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TreeView 설정
|
// TreeView 설정
|
||||||
@@ -175,7 +165,6 @@ namespace SHI.Modal
|
|||||||
// selectionChanged: 선택 변경 시 호출
|
// selectionChanged: 선택 변경 시 호출
|
||||||
if (_treeView != null)
|
if (_treeView != null)
|
||||||
{
|
{
|
||||||
_treeView.makeItem = MakeTreeItem;
|
|
||||||
_treeView.bindItem = BindTreeItem;
|
_treeView.bindItem = BindTreeItem;
|
||||||
_treeView.selectionChanged += OnTreeViewSelectionChanged;
|
_treeView.selectionChanged += OnTreeViewSelectionChanged;
|
||||||
}
|
}
|
||||||
@@ -249,6 +238,59 @@ namespace SHI.Modal
|
|||||||
{
|
{
|
||||||
_treeView.SetSelection(new List<int> { itemId });
|
_treeView.SetSelection(new List<int> { itemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 지정된 이름 목록에 해당하는 항목만 표시하고 나머지는 숨깁니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">표시할 항목들의 이름 목록</param>
|
||||||
|
/// <param name="depth">검색 깊이 (1=1뎁스 자식만, 2=2뎁스까지, 0이하=전체)</param>
|
||||||
|
public void ShowItems(List<string> items, int depth = 1)
|
||||||
|
{
|
||||||
|
if (_originalRoots == null || _originalRoots.Count == 0) return;
|
||||||
|
|
||||||
|
var visibleNames = new HashSet<string>(items ?? new List<string>());
|
||||||
|
|
||||||
|
foreach (var root in _originalRoots)
|
||||||
|
{
|
||||||
|
SetVisibilityByNames(root, visibleNames, depth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI 갱신
|
||||||
|
_treeView?.RefreshItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 재귀적으로 항목의 가시성을 이름 목록에 따라 설정합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">현재 노드</param>
|
||||||
|
/// <param name="visibleNames">표시할 이름 목록</param>
|
||||||
|
/// <param name="maxDepth">최대 검색 깊이 (0이하=무제한)</param>
|
||||||
|
/// <param name="currentDepth">현재 깊이</param>
|
||||||
|
private void SetVisibilityByNames(TreeListItemData node, HashSet<string> visibleNames, int maxDepth, int currentDepth)
|
||||||
|
{
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
// maxDepth <= 0 이면 전체 검색, 아니면 깊이 제한 체크
|
||||||
|
bool isWithinDepth = maxDepth <= 0 || currentDepth < maxDepth;
|
||||||
|
|
||||||
|
if (isWithinDepth)
|
||||||
|
{
|
||||||
|
// 이름이 목록에 있으면 visible, 없으면 hidden
|
||||||
|
node.IsVisible = visibleNames.Contains(node.name);
|
||||||
|
|
||||||
|
// 가시성 변경 이벤트 발송
|
||||||
|
OnVisibilityChanged?.Invoke(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 자식들도 재귀적으로 처리
|
||||||
|
if (node.children != null)
|
||||||
|
{
|
||||||
|
foreach (var child in node.children)
|
||||||
|
{
|
||||||
|
SetVisibilityByNames(child, visibleNames, maxDepth, currentDepth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 선택 처리 (Selection Handling)
|
#region 선택 처리 (Selection Handling)
|
||||||
@@ -320,20 +362,8 @@ namespace SHI.Modal
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region TreeView 항목 생성/바인딩 (TreeView Item Creation/Binding)
|
#region TreeView 항목 바인딩 (TreeView Item Creation/Binding)
|
||||||
/// <summary>
|
|
||||||
/// TreeView의 새 행(Row) UI를 생성합니다.
|
|
||||||
/// TreeView의 makeItem 콜백으로 사용됩니다.
|
|
||||||
/// 스크롤 시 재사용되는 풀링 시스템에서 호출됩니다.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>항목 템플릿에서 생성된 VisualElement</returns>
|
|
||||||
private VisualElement MakeTreeItem()
|
|
||||||
{
|
|
||||||
// UXML 템플릿을 복제하여 새 항목 생성
|
|
||||||
var templateContainer = _itemTemplate.Instantiate();
|
|
||||||
return templateContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 데이터를 UI 요소에 바인딩합니다.
|
/// 데이터를 UI 요소에 바인딩합니다.
|
||||||
/// TreeView의 bindItem 콜백으로 사용됩니다.
|
/// TreeView의 bindItem 콜백으로 사용됩니다.
|
||||||
@@ -419,6 +449,15 @@ namespace SHI.Modal
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 검색 기능 (Search Functionality)
|
#region 검색 기능 (Search Functionality)
|
||||||
|
/// <summary>
|
||||||
|
/// 검색 필드 값 변경 콜백입니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="evt">값 변경 이벤트</param>
|
||||||
|
private void OnSearchValueChanged(ChangeEvent<string> evt)
|
||||||
|
{
|
||||||
|
OnSearch(evt.newValue);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 검색어에 따라 트리를 필터링합니다.
|
/// 검색어에 따라 트리를 필터링합니다.
|
||||||
/// 검색어가 비어있으면 원본 데이터로 복원됩니다.
|
/// 검색어가 비어있으면 원본 데이터로 복원됩니다.
|
||||||
@@ -536,5 +575,50 @@ namespace SHI.Modal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
/// <summary>
|
||||||
|
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// 검색 필드 이벤트 해제
|
||||||
|
if (_searchField != null)
|
||||||
|
{
|
||||||
|
_searchField.UnregisterValueChangedCallback(OnSearchValueChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TreeView 이벤트 핸들러 해제
|
||||||
|
if (_treeView != null)
|
||||||
|
{
|
||||||
|
_treeView.selectionChanged -= OnTreeViewSelectionChanged;
|
||||||
|
_treeView.bindItem = null;
|
||||||
|
_treeView.makeItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 외부 이벤트 구독자 정리
|
||||||
|
OnVisibilityChanged = null;
|
||||||
|
OnSelectionChanged = null;
|
||||||
|
OnClosed = null;
|
||||||
|
|
||||||
|
// 데이터 정리
|
||||||
|
_originalRoots.Clear();
|
||||||
|
_rootData?.Clear();
|
||||||
|
_rootData = null;
|
||||||
|
_previouslySelectedItem = null;
|
||||||
|
|
||||||
|
// ID 시드 초기화
|
||||||
|
_idSeed = 1;
|
||||||
|
|
||||||
|
// UI 참조 정리
|
||||||
|
_searchField = null;
|
||||||
|
_treeView = null;
|
||||||
|
_closeButton = null;
|
||||||
|
_clearButton = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Assets/StreamingAssets/B11TC.glb
Normal file
BIN
Assets/StreamingAssets/B11TC.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/B11TC.glb.meta
Normal file
7
Assets/StreamingAssets/B11TC.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aaa870cd5572f574f9e0f35548fa2023
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/StreamingAssets/B16VC.glb
Normal file
BIN
Assets/StreamingAssets/B16VC.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/B16VC.glb.meta
Normal file
7
Assets/StreamingAssets/B16VC.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 39d2ad74d28f1964a8bee00a8430137b
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/StreamingAssets/E11VC.glb
Normal file
BIN
Assets/StreamingAssets/E11VC.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/E11VC.glb.meta
Normal file
7
Assets/StreamingAssets/E11VC.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: df415f0f9df02cb47b8b6a466dcaeccc
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/StreamingAssets/E41UC.glb
Normal file
BIN
Assets/StreamingAssets/E41UC.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/E41UC.glb.meta
Normal file
7
Assets/StreamingAssets/E41UC.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3d1bea67a9624cf479b1a59c4654226d
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/StreamingAssets/M11UC.glb
Normal file
BIN
Assets/StreamingAssets/M11UC.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/M11UC.glb.meta
Normal file
7
Assets/StreamingAssets/M11UC.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 929c8703b7d263c4495d923b7236ce63
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1 +1,3 @@
|
|||||||
@import url("unity-theme://default");
|
@import url("unity-theme://default");
|
||||||
|
|
||||||
|
@import url("/Assets/Resources/SHI/Modal/Modal.uss");
|
||||||
|
|||||||
Reference in New Issue
Block a user