ADD : 스트림 덱 기능 추가를 위한 프로젝트와 스크립트 추가

This commit is contained in:
KINDNICK 2025-06-28 02:20:01 +09:00
parent ac631592e8
commit b92ba71dd7
363 changed files with 15271 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 56444d14b6c5a3e47a3952005eb45071
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aaf22bd5628ec0b48846a14ad7d42515
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,156 @@
using UnityEngine;
using UnityEditor;
namespace Klak.Hap
{
[CanEditMultipleObjects]
[CustomEditor(typeof(HapPlayer))]
sealed class HapPlayerEditor : Editor
{
SerializedProperty _filePath;
SerializedProperty _pathMode;
SerializedProperty _time;
SerializedProperty _speed;
SerializedProperty _loop;
SerializedProperty _targetTexture;
SerializedProperty _flipHorizontal;
SerializedProperty _flipVertical;
static class Labels
{
public static readonly GUIContent Property = new GUIContent("Property");
public static readonly GUIContent Select = new GUIContent("Select");
}
string _sourceInfo;
void ShowSourceInfo(HapPlayer player)
{
if (!player.enabled || !player.gameObject.activeInHierarchy) return;
if (!player.isValid)
{
EditorGUILayout.HelpBox(
"Failed to open file. " +
"Please specify a valid HAP-encoded .mov file.",
MessageType.Warning
);
return;
}
if (_sourceInfo == null)
_sourceInfo = string.Format(
"Codec: {0}\n" +
"Frame dimensions: {1} x {2}\n" +
"Stream duration: {3:0.00}\n" +
"Frame rate: {4:0.00}",
player.codecType,
player.frameWidth, player.frameHeight,
player.streamDuration,
player.frameCount / player.streamDuration
);
EditorGUILayout.HelpBox(_sourceInfo, MessageType.None);
}
void OnEnable()
{
_filePath = serializedObject.FindProperty("_filePath");
_pathMode = serializedObject.FindProperty("_pathMode");
_time = serializedObject.FindProperty("_time");
_speed = serializedObject.FindProperty("_speed");
_loop = serializedObject.FindProperty("_loop");
_targetTexture = serializedObject.FindProperty("_targetTexture");
_flipHorizontal = serializedObject.FindProperty("_flipHorizontal");
_flipVertical = serializedObject.FindProperty("_flipVertical");
}
public override void OnInspectorGUI()
{
var reload = false;
serializedObject.Update();
// Source infomation
if (!_filePath.hasMultipleDifferentValues &&
!_pathMode.hasMultipleDifferentValues &&
!string.IsNullOrEmpty(_filePath.stringValue))
{
ShowSourceInfo((HapPlayer)target);
}
// Source file (드래그 앤 드롭 + 파일 브라우저)
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
EditorGUILayout.DelayedTextField(_filePath);
if (GUILayout.Button("파일 선택", GUILayout.Width(80)))
{
string path = EditorUtility.OpenFilePanel("HAP MOV 파일 선택", Application.dataPath, "mov");
if (!string.IsNullOrEmpty(path))
{
string streamingPath = Application.streamingAssetsPath;
if (path.StartsWith(streamingPath))
path = path.Substring(streamingPath.Length + 1);
_filePath.stringValue = path;
}
}
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck()) reload = true;
// 드래그 앤 드롭 지원
Rect dropRect = GUILayoutUtility.GetLastRect();
if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
{
if (dropRect.Contains(Event.current.mousePosition))
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (Event.current.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (var dragged in DragAndDrop.paths)
{
if (dragged.ToLower().EndsWith(".mov"))
{
string path = dragged;
string streamingPath = Application.streamingAssetsPath;
if (path.StartsWith(streamingPath))
path = path.Substring(streamingPath.Length + 1);
_filePath.stringValue = path;
GUI.FocusControl(null);
reload = true;
break;
}
}
Event.current.Use();
}
}
}
// Playback control
EditorGUILayout.PropertyField(_time);
EditorGUILayout.PropertyField(_speed);
EditorGUILayout.PropertyField(_loop);
// Flip options
EditorGUILayout.Space();
EditorGUILayout.LabelField("Flip Options", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_flipHorizontal, new GUIContent("Flip Horizontal"));
EditorGUILayout.PropertyField(_flipVertical, new GUIContent("Flip Vertical"));
// Target texture
EditorGUILayout.PropertyField(_targetTexture);
serializedObject.ApplyModifiedProperties();
if (reload)
{
foreach (HapPlayer hp in targets)
{
hp.SendMessage("OnDestroy");
hp.SendMessage("LateUpdate");
}
_sourceInfo = null;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a374e606a0d24345b93bc348a6fbf54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@
{
"name": "Klak.Hap.Editor",
"references": [
"Klak.Hap"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 26694ad9b328c7148a8fb73421baa7fe
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
namespace Klak.Hap
{
static class MaterialPropertySelector
{
#region Public method
// Material property drop-down list
public static void DropdownList(
SerializedProperty rendererProperty,
SerializedProperty materialProperty
)
{
// Try retrieving the target shader.
var shader = RetrieveTargetShader(rendererProperty);
// Abandon the current value if it failed to get a shader.
if (shader == null)
{
materialProperty.stringValue = "";
return;
}
// Cache property names found in the target shader.
CachePropertyNames(shader);
// Abandon the current value if there is no property candidate.
if (_propertyNames.Length == 0)
{
materialProperty.stringValue = "";
return;
}
// Show the dropdown list.
var index = Array.IndexOf(_propertyNames, materialProperty.stringValue);
var newIndex = EditorGUILayout.Popup("Property", index, _propertyNames);
// Update the serialized property if the selection was changed.
if (index != newIndex) materialProperty.stringValue = _propertyNames[newIndex];
}
#endregion
#region Private members
static string[] _propertyNames; // Property name list
static Shader _cachedShader; // Shader used to cache the name list
// Retrieve a shader from a given renderer.
static Shader RetrieveTargetShader(SerializedProperty rendererProperty)
{
var renderer = rendererProperty.objectReferenceValue as Renderer;
if (renderer == null) return null;
var material = renderer.sharedMaterial;
if (material == null) return null;
return material.shader;
}
// Cache property names provided within a specified shader.
static void CachePropertyNames(Shader shader)
{
// Exit early when the shader is same to the cached one.
if (shader == _cachedShader) return;
var temp = new List<string>();
var count = ShaderUtil.GetPropertyCount(shader);
for (var i = 0; i < count; i++)
{
var propType = ShaderUtil.GetPropertyType(shader, i);
if (propType == ShaderUtil.ShaderPropertyType.TexEnv)
temp.Add(ShaderUtil.GetPropertyName(shader, i));
}
_propertyNames = temp.ToArray();
_cachedShader = shader;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe3e2d486287f2f40936cd00c558818d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,215 @@
License summary:
KlakHAP - MIT license
HAP codec - FreeBSD license
Snappy - BSD 3-clause license
MP4 demuxer - CC0 (public domain)
-------------------------------------------------------------------------------
KlakHAP
https://github.com/keijiro/KlakHap
-------------------------------------------------------------------------------
Copyright (c) 2019 Unity Technologies
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------
HAP
https://github.com/Vidvox/hap
-------------------------------------------------------------------------------
Copyright (c) 2012-2013, Tom Butterworth and Vidvox LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Snappy
https://github.com/google/snappy
-------------------------------------------------------------------------------
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Minimalistic MP4 muxer & demuxer
https://github.com/aspt/mp4
-------------------------------------------------------------------------------
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cafc978caabfcd84e8dc32d52b4f2b79
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd3bd64373abe6d4bac999a0b5b69bcd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 47a9aaa064c67514da9f5ff173be35fb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,88 @@
fileFormatVersion: 2
guid: 467f13044a0a9364795fef0b526e5eed
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
platformData:
- first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 1
Exclude Win: 0
Exclude Win64: 0
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: x86_64
DefaultValueInitialized: true
OS: Linux
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 93e3b14ab64306d4a85ce4feb6c9b2fd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,73 @@
fileFormatVersion: 2
guid: 849058cd649269543a8d95c4703cf3d9
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 0
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: OSX
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: x86_64
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afcc650d93eee184aa34e7d62096bf27
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,88 @@
fileFormatVersion: 2
guid: e794ded0c0aa5a4449367e5b2b2d96ed
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
platformData:
- first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 0
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 0
Exclude Win: 1
Exclude Win64: 0
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: x86_64
DefaultValueInitialized: true
OS: Windows
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: None
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/External/jp.keijiro.klak.hap@9a47acf6295e/README.md (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 76ac0e324de156547a580e86fdd8ea2f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2b78bdcad9737cf48a8f9b23254b7f84
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,57 @@
Shader "Klak/HAP"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct Attributes
{
float4 position : POSITION;
float2 texcoord : TEXCOORD;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 position : SV_Position;
float2 texcoord : TEXCOORD;
};
sampler2D _MainTex;
float4 _MainTex_ST;
Varyings Vertex(Attributes input)
{
UNITY_SETUP_INSTANCE_ID(input);
Varyings output;
output.position = UnityObjectToClipPos(input.position);
output.texcoord = TRANSFORM_TEX(input.texcoord, _MainTex);
output.texcoord.y = 1 - output.texcoord.y;
return output;
}
fixed4 Fragment(Varyings input) : SV_Target
{
return tex2D(_MainTex, input.texcoord);
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex Vertex
#pragma fragment Fragment
#pragma multi_compile_instancing
ENDCG
}
}
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 49f216cf9a0966d4493e7ba1af11c3d6
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
Shader "Klak/HAP Alpha"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct Attributes
{
float4 position : POSITION;
float2 texcoord : TEXCOORD;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 position : SV_Position;
float2 texcoord : TEXCOORD;
};
sampler2D _MainTex;
float4 _MainTex_ST;
Varyings Vertex(Attributes input)
{
UNITY_SETUP_INSTANCE_ID(input);
Varyings output;
output.position = UnityObjectToClipPos(input.position);
output.texcoord = TRANSFORM_TEX(input.texcoord, _MainTex);
output.texcoord.y = 1 - output.texcoord.y;
return output;
}
fixed4 Fragment(Varyings input) : SV_Target
{
return tex2D(_MainTex, input.texcoord);
}
ENDCG
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex Vertex
#pragma fragment Fragment
#pragma multi_compile_instancing
ENDCG
}
}
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 90b82a87346a5254286d43f76863782c
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,72 @@
Shader "Klak/HAP Q"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct Attributes
{
float4 position : POSITION;
float2 texcoord : TEXCOORD;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 position : SV_Position;
float2 texcoord : TEXCOORD;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half3 CoCgSY2RGB(half4 i)
{
#if !defined(UNITY_COLORSPACE_GAMMA)
i.xyz = LinearToGammaSpace(i.xyz);
#endif
i.xy -= half2(0.50196078431373, 0.50196078431373);
half s = 1 / ((i.z * (255.0 / 8)) + 1);
half3 rgb = half3(i.x - i.y, i.y, -i.x - i.y) * s + i.w;
#if !defined(UNITY_COLORSPACE_GAMMA)
rgb = GammaToLinearSpace(rgb);
#endif
return rgb;
}
Varyings Vertex(Attributes input)
{
UNITY_SETUP_INSTANCE_ID(input);
Varyings output;
output.position = UnityObjectToClipPos(input.position);
output.texcoord = TRANSFORM_TEX(input.texcoord, _MainTex);
output.texcoord.y = 1 - output.texcoord.y;
return output;
}
fixed4 Fragment(Varyings input) : SV_Target
{
return fixed4(CoCgSY2RGB(tex2D(_MainTex, input.texcoord)), 1);
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma multi_compile _ UNITY_COLORSPACE_GAMMA
#pragma vertex Vertex
#pragma fragment Fragment
#pragma multi_compile_instancing
ENDCG
}
}
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 807e261a8905cde469501ab123338e05
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a1ee959e394fc54bb43e56fdae7a146
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,4 @@
namespace Klak.Hap
{
public enum CodecType { Unsupported, Hap, HapQ, HapAlpha }
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec81190dde6b4d24fb7fdb8f562fccbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
Shader "Hidden/FlipBlit"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_FlipX ("Flip X", Float) = 0
_FlipY ("Flip Y", Float) = 0
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float _FlipX;
float _FlipY;
struct appdata_t { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = i.uv;
if (_FlipX > 0.5) uv.x = 1.0 - uv.x;
if (_FlipY > 0.5) uv.y = 1.0 - uv.y;
return tex2D(_MainTex, uv);
}
ENDCG
}
}
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 83fc09b934829ee489c1947480fcc03e
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,339 @@
using UnityEngine;
using UnityEngine.Playables;
#if KLAKHAP_HAS_TIMELINE
using UnityEngine.Timeline;
#endif
namespace Klak.Hap
{
[ExecuteInEditMode, AddComponentMenu("Klak/HAP/HAP Player")]
#if KLAKHAP_HAS_TIMELINE
public sealed class HapPlayer : MonoBehaviour , ITimeControl, IPropertyPreview
#else
public sealed class HapPlayer : MonoBehaviour
#endif
{
#region Editable attributes
public enum PathMode { StreamingAssets, LocalFileSystem }
[SerializeField] PathMode _pathMode = PathMode.StreamingAssets;
[SerializeField] string _filePath = "";
[SerializeField] float _time = 0;
[SerializeField, Range(-10, 10)] float _speed = 1;
[SerializeField] bool _loop = true;
[SerializeField] RenderTexture _targetTexture = null;
[SerializeField] string _targetMaterialProperty = "_MainTex";
[SerializeField] bool _flipHorizontal = false;
[SerializeField] bool _flipVertical = true;
#endregion
#region Public properties
public float time {
get { return _time; }
set { _time = value; }
}
public float speed {
get { return _speed; }
set { _speed = value; }
}
public bool loop {
get { return _loop; }
set { _loop = value; }
}
public RenderTexture targetTexture {
get { return _targetTexture; }
set { _targetTexture = value; }
}
public string targetMaterialProperty {
get { return _targetMaterialProperty; }
set { _targetMaterialProperty = value; }
}
public bool flipHorizontal {
get { return _flipHorizontal; }
set { _flipHorizontal = value; }
}
public bool flipVertical {
get { return _flipVertical; }
set { _flipVertical = value; }
}
#endregion
#region Read-only properties
public bool isValid { get { return _demuxer != null; } }
public int frameWidth { get { return _demuxer?.Width ?? 0; } }
public int frameHeight { get { return _demuxer?.Height ?? 0; } }
public int frameCount { get { return _demuxer?.FrameCount ?? 0; } }
public double streamDuration { get { return _demuxer?.Duration ?? 0; } }
public CodecType codecType { get {
return Utility.DetermineCodecType(_demuxer?.VideoType ?? 0);
} }
public string resolvedFilePath { get {
if (_pathMode == PathMode.StreamingAssets)
return System.IO.Path.Combine(Application.streamingAssetsPath, _filePath);
else
return _filePath;
} }
public Texture2D texture { get { return _texture; } }
#endregion
#region Public methods
public void Open(string filePath, PathMode pathMode = PathMode.StreamingAssets)
{
if (_demuxer != null)
{
Debug.LogError("Stream has already been opened.");
return;
}
_filePath = filePath;
_pathMode = pathMode;
OpenInternal();
}
public void UpdateNow()
=> LateUpdate();
#endregion
#region Private members
Demuxer _demuxer;
StreamReader _stream;
Decoder _decoder;
Texture2D _texture;
TextureUpdater _updater;
float _storedTime;
float _storedSpeed;
// Flip-related variables
Material _flipMaterial;
void OpenInternal()
{
// Demuxer instantiation
_demuxer = new Demuxer(resolvedFilePath);
if (!_demuxer.IsValid)
{
if (Application.isPlaying)
{
Debug.LogError("Failed to open stream (" + resolvedFilePath + ").");
enabled = false;
}
_demuxer.Dispose();
_demuxer = null;
return;
}
_stream = new StreamReader(_demuxer, _time, _speed / 60);
(_storedTime, _storedSpeed) = (_time, _speed);
_decoder = new Decoder(
_stream, _demuxer.Width, _demuxer.Height, _demuxer.VideoType
);
_texture = new Texture2D(
_demuxer.Width, _demuxer.Height,
Utility.DetermineTextureFormat(_demuxer.VideoType), false
);
_texture.wrapMode = TextureWrapMode.Clamp;
_texture.hideFlags = HideFlags.DontSave;
_updater = new TextureUpdater(_texture, _decoder);
}
void UpdateTargetTexture()
{
if (_targetTexture == null || _texture == null || _demuxer == null) return;
if (this == null || !enabled) return;
if (_flipMaterial == null)
{
_flipMaterial = new Material(Shader.Find("Hidden/FlipBlit"));
_flipMaterial.hideFlags = HideFlags.DontSave;
}
_flipMaterial.SetFloat("_FlipX", _flipHorizontal ? 1f : 0f);
_flipMaterial.SetFloat("_FlipY", _flipVertical ? 1f : 0f);
try
{
Graphics.Blit(_texture, _targetTexture, _flipMaterial);
}
catch (System.NullReferenceException)
{
return;
}
}
#endregion
#region ITimeControl implementation
bool _externalTime;
public void OnControlTimeStart()
{
_externalTime = true;
// In the external time mode, we can't know the actual playback
// speed but sure that it's positive (Control Track doesn't support
// reverse playback), so we assume that the speed is 1.0.
// Cons: Resync could happen every frame for high speed play back.
_speed = 1;
}
public void OnControlTimeStop()
{
_externalTime = false;
}
public void SetTime(double time)
{
_time = (float)time;
_speed = 1;
}
#endregion
#region IPropertyPreview implementation
#if KLAKHAP_HAS_TIMELINE
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
{
driver.AddFromName<HapPlayer>(gameObject, "_time");
}
#endif
#endregion
#region MonoBehaviour implementation
void OnDestroy()
{
if (_updater != null)
{
_updater.Dispose();
_updater = null;
}
if (_decoder != null)
{
_decoder.Dispose();
_decoder = null;
}
if (_stream != null)
{
_stream.Dispose();
_stream = null;
}
if (_demuxer != null)
{
_demuxer.Dispose();
_demuxer = null;
}
Utility.Destroy(_texture);
Utility.Destroy(_flipMaterial);
}
#if UNITY_EDITOR
void OnValidate()
{
// 더 이상 필요 없음: 렌더러 관련 클리어 코드 제거
}
#endif
int _lastUpdateFrameCount = -1;
void LateUpdate()
{
if (!enabled || this == null) return;
if (Time.frameCount == _lastUpdateFrameCount) return;
_lastUpdateFrameCount = Time.frameCount;
if (_demuxer == null && !string.IsNullOrEmpty(_filePath))
OpenInternal();
if (_demuxer == null) return;
var duration = (float)_demuxer.Duration;
var dt = duration / _demuxer.FrameCount;
var resync = _time < _storedTime || _time > _storedTime + dt;
if (_speed != _storedSpeed)
{
resync = true;
_storedSpeed = _speed;
}
var t = _loop ? _time : Mathf.Clamp(_time, 0, duration - 1e-4f);
var bgdec = !resync && Application.isPlaying;
if (resync && _stream != null) _stream.Restart(t, _speed / 60);
if (_decoder != null && _updater != null)
{
if (TextureUpdater.AsyncSupport)
{
if (bgdec) _decoder.UpdateAsync(t); else _decoder.UpdateSync(t);
_updater.RequestAsyncUpdate();
}
#if !HAP_NO_DELAY
else if (bgdec)
{
_updater.UpdateNow();
_decoder.UpdateAsync(t);
}
#endif
else
{
_decoder.UpdateSync(t);
_updater.UpdateNow();
}
}
if (Application.isPlaying && !_externalTime)
_time += Time.deltaTime * _speed;
_storedTime = _time;
// 렌더 텍스처만 업데이트
if (this != null && enabled)
{
try
{
if (_targetTexture != null)
UpdateTargetTexture();
}
catch (System.NullReferenceException ex)
{
Debug.LogWarning($"HapPlayer: Null reference in external object updates - {ex.Message}");
}
catch (System.Exception ex)
{
Debug.LogWarning($"HapPlayer: Unexpected error in external object updates - {ex.Message}");
}
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8ea76979ef31fb42882178890076b16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9826f2ed5102c594db39cb240eb1a004
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,146 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Klak.Hap
{
internal sealed class Decoder : IDisposable
{
#region Initialization/finalization
public Decoder(StreamReader stream, int width, int height, int videoType)
{
_stream = stream;
// Plugin initialization
_plugin = KlakHap_CreateDecoder(width, height, videoType);
_id = ++_instantiationCount;
KlakHap_AssignDecoder(_id, _plugin);
// By default, start from the first frame.
_time = 0;
// Decoder thread startup
_resume.req = new AutoResetEvent(true);
_resume.ack = new AutoResetEvent(false);
_thread = new Thread(DecoderThread);
_thread.Start();
}
public void Dispose()
{
if (_thread != null)
{
_terminate = true;
_resume.req.Set();
_thread.Join();
_thread = null;
}
if (_plugin != IntPtr.Zero)
{
KlakHap_AssignDecoder(_id, IntPtr.Zero);
KlakHap_DestroyDecoder(_plugin);
_plugin = IntPtr.Zero;
}
}
#endregion
#region Public members
public uint CallbackID { get { return _id; } }
public int BufferSize { get {
return KlakHap_GetDecoderBufferSize(_plugin);
} }
public void UpdateSync(float time)
{
_time = time;
var buffer = _stream.Advance(_time);
if (buffer != null)
KlakHap_DecodeFrame(_plugin, buffer.PluginPointer);
}
public void UpdateAsync(float time)
{
_time = time;
_resume.req.Set();
_resume.ack.WaitOne();
}
public IntPtr LockBuffer()
{
return KlakHap_LockDecoderBuffer(_plugin);
}
public void UnlockBuffer()
{
KlakHap_UnlockDecoderBuffer(_plugin);
}
#endregion
#region Private members
static uint _instantiationCount;
IntPtr _plugin;
uint _id;
Thread _thread;
(AutoResetEvent req, AutoResetEvent ack) _resume;
bool _terminate;
StreamReader _stream;
float _time;
#endregion
#region Thread function
void DecoderThread()
{
while (true)
{
_resume.req.WaitOne();
_resume.ack.Set();
if (_terminate) break;
var buffer = _stream.Advance(_time);
if (buffer == null) continue;
KlakHap_DecodeFrame(_plugin, buffer.PluginPointer);
}
}
#endregion
#region Native plugin entry points
[DllImport("KlakHap")]
internal static extern IntPtr KlakHap_CreateDecoder(int width, int height, int typeID);
[DllImport("KlakHap")]
internal static extern void KlakHap_DestroyDecoder(IntPtr decoder);
[DllImport("KlakHap")]
internal static extern void KlakHap_AssignDecoder(uint id, IntPtr decoder);
[DllImport("KlakHap")]
internal static extern void KlakHap_DecodeFrame(IntPtr decoder, IntPtr input);
[DllImport("KlakHap")]
internal static extern IntPtr KlakHap_LockDecoderBuffer(IntPtr decoder);
[DllImport("KlakHap")]
internal static extern void KlakHap_UnlockDecoderBuffer(IntPtr decoder);
[DllImport("KlakHap")]
internal static extern int KlakHap_GetDecoderBufferSize(IntPtr decoder);
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb63cbce06802f644820b09291d8dfb0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,103 @@
using System;
using System.Runtime.InteropServices;
namespace Klak.Hap
{
internal sealed class Demuxer : IDisposable
{
#region Public properties
public bool IsValid { get { return _plugin != IntPtr.Zero; } }
public int Width { get { return _width; } }
public int Height { get { return _height; } }
public int VideoType { get { return _videoType; } }
public double Duration { get { return _duration; } }
public int FrameCount { get { return _frameCount; } }
#endregion
#region Initialization/finalization
public Demuxer(string filePath)
{
_plugin = KlakHap_OpenDemuxer(filePath);
if (KlakHap_DemuxerIsValid(_plugin) == 0)
{
// Instantiation failed; Close and stop.
KlakHap_CloseDemuxer(_plugin);
_plugin = IntPtr.Zero;
return;
}
// Video properties
_width = KlakHap_GetVideoWidth(_plugin);
_height = KlakHap_GetVideoHeight(_plugin);
_videoType = KlakHap_AnalyzeVideoType(_plugin);
_duration = KlakHap_GetDuration(_plugin);
_frameCount = KlakHap_CountFrames(_plugin);
}
public void Dispose()
{
if (_plugin != IntPtr.Zero)
{
KlakHap_CloseDemuxer(_plugin);
_plugin = IntPtr.Zero;
}
}
#endregion
#region Public methods
public void ReadFrame(ReadBuffer buffer, int index, float time)
{
KlakHap_ReadFrame(_plugin, index, buffer.PluginPointer);
buffer.Index = index;
buffer.Time = time;
}
#endregion
#region Private members
IntPtr _plugin;
int _width, _height, _videoType;
double _duration;
int _frameCount;
#endregion
#region Native plugin entry points
[DllImport("KlakHap")]
internal static extern IntPtr KlakHap_OpenDemuxer(string filepath);
[DllImport("KlakHap")]
internal static extern void KlakHap_CloseDemuxer(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern int KlakHap_DemuxerIsValid(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern int KlakHap_CountFrames(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern double KlakHap_GetDuration(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern int KlakHap_GetVideoWidth(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern int KlakHap_GetVideoHeight(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern int KlakHap_AnalyzeVideoType(IntPtr demuxer);
[DllImport("KlakHap")]
internal static extern void KlakHap_ReadFrame(IntPtr demuxer, int frameNumber, IntPtr buffer);
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01e2f3e55fcbb10448e3b4976da8097d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,48 @@
using System;
using System.Runtime.InteropServices;
namespace Klak.Hap
{
internal sealed class ReadBuffer : IDisposable
{
#region Initialization/finalization
public IntPtr PluginPointer { get { return _plugin; } }
public int Index { get; set; }
public float Time { get; set; }
#endregion
#region Initialization/finalization
public ReadBuffer()
{
_plugin = KlakHap_CreateReadBuffer();
Index = Int32.MaxValue;
Time = Single.MaxValue;
}
public void Dispose()
{
KlakHap_DestroyReadBuffer(_plugin);
}
#endregion
#region Private members
IntPtr _plugin;
#endregion
#region Native plugin entry points
[DllImport("KlakHap")]
internal static extern IntPtr KlakHap_CreateReadBuffer();
[DllImport("KlakHap")]
internal static extern void KlakHap_DestroyReadBuffer(IntPtr buffer);
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4489c695e7711e44bbe985cac12755c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace Klak.Hap
{
internal sealed class StreamReader : IDisposable
{
#region Public methods
public StreamReader(Demuxer demuxer, float time, float delta)
{
_demuxer = demuxer;
_leadQueue = new Queue<ReadBuffer>();
_freeBuffers = new List<ReadBuffer>();
// Initial buffer entry allocation
_freeBuffers.Add(new ReadBuffer());
_freeBuffers.Add(new ReadBuffer());
_freeBuffers.Add(new ReadBuffer());
_freeBuffers.Add(new ReadBuffer());
// Initial playback settings
_restart = (time, SafeDelta(delta));
// Reader thread startup
_updateEvent = new AutoResetEvent(true);
_readEvent = new AutoResetEvent(false);
_thread = new Thread(ReaderThread);
_thread.Start();
}
public void Dispose()
{
if (_thread != null)
{
_terminate = true;
_updateEvent.Set();
_thread.Join();
_thread = null;
}
if (_updateEvent != null)
{
_updateEvent.Dispose();
_updateEvent = null;
}
if (_readEvent != null)
{
_readEvent.Dispose();
_readEvent = null;
}
if (_current != null)
{
_current.Dispose();
_current = null;
}
if (_leadQueue != null)
{
foreach (var rb in _leadQueue) rb.Dispose();
_leadQueue.Clear();
_leadQueue = null;
}
if (_freeBuffers != null)
{
foreach (var rb in _freeBuffers) rb.Dispose();
_freeBuffers.Clear();
_freeBuffers = null;
}
}
public void Restart(float time, float delta)
{
// Restart request
lock (_restartLock) _restart = (time, SafeDelta(delta));
// Wait for reset/read on the reader thread.
_readEvent.Reset();
while (_restart != null)
{
_updateEvent.Set();
_readEvent.WaitOne();
}
}
public ReadBuffer Advance(float time)
{
// Add an epsilon-ish value to avoid rounding error.
time += 1e-6f;
var changed = false;
// There is no slow path in this function, so we prefer holding
// the queue lock for the entire function block rather than
// acquiring/releasing it for each operation.
lock (_queueLock)
{
// Scan the lead queue.
while (_leadQueue.Count > 0)
{
var peek = _leadQueue.Peek();
if (_current != null)
{
if (_current.Time <= peek.Time)
{
// Forward playback case:
// Break if it hasn't reached the next frame.
if (time < peek.Time) break;
}
else
{
// Reverse playback case:
// Break if it's still on the current frame.
if (_current.Time < time) break;
}
// Free the current frame before replacing it.
_freeBuffers.Add(_current);
}
_current = _leadQueue.Dequeue();
changed = true;
}
}
// Poke the reader thread.
_updateEvent.Set();
// Only returns a buffer object when the frame was changed.
return changed ? _current : null;
}
#endregion
#region Private members
// Assigned demuxer
Demuxer _demuxer;
// Thread and synchronization objects
Thread _thread;
AutoResetEvent _updateEvent;
AutoResetEvent _readEvent;
bool _terminate;
// Read buffer objects
ReadBuffer _current;
Queue<ReadBuffer> _leadQueue;
List<ReadBuffer> _freeBuffers;
readonly object _queueLock = new object();
// Restart request
(float, float)? _restart;
readonly object _restartLock = new object();
// Used to avoid too small delta time values.
float SafeDelta(float delta)
{
var min = (float)(_demuxer.Duration / _demuxer.FrameCount);
return Math.Max(Math.Abs(delta), min) * (delta < 0 ? -1 : 1);
}
#endregion
#region Thread function
void ReaderThread()
{
// Initial time settings from the restart request tuple
var (time, delta) = _restart.Value;
_restart = null;
// Stream attributes
var totalTime = _demuxer.Duration;
var totalFrames = _demuxer.FrameCount;
while (true)
{
// Synchronization with the parent thread
_updateEvent.WaitOne();
if (_terminate) break;
// Check if there is a restart request.
lock (_restartLock) if (_restart != null)
{
// Flush out the current contents of the lead queue.
lock (_queueLock) while (_leadQueue.Count > 0)
_freeBuffers.Add(_leadQueue.Dequeue());
// Apply the restart request.
(time, delta) = _restart.Value;
_restart = null;
}
// Time -> Frame count
// Rounding strategy: We don't prefer Math.Round because it can
// show a frame before the playhead reaches it (especially when
// using slow-mo). On the other hand, Math.Floor causes frame
// skipping due to rounding errors. To avoid these problems,
// we use the "adding a very-very small fractional frame"
// approach. 1/1000 might be safe and enough for all the cases.
var frameCount = (int)(time * totalFrames / totalTime + 1e-3f);
// Frame count -> Frame snapped time
var snappedTime = (float)(frameCount * totalTime / totalFrames);
// Frame count -> Wrapped frame number
var frameNumber = frameCount % totalFrames;
if (frameNumber < 0) frameNumber += totalFrames;
lock (_queueLock)
{
// Do nothing if there is no free buffer; It indicates that
// the lead queue is fully filled.
if (_freeBuffers.Count == 0) continue;
ReadBuffer buffer = null;
// Look for a free buffer that has the same frame number.
foreach (var temp in _freeBuffers)
{
if (temp.Index == frameNumber)
{
buffer = temp;
break;
}
}
if (buffer != null)
{
// Reuse the found buffer; Although we can use it
// without reading frame data, the time field should
// be updated to handle wrapping-around hits.
_freeBuffers.Remove(buffer);
buffer.Time = snappedTime;
}
else
{
// Allocate a buffer from the free buffer list.
buffer = _freeBuffers[_freeBuffers.Count - 1];
_freeBuffers.RemoveAt(_freeBuffers.Count - 1);
// Frame data read
_demuxer.ReadFrame(buffer, frameNumber, snappedTime);
}
// Push the buffer to the lead queue.
_leadQueue.Enqueue(buffer);
}
_readEvent.Set();
time += delta;
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5b51807ae8c7e749b98a930666f136b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,77 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
namespace Klak.Hap
{
internal sealed class TextureUpdater : IDisposable
{
#region Public properties
public static bool AsyncSupport { get {
return SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11;
} }
#endregion
#region Public methods
public TextureUpdater(Texture2D texture, Decoder decoder)
{
_texture = texture;
_decoder = decoder;
if (AsyncSupport)
{
_command = new CommandBuffer();
_command.name = "Klak HAP";
_command.IssuePluginCustomTextureUpdateV2(
KlakHap_GetTextureUpdateCallback(),
texture, decoder.CallbackID
);
}
}
public void Dispose()
{
if (_command != null)
{
_command.Dispose();
_command = null;
}
}
public void UpdateNow()
{
_texture.LoadRawTextureData(
_decoder.LockBuffer(),
_decoder.BufferSize
);
_texture.Apply();
_decoder.UnlockBuffer();
}
public void RequestAsyncUpdate()
{
if (_command != null) Graphics.ExecuteCommandBuffer(_command);
}
#endregion
#region Private fields
Texture2D _texture;
Decoder _decoder;
CommandBuffer _command;
#endregion
#region Native plugin entry points
[DllImport("KlakHap")]
internal static extern IntPtr KlakHap_GetTextureUpdateCallback();
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 430fee5c2bdcf0c4492d6b8d742439f8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,48 @@
using UnityEngine;
namespace Klak.Hap
{
internal static class Utility
{
public static void Destroy(Object o)
{
if (o == null) return;
if (Application.isPlaying)
Object.Destroy(o);
else
Object.DestroyImmediate(o);
}
public static CodecType DetermineCodecType(int videoType)
{
switch (videoType & 0xf)
{
case 0xb: return CodecType.Hap;
case 0xe: return CodecType.HapAlpha;
case 0xf: return CodecType.HapQ;
}
return CodecType.Unsupported;
}
public static TextureFormat DetermineTextureFormat(int videoType)
{
switch (videoType & 0xf)
{
case 0xb: return TextureFormat.DXT1;
case 0xe: return TextureFormat.DXT5;
case 0xf: return TextureFormat.DXT5;
case 0xc: return TextureFormat.BC7;
case 0x1: return TextureFormat.BC4;
}
return TextureFormat.DXT1;
}
public static Shader DetermineBlitShader(int videoType)
{
if ((videoType & 0xf) == 0xf)
return Shader.Find("Klak/HAP Q");
else
return Shader.Find("Klak/HAP");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6abcc4e09417604ca245a0c5a5a1f0c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
{
"name": "Klak.Hap",
"references": [
"GUID:f06555f75b070af458a003d92f9efb00"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.timeline",
"expression": "1.0.0",
"define": "KLAKHAP_HAS_TIMELINE"
}
],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d30a992d73d108f4eaaafff3ed9490e3
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/External/jp.keijiro.klak.hap@9a47acf6295e/package.json (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 52bab5075038b8c46ac3f7bb51b068b2
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4fcd9eb3395efa844aedc41188018b29
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
using UnityEngine;
public class DebugTest : MonoBehaviour
{
public void DebugLog()
{
Debug.Log("message");
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 88aefb43401e8a94f820bc478c2202d5

View File

@ -0,0 +1,101 @@
using UnityEngine;
public class SimpleEventExample : MonoBehaviour
{
[Header("StreamDock 통신")]
[SerializeField] private SimpleStreamDockCommunicator streamDock;
void Start()
{
// StreamDock 이벤트 구독
if (streamDock != null)
{
streamDock.OnStreamDockMessageReceived.AddListener(OnStreamDockMessage);
streamDock.OnConnected.AddListener(OnStreamDockConnected);
streamDock.OnDisconnected.AddListener(OnStreamDockDisconnected);
}
}
/// <summary>
/// StreamDock에서 메시지 수신 시 처리
/// </summary>
private void OnStreamDockMessage(string eventType, object data)
{
//Debug.Log($"StreamDock 이벤트 수신: {eventType}");
switch (eventType)
{
case "button_clicked":
// 버튼 클릭 시 실행할 코드
Debug.Log("버튼 클릭 이벤트 실행!");
DoSomething();
break;
case "dial_rotate":
// 다이얼 회전 시 실행할 코드
Debug.Log("다이얼 회전 이벤트 실행!");
DoSomethingElse();
break;
case "dial_press":
// 다이얼 누름 시 실행할 코드
Debug.Log("다이얼 누름 이벤트 실행!");
DoAnotherThing();
break;
}
}
/// <summary>
/// StreamDock 연결 시 처리
/// </summary>
private void OnStreamDockConnected()
{
Debug.Log("StreamDock에 연결되었습니다!");
}
/// <summary>
/// StreamDock 연결 해제 시 처리
/// </summary>
private void OnStreamDockDisconnected()
{
Debug.Log("StreamDock 연결이 해제되었습니다.");
}
// 여기에 원하는 동작들을 구현하세요
private void DoSomething()
{
Debug.Log("버튼 클릭으로 실행된 동작!");
// 예: 오브젝트 활성화/비활성화, 애니메이션 재생, 사운드 재생 등
}
private void DoSomethingElse()
{
Debug.Log("다이얼 회전으로 실행된 동작!");
// 예: 볼륨 조절, 카메라 회전, 값 변경 등
}
private void DoAnotherThing()
{
Debug.Log("다이얼 누름으로 실행된 동작!");
// 예: 특수 기능 실행, 모드 변경 등
}
// 공개 메서드들 (Inspector에서 호출 가능)
[ContextMenu("테스트 동작 1")]
public void TestAction1()
{
DoSomething();
}
[ContextMenu("테스트 동작 2")]
public void TestAction2()
{
DoSomethingElse();
}
[ContextMenu("테스트 동작 3")]
public void TestAction3()
{
DoAnotherThing();
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e65616cccbfc27348b6c11acf939a5f4

View File

@ -0,0 +1,274 @@
using UnityEngine;
using UnityEngine.Events;
using System;
using System.Collections;
using System.Text;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
[System.Serializable]
public class StreamDockEvent : UnityEvent<string, object> { }
public class SimpleStreamDockCommunicator : MonoBehaviour
{
[Header("연결 설정")]
[SerializeField] private string serverUrl = "ws://localhost:15732";
[SerializeField] private bool autoConnect = true;
[SerializeField] private float reconnectInterval = 5f;
[Header("이벤트")]
public StreamDockEvent OnStreamDockMessageReceived;
public UnityEvent OnConnected;
public UnityEvent OnDisconnected;
// 내부 변수
private ClientWebSocket webSocket;
private CancellationTokenSource cancellationTokenSource;
private bool isConnecting = false;
private bool isConnected = false;
// 프로퍼티
public bool IsConnected => isConnected;
void Start()
{
if (autoConnect)
{
ConnectToStreamDock();
}
}
void OnDestroy()
{
DisconnectFromStreamDock();
}
/// <summary>
/// StreamDock에 연결
/// </summary>
public async void ConnectToStreamDock()
{
if (isConnecting || isConnected) return;
isConnecting = true;
try
{
webSocket = new ClientWebSocket();
cancellationTokenSource = new CancellationTokenSource();
Debug.Log($"StreamDock에 연결 중... {serverUrl}");
await webSocket.ConnectAsync(new Uri(serverUrl), cancellationTokenSource.Token);
isConnected = true;
isConnecting = false;
Debug.Log("StreamDock에 연결되었습니다!");
OnConnected?.Invoke();
// 메시지 수신 시작
_ = ReceiveMessages();
}
catch (Exception e)
{
Debug.LogError($"StreamDock 연결 실패: {e.Message}");
isConnecting = false;
OnDisconnected?.Invoke();
// 재연결 시도
StartCoroutine(TryReconnect());
}
}
/// <summary>
/// StreamDock 연결 해제
/// </summary>
public async void DisconnectFromStreamDock()
{
if (!isConnected) return;
try
{
cancellationTokenSource?.Cancel();
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Unity 종료", CancellationToken.None);
}
}
catch (Exception e)
{
Debug.LogError($"연결 해제 중 오류: {e.Message}");
}
finally
{
isConnected = false;
webSocket?.Dispose();
webSocket = null;
OnDisconnected?.Invoke();
}
}
/// <summary>
/// StreamDock으로 메시지 전송
/// </summary>
public async void SendMessageToStreamDock(string eventType, object data = null)
{
if (!isConnected)
{
Debug.LogWarning("StreamDock에 연결되지 않았습니다.");
return;
}
try
{
var message = new
{
type = eventType,
data = data,
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
string jsonMessage = JsonUtility.ToJson(message);
byte[] buffer = Encoding.UTF8.GetBytes(jsonMessage);
await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, cancellationTokenSource.Token);
Debug.Log($"StreamDock으로 메시지 전송: {eventType}");
}
catch (Exception e)
{
Debug.LogError($"메시지 전송 실패: {e.Message}");
}
}
/// <summary>
/// 메시지 수신 처리
/// </summary>
private async Task ReceiveMessages()
{
var buffer = new byte[4096];
try
{
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationTokenSource.Token);
if (result.MessageType == WebSocketMessageType.Text)
{
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
ProcessReceivedMessage(message);
}
else if (result.MessageType == WebSocketMessageType.Close)
{
Debug.Log("StreamDock에서 연결을 종료했습니다.");
break;
}
}
}
catch (Exception e)
{
Debug.LogError($"메시지 수신 중 오류: {e.Message}");
}
finally
{
isConnected = false;
OnDisconnected?.Invoke();
StartCoroutine(TryReconnect());
}
}
/// <summary>
/// 수신된 메시지 처리
/// </summary>
private void ProcessReceivedMessage(string message)
{
try
{
//Debug.Log($"StreamDock에서 메시지 수신: {message}");
// JSON 파싱 (간단한 구조)
if (message.Contains("type"))
{
// UnityEvent 호출
OnStreamDockMessageReceived?.Invoke("streamdock_message", message);
// 특정 이벤트 타입 처리
if (message.Contains("streamdock_button_clicked"))
{
HandleButtonClick(message);
}
else if (message.Contains("dial_rotate"))
{
HandleDialRotate(message);
}
else if (message.Contains("dial_press"))
{
HandleDialPress(message);
}
}
}
catch (Exception e)
{
Debug.LogError($"메시지 처리 중 오류: {e.Message}");
}
}
/// <summary>
/// 버튼 클릭 이벤트 처리
/// </summary>
private void HandleButtonClick(string message)
{
Debug.Log("StreamDock 버튼이 클릭되었습니다!");
OnStreamDockMessageReceived?.Invoke("button_clicked", message);
}
/// <summary>
/// 다이얼 회전 이벤트 처리
/// </summary>
private void HandleDialRotate(string message)
{
Debug.Log("StreamDock 다이얼이 회전했습니다!");
OnStreamDockMessageReceived?.Invoke("dial_rotate", message);
}
/// <summary>
/// 다이얼 누름 이벤트 처리
/// </summary>
private void HandleDialPress(string message)
{
Debug.Log("StreamDock 다이얼이 눌렸습니다!");
OnStreamDockMessageReceived?.Invoke("dial_press", message);
}
/// <summary>
/// 재연결 시도
/// </summary>
private IEnumerator TryReconnect()
{
yield return new WaitForSeconds(reconnectInterval);
if (!isConnected && !isConnecting)
{
Debug.Log("StreamDock 재연결 시도...");
ConnectToStreamDock();
}
}
// 테스트용 메서드들
[ContextMenu("테스트 메시지 전송")]
public void SendTestMessage()
{
SendMessageToStreamDock("test_message", new { message = "Unity에서 테스트 메시지" });
}
[ContextMenu("커스텀 이벤트 전송")]
public void SendCustomEvent()
{
SendMessageToStreamDock("custom_event", new { action = "test_action", value = 123 });
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dd2d6415e76fac64ea5a022f3cfefa97

62
StreamDock-Plugin-SDK/.gitignore vendored Normal file
View File

@ -0,0 +1,62 @@
**/.vscode/
**/.vs/
# Package files
**/package-lock.json
# Non-npm lockfiles
**/pnpm-lock.yaml
**/yarn.lock
**/deno.lock
**/bun.lockb
**/bun.lock
# Dependencies
**/node_modules/
# Build outputs
**/build/
**/dist/
**/*.build/
**/out/
**/__pycache__/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE and editor files
**/.idea/
**/*.swp
**/*.swo
**/*.swn
**/.history/
*.sublime-workspace
*.sublime-project
# Logs and databases
**/logs
**/*.log
**/*.sqlite
**/*.db
# Environment variables
.env
.env.local
.env.*.local
.env.development
.env.test
.env.production
# Testing
**/coverage/
**/.nyc_output/
# Temporary files
**/tmp/
**/temp/

BIN
StreamDock-Plugin-SDK/Icon/unity.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 MiraboxSpace
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
StreamDock-Plugin-SDK/README.md (Stored with Git LFS) Normal file

Binary file not shown.

BIN
StreamDock-Plugin-SDK/README.zh-CN.md (Stored with Git LFS) Normal file

Binary file not shown.

BIN
StreamDock-Plugin-SDK/README_한국어.md (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>插件调试</title>
</head>
<body>
<script src="./utils/common.js"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@ -0,0 +1,19 @@
/// <reference path="./utils/common.js" />
/// <reference path="./utils/axios.js" />
const plugin = new Plugins("xxx");
// 操作一
plugin.action1 = new Actions({
default: {},
_willAppear({ context }) {
window.socket.setTitle(context, "Hello world!");
},
_willDisappear({ context }) { },
dialRotate(data) {//旋钮旋转
console.log(data);
},
dialDown(data) {//旋钮按下
console.log(data);
}
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
const Timer = new Worker('./utils/worker.js');
const TimerSubscribe = { setTimeout: {}, setInterval: {} };
Timer.addEventListener('message', function ({ data }) {
TimerSubscribe[data.event][data.id]?.();
});
class Actions {
constructor(data) {
this.data = {};
this.default = {};
Object.assign(this, data);
}
// 属性检查器显示时
static currentAction = null;
static currentContext = null;
propertyInspectorDidAppear({ action, context }) {
Actions.currentAction = action;
Actions.currentContext = context;
this._propertyInspectorDidAppear?.(data);
}
// 初始化数据
willAppear(data) {
const { context, payload: { settings } } = data;
this.data[context] = Object.assign({ ...this.default }, settings);
this._willAppear?.(data);
}
// 行动销毁
willDisappear(data) {
this._willDisappear?.(data);
delete this.data[data.context];
}
}
class Plugins {
constructor(name) {
this.name = name;
}
// 延时器/清除延时
clearTimeout(id) { Timer.postMessage({ event: 'clearTimeout', id }); }
setTimeout(id, callback, delay) {
this.clearTimeout(id);
TimerSubscribe.setTimeout[id] = callback;
Timer.postMessage({
event: 'setTimeout',
id, delay
});
}
// 定时器/清除定时器
clearInterval(id) { Timer.postMessage({ event: 'clearInterval', id }); }
setInterval(id, callback, delay) {
this.clearInterval(id);
TimerSubscribe.setInterval[id] = callback;
Timer.postMessage({
event: 'setInterval',
id, delay
});
}
}
// 软件通信
window.connectElgatoStreamDeckSocket = function () {
const uuid = arguments[1], event = arguments[2];
window.info = JSON.parse(arguments[3]);
window.socket = new WebSocket("ws://127.0.0.1:" + arguments[0]);
// 打开网页
WebSocket.prototype.openUrl = function (url) {
this.send(JSON.stringify({
event: "openUrl",
payload: { url }
}));
};
// 与当前属性检查器通信
WebSocket.prototype.sendToPropertyInspector = function (payload) {
this.send(JSON.stringify({
payload,
event: "sendToPropertyInspector",
action: Actions.currentAction,
context: Actions.currentContext
}));
};
// 保存持久化数据
WebSocket.prototype.setSettings = function (context, payload) {
this.send(JSON.stringify({
event: "setSettings",
context, payload
}));
};
// 设置背景
WebSocket.prototype.setImage = function (context, url) {
const image = new Image();
image.src = url;
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
this.send(JSON.stringify({
event: "setImage",
context, payload: {
target: 0,
image: canvas.toDataURL("image/png")
}
}));
};
};
// 设置标题
WebSocket.prototype.setTitle = function (context, str, row = 0, num = 6) {
let newStr = '';
if (row) {
let nowRow = 1, strArr = str.split('');
strArr.forEach((item, index) => {
if (nowRow < row && index >= nowRow * num) {
nowRow++;
newStr += '\n';
}
if (nowRow <= row && index < nowRow * num) {
newStr += item;
}
});
if (strArr.length > row * num) {
newStr = newStr.substring(0, newStr.length - 1);
newStr += '..';
}
}
this.send(JSON.stringify({
event: "setTitle",
context, payload: {
target: 0,
title: newStr || str
}
}));
};
// 设置状态
WebSocket.prototype.setState = function (context, state) {
this.send(JSON.stringify({
event: "setState",
payload: { state },
context
}));
};
window.socket.onopen = () => window.socket.send(JSON.stringify({ uuid, event }));
window.socket.onmessage = e => {
const data = JSON.parse(e.data);
plugin[data.action?.split('.').pop()]?.[data.event]?.(data);
plugin[data.event]?.(data);
};
};

View File

@ -0,0 +1,30 @@
const that = this, Timer = {}
const handle = {
setTimeout(data) {
Timer[data.id] = setTimeout(() => {
that.self.postMessage({
event: 'setTimeout',
id: data.id
})
}, data.delay)
},
setInterval(data) {
Timer[data.id] = setInterval(() => {
that.self.postMessage({
event: 'setInterval',
id: data.id
})
}, data.delay)
},
clearTimeout(data) {
clearTimeout(Timer[data.id])
},
clearInterval(data) {
clearInterval(Timer[data.id])
}
}
this.self.onmessage = function ({ data }) {
handle[data?.event]?.(data)
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>模板 - 属性检查器</title>
<link rel="stylesheet" href="../../static/css/sdpi.css">
</head>
<body>
<div class="sdpi-wrapper" style="display: none">
<div class="sdpi-heading">模板</div>
</div>
<script src="../utils/common.js"></script>
<script src="../utils/action.js"></script>
<script src="index.js"></script>
</body>
</html>

View File

@ -0,0 +1,21 @@
/// <reference path="../utils/common.js" />
/// <reference path="../utils/action.js" />
/**
* 基础参数说明:
* @global websocket uuid action context settings lang
* @settings local back 是否国际化 | 是否自行回显
* @policy dom propEvent 缓存文档元素 | 软件触发事件 - 策略模式
* =======================================================================>
*/
const $local = false, $back = false, $dom = {
main: $('.sdpi-wrapper')
};
const $propEvent = {
didReceiveSettings(data) {
console.log(data);
},
sendToPropertyInspector(data) { }
};

View File

@ -0,0 +1,130 @@
/**
* PropertyInspector 2.5.0 新特性 =>
*
* 1 => 工具与主文件相分离 - 按需引入
* 2 => $settings - 全局持久化数据代理
* 3 => 无需关注上下文 - 随时随地与插件通信
* 4 => 注意事项: 为了避免命名冲突请勿使用 $ 相关的名称以及JQuery库
*
* ===== CJHONG ========================================== 2023.10.10 =====>
*/
let $websocket, $uuid, $action, $context, $settings, $lang, $FileID = '';
// 与插件通信
WebSocket.prototype.sendToPlugin = function (payload) {
this.send(JSON.stringify({
event: "sendToPlugin",
action: $action,
context: $uuid,
payload
}));
};
// 设置状态
WebSocket.prototype.setState = function (state) {
this.send(JSON.stringify({
event: "setState",
context: $context,
payload: { state }
}));
};
// 设置背景
WebSocket.prototype.setImage = function (url) {
let image = new Image();
image.src = url;
image.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
this.send(JSON.stringify({
event: "setImage",
context: $context,
payload: {
target: 0,
image: canvas.toDataURL("image/png")
}
}));
};
};
// 打开网页
WebSocket.prototype.openUrl = function (url) {
this.send(JSON.stringify({
event: "openUrl",
payload: { url }
}));
};
// 保存持久化数据
WebSocket.prototype.saveData = $.debounce(function (payload) {
this.send(JSON.stringify({
event: "setSettings",
context: $uuid,
payload
}));
});
// StreamDock 软件入口函数
async function connectElgatoStreamDeckSocket(port, uuid, event, app, info) {
info = JSON.parse(info);
$uuid = uuid; $action = info.action; $context = info.context;
$websocket = new WebSocket('ws://127.0.0.1:' + port);
$websocket.onopen = () => $websocket.send(JSON.stringify({ event, uuid }));
// 持久数据代理
$websocket.onmessage = e => {
let data = JSON.parse(e.data);
if (data.event === 'didReceiveSettings') {
$settings = new Proxy(data.payload.settings, {
get(target, property) {
return target[property];
},
set(target, property, value) {
target[property] = value;
$websocket.saveData(data.payload.settings);
}
});
if (!$back) $dom.main.style.display = 'block';
}
$propEvent[data.event]?.(data.payload);
};
// 自动翻译页面
if (!$local) return;
$lang = await new Promise(resolve => {
const req = new XMLHttpRequest();
req.open('GET', `../../${JSON.parse(app).application.language}.json`);
req.send();
req.onreadystatechange = () => {
if (req.readyState === 4) {
resolve(JSON.parse(req.responseText).Localization);
}
};
});
// 遍历文本节点并翻译所有文本节点
const walker = document.createTreeWalker($dom.main, NodeFilter.SHOW_TEXT, (e) => {
return e.data.trim() && NodeFilter.FILTER_ACCEPT;
});
while (walker.nextNode()) {
console.log(walker.currentNode.data);
walker.currentNode.data = $lang[walker.currentNode.data];
}
// placeholder 特殊处理
const translate = item => {
if (item.placeholder?.trim()) {
console.log(item.placeholder);
item.placeholder = $lang[item.placeholder];
}
};
$('input', true).forEach(translate);
$('textarea', true).forEach(translate);
}
// StreamDock 文件路径回调
Array.from($('input[type="file"]', true)).forEach(item => item.addEventListener('click', () => $FileID = item.id));
const onFilePickerReturn = (url) => $emit.send(`File-${$FileID}`, JSON.parse(url));

View File

@ -0,0 +1,81 @@
// 自定义事件类
class EventPlus {
constructor() {
this.event = new EventTarget();
}
on(name, callback) {
this.event.addEventListener(name, e => callback(e.detail));
}
send(name, data) {
this.event.dispatchEvent(new CustomEvent(name, {
detail: data,
bubbles: false,
cancelable: false
}));
}
}
// 补零
String.prototype.fill = function () {
return this >= 10 ? this : '0' + this;
};
// unicode编码转换字符串
String.prototype.uTs = function () {
return eval('"' + Array.from(this).join('') + '"');
};
// 字符串转换unicode编码
String.prototype.sTu = function (str = '') {
Array.from(this).forEach(item => str += `\\u${item.charCodeAt(0).toString(16)}`);
return str;
};
// 全局变量/方法
const $emit = new EventPlus(), $ = (selector, isAll = false) => {
const element = document.querySelector(selector), methods = {
on: function (event, callback) {
this.addEventListener(event, callback);
},
attr: function (name, value = '') {
value && this.setAttribute(name, value);
return this;
}
};
if (!isAll && element) {
return Object.assign(element, methods);
} else if (!isAll && !element) {
throw `HTML没有 ${selector} 元素! 请检查是否拼写错误`;
}
return Array.from(document.querySelectorAll(selector)).map(item => Object.assign(item, methods));
};
// 节流函数
$.throttle = (fn, delay) => {
let Timer = null;
return function () {
if (Timer) return;
Timer = setTimeout(() => {
fn.apply(this, arguments);
Timer = null;
}, delay);
};
};
// 防抖函数
$.debounce = (fn, delay) => {
let Timer = null;
return function () {
clearTimeout(Timer);
Timer = setTimeout(() => fn.apply(this, arguments), delay);
};
};
// 绑定限制数字方法
Array.from($('input[type="num"]', true)).forEach(item => {
item.addEventListener('input', function limitNum() {
if (!item.value || /^\d+$/.test(item.value)) return;
item.value = item.value.slice(0, -1);
limitNum(item);
});
});

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="6" viewBox="0 0 12 6">
<polygon fill="#8E8E92" fill-rule="evenodd" points="5 4 9 0 10 1 5 6 0 1 1 0"/>
</svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="10" viewBox="0 0 12 10">
<polygon fill="#FFF" points="7.2 7.5 7.2 -1.3 8.7 -1.3 8.6 9.1 2.7 8.7 2.7 7.2" transform="rotate(37 5.718 3.896)"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

View File

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#9C9C9C" fill-rule="nonzero" d="M1,5 L1,14 L14,14 L14,5 L1,5 Z M0,1 L15,1 L15,15 L0,15 L0,1 Z M14,4 L14,2 L1,2 L1,4 L14,4 Z"/>
<rect width="1" height="1" x="2" fill="#9C9C9C" fill-rule="nonzero"/>
<rect width="1" height="1" x="12" fill="#9C9C9C" fill-rule="nonzero"/>
<g transform="translate(3 7)">
<rect width="1" height="1" x="2" fill="#9C9C9C"/>
<rect width="1" height="1" fill="#666"/>
<rect width="1" height="1" x="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" fill="#9C9C9C"/>
<rect width="1" height="1" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="2" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="4" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="2" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="4" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" y="4" fill="#666"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="#9C9C9C">
<path d="M15,15 L1.77635684e-15,15 L1.77635684e-15,1 L15,1 L15,15 Z M5,7 L5,8 L6,8 L6,7 L5,7 Z M3,7 L3,8 L4,8 L4,7 L3,7 Z M7,7 L7,8 L8,8 L8,7 L7,7 Z M9,7 L9,8 L10,8 L10,7 L9,7 Z M11,7 L11,8 L12,8 L12,7 L11,7 Z M3,9 L3,10 L4,10 L4,9 L3,9 Z M5,9 L5,10 L6,10 L6,9 L5,9 Z M7,9 L7,10 L8,10 L8,9 L7,9 Z M9,9 L9,10 L10,10 L10,9 L9,9 Z M11,9 L11,10 L12,10 L12,9 L11,9 Z M3,11 L3,12 L4,12 L4,11 L3,11 Z M5,11 L5,12 L6,12 L6,11 L5,11 Z M7,11 L7,12 L8,12 L8,11 L7,11 Z M9,11 L9,12 L10,12 L10,11 L9,11 Z M11,11 L11,12 L12,12 L12,11 L11,11 Z M14,4 L14,2 L1,2 L1,4 L14,4 Z"/>
<rect width="1" height="1" x="2"/>
<rect width="1" height="1" x="12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Keyshape -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="46" height="46" viewBox="0 0 46 46">
<style>
@keyframes a0_t { 0% { transform: translate(0px,0px) scale(1,1); } 50% { transform: translate(23px,0px) scale(0,1); } 100% { transform: translate(0px,0px) scale(1,1); } }
@keyframes a0_f { 0% { fill: #d8d8d8; } 100% { fill: #d8d8d8; } }
@keyframes a0_d { 0% { d: path('M26.7002,16.1301L26.6501,16.0924L18.4447,11.4398L14.3373,13.7874L14.3373,31.9187L18.4511,34.2437L30.0827,27.5941L26.0878,25.3292L18.3286,29.7648L18.3286,15.9149L40.8363,28.6881L40.8228,28.7688C39.6065,32.4179,37.3047,35.5576,34.1665,37.8484C30.9632,40.1865,27.1667,41.4225,23.1877,41.4225C12.9466,41.4225,4.6147,33.1581,4.6147,22.9999C4.6147,12.8415,12.9466,4.57726,23.1877,4.57726C31.0308,4.57726,38.0657,9.51032,40.6929,16.8527C40.888,17.3976,41.0585,17.9576,41.201,18.5197L45.3224,16.1504C45.1466,15.5908,44.9476,15.0335,44.7302,14.4923C43.0331,10.2647,40.1339,6.65801,36.3464,4.0619C32.4692,1.40466,27.9189,0,23.1877,0C16.994,0,11.1711,2.39237,6.7916,6.73651C2.4119,11.0806,0,16.8563,0,22.9999C0,29.1433,2.4119,34.9192,6.7916,39.2635C11.1711,43.6076,16.994,46,23.1877,46C28.6714,46,33.9912,44.0654,38.1673,40.553C42.2755,37.0973,45.0531,32.3177,46,27.0878L26.7002,16.1301L26.7002,16.1301Z'); } 100% { d: path('M26.7002,16.1301L26.6501,16.0924L18.4447,11.4398L14.3373,13.7874L14.3373,31.9187L18.4511,34.2437L30.0827,27.5941L26.0878,25.3292L18.3286,29.7648L18.3286,15.9149L40.8363,28.6881L40.8228,28.7688C39.6065,32.4179,37.3047,35.5576,34.1665,37.8484C30.9632,40.1865,27.1667,41.4225,23.1877,41.4225C12.9466,41.4225,4.6147,33.1581,4.6147,22.9999C4.6147,12.8415,12.9466,4.57726,23.1877,4.57726C31.0308,4.57726,38.0657,9.51032,40.6929,16.8527C40.888,17.3976,41.0585,17.9576,41.201,18.5197L45.3224,16.1504C45.1466,15.5908,44.9476,15.0335,44.7302,14.4923C43.0331,10.2647,40.1339,6.65801,36.3464,4.0619C32.4692,1.40466,27.9189,0,23.1877,0C16.994,0,11.1711,2.39237,6.7916,6.73651C2.4119,11.0806,0,16.8563,0,22.9999C0,29.1433,2.4119,34.9192,6.7916,39.2635C11.1711,43.6076,16.994,46,23.1877,46C28.6714,46,33.9912,44.0654,38.1673,40.553C42.2755,37.0973,45.0531,32.3177,46,27.0878L26.7002,16.1301L26.7002,16.1301Z'); } }
</style>
<path fill="#d8d8d8" fill-rule="evenodd" d="M26.7002,16.1301L26.6501,16.0924L18.4447,11.4398L14.3373,13.7874L14.3373,31.9187L18.4511,34.2437L30.0827,27.5941L26.0878,25.3292L18.3286,29.7648L18.3286,15.9149L40.8363,28.6881L40.8228,28.7688C39.6065,32.4179,37.3047,35.5576,34.1665,37.8484C30.9632,40.1865,27.1667,41.4225,23.1877,41.4225C12.9466,41.4225,4.6147,33.1581,4.6147,22.9999C4.6147,12.8415,12.9466,4.57726,23.1877,4.57726C31.0308,4.57726,38.0657,9.51032,40.6929,16.8527C40.888,17.3976,41.0585,17.9576,41.201,18.5197L45.3224,16.1504C45.1466,15.5908,44.9476,15.0335,44.7302,14.4923C43.0331,10.2647,40.1339,6.65801,36.3464,4.0619C32.4692,1.40466,27.9189,0,23.1877,0C16.994,0,11.1711,2.39237,6.7916,6.73651C2.4119,11.0806,0,16.8563,0,22.9999C0,29.1433,2.4119,34.9192,6.7916,39.2635C11.1711,43.6076,16.994,46,23.1877,46C28.6714,46,33.9912,44.0654,38.1673,40.553C42.2755,37.0973,45.0531,32.3177,46,27.0878L26.7002,16.1301L26.7002,16.1301Z" style="animation: 6s linear infinite both a0_t, 6s linear infinite both a0_f, 6s linear infinite both a0_d;"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="6" viewBox="0 0 6 6">
<circle cx="3" cy="3" r="3" fill="#FFF"/>
</svg>

After

Width:  |  Height:  |  Size: 131 B

View File

@ -0,0 +1,230 @@
:root{--sdpi-bgcolor:#2D2D2D;--sdpi-background:#3D3D3D;--sdpi-color:#d8d8d8;--sdpi-bordercolor:#3a3a3a;--sdpi-buttonbordercolor:#969696;--sdpi-borderradius:0px;--sdpi-width:224px;--sdpi-fontweight:600;--sdpi-letterspacing:-0.25pt}
html{--sdpi-bgcolor:#2D2D2D;--sdpi-background:#3D3D3D;--sdpi-color:#d8d8d8;--sdpi-bordercolor:#3a3a3a;--sdpi-buttonbordercolor:#969696;--sdpi-borderradius:0px;--sdpi-width:224px;--sdpi-fontweight:600;--sdpi-letterspacing:-0.25pt;height:100%;width:100%;overflow:hidden;touch-action:none;user-select:none}
html,body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:9pt;background-color:var(--sdpi-bgcolor);color:#9a9a9a}
body{height:100%;padding:0;overflow-x:hidden;overflow-y:auto;margin:0;-webkit-overflow-scrolling:touch;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased}
mark{background-color:var(--sdpi-bgcolor);color:var(--sdpi-color)}
hr,hr2{-webkit-margin-before:1em;-webkit-margin-after:1em;border-style:none;background:var(--sdpi-background);height:1px}
hr2,.sdpi-heading{display:flex;flex-basis:100%;align-items:center;color:inherit;font-size:9pt;margin:8px 0px}
.sdpi-heading::before,.sdpi-heading::after{content:"";flex-grow:1;background:var(--sdpi-background);height:1px;font-size:0px;line-height:0px;margin:0px 16px}
hr2{height:2px}
hr,hr2{margin-left:16px;margin-right:16px}
.sdpi-item-value,option,input,select,button{font-size:10pt;font-weight:var(--sdpi-fontweight);letter-spacing:var(--sdpi-letterspacing)}
.win .sdpi-item-value,.win option,.win input,.win select,.win button{font-size:11px;font-style:normal;letter-spacing:inherit;font-weight:100}
.win button{font-size:12px}
::-webkit-progress-value,meter::-webkit-meter-optimum-value{border-radius:2px}
::-webkit-progress-bar,meter::-webkit-meter-bar{border-radius:3px;background:var(--sdpi-background)}
::-webkit-progress-bar:active,meter::-webkit-meter-bar:active{border-radius:3px;background:#222222}
::-webkit-progress-value:active,meter::-webkit-meter-optimum-value:active{background:#99f}
progress,progress.sdpi-item-value{min-height:5px !important;height:5px;background-color:#303030}
progress{margin-top:8px !important;margin-bottom:8px !important}
.full progress,progress.full{margin-top:3px !important}
::-webkit-progress-inner-element{background-color:transparent}
.sdpi-item[type="progress"]{margin-top:4px !important;margin-bottom:12px;min-height:15px}
.sdpi-item-child.full:last-child{margin-bottom:4px}
.tabs{display:flex;border-bottom:1px solid #D7DBDD}
.tab{cursor:pointer;padding:5px 30px;color:#16a2d7;font-size:9pt;border-bottom:2px solid transparent}
.tab.is-tab-selected{border-bottom-color:#4ebbe4}
select{width:100%;-webkit-appearance:none;-moz-appearance:none;-o-appearance:none;appearance:none;background:url(caret.svg) no-repeat 97% center}
label.sdpi-file-label,input[type="button"],input[type="submit"],input[type="reset"],input[type="file"],input[type=file]::-webkit-file-upload-button,button,select{color:var(--sdpi-color);border:1pt solid #303030;font-size:8pt;background-color:var(--sdpi-background);border-radius:var(--sdpi-borderradius)}
label.sdpi-file-label,input[type="button"],input[type="submit"],input[type="reset"],input[type="file"],input[type=file]::-webkit-file-upload-button,button{border:1pt solid var(--sdpi-buttonbordercolor);border-radius:var(--sdpi-borderradius);border-color:var(--sdpi-buttonbordercolor);min-height:23px !important;height:23px !important;margin-right:8px}
input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
input[type="file"]{border-radius:var(--sdpi-borderradius);max-width:220px}
option{height:1.5em;padding:4px}
.sdpi-wrapper{overflow-x:hidden;height:100%}
.sdpi-item{display:flex;flex-direction:row;min-height:32px;align-items:center;margin-top:2px;max-width:344px;-webkit-user-drag:none}
.sdpi-item:first-child{margin-top:-1px}
.sdpi-item:last-child{margin-bottom:0px}
.sdpi-item>*:not(.sdpi-item-label):not(meter):not(details):not(canvas){min-height:26px;padding:0px 4px 0px 4px}
.sdpi-item>*:not(.sdpi-item-label.empty):not(meter){min-height:26px;padding:0px 4px 0px 4px}
.sdpi-item-group{padding:0 !important}
meter.sdpi-item-value{margin-left:6px}
.sdpi-item[type="group"]{display:block;margin-top:12px;margin-bottom:12px;flex-direction:unset;text-align:left}
.sdpi-item[type="group"]>.sdpi-item-label,.sdpi-item[type="group"].sdpi-item-label{width:96%;text-align:left;font-weight:700;margin-bottom:4px;padding-left:4px}
dl,ul,ol{-webkit-margin-before:0px;-webkit-margin-after:4px;-webkit-padding-start:1em;max-height:90px;overflow-y:scroll;cursor:pointer;user-select:none}
table.sdpi-item-value,dl.sdpi-item-value,ul.sdpi-item-value,ol.sdpi-item-value{-webkit-margin-before:4px;-webkit-margin-after:8px;-webkit-padding-start:1em;width:var(--sdpi-width);text-align:center}
table>caption{margin:2px}
.list,.sdpi-item[type="list"]{align-items:baseline}
.sdpi-item-label{text-align:right;flex:none;width:94px;padding-right:4px;font-weight:600}
.win .sdpi-item-label,.sdpi-item-label>small{font-weight:normal}
.sdpi-item-label:after{content:":"}
.sdpi-item-label.empty:after{content:""}
.sdpi-test,.sdpi-item-value{flex:1 0 0;margin-right:14px;margin-left:4px;justify-content:space-evenly}
canvas.sdpi-item-value{max-width:144px;max-height:144px;width:144px;height:144px;margin:0 auto;cursor:pointer}
input.sdpi-item-value{margin-left:5px}
.sdpi-item-value button,button.sdpi-item-value{margin-left:6px;margin-right:14px}
.sdpi-item-value.range{margin-left:0px}
table,dl.sdpi-item-value,ul.sdpi-item-value,ol.sdpi-item-value,.sdpi-item-value>dl,.sdpi-item-value>ul,.sdpi-item-value>ol{list-style-type:none;list-style-position:outside;margin-left:-4px;margin-right:-4px;padding:4px;border:1px solid var(--sdpi-bordercolor)}
dl.sdpi-item-value,ul.sdpi-item-value,ol.sdpi-item-value,.sdpi-item-value>ol{list-style-type:none;list-style-position:inside;margin-left:5px;margin-right:12px;padding:4px !important;display:flex;flex-direction:column}
.two-items li{display:flex}
.two-items li>*:first-child{flex:0 0 50%;text-align:left}
.two-items.thirtyseventy li>*:first-child{flex:0 0 30%}
ol.sdpi-item-value,.sdpi-item-value>ol[listtype="none"]{list-style-type:none}
ol.sdpi-item-value[type="decimal"],.sdpi-item-value>ol[type="decimal"]{list-style-type:decimal}
ol.sdpi-item-value[type="decimal-leading-zero"],.sdpi-item-value>ol[type="decimal-leading-zero"]{list-style-type:decimal-leading-zero}
ol.sdpi-item-value[type="lower-alpha"],.sdpi-item-value>ol[type="lower-alpha"]{list-style-type:lower-alpha}
ol.sdpi-item-value[type="upper-alpha"],.sdpi-item-value>ol[type="upper-alpha"]{list-style-type:upper-alpha}
ol.sdpi-item-value[type="upper-roman"],.sdpi-item-value>ol[type="upper-roman"]{list-style-type:upper-roman}
ol.sdpi-item-value[type="lower-roman"],.sdpi-item-value>ol[type="lower-roman"]{list-style-type:upper-roman}
tr:nth-child(even),.sdpi-item-value>ul>li:nth-child(even),.sdpi-item-value>ol>li:nth-child(even),li:nth-child(even){background-color:rgba(0,0,0,.2)}
td:hover,.sdpi-item-value>ul>li:hover:nth-child(even),.sdpi-item-value>ol>li:hover:nth-child(even),li:hover:nth-child(even),li:hover{background-color:rgba(255,255,255,.1)}
td.selected,td.selected:hover,li.selected:hover,li.selected{color:white;background-color:#77f}
tr{border:1px solid var(--sdpi-bordercolor)}
td{border-right:1px solid var(--sdpi-bordercolor)}
tr:last-child,td:last-child{border:none}
.sdpi-item-value.select,.sdpi-item-value>select{margin-right:13px;margin-left:4px}
.sdpi-item-child,.sdpi-item-group>.sdpi-item>input[type="color"]{margin-top:0.4em;margin-right:4px}
.full,.full *,.sdpi-item-value.full,.sdpi-item-child>full>*,.sdpi-item-child.full,.sdpi-item-child.full>*,.full>.sdpi-item-child,.full>.sdpi-item-child>*{display:flex;flex:1 1 0;margin-bottom:4px;margin-left:0px;width:100%;justify-content:space-evenly}
.sdpi-item-group>.sdpi-item>input[type="color"]{margin-top:0px}
::-webkit-calendar-picker-indicator:focus,input[type=file]::-webkit-file-upload-button:focus,button:focus,textarea:focus,input:focus,select:focus,option:focus,details:focus,summary:focus,.custom-select select{outline:none}
summary{cursor:default;padding-left:90px;padding-right:70px}
.pointer,summary .pointer{cursor:pointer}
details *{font-size:12px;font-weight:normal;word-break:break-all;}
details.message{padding:4px 18px 4px 12px}
details.message summary{min-height:18px}
details.message:first-child{margin-top:4px;margin-left:0;padding-left:102px}
details.message h1{text-align:left}
.message>summary::-webkit-details-marker{display:none}
.info20,.question,.caution,.info{background-repeat:no-repeat;background-position:72px center}
.info20{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A")}
.info{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A")}
.info2{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A")}
.sdpi-more-info{background-image:linear-gradient(to right,#00000000 0%,#00000040 80%),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A")}
.caution{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A")}
.question{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A")}
.sdpi-more-info{position:fixed;left:0px;right:0px;bottom:0px;min-height:16px;padding-right:16px;text-align:right;-webkit-touch-callout:none;cursor:pointer;user-select:none;background-position:right center;background-repeat:no-repeat;border-radius:var(--sdpi-borderradius);text-decoration:none;color:var(--sdpi-color)}
.sdpi-more-info-button{display:flex;align-self:right;margin-left:auto;position:fixed;right:17px;bottom:0px;user-select:none}
.sdpi-bottom-bar{display:flex;align-self:right;margin-left:auto;position:fixed;right:17px;bottom:0px;user-select:none}
.sdpi-bottom-bar.right{right:0px}
.sdpi-bottom-bar button{min-height:20px !important;height:20px !important}
details a{background-position:right !important;min-height:24px;display:inline-block;line-height:24px;padding-right:28px}
input:not([type="range"]),textarea{-webkit-appearance:none;appearance:none;background:var(--sdpi-background);color:var(--sdpi-color);font-weight:normal;font-size:9pt;border:none;margin-top:2px;margin-bottom:2px;min-width:219px}
textarea+label{display:flex;justify-content:flex-end}
input[type="radio"],input[type="checkbox"]{display:none}
input[type="radio"]+label,input[type="checkbox"]+label{font-size:9pt;color:var(--sdpi-color);font-weight:normal;margin-right:8px}
input[type="radio"]+label:after,input[type="checkbox"]+label:after{content:" " !important}
.sdpi-item[type="radio"]>.sdpi-item-value,.sdpi-item[type="checkbox"]>.sdpi-item-value{padding-top:2px}
.sdpi-item[type="checkbox"]>.sdpi-item-value>*{margin-top:4px}
.sdpi-item[type="checkbox"] .sdpi-item-child,.sdpi-item[type="radio"] .sdpi-item-child{display:inline-block}
.sdpi-item[type="range"] .sdpi-item-value,.sdpi-item[type="meter"] .sdpi-item-child,.sdpi-item[type="progress"] .sdpi-item-child{display:flex}
.sdpi-item[type="range"] .sdpi-item-value{min-height:26px}
.sdpi-item[type="range"] .sdpi-item-value span,.sdpi-item[type="meter"] .sdpi-item-child span,.sdpi-item[type="progress"] .sdpi-item-child span{margin-top:-2px;min-width:8px;text-align:right;user-select:none;cursor:pointer;-webkit-user-select:none;user-select:none}
.sdpi-item[type="range"] .sdpi-item-value span{margin-top:7px;text-align:right}
span+input[type="range"]{display:flex;max-width:168px}
.sdpi-item[type="range"] .sdpi-item-value span:first-child,.sdpi-item[type="meter"] .sdpi-item-child span:first-child,.sdpi-item[type="progress"] .sdpi-item-child span:first-child{margin-right:4px}
.sdpi-item[type="range"] .sdpi-item-value span:last-child,.sdpi-item[type="meter"] .sdpi-item-child span:last-child,.sdpi-item[type="progress"] .sdpi-item-child span:last-child{margin-left:4px}
.reverse{transform:rotate(180deg)}
.sdpi-item[type="meter"] .sdpi-item-child meter+span:last-child{margin-left:-10px}
.sdpi-item[type="progress"] .sdpi-item-child meter+span:last-child{margin-left:-14px}
.sdpi-item[type="radio"]>.sdpi-item-value>*{margin-top:2px}
details{padding:8px 18px 8px 12px;min-width:86px}
details>h4{border-bottom:1px solid var(--sdpi-bordercolor)}
legend{display:none}
.sdpi-item-value>textarea{padding:0px;width:219px;margin-left:1px;margin-top:3px;padding:4px}
input[type="radio"]+label span,input[type="checkbox"]+label span{display:inline-block;width:16px;height:16px;margin:2px 4px 2px 0;border-radius:3px;vertical-align:middle;background:var(--sdpi-background);cursor:pointer;border:1px solid rgb(0,0,0,.2)}
input[type="radio"]+label span{border-radius:100%}
input[type="radio"]:checked+label span,input[type="checkbox"]:checked+label span{background-color:#77f;background-image:url(check.svg);background-repeat:no-repeat;background-position:center center;border:1px solid rgb(0,0,0,.4)}
input[type="radio"]:active:checked+label span,input[type="radio"]:active+label span,input[type="checkbox"]:active:checked+label span,input[type="checkbox"]:active+label span{background-color:#303030}
input[type="radio"]:checked+label span{background-image:url(rcheck.svg)}
input[type="range"]{width:var(--sdpi-width);height:30px;overflow:hidden;cursor:pointer;background:transparent !important}
.sdpi-item>input[type="range"]{margin-left:2px;max-width:var(--sdpi-width);width:var(--sdpi-width);padding:0px;margin-top:2px}
input[type="range"]::-webkit-slider-runnable-track{height:5px;background:#979797;border-radius:3px;padding:0px !important;border:1px solid var(--sdpi-background)}
input[type="range"]::-webkit-slider-thumb{position:relative;-webkit-appearance:none;background-color:var(--sdpi-color);width:12px;height:12px;border-radius:20px;margin-top:-5px;border:none}
input[type="range" i]{margin:0}
input[type="range"]::-webkit-slider-thumb::before{position:absolute;content:"";height:5px;width:500px;left:-502px;top:8px;background:#77f}
input[type="color"]{min-width:32px;min-height:32px;width:32px;height:32px;padding:0;background-color:var(--sdpi-bgcolor);flex:none}
::-webkit-color-swatch{min-width:24px}
textarea{height:3em;word-break:break-word;line-height:1.5em}
.textarea{padding:0px !important}
textarea{width:219px;height:96%;min-height:6em;resize:none;border-radius:var(--sdpi-borderradius)}
.sdpi-item.card-carousel-wrapper,.sdpi-item>.card-carousel-wrapper{padding:0}
.card-carousel-wrapper{display:flex;align-items:center;justify-content:center;margin:12px auto;color:#666a73}
.card-carousel{display:flex;justify-content:center;width:278px}
.card-carousel--overflow-container{overflow:hidden}
.card-carousel--nav__left,.card-carousel--nav__right{width:12px;height:12px;border-top:2px solid #42b883;border-right:2px solid #42b883;cursor:pointer;margin:0 4px;transition:transform 150ms linear}
.card-carousel--nav__left[disabled],.card-carousel--nav__right[disabled]{opacity:0.2;border-color:black}
.card-carousel--nav__left{transform:rotate(-135deg)}
.card-carousel--nav__left:active{transform:rotate(-135deg) scale(0.85)}
.card-carousel--nav__right{transform:rotate(45deg)}
.card-carousel--nav__right:active{transform:rotate(45deg) scale(0.85)}
.card-carousel-cards{display:flex;transition:transform 150ms ease-out;transform:translatex(0px)}
.card-carousel-cards .card-carousel--card{margin:0 5px;cursor:pointer;background-color:#fff;border-radius:4px;z-index:3}
.xxcard-carousel-cards .card-carousel--card:first-child{margin-left:0}
.xxcard-carousel-cards .card-carousel--card:last-child{margin-right:0}
.card-carousel-cards .card-carousel--card img{vertical-align:bottom;border-top-left-radius:4px;border-top-right-radius:4px;transition:opacity 150ms linear;width:60px}
.card-carousel-cards .card-carousel--card img:hover{opacity:0.5}
.card-carousel-cards .card-carousel--card--footer{border-top:0;max-width:80px;overflow:hidden;display:flex;height:100%;flex-direction:column}
.card-carousel-cards .card-carousel--card--footer p{padding:3px 0;margin:0;margin-bottom:2px;font-size:15px;font-weight:500;color:#2c3e50}
.card-carousel-cards .card-carousel--card--footer p:nth-of-type(2){font-size:12px;font-weight:300;padding:6px;color:#666a73}
h1{font-size:1.3em;font-weight:500;text-align:center;margin-bottom:12px}
::-webkit-datetime-edit{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";background:url(elg_calendar_inv.svg) no-repeat left center;padding-right:1em;padding-left:25px;background-position:4px 0px}
::-webkit-calendar-picker-indicator{background:transparent;font-size:17px}
::-webkit-calendar-picker-indicator:focus{background-color:rgba(0,0,0,0.2)}
input[type="date"]{-webkit-align-items:center;align-items:center;display:-webkit-inline-flex;font-family:monospace;overflow:hidden;padding:0;-webkit-padding-start:1px}
input::-webkit-datetime-edit{-webkit-flex:1;-webkit-user-modify:read-only !important;display:inline-block;min-width:0;overflow:hidden}
input[type="file"]{opacity:0;display:none}
.sdpi-item>input[type="file"]{opacity:1;display:flex}
input[type="file"]+span{display:flex;flex:0 1 auto;background-color:#0000ff50}
label.sdpi-file-label{cursor:pointer;user-select:none;display:inline-block;min-height:21px !important;height:21px !important;line-height:20px;padding:0px 4px;margin:auto;margin-right:0px}
.sdpi-file-label>label:active,.sdpi-file-label.file:active,label.sdpi-file-label:active,label.sdpi-file-info:active,input[type="file"]::-webkit-file-upload-button:active,button:active{background-color:var(--sdpi-color);color:#303030}
input:required:invalid,input:focus:invalid{background:var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center}
input:required:valid{background:var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center}
.tooltip,:tooltip,:title{color:yellow}
.sdpi-item-group.file{width:232px;display:flex;align-items:center}
.sdpi-file-info{overflow-wrap:break-word;word-wrap:break-word;hyphens:auto;min-width:132px;max-width:144px;max-height:32px;margin-top:0px;margin-left:5px;display:inline-block;overflow:hidden;padding:6px 4px;background-color:var(--sdpi-background)}
::-webkit-scrollbar{width:8px}
::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3)}
::-webkit-scrollbar-thumb{background-color:#999999;outline:1px solid slategrey;border-radius:8px}
a{color:#7397d2}
.testcontainer{display:flex;background-color:#0000ff20;max-width:400px;height:200px;align-content:space-evenly}
input[type=range]{-webkit-appearance:none;appearance:none;height:6px;margin-top:12px;z-index:0;overflow:visible}
:-webkit-slider-thumb{-webkit-appearance:none;background-color:var(--sdpi-color);width:16px;height:16px;border-radius:20px;margin-top:-6px;border:1px solid #999999}
.sdpi-item[type="range"] .sdpi-item-group{display:flex;flex-direction:column}
.xxsdpi-item[type="range"] .sdpi-item-group input{max-width:204px}
.sdpi-item[type="range"] .sdpi-item-group span{margin-left:0px !important}
.sdpi-item[type="range"] .sdpi-item-group>.sdpi-item-child{display:flex;flex-direction:row}
.rangeLabel{position:absolute;font-weight:normal;margin-top:22px}
:disabled{color:#993333}
select,select option{color:var(--sdpi-color)}
select.disabled,select option:disabled{color:#fd9494;font-style:italic}
.runningAppsContainer{display:none}
.one-line{min-height:1.5em}
.two-lines{min-height:3em}
.three-lines{min-height:4.5em}
.four-lines{min-height:6em}
.min80>.sdpi-item-child{min-width:80px}
.min100>.sdpi-item-child{min-width:100px}
.min120>.sdpi-item-child{min-width:120px}
.min140>.sdpi-item-child{min-width:140px}
.min160>.sdpi-item-child{min-width:160px}
.min200>.sdpi-item-child{min-width:200px}
.max40{flex-basis:40%;flex-grow:0}
.max30{flex-basis:30%;flex-grow:0}
.max20{flex-basis:20%;flex-grow:0}
.up20{margin-top:-20px}
.alignCenter{align-items:center}
.alignTop{align-items:flex-start}
.alignBaseline{align-items:baseline}
.noMargins,.noMargins *,.noInnerMargins *{margin:0;padding:0}
.hidden{display:none}
.icon-help,.icon-help-line,.icon-help-fill,.icon-help-inv,.icon-brighter,.icon-darker,.icon-warmer,.icon-cooler{min-width:20px;width:20px;background-repeat:no-repeat;opacity:1}
.icon-help:active,.icon-help-line:active,.icon-help-fill:active,.icon-help-inv:active,.icon-brighter:active,.icon-darker:active,.icon-warmer:active,.icon-cooler:active{opacity:0.5}
.icon-brighter,.icon-darker,.icon-warmer,.icon-cooler{margin-top:5px !important}
.icon-help,.icon-help-line,.icon-help-fill,.icon-help-inv{cursor:pointer;margin:0px;margin-left:4px}
.icon-brighter{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E")}
.icon-darker{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E")}
.icon-warmer{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E")}
.icon-cooler{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E")}
.icon-help{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' d='M11.292 12.516l.022 1.782H9.07v-1.804c0-1.98 1.276-2.574 2.662-3.278h-.022c.814-.44 1.65-.88 1.694-2.2.044-1.386-1.122-2.728-3.234-2.728-1.518 0-2.662.902-3.366 2.354L5 5.608C5.946 3.584 7.662 2 10.17 2c3.564 0 5.632 2.442 5.588 5.06-.066 2.618-1.716 3.41-3.102 4.158-.704.374-1.364.682-1.364 1.298zm-1.122 2.442c.858 0 1.452.594 1.452 1.452 0 .682-.594 1.408-1.452 1.408-.77 0-1.386-.726-1.386-1.408 0-.858.616-1.452 1.386-1.452z'/%3E%3C/svg%3E")}
.icon-help-line{background-image:url("data:image/svg+xml,%3Csvg width='20' height='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-1a9 9 0 1 0 0-18 9 9 0 0 0 0 18z'/%3E%3Cpath d='M10.848 12.307l.02 1.578H8.784v-1.597c0-1.753 1.186-2.278 2.474-2.901h-.02c.756-.39 1.533-.78 1.574-1.948.041-1.226-1.043-2.414-3.006-2.414-1.41 0-2.474.798-3.128 2.083L5 6.193C5.88 4.402 7.474 3 9.805 3 13.118 3 15.04 5.161 15 7.478c-.061 2.318-1.595 3.019-2.883 3.68-.654.332-1.268.604-1.268 1.15zM9.805 14.47c.798 0 1.35.525 1.35 1.285 0 .603-.552 1.246-1.35 1.246-.715 0-1.288-.643-1.288-1.246 0-.76.573-1.285 1.288-1.285z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E")}
.icon-help-fill{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23999'/%3E%3Cpath fill='%23FFF' fill-rule='nonzero' d='M8.368 7.189H5C5 3.5 7.668 2 10.292 2 13.966 2 16 4.076 16 7.012c0 3.754-3.849 3.136-3.849 5.211v1.656H8.455v-1.832c0-2.164 1.4-2.893 2.778-3.6.437-.242 1.006-.574 1.006-1.236 0-2.208-3.871-2.142-3.871-.022zM10.25 18a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5z'/%3E%3C/g%3E%3C/svg%3E")}
.icon-help-inv{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zM8.368 7.189c0-2.12 3.87-2.186 3.87.022 0 .662-.568.994-1.005 1.236-1.378.707-2.778 1.436-2.778 3.6v1.832h3.696v-1.656c0-2.075 3.849-1.457 3.849-5.21C16 4.075 13.966 2 10.292 2 7.668 2 5 3.501 5 7.189h3.368zM10.25 18a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z'/%3E%3C/svg%3E")}
.kelvin::after{content:"K"}
.mired::after{content:" Mired"}
.percent::after{content:"%"}
.sdpi-item-value+.icon-cooler,.sdpi-item-value+.icon-warmer{margin-left:0px !important;margin-top:15px !important}
input[type="range"].colorbrightness::-webkit-slider-runnable-track,input[type="range"].colortemperature::-webkit-slider-runnable-track{height:8px;background:#979797;border-radius:4px;background-image:linear-gradient(to right,#94d0ec,#ffb165)}
input[type="range"].colorbrightness::-webkit-slider-runnable-track{background-color:#efefef;background-image:linear-gradient(to right,black,rgba(0,0,0,0))}
input[type="range"].colorbrightness::-webkit-slider-thumb,input[type="range"].colortemperature::-webkit-slider-thumb{width:16px;height:16px;border-radius:20px;margin-top:-5px;background-color:#86c6e8;box-shadow:0px 0px 1px #000000;border:1px solid #d8d8d8}
.sdpi-info-label{display:inline-block;user-select:none;position:absolute;height:15px;width:auto;text-align:center;border-radius:4px;min-width:44px;max-width:80px;background:white;font-size:11px;color:black;z-index:1000;box-shadow:0px 0px 12px rgba(0,0,0,.8);padding:2px}
.sdpi-info-label.hidden{opacity:0;transition:opacity 0.25s linear}
.sdpi-info-label.shown{position:absolute;opacity:1;transition:opacity 0.25s ease-out}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,46 @@
const path = require('path');
const fs = require('fs-extra');
console.log('开始执行自动化构建...');
const currentDir = __dirname;
// 获取父文件夹的路径
const parentDir = path.join(currentDir, '..');
// 获取父文件夹的名称
const PluginName = path.basename(parentDir);
const PluginPath = path.join(process.env.APPDATA, 'HotSpot/StreamDock/plugins', PluginName);
try {
// 删除旧的插件目录
fs.removeSync(PluginPath);
// 确保目标目录存在
fs.ensureDirSync(path.dirname(PluginPath));
// 复制当前目录到目标路径,排除 node_modules
fs.copySync(path.resolve(__dirname, '..'), PluginPath, {
filter: (src) => {
const relativePath = path.relative(path.resolve(__dirname, '..'), src);
// 排除 'node_modules' 和 '.git' 目录及其子文件
return !relativePath.startsWith('plugin\\node_modules')
&&!relativePath.startsWith('plugin\\index.js')
&&!relativePath.startsWith('plugin\\package.json')
&&!relativePath.startsWith('plugin\\package-lock.json')
&&!relativePath.startsWith('plugin\\yarn.lock')
&&!relativePath.startsWith('plugin\\build')
&&!relativePath.startsWith('plugin\\log')
&&!relativePath.startsWith('.git')
&&!relativePath.startsWith('.vscode');
}
});
fs.copySync( path.join(__dirname, "build"), path.join(PluginPath,'plugin'))
console.log(`插件 "${PluginName}" 已成功复制到 "${PluginPath}"`);
console.log('构建成功-------------');
} catch (err) {
console.error(`复制出错 "${PluginName}":`, err);
}

View File

@ -0,0 +1,19 @@
const { Plugins, Actions, log } = require('./utils/plugin');
const plugin = new Plugins();
// 操作一
plugin.action1 = new Actions({
default: {},
_willAppear({ context }) {
plugin.setTitle(context, "Hello world!");
},
_willDisappear(data) { },
_propertyInspectorDidAppear(data) { },
dialRotate(data) {//旋钮旋转
log.info(data);
},
dialDown(data) {//旋钮按下
log.info(data);
}
});

View File

@ -0,0 +1,139 @@
// 配置日志文件
const now = new Date();
const log = require('log4js').configure({
appenders: {
file: { type: 'file', filename: `./log/${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}.log` }
},
categories: {
default: { appenders: ['file'], level: 'info' }
}
}).getLogger();
// 主线程错误处理
process.on('uncaughtException', (error) => {
log.error('Uncaught Exception:', error);
});
process.on('unhandledRejection', (reason) => {
log.error('Unhandled Rejection:', reason);
});
// 插件类
const ws = require('ws');
class Plugins {
static language = process.argv[5];
constructor() {
if (Plugins.instance) {
return Plugins.instance;
}
this.ws = new ws("ws://127.0.0.1:" + process.argv[2]);
this.ws.on('open', () => this.ws.send(JSON.stringify({ uuid: process.argv[3], event: process.argv[4] })));
this.ws.on('close', process.exit);
this.ws.on('message', e => {
const data = JSON.parse(e.toString());
const action = data.action?.split('.').pop();
this[action]?.[data.event]?.(data);
this[data.event]?.(data);
});
Plugins.instance = this;
}
// 设置标题
setTitle(context, str, row = 0, num = 6) {
let newStr = '';
if (row) {
let nowRow = 1, strArr = str.split('');
strArr.forEach((item, index) => {
if (nowRow < row && index >= nowRow * num) { nowRow++; newStr += '\n'; }
if (nowRow <= row && index < nowRow * num) { newStr += item; }
});
if (strArr.length > row * num) { newStr = newStr.substring(0, newStr.length - 1); newStr += '..'; }
}
this.ws.send(JSON.stringify({
event: "setTitle",
context, payload: {
target: 0,
title: newStr || str
}
}));
}
// 设置背景
setImage(context, url) {
const image = new Image();
image.src = url; image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
this.ws.send(JSON.stringify({
event: "setImage",
context, payload: {
target: 0,
image: canvas.toDataURL("image/png")
}
}));
};
}
// 设置状态
setState(context, state) {
this.ws.send(JSON.stringify({
event: "setState",
context, payload: { state }
}));
}
// 保存持久化数据
setSettings(context, payload) {
this.ws.send(JSON.stringify({
event: "setSettings",
context, payload
}));
}
// 发送给属性检测器
sendToPropertyInspector(payload) {
this.ws.send(JSON.stringify({
action: Actions.currentAction,
context: Actions.currentContext,
payload, event: "sendToPropertyInspector"
}));
}
// 用默认浏览器打开网页
openUrl(url) {
this.ws.send(JSON.stringify({
event: "openUrl",
payload: { url }
}));
}
};
// 操作类
class Actions {
constructor(data) {
this.data = {};
this.default = {};
Object.assign(this, data);
}
// 属性检查器显示时
static currentAction = null;
static currentContext = null;
propertyInspectorDidAppear(data) {
Actions.currentAction = data.action;
Actions.currentContext = data.context;
this._propertyInspectorDidAppear?.(data);
}
// 初始化数据
willAppear(data) {
const { context, payload: { settings } } = data;
this.data[context] = Object.assign({ ...this.default }, settings);
this._willAppear?.(data);
}
// 行动销毁
willDisappear(data) {
this._willDisappear?.(data);
delete this.data[data.context];
}
}
module.exports = {
log,
Plugins,
Actions,
};

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>模板 - 属性检查器</title>
<link rel="stylesheet" href="../../static/css/sdpi.css">
</head>
<body>
<div class="sdpi-wrapper" style="display: none">
<div class="sdpi-heading">模板</div>
</div>
<script src="../utils/common.js"></script>
<script src="../utils/action.js"></script>
<script src="index.js"></script>
</body>
</html>

View File

@ -0,0 +1,14 @@
/// <reference path="../utils/common.js" />
/// <reference path="../utils/action.js" />
// $local 是否国际化
// $back 是否自行决定回显时机
// $dom 获取文档元素 - 不是动态的都写在这里面
const $local = false, $back = false, $dom = {
main: $('.sdpi-wrapper')
};
const $propEvent = {
didReceiveSettings(data) { },
sendToPropertyInspector(data) { }
};

View File

@ -0,0 +1,131 @@
/**
* PropertyInspector 2.5.0 新特性 =>
*
* 1 => 工具与主文件相分离 - 按需引入
* 2 => $settings - 全局持久化数据代理
* 3 => 无需关注上下文 - 随时随地与插件通信
* 4 => 注意事项: 为了避免命名冲突请勿使用 $ 相关的名称以及JQuery库
*
* ===== CJHONG ========================================== 2023.10.10 =====>
*/
let $websocket, $uuid, $action, $context, $settings, $lang, $FileID = '';
// 与插件通信
WebSocket.prototype.sendToPlugin = function (payload) {
this.send(JSON.stringify({
event: "sendToPlugin",
action: $action,
context: $uuid,
payload
}));
};
// 设置状态
WebSocket.prototype.setState = function (state) {
this.send(JSON.stringify({
event: "setState",
context: $context,
payload: { state }
}));
};
// 设置背景
WebSocket.prototype.setImage = function (url) {
let image = new Image();
image.src = url;
image.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
this.send(JSON.stringify({
event: "setImage",
context: $context,
payload: {
target: 0,
image: canvas.toDataURL("image/png")
}
}));
};
};
// 打开网页
WebSocket.prototype.openUrl = function (url) {
this.send(JSON.stringify({
event: "openUrl",
payload: { url }
}));
};
// 保存持久化数据
WebSocket.prototype.saveData = $.debounce(function (payload) {
this.send(JSON.stringify({
event: "setSettings",
context: $uuid,
payload
}));
});
// StreamDock 软件入口函数
async function connectElgatoStreamDeckSocket(port, uuid, event, app, info) {
info = JSON.parse(info);
$uuid = uuid; $action = info.action;
$context = info.context;
$websocket = new WebSocket('ws://127.0.0.1:' + port);
$websocket.onopen = () => $websocket.send(JSON.stringify({ event, uuid }));
// 持久数据代理
$websocket.onmessage = e => {
let data = JSON.parse(e.data);
if (data.event === 'didReceiveSettings') {
$settings = new Proxy(data.payload.settings, {
get(target, property) {
return target[property];
},
set(target, property, value) {
target[property] = value;
$websocket.saveData(data.payload.settings);
}
});
if (!$back) $dom.main.style.display = 'block';
}
$propEvent[data.event]?.(data.payload);
};
// 自动翻译页面
if (!$local) return;
$lang = await new Promise(resolve => {
const req = new XMLHttpRequest();
req.open('GET', `../../${JSON.parse(app).application.language}.json`);
req.send();
req.onreadystatechange = () => {
if (req.readyState === 4) {
resolve(JSON.parse(req.responseText).Localization);
}
};
});
// 遍历文本节点并翻译所有文本节点
const walker = document.createTreeWalker($dom.main, NodeFilter.SHOW_TEXT, (e) => {
return e.data.trim() && NodeFilter.FILTER_ACCEPT;
});
while (walker.nextNode()) {
console.log(walker.currentNode.data);
walker.currentNode.data = $lang[walker.currentNode.data];
}
// placeholder 特殊处理
const translate = item => {
if (item.placeholder?.trim()) {
console.log(item.placeholder);
item.placeholder = $lang[item.placeholder];
}
};
$('input', true).forEach(translate);
$('textarea', true).forEach(translate);
}
// StreamDock 文件路径回调
Array.from($('input[type="file"]', true)).forEach(item => item.addEventListener('click', () => $FileID = item.id));
const onFilePickerReturn = (url) => $emit.send(`File-${$FileID}`, JSON.parse(url));

View File

@ -0,0 +1,81 @@
// 自定义事件类
class EventPlus {
constructor() {
this.event = new EventTarget();
}
on(name, callback) {
this.event.addEventListener(name, e => callback(e.detail));
}
send(name, data) {
this.event.dispatchEvent(new CustomEvent(name, {
detail: data,
bubbles: false,
cancelable: false
}));
}
}
// 补零
String.prototype.fill = function () {
return this >= 10 ? this : '0' + this;
};
// unicode编码转换字符串
String.prototype.uTs = function () {
return eval('"' + Array.from(this).join('') + '"');
};
// 字符串转换unicode编码
String.prototype.sTu = function (str = '') {
Array.from(this).forEach(item => str += `\\u${item.charCodeAt(0).toString(16)}`);
return str;
};
// 全局变量/方法
const $emit = new EventPlus(), $ = (selector, isAll = false) => {
const element = document.querySelector(selector), methods = {
on: function (event, callback) {
this.addEventListener(event, callback);
},
attr: function (name, value = '') {
value && this.setAttribute(name, value);
return this;
}
};
if (!isAll && element) {
return Object.assign(element, methods);
} else if (!isAll && !element) {
throw `HTML没有 ${selector} 元素! 请检查是否拼写错误`;
}
return Array.from(document.querySelectorAll(selector)).map(item => Object.assign(item, methods));
};
// 节流函数
$.throttle = (fn, delay) => {
let Timer = null;
return function () {
if (Timer) return;
Timer = setTimeout(() => {
fn.apply(this, arguments);
Timer = null;
}, delay);
};
};
// 防抖函数
$.debounce = (fn, delay) => {
let Timer = null;
return function () {
clearTimeout(Timer);
Timer = setTimeout(() => fn.apply(this, arguments), delay);
};
};
// 绑定限制数字方法
Array.from($('input[type="num"]', true)).forEach(item => {
item.addEventListener('input', function limitNum() {
if (!item.value || /^\d+$/.test(item.value)) return;
item.value = item.value.slice(0, -1);
limitNum(item);
});
});

Some files were not shown because too many files have changed in this diff Show More