Init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/Binaries
|
||||
/.vs
|
||||
/.idea
|
||||
/Intermediate
|
||||
/Saved
|
||||
13
.vsconfig
Normal file
13
.vsconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.Net.Component.4.6.2.TargetingPack",
|
||||
"Microsoft.VisualStudio.Component.VC.14.36.17.6.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.22000",
|
||||
"Microsoft.VisualStudio.Workload.CoreEditor",
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"Microsoft.VisualStudio.Workload.NativeGame"
|
||||
]
|
||||
}
|
||||
3
Arona.sln.DotSettings.user
Normal file
3
Arona.sln.DotSettings.user
Normal file
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003Acstdlib_002Fl_003AH_0021_003FVisual_0020Studio_003FVC_003FTools_003FMSVC_003F14_002E38_002E33130_003Finclude_003Fcstdlib/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003Avolk_002Ec_002Fl_003AF_0021_003FUnreal_0020Engine_003FUnreal_0020Engine_0020Source_003FEngine_003FSource_003FThirdParty_003FVulkan_003FInclude_003FVolk_003Fvolk_002Ec_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
18
Arona.uproject
Normal file
18
Arona.uproject
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"EngineAssociation": "{15DECA3A-4D95-597C-AAD4-38A8F659756E}",
|
||||
"Category": "",
|
||||
"Description": "",
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "Arona",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "AronaModule",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
Config/AronaSettings.ini
Normal file
2
Config/AronaSettings.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[Skin]
|
||||
Skin=Default
|
||||
31
Content/Shader/Arona/D3D/Test.hlsl
Normal file
31
Content/Shader/Arona/D3D/Test.hlsl
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
// Texture2D WaveformTexture;
|
||||
// SamplerState WaveformTextureSampler;
|
||||
|
||||
struct VertexOut
|
||||
{
|
||||
float4 Position : SV_POSITION;
|
||||
float4 Color : COLOR0;
|
||||
float4 SecondaryColor : COLOR1;
|
||||
float4 TextureCoordinates : TEXCOORD0;
|
||||
};
|
||||
|
||||
|
||||
float4 Main(VertexOut InVertex) : SV_Target
|
||||
{
|
||||
// float4 WaveformData = WaveformTexture.Sample(WaveformTextureSampler, InVertex.TextureCoordinates.xy * InVertex.TextureCoordinates.zw);
|
||||
|
||||
// float4 Color;
|
||||
// float Top = WaveformData.r;
|
||||
// float Buttom = WaveformData.g;
|
||||
|
||||
// if(InVertex.TextureCoordinates.y >= Buttom && InVertex.TextureCoordinates.y <= Top)
|
||||
// {
|
||||
// Color = float4(0, 1, 0, 1);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Color = float4(0, 0, 0, 0);
|
||||
// }
|
||||
return InVertex.TextureCoordinates;
|
||||
}
|
||||
42
Content/Shader/Arona/D3D/WaveformCS.hlsl
Normal file
42
Content/Shader/Arona/D3D/WaveformCS.hlsl
Normal file
@@ -0,0 +1,42 @@
|
||||
RWTexture2D<float4> Result;
|
||||
RWStructuredBuffer<float> Samples;
|
||||
cbuffer Params : register(b0)
|
||||
{
|
||||
float4 WaveColor;
|
||||
float4 BgColor;
|
||||
float LineUV;
|
||||
};
|
||||
|
||||
[numthreads(1, 1, 1)]
|
||||
void Main(uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
uint width, height;
|
||||
Result.GetDimensions(width, height);
|
||||
|
||||
// float2 uv = float2(id.xy / float2(width, height));
|
||||
// uv.y = abs(((1.0 - uv.y) - 0.5) * 2); // 居中
|
||||
|
||||
int X = id.x;
|
||||
int Y = id.y;
|
||||
|
||||
// float4 color = lerp(BgColor, WaveColor, min(step(value.x, uv.y), step(uv.y , value.y)));
|
||||
|
||||
float Top = Samples[X * 2] + 1; // -1;
|
||||
float Bottom = Samples[X * 2 + 1] + 1; // 1;
|
||||
Top = min(Top, 1);
|
||||
Bottom = max(Bottom, 0);
|
||||
Top *= height;
|
||||
Top *= 0.5;
|
||||
Bottom *= height;
|
||||
Bottom *= 0.5;
|
||||
|
||||
|
||||
if ((id.y <= Bottom && id.y >= Top) || (Y == height / 2))
|
||||
{
|
||||
Result[id.xy] = WaveColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result[id.xy] = BgColor;
|
||||
}
|
||||
}
|
||||
23
Content/Shader/Arona/OpenGL/Test.glsl
Normal file
23
Content/Shader/Arona/OpenGL/Test.glsl
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
// handle differences between ES and full GL shaders
|
||||
#if PLATFORM_USES_GLES
|
||||
precision highp float;
|
||||
#else
|
||||
// #version 120 at the beginning is added in FSlateOpenGLShader::CompileShader()
|
||||
#extension GL_EXT_gpu_shader4 : enable
|
||||
#endif
|
||||
|
||||
// uniform sampler2D WaveformTexture;
|
||||
|
||||
in vec4 Position;
|
||||
in vec4 TexCoords;
|
||||
in vec4 Color;
|
||||
in vec4 SecondaryColor;
|
||||
out vec4 fragColor;
|
||||
void main()
|
||||
{
|
||||
vec4 OutColor = vec4(0.262,1.000,0.217,1.000);
|
||||
// gl_FragColor = texture2D(WaveformTexture, TexCoords.xy);
|
||||
fragColor = TexCoords;
|
||||
}
|
||||
56
Content/Shader/Arona/OpenGL/WaveformCS.glsl
Normal file
56
Content/Shader/Arona/OpenGL/WaveformCS.glsl
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
// handle differences between ES and full GL shaders
|
||||
#if PLATFORM_USES_GLES
|
||||
precision highp float;
|
||||
#else
|
||||
// #version 120 at the beginning is added in FSlateOpenGLShader::CompileShader()
|
||||
#extension GL_EXT_gpu_shader4 : enable
|
||||
#endif
|
||||
layout (local_size_x = 1,local_size_y = 1,local_size_z = 1) in;
|
||||
|
||||
vec4 lerp(vec4 a, vec4 b, float x)
|
||||
{
|
||||
return a + x * (b - a);
|
||||
}
|
||||
|
||||
|
||||
layout (rgba8, binding = 2) writeonly uniform image2D Result;
|
||||
layout (std430, binding = 0) buffer Samples
|
||||
{
|
||||
float AudioSamples[];
|
||||
};
|
||||
layout (std430, binding = 1) buffer Params
|
||||
{
|
||||
vec4 WaveColor;
|
||||
vec4 BgColor;
|
||||
float LineUV;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 size = vec2(imageSize(Result));
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
int X = pos.x;
|
||||
int Y = pos.y;
|
||||
|
||||
float height = size.y;
|
||||
float Top = AudioSamples[X * 2] + 1; // -1;
|
||||
float Bottom = AudioSamples[X * 2 + 1] + 1; // 1;
|
||||
Top = min(Top, 1);
|
||||
Bottom = max(Bottom, 0);
|
||||
Top *= height;
|
||||
Top *= 0.5;
|
||||
Bottom *= height;
|
||||
Bottom *= 0.5;
|
||||
|
||||
if ((Y <= Bottom && Y >= Top) || (Y == int(height / 2)))
|
||||
{
|
||||
imageStore(Result, pos, WaveColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageStore(Result, pos, BgColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
half3 LinearTo709Branchless(half3 lin)
|
||||
{
|
||||
lin = max(6.10352e-5, lin); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
|
||||
return min(lin * 4.5, pow(max(lin, 0.018), 0.45) * 1.099 - 0.099);
|
||||
}
|
||||
|
||||
half3 LinearToSrgbBranchless(half3 lin)
|
||||
{
|
||||
lin = max(6.10352e-5, lin); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
|
||||
return min(lin * 12.92, pow(max(lin, 0.00313067), 1.0/2.4) * 1.055 - 0.055);
|
||||
// Possible that mobile GPUs might have native pow() function?
|
||||
//return min(lin * 12.92, exp2(log2(max(lin, 0.00313067)) * (1.0/2.4) + log2(1.055)) - 0.055);
|
||||
}
|
||||
|
||||
half LinearToSrgbBranchingChannel(half lin)
|
||||
{
|
||||
if(lin < 0.00313067) return lin * 12.92;
|
||||
return pow(lin, (1.0/2.4)) * 1.055 - 0.055;
|
||||
}
|
||||
|
||||
half3 LinearToSrgbBranching(half3 lin)
|
||||
{
|
||||
return half3(
|
||||
LinearToSrgbBranchingChannel(lin.r),
|
||||
LinearToSrgbBranchingChannel(lin.g),
|
||||
LinearToSrgbBranchingChannel(lin.b));
|
||||
}
|
||||
|
||||
half3 sRGBToLinear( half3 Color )
|
||||
{
|
||||
Color = max(6.10352e-5, Color); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
|
||||
return Color > 0.04045 ? pow( Color * (1.0 / 1.055) + 0.0521327, 2.4 ) : Color * (1.0 / 12.92);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GammaCurveRatio The curve ratio compared to a 2.2 standard gamma, e.g. 2.2 / DisplayGamma. So normally the value is 1.
|
||||
*/
|
||||
half3 ApplyGammaCorrection(float3 LinearColor, float GammaCurveRatio)
|
||||
{
|
||||
// Apply "gamma" curve adjustment.
|
||||
float3 CorrectedColor = pow(LinearColor, GammaCurveRatio);
|
||||
|
||||
#if MAC
|
||||
// Note, MacOSX native output is raw gamma 2.2 not sRGB!
|
||||
CorrectedColor = pow(CorrectedColor, 1.0/2.2);
|
||||
#else
|
||||
#if USE_709
|
||||
// Didn't profile yet if the branching version would be faster (different linear segment).
|
||||
CorrectedColor = LinearTo709Branchless(CorrectedColor);
|
||||
#else
|
||||
CorrectedColor = LinearToSrgbBranching(CorrectedColor);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return CorrectedColor;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "GammaCorrectionCommon.hlsl"
|
||||
|
||||
cbuffer PerElementVSConstants
|
||||
{
|
||||
matrix WorldViewProjection;
|
||||
}
|
||||
|
||||
struct VertexOut
|
||||
{
|
||||
float4 Position : SV_POSITION;
|
||||
float4 Color : COLOR0;
|
||||
float4 SecondaryColor : COLOR1;
|
||||
float4 TextureCoordinates : TEXCOORD0;
|
||||
};
|
||||
|
||||
VertexOut Main(
|
||||
in float2 InPosition : POSITION,
|
||||
in float4 InTextureCoordinates : TEXCOORD0,
|
||||
in float2 MaterialTexCoords : TEXCOORD1,
|
||||
in float4 InColor : COLOR0,
|
||||
in float4 InSecondaryColor : COLOR1
|
||||
)
|
||||
{
|
||||
VertexOut Out;
|
||||
|
||||
Out.Position = mul( WorldViewProjection, float4( InPosition.xy, 0, 1 ) );
|
||||
|
||||
Out.TextureCoordinates = InTextureCoordinates;
|
||||
|
||||
InColor.rgb = sRGBToLinear(InColor.rgb);
|
||||
InSecondaryColor.rgb = sRGBToLinear(InSecondaryColor.rgb);
|
||||
|
||||
Out.Color = InColor;
|
||||
Out.SecondaryColor = InSecondaryColor;
|
||||
|
||||
return Out;
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "GammaCorrectionCommon.hlsl"
|
||||
|
||||
// Shader types
|
||||
#define ESlateShader::Default 0
|
||||
#define ESlateShader::Border 1
|
||||
#define ESlateShader::GrayscaleFont 2
|
||||
#define ESlateShader::ColorFont 3
|
||||
#define ESlateShader::LineSegment 4
|
||||
#define ESlateShader::PostProcess 6
|
||||
#define ESlateShader::RoundedBox 7
|
||||
|
||||
#define USE_LEGACY_DISABLED_EFFECT 0
|
||||
|
||||
Texture2D ElementTexture;
|
||||
SamplerState ElementTextureSampler;
|
||||
|
||||
cbuffer PerFramePSConstants
|
||||
{
|
||||
/** Display gamma x:gamma curve adjustment, y:inverse gamma (1/GEngine->DisplayGamma) */
|
||||
float2 GammaValues;
|
||||
};
|
||||
|
||||
cbuffer PerElementPSConstants
|
||||
{
|
||||
float4 ShaderParams; // 16 bytes
|
||||
float4 ShaderParams2; // 16 bytes
|
||||
uint ShaderType; // 4 bytes
|
||||
uint IgnoreTextureAlpha; // 4 bytes
|
||||
uint DisableEffect; // 4 bytes
|
||||
uint UNUSED[1]; // 4 bytes
|
||||
};
|
||||
|
||||
struct VertexOut
|
||||
{
|
||||
float4 Position : SV_POSITION;
|
||||
float4 Color : COLOR0;
|
||||
float4 SecondaryColor : COLOR1;
|
||||
float4 TextureCoordinates : TEXCOORD0;
|
||||
};
|
||||
|
||||
float3 Hue( float H )
|
||||
{
|
||||
float R = abs(H * 6 - 3) - 1;
|
||||
float G = 2 - abs(H * 6 - 2);
|
||||
float B = 2 - abs(H * 6 - 4);
|
||||
return saturate( float3(R,G,B) );
|
||||
}
|
||||
|
||||
float3 GammaCorrect(float3 InColor)
|
||||
{
|
||||
float3 CorrectedColor = InColor;
|
||||
|
||||
if ( GammaValues.y != 1.0f )
|
||||
{
|
||||
CorrectedColor = ApplyGammaCorrection(CorrectedColor, GammaValues.x);
|
||||
}
|
||||
|
||||
return CorrectedColor;
|
||||
}
|
||||
|
||||
float4 GetGrayscaleFontElementColor( VertexOut InVertex )
|
||||
{
|
||||
float4 OutColor = InVertex.Color;
|
||||
|
||||
OutColor.a *= ElementTexture.Sample(ElementTextureSampler, InVertex.TextureCoordinates.xy).a;
|
||||
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
float4 GetColorFontElementColor(VertexOut InVertex)
|
||||
{
|
||||
float4 OutColor = InVertex.Color;
|
||||
|
||||
OutColor *= ElementTexture.Sample(ElementTextureSampler, InVertex.TextureCoordinates.xy);
|
||||
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
float4 GetColor( VertexOut InVertex, float2 UV )
|
||||
{
|
||||
float4 FinalColor;
|
||||
|
||||
float4 BaseColor = ElementTexture.Sample(ElementTextureSampler, UV );
|
||||
if( IgnoreTextureAlpha != 0 )
|
||||
{
|
||||
BaseColor.a = 1.0f;
|
||||
}
|
||||
|
||||
FinalColor = BaseColor*InVertex.Color;
|
||||
return FinalColor;
|
||||
}
|
||||
|
||||
float4 GetDefaultElementColor( VertexOut InVertex )
|
||||
{
|
||||
return GetColor( InVertex, InVertex.TextureCoordinates.xy*InVertex.TextureCoordinates.zw );
|
||||
}
|
||||
|
||||
float4 GetBorderElementColor( VertexOut InVertex )
|
||||
{
|
||||
float2 NewUV;
|
||||
if( InVertex.TextureCoordinates.z == 0.0f && InVertex.TextureCoordinates.w == 0.0f )
|
||||
{
|
||||
NewUV = InVertex.TextureCoordinates.xy;
|
||||
}
|
||||
else
|
||||
{
|
||||
float2 MinUV;
|
||||
float2 MaxUV;
|
||||
|
||||
if( InVertex.TextureCoordinates.z > 0 )
|
||||
{
|
||||
MinUV = float2(ShaderParams.x,0);
|
||||
MaxUV = float2(ShaderParams.y,1);
|
||||
InVertex.TextureCoordinates.w = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
MinUV = float2(0,ShaderParams.z);
|
||||
MaxUV = float2(1,ShaderParams.w);
|
||||
InVertex.TextureCoordinates.z = 1.0f;
|
||||
}
|
||||
|
||||
NewUV = InVertex.TextureCoordinates.xy*InVertex.TextureCoordinates.zw;
|
||||
NewUV = frac(NewUV);
|
||||
NewUV = lerp(MinUV,MaxUV,NewUV);
|
||||
}
|
||||
|
||||
return GetColor( InVertex, NewUV );
|
||||
}
|
||||
|
||||
float GetRoundedBoxDistance(float2 pos, float2 center, float radius, float inset)
|
||||
{
|
||||
// distance from center
|
||||
pos = abs(pos - center);
|
||||
|
||||
// distance from the inner corner
|
||||
pos = pos - (center - float2(radius + inset, radius + inset));
|
||||
|
||||
// use distance to nearest edge when not in quadrant with radius
|
||||
// this handles an edge case when radius is very close to thickness
|
||||
// otherwise we're in the quadrant with the radius,
|
||||
// just use the analytic signed distance function
|
||||
return lerp( length(pos) - radius, max(pos.x - radius, pos.y - radius), float(pos.x <= 0 || pos.y <=0) );
|
||||
}
|
||||
|
||||
float4 GetRoundedBoxElementColor( VertexOut InVertex )
|
||||
{
|
||||
const float2 size = ShaderParams.zw;
|
||||
float2 pos = size * InVertex.TextureCoordinates.xy;
|
||||
float2 center = size / 2.0;
|
||||
|
||||
//X = Top Left, Y = Top Right, Z = Bottom Right, W = Bottom Left */
|
||||
float4 cornerRadii = ShaderParams2;
|
||||
|
||||
// figure out which radius to use based on which quadrant we're in
|
||||
float2 quadrant = step(InVertex.TextureCoordinates.xy, float2(.5,.5));
|
||||
|
||||
float left = lerp(cornerRadii.y, cornerRadii.x, quadrant.x);
|
||||
float right = lerp(cornerRadii.z, cornerRadii.w, quadrant.x);
|
||||
float radius = lerp(right, left, quadrant.y);
|
||||
|
||||
float thickness = ShaderParams.y;
|
||||
|
||||
// Compute the distances internal and external to the border outline
|
||||
float dext = GetRoundedBoxDistance(pos, center, radius, 0.0);
|
||||
float din = GetRoundedBoxDistance(pos, center, max(radius - thickness, 0), thickness);
|
||||
|
||||
// Compute the border intensity and fill intensity with a smooth transition
|
||||
float spread = 0.5;
|
||||
float bi = smoothstep(spread, -spread, dext);
|
||||
float fi = smoothstep(spread, -spread, din);
|
||||
|
||||
// alpha blend the external color
|
||||
float4 fill = GetColor(InVertex, InVertex.TextureCoordinates.xy * InVertex.TextureCoordinates.zw);
|
||||
float4 border = InVertex.SecondaryColor;
|
||||
float4 OutColor = lerp(border, fill, float(thickness > radius));
|
||||
OutColor.a = 0.0;
|
||||
|
||||
// blend in the border and fill colors
|
||||
OutColor = lerp(OutColor, border, bi);
|
||||
OutColor = lerp(OutColor, fill, fi);
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
float4 GetLineSegmentElementColor( VertexOut InVertex )
|
||||
{
|
||||
const float2 Gradient = InVertex.TextureCoordinates;
|
||||
|
||||
const float2 OutsideFilterUV = float2(1.0f, 1.0f);
|
||||
const float2 InsideFilterUV = float2(ShaderParams.x, 0.0f);
|
||||
const float2 LineCoverage = smoothstep(OutsideFilterUV, InsideFilterUV, abs(Gradient));
|
||||
|
||||
float4 Color = InVertex.Color;
|
||||
Color.a *= LineCoverage.x * LineCoverage.y;
|
||||
return Color;
|
||||
}
|
||||
|
||||
float PseudoRandom(float2 xy)
|
||||
{
|
||||
float2 pos = frac(xy / 128.0f) * 128.0f + float2(-64.340622f, -72.465622f);
|
||||
|
||||
// found by experimentation
|
||||
return frac(dot(pos.xyx * pos.xyy, float3(20.390625f, 60.703125f, 2.4281209f)));
|
||||
}
|
||||
|
||||
float4 GetPostProcessColor( VertexOut InVertex )
|
||||
{
|
||||
return InVertex.Color;
|
||||
}
|
||||
|
||||
float4 Main( VertexOut InVertex ) : SV_Target
|
||||
{
|
||||
float4 OutColor;
|
||||
|
||||
if( ShaderType == ESlateShader::Default )
|
||||
{
|
||||
OutColor = GetDefaultElementColor( InVertex );
|
||||
}
|
||||
else if( ShaderType == ESlateShader::RoundedBox)
|
||||
{
|
||||
OutColor = GetRoundedBoxElementColor( InVertex );
|
||||
}
|
||||
else if( ShaderType == ESlateShader::Border )
|
||||
{
|
||||
OutColor = GetBorderElementColor( InVertex );
|
||||
}
|
||||
else if( ShaderType == ESlateShader::GrayscaleFont )
|
||||
{
|
||||
OutColor = GetGrayscaleFontElementColor( InVertex );
|
||||
}
|
||||
else if (ShaderType == ESlateShader::ColorFont)
|
||||
{
|
||||
OutColor = GetColorFontElementColor(InVertex);
|
||||
}
|
||||
else if (ShaderType == ESlateShader::PostProcess)
|
||||
{
|
||||
OutColor = GetPostProcessColor(InVertex);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColor = GetLineSegmentElementColor( InVertex );
|
||||
}
|
||||
|
||||
// gamma correct
|
||||
OutColor.rgb = GammaCorrect(OutColor.rgb);
|
||||
|
||||
if (DisableEffect)
|
||||
{
|
||||
#if USE_LEGACY_DISABLED_EFFECT
|
||||
|
||||
//desaturate
|
||||
float3 LumCoeffs = float3( 0.3, 0.59, .11 );
|
||||
float Lum = dot( LumCoeffs, OutColor.rgb );
|
||||
OutColor.rgb = lerp( OutColor.rgb, float3(Lum,Lum,Lum), .8 );
|
||||
|
||||
float3 Grayish = {.4, .4, .4};
|
||||
|
||||
// lerp between desaturated color and gray color based on distance from the desaturated color to the gray
|
||||
OutColor.rgb = lerp( OutColor.rgb, Grayish, clamp( distance( OutColor.rgb, Grayish ), 0, .8) );
|
||||
#else
|
||||
OutColor.a *= .45f;
|
||||
#endif
|
||||
}
|
||||
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
// #version 120 at the beginning is added in FSlateOpenGLShader::CompileShader()
|
||||
|
||||
uniform mat4 ViewProjectionMatrix;
|
||||
|
||||
// Per vertex
|
||||
in vec4 InTexCoords;
|
||||
in vec2 InPosition;
|
||||
in vec4 InColor;
|
||||
in vec4 InSecondaryColor;
|
||||
|
||||
// Between vertex and pixel shader
|
||||
out vec4 Position;
|
||||
out vec4 TexCoords;
|
||||
out vec4 Color;
|
||||
out vec4 SecondaryColor;
|
||||
|
||||
vec3 powScalar(vec3 values, float power)
|
||||
{
|
||||
return vec3(pow(values.x, power), pow(values.y, power), pow(values.z, power));
|
||||
}
|
||||
|
||||
float sRGBToLinearChannel( float ColorChannel )
|
||||
{
|
||||
return ColorChannel > 0.04045 ? pow( ColorChannel * (1.0 / 1.055) + 0.0521327, 2.4 ) : ColorChannel * (1.0 / 12.92);
|
||||
}
|
||||
|
||||
vec3 sRGBToLinear( vec3 Color )
|
||||
{
|
||||
return vec3(
|
||||
sRGBToLinearChannel(Color.r),
|
||||
sRGBToLinearChannel(Color.g),
|
||||
sRGBToLinearChannel(Color.b));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
TexCoords = InTexCoords;
|
||||
|
||||
Color.rgb = sRGBToLinear(InColor.rgb);
|
||||
Color.a = InColor.a;
|
||||
|
||||
SecondaryColor.rgb = sRGBToLinear(InSecondaryColor.rgb);
|
||||
SecondaryColor.a = InSecondaryColor.a;
|
||||
|
||||
Position = vec4( InPosition, 0, 1 );
|
||||
|
||||
gl_Position = ViewProjectionMatrix * vec4( InPosition, 0, 1 );
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
// handle differences between ES and full GL shaders
|
||||
#if PLATFORM_USES_GLES
|
||||
precision highp float;
|
||||
#else
|
||||
// #version 120 at the beginning is added in FSlateOpenGLShader::CompileShader()
|
||||
#extension GL_EXT_gpu_shader4 : enable
|
||||
#endif
|
||||
|
||||
#ifndef USE_709
|
||||
#define USE_709 0
|
||||
#endif // USE_709
|
||||
|
||||
// Shader types
|
||||
#define ST_Default 0
|
||||
#define ST_Border 1
|
||||
#define ST_GrayscaleFont 2
|
||||
#define ST_ColorFont 3
|
||||
#define ST_Line 4
|
||||
#define ST_RoundedBox 7
|
||||
|
||||
#define USE_LEGACY_DISABLED_EFFECT 0
|
||||
|
||||
/** Display gamma x:gamma curve adjustment, y:inverse gamma (1/GEngine->DisplayGamma) */
|
||||
uniform vec2 GammaValues = vec2(1, 1/2.2);
|
||||
|
||||
// Draw effects
|
||||
uniform bool EffectsDisabled;
|
||||
uniform bool IgnoreTextureAlpha;
|
||||
|
||||
uniform vec4 ShaderParams;
|
||||
uniform vec4 ShaderParams2;
|
||||
uniform int ShaderType;
|
||||
uniform sampler2D ElementTexture;
|
||||
|
||||
#if PLATFORM_MAC
|
||||
// GL_TEXTURE_RECTANGLE_ARB support, used by the web surface on macOS
|
||||
uniform bool UseTextureRectangle;
|
||||
uniform sampler2DRect ElementRectTexture;
|
||||
uniform vec2 Size;
|
||||
#endif
|
||||
|
||||
in vec4 Position;
|
||||
in vec4 TexCoords;
|
||||
in vec4 Color;
|
||||
in vec4 SecondaryColor;
|
||||
out vec4 fragColor;
|
||||
|
||||
vec3 maxWithScalar(float test, vec3 values)
|
||||
{
|
||||
return vec3(max(test, values.x), max(test, values.y), max(test, values.z));
|
||||
}
|
||||
|
||||
vec3 powScalar(vec3 values, float power)
|
||||
{
|
||||
return vec3(pow(values.x, power), pow(values.y, power), pow(values.z, power));
|
||||
}
|
||||
|
||||
vec3 LinearTo709Branchless(vec3 lin)
|
||||
{
|
||||
lin = maxWithScalar(6.10352e-5, lin); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
|
||||
return min(lin * 4.5, powScalar(maxWithScalar(0.018, lin), 0.45) * 1.099 - 0.099);
|
||||
}
|
||||
|
||||
vec3 LinearToSrgbBranchless(vec3 lin)
|
||||
{
|
||||
lin = maxWithScalar(6.10352e-5, lin); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
|
||||
return min(lin * 12.92, powScalar(maxWithScalar(0.00313067, lin), 1.0/2.4) * 1.055 - 0.055);
|
||||
// Possible that mobile GPUs might have native pow() function?
|
||||
//return min(lin * 12.92, exp2(log2(max(lin, 0.00313067)) * (1.0/2.4) + log2(1.055)) - 0.055);
|
||||
}
|
||||
|
||||
float LinearToSrgbBranchingChannel(float lin)
|
||||
{
|
||||
if(lin < 0.00313067) return lin * 12.92;
|
||||
return pow(lin, (1.0/2.4)) * 1.055 - 0.055;
|
||||
}
|
||||
|
||||
vec3 LinearToSrgbBranching(vec3 lin)
|
||||
{
|
||||
return vec3(
|
||||
LinearToSrgbBranchingChannel(lin.r),
|
||||
LinearToSrgbBranchingChannel(lin.g),
|
||||
LinearToSrgbBranchingChannel(lin.b));
|
||||
}
|
||||
|
||||
float sRGBToLinearChannel( float ColorChannel )
|
||||
{
|
||||
return ColorChannel > 0.04045 ? pow( ColorChannel * (1.0 / 1.055) + 0.0521327, 2.4 ) : ColorChannel * (1.0 / 12.92);
|
||||
}
|
||||
|
||||
vec3 sRGBToLinear( vec3 Color )
|
||||
{
|
||||
return vec3(sRGBToLinearChannel(Color.r),
|
||||
sRGBToLinearChannel(Color.g),
|
||||
sRGBToLinearChannel(Color.b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GammaCurveRatio The curve ratio compared to a 2.2 standard gamma, e.g. 2.2 / DisplayGamma. So normally the value is 1.
|
||||
*/
|
||||
vec3 ApplyGammaCorrection(vec3 LinearColor, float GammaCurveRatio)
|
||||
{
|
||||
// Apply "gamma" curve adjustment.
|
||||
vec3 CorrectedColor = powScalar(LinearColor, GammaCurveRatio);
|
||||
|
||||
#if PLATFORM_MAC
|
||||
// Note, MacOSX native output is raw gamma 2.2 not sRGB!
|
||||
//CorrectedColor = pow(CorrectedColor, 1.0/2.2);
|
||||
CorrectedColor = LinearToSrgbBranching(CorrectedColor);
|
||||
#else
|
||||
#if USE_709
|
||||
// Didn't profile yet if the branching version would be faster (different linear segment).
|
||||
CorrectedColor = LinearTo709Branchless(CorrectedColor);
|
||||
#else
|
||||
CorrectedColor = LinearToSrgbBranching(CorrectedColor);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
return CorrectedColor;
|
||||
}
|
||||
|
||||
vec3 GammaCorrect(vec3 InColor)
|
||||
{
|
||||
vec3 CorrectedColor = InColor;
|
||||
|
||||
// gamma correct
|
||||
//#if PLATFORM_USES_GLES
|
||||
// OutColor.rgb = sqrt( OutColor.rgb );
|
||||
//#else
|
||||
// OutColor.rgb = pow(OutColor.rgb, vec3(1.0/2.2));
|
||||
//#endif
|
||||
|
||||
#if !PLATFORM_USES_GLES
|
||||
if( GammaValues.y != 1.0f )
|
||||
{
|
||||
CorrectedColor = ApplyGammaCorrection(CorrectedColor, GammaValues.x);
|
||||
}
|
||||
#endif
|
||||
|
||||
return CorrectedColor;
|
||||
}
|
||||
|
||||
vec4 GetGrayscaleFontElementColor()
|
||||
{
|
||||
vec4 OutColor = Color;
|
||||
#if PLATFORM_LINUX
|
||||
OutColor.a *= texture2D(ElementTexture, TexCoords.xy).r; // OpenGL 3.2+ uses Red for single channel textures
|
||||
#else
|
||||
OutColor.a *= texture2D(ElementTexture, TexCoords.xy).a;
|
||||
#endif
|
||||
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
vec4 GetColorFontElementColor()
|
||||
{
|
||||
vec4 OutColor = Color;
|
||||
|
||||
OutColor *= texture2D(ElementTexture, TexCoords.xy);
|
||||
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
vec4 GetDefaultElementColor()
|
||||
{
|
||||
vec4 OutColor = Color;
|
||||
|
||||
vec4 TextureColor;
|
||||
#if PLATFORM_MAC
|
||||
if ( UseTextureRectangle )
|
||||
{
|
||||
TextureColor = texture2DRect(ElementRectTexture, TexCoords.xy*TexCoords.zw*Size).bgra;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
TextureColor = texture2D(ElementTexture, TexCoords.xy*TexCoords.zw);
|
||||
}
|
||||
|
||||
if( IgnoreTextureAlpha )
|
||||
{
|
||||
TextureColor.a = 1.0;
|
||||
}
|
||||
OutColor *= TextureColor;
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
vec4 GetBorderElementColor()
|
||||
{
|
||||
vec4 OutColor = Color;
|
||||
vec4 InTexCoords = TexCoords;
|
||||
vec2 NewUV;
|
||||
if( InTexCoords.z == 0.0 && InTexCoords.w == 0.0 )
|
||||
{
|
||||
NewUV = InTexCoords.xy;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec2 MinUV;
|
||||
vec2 MaxUV;
|
||||
|
||||
if( InTexCoords.z > 0.0 )
|
||||
{
|
||||
MinUV = vec2(ShaderParams.x,0.0);
|
||||
MaxUV = vec2(ShaderParams.y,1.0);
|
||||
InTexCoords.w = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
MinUV = vec2(0.0,ShaderParams.z);
|
||||
MaxUV = vec2(1.0,ShaderParams.w);
|
||||
InTexCoords.z = 1.0;
|
||||
}
|
||||
|
||||
NewUV = InTexCoords.xy*InTexCoords.zw;
|
||||
NewUV = fract(NewUV);
|
||||
NewUV = mix(MinUV,MaxUV,NewUV);
|
||||
|
||||
}
|
||||
|
||||
vec4 TextureColor = texture2D(ElementTexture, NewUV);
|
||||
if( IgnoreTextureAlpha )
|
||||
{
|
||||
TextureColor.a = 1.0;
|
||||
}
|
||||
|
||||
OutColor *= TextureColor;
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
float GetRoundedBoxDistance(vec2 pos, vec2 center, float radius, float inset)
|
||||
{
|
||||
// distance from center
|
||||
pos = abs(pos - center);
|
||||
|
||||
// distance from the inner corner
|
||||
pos = pos - (center - vec2(radius + inset, radius + inset));
|
||||
|
||||
// use distance to nearest edge when not in quadrant with radius
|
||||
// this handles an edge case when radius is very close to thickness
|
||||
// otherwise we're in the quadrant with the radius,
|
||||
// just use the analytic signed distance function
|
||||
return mix( length(pos) - radius,
|
||||
max(pos.x - radius, pos.y - radius),
|
||||
float(pos.x <= 0 || pos.y <=0) );
|
||||
}
|
||||
|
||||
vec4 GetRoundedBoxElementColor()
|
||||
{
|
||||
vec2 size = ShaderParams.zw;
|
||||
vec2 pos = size * TexCoords.xy;
|
||||
vec2 center = size / 2.0;
|
||||
|
||||
//X = Top Left, Y = Top Right, Z = Bottom Right, W = Bottom Left */
|
||||
vec4 cornerRadii = ShaderParams2;
|
||||
|
||||
// figure out which radius to use based on which quadrant we're in
|
||||
vec2 quadrant = step(TexCoords.xy, vec2(.5,.5));
|
||||
|
||||
float left = mix(cornerRadii.y, cornerRadii.x, quadrant.x);
|
||||
float right = mix(cornerRadii.z, cornerRadii.w, quadrant.x);
|
||||
float radius = mix(right, left, quadrant.y);
|
||||
|
||||
float thickness = ShaderParams.y;
|
||||
|
||||
// Compute the distances internal and external to the border outline
|
||||
float dext = GetRoundedBoxDistance(pos, center, radius, 0.0);
|
||||
float din = GetRoundedBoxDistance(pos, center, max(radius - thickness, 0), thickness);
|
||||
|
||||
// Compute the border intensity and fill intensity with a smooth transition
|
||||
float spread = 0.5;
|
||||
float bi = smoothstep(spread, -spread, dext);
|
||||
float fi = smoothstep(spread, -spread, din);
|
||||
|
||||
// alpha blend the external color
|
||||
vec4 fill = GetDefaultElementColor();
|
||||
vec4 border = SecondaryColor;
|
||||
vec4 OutColor = mix(border, fill, float(thickness > radius));
|
||||
OutColor.a = 0.0;
|
||||
|
||||
// blend in the border and fill colors
|
||||
OutColor = mix(OutColor, border, bi);
|
||||
OutColor = mix(OutColor, fill, fi);
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
vec4 GetLineSegmentElementColor()
|
||||
{
|
||||
vec2 Gradient = TexCoords.xy;
|
||||
|
||||
vec2 OutsideFilterUV = vec2(1.0, 1.0);
|
||||
vec2 InsideFilterUV = vec2(ShaderParams.x, 0.0);
|
||||
vec2 LineCoverage = smoothstep(OutsideFilterUV, InsideFilterUV, abs(Gradient));
|
||||
|
||||
vec4 OutColor = Color;
|
||||
OutColor.a *= LineCoverage.x * LineCoverage.y;
|
||||
return OutColor;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 OutColor;
|
||||
|
||||
if( ShaderType == ST_Default )
|
||||
{
|
||||
OutColor = GetDefaultElementColor();
|
||||
}
|
||||
else if( ShaderType == ST_RoundedBox )
|
||||
{
|
||||
OutColor = GetRoundedBoxElementColor();
|
||||
}
|
||||
else if( ShaderType == ST_Border )
|
||||
{
|
||||
OutColor = GetBorderElementColor();
|
||||
}
|
||||
else if( ShaderType == ST_GrayscaleFont )
|
||||
{
|
||||
OutColor = GetGrayscaleFontElementColor();
|
||||
}
|
||||
else if( ShaderType == ST_ColorFont )
|
||||
{
|
||||
OutColor = GetColorFontElementColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColor = GetLineSegmentElementColor();
|
||||
}
|
||||
|
||||
// gamma correct
|
||||
OutColor.rgb = GammaCorrect(OutColor.rgb);
|
||||
|
||||
if( EffectsDisabled )
|
||||
{
|
||||
#if USE_LEGACY_DISABLED_EFFECT
|
||||
//desaturate
|
||||
vec3 LumCoeffs = vec3( 0.3, 0.59, .11 );
|
||||
float Lum = dot( LumCoeffs, OutColor.rgb );
|
||||
OutColor.rgb = mix( OutColor.rgb, vec3(Lum,Lum,Lum), .8 );
|
||||
|
||||
vec3 Grayish = vec3(0.4, 0.4, 0.4);
|
||||
|
||||
OutColor.rgb = mix( OutColor.rgb, Grayish, clamp( distance( OutColor.rgb, Grayish ), 0.0, 0.8) );
|
||||
#else
|
||||
OutColor.a *= .45f;
|
||||
#endif
|
||||
}
|
||||
|
||||
fragColor = OutColor.bgra;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
#if PLATFORM_USES_GLES
|
||||
precision highp float;
|
||||
#else
|
||||
// #version 120 at the beginning is added in FSlateOpenGLShader::CompileShader()
|
||||
#extension GL_EXT_gpu_shader4 : enable
|
||||
#endif
|
||||
|
||||
varying vec2 textureCoordinate;
|
||||
|
||||
uniform sampler2D SplashTexture;
|
||||
|
||||
void main()
|
||||
{
|
||||
// OpenGL has 0,0 the "math" way
|
||||
vec2 tc = vec2(textureCoordinate.s, 1.0-textureCoordinate.t);
|
||||
|
||||
gl_FragColor = texture2D(SplashTexture, tc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
attribute vec2 InPosition;
|
||||
|
||||
varying vec2 textureCoordinate;
|
||||
|
||||
void main()
|
||||
{
|
||||
// We do not need texture coordinates. We calculate using position.
|
||||
textureCoordinate = InPosition * 0.5 + 0.5;
|
||||
|
||||
gl_Position = vec4(InPosition, 0.0, 1.0);
|
||||
|
||||
}
|
||||
BIN
Content/Skin/Default/BlackBrush.png
Normal file
BIN
Content/Skin/Default/BlackBrush.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 B |
BIN
Content/Skin/Default/CloseButton_Normal.png
Normal file
BIN
Content/Skin/Default/CloseButton_Normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 556 B |
BIN
Content/Skin/Default/PianoRollBackground.png
Normal file
BIN
Content/Skin/Default/PianoRollBackground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 MiB |
BIN
Content/Skin/Default/Roboto-Light.ttf
Normal file
BIN
Content/Skin/Default/Roboto-Light.ttf
Normal file
Binary file not shown.
19
Content/Skin/Default/Skin.ini
Normal file
19
Content/Skin/Default/Skin.ini
Normal file
@@ -0,0 +1,19 @@
|
||||
[Resource]
|
||||
WhiteBrush="WhiteBrush"
|
||||
|
||||
VolumeMeter="VolumeMeter"
|
||||
VolumeMeterSpace=1
|
||||
|
||||
DefaultFont="Roboto-Light"
|
||||
DefaultFontSize=12
|
||||
|
||||
PianoRollBackground="PianoRollBackground"
|
||||
PianoRollBackgroundSize="X=3840, Y=2160"
|
||||
|
||||
CloseButtonSize="X=16, Y=16"
|
||||
CloseButtonNormal="CloseButton_Normal"
|
||||
CloseButtonHover="CloseButton_Hover"
|
||||
CloseButtonPress="CloseButton_Press"
|
||||
|
||||
WhiteKey="WhiteBrush"
|
||||
BlackKey="BlackBrush"
|
||||
BIN
Content/Skin/Default/VolumeMeter.png
Normal file
BIN
Content/Skin/Default/VolumeMeter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Content/Skin/Default/WhiteBrush.png
Normal file
BIN
Content/Skin/Default/WhiteBrush.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 B |
BIN
Content/Skin/Default/{9F6605E9-2440-C398-95A5-D5F1BC998087}.jpg
Normal file
BIN
Content/Skin/Default/{9F6605E9-2440-C398-95A5-D5F1BC998087}.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 MiB |
24
Source/Arona.Target.cs
Normal file
24
Source/Arona.Target.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[SupportedPlatforms(UnrealPlatformClass.Desktop)]
|
||||
[SupportedPlatforms("IOS")]
|
||||
public class AronaTarget : TargetRules
|
||||
{
|
||||
public AronaTarget(TargetInfo Target) : base(Target)
|
||||
{
|
||||
Type = TargetType.Program;
|
||||
LinkType = TargetLinkType.Default;
|
||||
LaunchModuleName = "Arona";
|
||||
|
||||
// Arona.exe has no exports, so no need to verify that a .lib and .exp file was emitted by
|
||||
// the linker.
|
||||
bHasExports = false;
|
||||
|
||||
// Make sure to get all code in SlateEditorStyle compiled in
|
||||
bBuildDeveloperTools = false;
|
||||
bUsesSlate = true;
|
||||
}
|
||||
}
|
||||
55
Source/Arona/Arona.Build.cs
Normal file
55
Source/Arona/Arona.Build.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using System.IO;
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class Arona : ModuleRules
|
||||
{
|
||||
public Arona(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
PublicIncludePaths.Add(Path.Combine(EngineDirectory, "Source", "Runtime/Launch/Public"));
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[] {
|
||||
"AppFramework",
|
||||
"Core",
|
||||
"ApplicationCore",
|
||||
"Projects",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"StandaloneRenderer",
|
||||
"DesktopPlatform",
|
||||
"AronaCore",
|
||||
"SignalProcessing"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePathModuleNames.AddRange(
|
||||
new string[] {
|
||||
"StandaloneRenderer"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.Add(Path.Combine(EngineDirectory, "Source", "Runtime/Launch/Private")); // For LaunchEngineLoop.cpp include
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.IOS || Target.Platform == UnrealTargetPlatform.TVOS)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string [] {
|
||||
"NetworkFile",
|
||||
"StreamingFile"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (Target.IsInPlatformGroup(UnrealPlatformGroup.Linux))
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[] {
|
||||
"UnixCommonStartup"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Source/Arona/EntryPoints/AronaApp.cpp
Normal file
108
Source/Arona/EntryPoints/AronaApp.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaApp.h"
|
||||
|
||||
#include "App.h"
|
||||
#include "AronaMain.h"
|
||||
#include "ConfigCacheIni.h"
|
||||
#include "OutputDeviceRedirector.h"
|
||||
#include "QueuedThreadPool.h"
|
||||
#include "QueuedThreadPoolWrapper.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Framework/Docking/TabManager.h"
|
||||
#include "Framework/Docking/WorkspaceItem.h"
|
||||
#include "StandaloneRenderer.h"
|
||||
#include "StdOutputDevice.h"
|
||||
#include "Singleton/SingletonManager.h"
|
||||
#include "Stats2.h"
|
||||
#include "Test.h"
|
||||
#include "Singleton/CallRateLimiterManager.h"
|
||||
#include "Thread/MainThreadEventList.h"
|
||||
#include "UI/Widget/WindowManager.h"
|
||||
|
||||
IMPLEMENT_APPLICATION(Arona, "Arona");
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Arona"
|
||||
|
||||
int32 Init(const TCHAR* CmdLine)
|
||||
{
|
||||
FCommandLine::Set(CmdLine);
|
||||
FGenericPlatformProcess::SetShaderDir(*FPaths::Combine(FPaths::ProjectContentDir(), TEXT("Shader")));
|
||||
GThreadPool = new FQueuedLowLevelThreadPool();
|
||||
InitLog();
|
||||
// BUG: Memoty leak
|
||||
// #if STATS
|
||||
// FThreadStats::StartThread();
|
||||
// #endif
|
||||
FPlatformMisc::PlatformPreInit();
|
||||
FConfigCacheIni::InitializeConfigSystem();
|
||||
FPlatformMisc::PlatformInit();
|
||||
FPlatformMisc::SetGracefulTerminationHandler();
|
||||
|
||||
{
|
||||
FTaskTagScope Scope(ETaskTag::EGameThread);
|
||||
FTaskGraphInterface::Startup(FPlatformMisc::NumberOfWorkerThreadsToSpawn());
|
||||
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread);
|
||||
|
||||
FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::TaskGraphSystemReady);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int RunArona( const TCHAR* CommandLine )
|
||||
{
|
||||
Init(CommandLine);
|
||||
|
||||
// crank up a normal Slate application using the platform's standalone renderer
|
||||
FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer());
|
||||
FSlateApplication::InitHighDPI(true);
|
||||
|
||||
FSingletonManager::GetInstance().Init();
|
||||
|
||||
FMainThreadEventList& MainThreadEventList = FMainThreadEventList::Get();
|
||||
FWindowManager& WindowManager = FWindowManager::Get();
|
||||
FCallRateLimiterManager& RateLimiterManager = FCallRateLimiterManager::Get();
|
||||
|
||||
// AronaTest();
|
||||
|
||||
constexpr float FrameRate = 1.f / 360.0f;
|
||||
FSlateApplication& SlateApplication = FSlateApplication::Get();
|
||||
FTaskGraphInterface& TaskGraphInterface = FTaskGraphInterface::Get();
|
||||
FTSTicker& CoreTicker = FTSTicker::GetCoreTicker();
|
||||
|
||||
// loop while the server does the rest
|
||||
while (!IsEngineExitRequested())
|
||||
{
|
||||
BeginExitIfRequested();
|
||||
|
||||
TaskGraphInterface.ProcessThreadUntilIdle(ENamedThreads::GameThread);
|
||||
CoreTicker.Tick(FApp::GetDeltaTime());
|
||||
SlateApplication.PumpMessages();
|
||||
MainThreadEventList.ProcessMessage();
|
||||
|
||||
SlateApplication.Tick();
|
||||
FPlatformProcess::SleepNoStats(FrameRate);
|
||||
RateLimiterManager.Update(FApp::GetDeltaTime());
|
||||
|
||||
GFrameCounter++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FSingletonManager::GetInstance().Release();
|
||||
FCoreDelegates::OnExit.Broadcast();
|
||||
FSlateApplication::Shutdown();
|
||||
FModuleManager::Get().UnloadModulesAtShutdown();
|
||||
FTaskGraphInterface::Shutdown();
|
||||
FPlatformMisc::PlatformTearDown();
|
||||
delete GThreadPool;
|
||||
// BUG: Memoty leak
|
||||
// #if STATS
|
||||
// FThreadStats::StopThread();
|
||||
// #endif
|
||||
TearDownLog();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
10
Source/Arona/EntryPoints/AronaApp.h
Normal file
10
Source/Arona/EntryPoints/AronaApp.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
* Run the Arona .
|
||||
*/
|
||||
int RunArona(const TCHAR* Commandline);
|
||||
32
Source/Arona/EntryPoints/AronaMain.cpp
Normal file
32
Source/Arona/EntryPoints/AronaMain.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaMain.h"
|
||||
#include "UI/Style/AronaStyle.h"
|
||||
#include "UI/AronaModuleCommands.h"
|
||||
|
||||
#include "Widgets/Docking/SDockTab.h"
|
||||
#include "Widgets/Layout/SBox.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FAronaModule"
|
||||
|
||||
void FAronaMain::StartupModule()
|
||||
{
|
||||
FAronaModuleCommands::Register();
|
||||
|
||||
AppCommands = MakeShareable(new FUICommandList);
|
||||
}
|
||||
|
||||
void FAronaMain::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
FAronaModuleCommands::Unregister();
|
||||
|
||||
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(AronaModuleTabName);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FAronaMain, AronaMain)
|
||||
23
Source/Arona/EntryPoints/AronaMain.h
Normal file
23
Source/Arona/EntryPoints/AronaMain.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FToolBarBuilder;
|
||||
class FMenuBuilder;
|
||||
static const FName AronaModuleTabName("AronaModule");
|
||||
|
||||
class FAronaMain : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
private:
|
||||
|
||||
private:
|
||||
TSharedPtr<class FUICommandList> AppCommands;
|
||||
};
|
||||
111
Source/Arona/EntryPoints/IOS/IOSAronaMain.cpp
Normal file
111
Source/Arona/EntryPoints/IOS/IOSAronaMain.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaApp.h"
|
||||
#include "IOS/IOSAppDelegate.h"
|
||||
#include "IOS/IOSCommandLineHelper.h"
|
||||
#include "IOS/SlateOpenGLESView.h"
|
||||
#include "Widgets/Testing/STestSuite.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
#define IOS_MAX_PATH 1024
|
||||
#define CMD_LINE_MAX 16384
|
||||
|
||||
FString GSavedCommandLine;
|
||||
|
||||
void FAppEntry::Suspend()
|
||||
{
|
||||
}
|
||||
|
||||
void FAppEntry::Resume()
|
||||
{
|
||||
}
|
||||
|
||||
void FAppEntry::SuspendTick()
|
||||
{
|
||||
}
|
||||
|
||||
bool FAppEntry::IsStartupMoviePlaying()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void FAppEntry::PreInit(IOSAppDelegate* AppDelegate, UIApplication* Application)
|
||||
{
|
||||
// make a controller object
|
||||
AppDelegate.SlateController = [[SlateOpenGLESViewController alloc] init];
|
||||
|
||||
// property owns it now
|
||||
[AppDelegate.SlateController release];
|
||||
|
||||
// point to the GL view we want to use
|
||||
AppDelegate.RootView = [AppDelegate.SlateController view];
|
||||
|
||||
[AppDelegate.Window setRootViewController:AppDelegate.SlateController];
|
||||
}
|
||||
|
||||
|
||||
void FAppEntry::PlatformInit()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void FAppEntry::Init()
|
||||
{
|
||||
// start up the main loop
|
||||
GEngineLoop.PreInit(FCommandLine::Get());
|
||||
|
||||
// move it to this thread
|
||||
SlateOpenGLESView* View = (SlateOpenGLESView*)[IOSAppDelegate GetDelegate].RootView;
|
||||
[EAGLContext setCurrentContext:View.Context];
|
||||
|
||||
// crank up a normal Slate application using the platform's standalone renderer
|
||||
FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer());
|
||||
|
||||
// Bring up the test suite.
|
||||
{
|
||||
RestoreSlateTestSuite();
|
||||
}
|
||||
|
||||
#if WITH_SHARED_POINTER_TESTS
|
||||
SharedPointerTesting::TestSharedPointer< ESPMode::Fast >();
|
||||
SharedPointerTesting::TestSharedPointer< ESPMode::ThreadSafe >();
|
||||
#endif
|
||||
|
||||
// loop while the server does the rest
|
||||
double LastTime = FPlatformTime::Seconds();
|
||||
}
|
||||
|
||||
|
||||
void FAppEntry::Tick()
|
||||
{
|
||||
FSlateApplication::Get().PumpMessages();
|
||||
FSlateApplication::Get().Tick();
|
||||
|
||||
// Sleep
|
||||
FPlatformProcess::Sleep( 0 );
|
||||
}
|
||||
|
||||
|
||||
void FAppEntry::Shutdown()
|
||||
{
|
||||
FSlateApplication::Shutdown();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
for (int32 Option = 1; Option < argc; Option++)
|
||||
{
|
||||
GSavedCommandLine += TEXT(" ");
|
||||
GSavedCommandLine += ANSI_TO_TCHAR(argv[Option]);
|
||||
}
|
||||
|
||||
FIOSCommandLineHelper::InitCommandArgs(FString());
|
||||
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([IOSAppDelegate class]));
|
||||
}
|
||||
}
|
||||
9
Source/Arona/EntryPoints/Linux/AronaMainLinux.cpp
Normal file
9
Source/Arona/EntryPoints/Linux/AronaMainLinux.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaApp.h"
|
||||
#include "UnixCommonStartup.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return CommonUnixMain(argc, argv, &RunArona);
|
||||
}
|
||||
85
Source/Arona/EntryPoints/Mac/AronaMainMac.cpp
Normal file
85
Source/Arona/EntryPoints/Mac/AronaMainMac.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaApp.h"
|
||||
#include "HAL/ExceptionHandling.h"
|
||||
#include "Mac/CocoaThread.h"
|
||||
|
||||
static FString GSavedCommandLine;
|
||||
|
||||
@interface UE4AppDelegate : NSObject <NSApplicationDelegate, NSFileManagerDelegate>
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UE4AppDelegate
|
||||
|
||||
//handler for the quit apple event used by the Dock menu
|
||||
- (void)handleQuitEvent:(NSAppleEventDescriptor*)Event withReplyEvent:(NSAppleEventDescriptor*)ReplyEvent
|
||||
{
|
||||
[NSApp terminate:self];
|
||||
}
|
||||
|
||||
- (void) runGameThread:(id)Arg
|
||||
{
|
||||
FPlatformMisc::SetGracefulTerminationHandler();
|
||||
FPlatformMisc::SetCrashHandler(nullptr);
|
||||
|
||||
RunArona(*GSavedCommandLine);
|
||||
|
||||
[NSApp terminate: self];
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)Sender;
|
||||
{
|
||||
if(!IsEngineExitRequested() || ([NSThread gameThread] && [NSThread gameThread] != [NSThread mainThread]))
|
||||
{
|
||||
RequestEngineExit(TEXT("applicationShouldTerminate"));
|
||||
return NSTerminateLater;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NSTerminateNow;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)Notification
|
||||
{
|
||||
//install the custom quit event handler
|
||||
NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
|
||||
[appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
|
||||
|
||||
RunGameThread(self, @selector(runGameThread:));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
for (int32 Option = 1; Option < argc; Option++)
|
||||
{
|
||||
GSavedCommandLine += TEXT(" ");
|
||||
FString Argument(ANSI_TO_TCHAR(argv[Option]));
|
||||
if (Argument.Contains(TEXT(" ")))
|
||||
{
|
||||
if (Argument.Contains(TEXT("=")))
|
||||
{
|
||||
FString ArgName;
|
||||
FString ArgValue;
|
||||
Argument.Split( TEXT("="), &ArgName, &ArgValue );
|
||||
Argument = FString::Printf( TEXT("%s=\"%s\""), *ArgName, *ArgValue );
|
||||
}
|
||||
else
|
||||
{
|
||||
Argument = FString::Printf(TEXT("\"%s\""), *Argument);
|
||||
}
|
||||
}
|
||||
GSavedCommandLine += Argument;
|
||||
}
|
||||
|
||||
SCOPED_AUTORELEASE_POOL;
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setDelegate:[UE4AppDelegate new]];
|
||||
[NSApp run];
|
||||
return 0;
|
||||
}
|
||||
17
Source/Arona/EntryPoints/Windows/AronaMainWindows.cpp
Normal file
17
Source/Arona/EntryPoints/Windows/AronaMainWindows.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "EntryPoints/AronaApp.h"
|
||||
#include "Windows/WindowsHWrapper.h"
|
||||
|
||||
|
||||
/**
|
||||
* WinMain, called when the application is started
|
||||
*/
|
||||
int WINAPI WinMain( _In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR, _In_ int nCmdShow )
|
||||
{
|
||||
// do the Arona thing
|
||||
RunArona(GetCommandLineW());
|
||||
|
||||
return 0;
|
||||
}
|
||||
65
Source/Arona/Render/UpdatableTexture.cpp
Normal file
65
Source/Arona/Render/UpdatableTexture.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "UpdatableTexture.h"
|
||||
|
||||
#include "Async.h"
|
||||
#include "Thread/MainThreadEventList.h"
|
||||
#include "SlateApplication.h"
|
||||
|
||||
DECLARE_THREAD_MESSAGE(FMainThreadEventList, UpdatableTexture,
|
||||
FUpdatableTexture* Texture;
|
||||
)
|
||||
{
|
||||
Args.Texture->Update();
|
||||
Args.Texture->UpdateCaller.Reset();
|
||||
UE_LOG(LogTemp, Log, TEXT("2"))
|
||||
}
|
||||
|
||||
FUpdatableTexture::FUpdatableTexture(FIntPoint InTextureSize) : UpdateCaller(&FUpdatableTexture::AsyncUpdate, this)
|
||||
{
|
||||
Data.Resize(InTextureSize);
|
||||
Texture = FSlateApplication::Get().GetRenderer()->CreateUpdatableTexture(Data.TextureSize.X, Data.TextureSize.Y);
|
||||
Texture->UpdateTexture(Data.Data);
|
||||
}
|
||||
|
||||
void FUpdatableTexture::Resize(FIntPoint NewSize)
|
||||
{
|
||||
if (NewSize.X > 16384)
|
||||
NewSize.X = 16384;
|
||||
if (NewSize.Y > 16384)
|
||||
NewSize.Y = 16384;
|
||||
|
||||
NextSize = NewSize;
|
||||
ReSized = true;
|
||||
}
|
||||
|
||||
void FUpdatableTexture::RequestUpdate(bool Async)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Request Update"))
|
||||
if (Async)
|
||||
{
|
||||
UpdateCaller.Call();
|
||||
}
|
||||
else
|
||||
{
|
||||
RedrawImage.ExecuteIfBound(Data);
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void FUpdatableTexture::Update()
|
||||
{
|
||||
if (ReSized)
|
||||
Texture->ResizeTexture(Data.TextureSize.X, Data.TextureSize.Y);
|
||||
Texture->UpdateTexture(Data.Data);
|
||||
ReSized = false;
|
||||
OnOverRedraw.ExecuteIfBound();
|
||||
}
|
||||
|
||||
void FUpdatableTexture::AsyncUpdate()
|
||||
{
|
||||
Async(EAsyncExecution::ThreadPool, [this]()
|
||||
{
|
||||
Data.Resize(NextSize);
|
||||
RedrawImage.ExecuteIfBound(Data);
|
||||
PUSH_THREAD_EVENT(UpdatableTexture, this);
|
||||
});
|
||||
}
|
||||
124
Source/Arona/Render/UpdatableTexture.h
Normal file
124
Source/Arona/Render/UpdatableTexture.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "RenderingCommon.h"
|
||||
#include "SlateUpdatableTexture.h"
|
||||
#include "Thread/ThreadMessage.h"
|
||||
#include "Misc/CallRateLimiter.h"
|
||||
|
||||
struct FImageData
|
||||
{
|
||||
TArray<uint8> Data;
|
||||
FIntPoint TextureSize = FIntPoint::ZeroValue;
|
||||
|
||||
bool Resize(FIntPoint NewSize)
|
||||
{
|
||||
if (NewSize == TextureSize)
|
||||
return false;
|
||||
if (NewSize.X <= 0 || NewSize.Y <= 0)
|
||||
return false;
|
||||
TextureSize = NewSize;
|
||||
Data.SetNumZeroed(TextureSize.X * TextureSize.Y * 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClearColor(const FColor& Color)
|
||||
{
|
||||
for (int32 x = 0; x < TextureSize.X; ++x)
|
||||
{
|
||||
for (int32 y = 0; y < TextureSize.Y; ++y)
|
||||
{
|
||||
DrawPixel(x, y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPixel(const FIntPoint& Pos, const FColor& Color)
|
||||
{
|
||||
DrawPixel(Pos.X, Pos.Y, Color);
|
||||
}
|
||||
|
||||
void DrawPixel(int32 X, int32 Y, const FColor& Color)
|
||||
{
|
||||
#if UE_BUILD_DEBUG
|
||||
const bool Error = X < 0 || X >= TextureSize.X || Y < 0 || Y >= TextureSize.Y;
|
||||
ensureMsgf(!Error, TEXT("X: %d, Y: %d, TextureSize: %d, %d"), X, Y, TextureSize.X, TextureSize.Y);
|
||||
if (Error)
|
||||
return;
|
||||
#endif
|
||||
const int32 Pixel = X + Y * TextureSize.X;
|
||||
uint8* Ptr = Data.GetData() + Pixel * 4;
|
||||
*Ptr++ = Color.R;
|
||||
*Ptr++ = Color.G;
|
||||
*Ptr++ = Color.B;
|
||||
*Ptr = Color.A;
|
||||
}
|
||||
|
||||
void DrawLine(const FIntPoint& P1, const FIntPoint& P2, const FColor& Color)
|
||||
{
|
||||
int32 x1 = P1.X;
|
||||
int32 y1 = P1.Y;
|
||||
const int32& x2 = P2.X;
|
||||
const int32& y2 = P2.Y;
|
||||
|
||||
const int dx = abs(x2 - x1);
|
||||
const int dy = abs(y2 - y1);
|
||||
const int sx = (x1 < x2) ? 1 : -1;
|
||||
const int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (true)
|
||||
{
|
||||
DrawPixel(x1, y1, Color);
|
||||
|
||||
if (x1 == x2 && y1 == y2)
|
||||
break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy)
|
||||
{
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx)
|
||||
{
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FColor& operator[](int32 PixelIndex)
|
||||
{
|
||||
return *(FColor*)(Data.GetData() + PixelIndex * 4);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FUpdatableImageDataEvent, FImageData&)
|
||||
|
||||
class FUpdatableTexture : public ISlateViewport, public TSharedFromThis<FUpdatableTexture>
|
||||
{
|
||||
FRIEND_THREAD_MESSAGE(UpdatableTexture)
|
||||
public:
|
||||
FUpdatableTexture(FIntPoint InTextureSize);
|
||||
|
||||
void Resize(FIntPoint NewSize);
|
||||
void RequestUpdate(bool Async = true);
|
||||
|
||||
virtual FIntPoint GetSize() const override { return Data.TextureSize; }
|
||||
virtual FSlateShaderResource* GetViewportRenderTargetTexture() const override { return Texture->GetSlateResource(); }
|
||||
virtual bool RequiresVsync() const override { return false; }
|
||||
ISlateViewport* GetViewportInterface() { return this; }
|
||||
|
||||
FUpdatableImageDataEvent RedrawImage;
|
||||
FSimpleDelegate OnOverRedraw;
|
||||
private:
|
||||
FCallOnce UpdateCaller;
|
||||
|
||||
void Update(); // 只能在主线程调用
|
||||
void AsyncUpdate();
|
||||
|
||||
FSlateUpdatableTexture* Texture;
|
||||
FImageData Data;
|
||||
bool ReSized = false;
|
||||
FIntPoint NextSize;
|
||||
};
|
||||
53
Source/Arona/Singleton/SingletonManager.cpp
Normal file
53
Source/Arona/Singleton/SingletonManager.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "SingletonManager.h"
|
||||
|
||||
#include "Misc/AronaConfig.h"
|
||||
#include "Singleton/CallRateLimiterManager.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "Singleton/MixerList.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "Singleton/PortAudioAPI.h"
|
||||
#include "UI/Widget/WindowManager.h"
|
||||
|
||||
#define REGISTER_MANAGER(ManagerClass) RegisterManager(&ManagerClass::Get());
|
||||
|
||||
void FSingletonManager::Init()
|
||||
{
|
||||
REGISTER_MANAGER(FCallRateLimiterManager)
|
||||
REGISTER_MANAGER(FAronaConfig)
|
||||
REGISTER_MANAGER(FMixerList)
|
||||
REGISTER_MANAGER(FPortAudioAPI)
|
||||
REGISTER_MANAGER(FPluginHostList)
|
||||
REGISTER_MANAGER(FMidiSequencer)
|
||||
REGISTER_MANAGER(FWindowManager)
|
||||
|
||||
for (ISingleton* Manager : Managers)
|
||||
{
|
||||
Manager->PostInit();
|
||||
}
|
||||
}
|
||||
|
||||
void FSingletonManager::Release()
|
||||
{
|
||||
for (int32 i = Managers.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
ISingleton* SingletonImpl = Managers[i];
|
||||
SingletonImpl->BeginRelease();
|
||||
}
|
||||
for (int32 i = Managers.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
ISingleton* SingletonImpl = Managers[i];
|
||||
SingletonImpl->Release();
|
||||
}
|
||||
Managers.Reset();
|
||||
}
|
||||
|
||||
void FSingletonManager::RegisterManager(ISingleton* Manager)
|
||||
{
|
||||
Manager->Init();
|
||||
Managers.Add(Manager);
|
||||
UE_LOG(SingletonLog, Log, TEXT("%s Registered"), *Manager->GetName().ToString());
|
||||
}
|
||||
|
||||
FSingletonManager::FSingletonManager()
|
||||
{
|
||||
}
|
||||
21
Source/Arona/Singleton/SingletonManager.h
Normal file
21
Source/Arona/Singleton/SingletonManager.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "Singleton/Singleton.h"
|
||||
|
||||
class FSingletonManager
|
||||
{
|
||||
public:
|
||||
static FSingletonManager& GetInstance()
|
||||
{
|
||||
static FSingletonManager Instance;
|
||||
return Instance;
|
||||
}
|
||||
static const char* GetName() { return "singleton_manager"; }
|
||||
void Init();
|
||||
void Release();
|
||||
|
||||
void RegisterManager(ISingleton* Manager);
|
||||
private:
|
||||
FSingletonManager();
|
||||
TArray<ISingleton*> Managers;
|
||||
};
|
||||
48
Source/Arona/Test.cpp
Normal file
48
Source/Arona/Test.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "Test.h"
|
||||
|
||||
#include "Midi/MifiFile.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "Singleton/PortAudioAPI.h"
|
||||
|
||||
void AronaTest()
|
||||
{
|
||||
TArray<FAudioDeviceInfo> AudioDeviceInfos = FPortAudioAPI::Get().GetDevices();
|
||||
for (const FAudioDeviceInfo& AudioDeviceInfo : AudioDeviceInfos)
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Audio Device%d: %s"), AudioDeviceInfo.DeviceIndex, *AudioDeviceInfo.Name);
|
||||
}
|
||||
|
||||
const FString& MidiFilePath = TEXT("E:\\Projects\\Arona\\脑浆炸裂女孩 - 初音ミク.mid");
|
||||
// const FString& PluginFilePath = TEXT("D:\\Projects\\Rolling\\4Front Piano x64.dll");
|
||||
const FString& PluginFilePath = TEXT("F:\\VST\\VST64\\4Front Piano x64.dll");
|
||||
|
||||
FMidiFile Midi;
|
||||
Midi.readFrom(MidiFilePath);
|
||||
|
||||
FMidiSequencer& MidiSequencer = FMidiSequencer::Get();
|
||||
// MidiSequencer.SetTicksPerQuarter(Midi.getTimeFormat());
|
||||
|
||||
FMidiPattern* MidiPattern = MidiSequencer.NewMidiPattern();
|
||||
MidiPattern->Name = TEXT("脑浆炸裂女孩 - 初音ミク");
|
||||
|
||||
for (int i = 0; i < Midi.getNumTracks(); ++i)
|
||||
{
|
||||
FPluginHost* Plugin = FPluginHostList::Get().TryLoadPlugin(PluginFilePath);
|
||||
FPluginHostList::Get().RegisterInstrument(Plugin);
|
||||
|
||||
const FMidiMessageSequence* Track = Midi.getTrack(i);
|
||||
FMidiMessageSequence& Sequence = MidiPattern->GetSequence(Plugin);
|
||||
Sequence.addSequence(*Track, 0);
|
||||
Sequence.updateMatchedPairs();
|
||||
}
|
||||
// FPluginHost* Kontakt = FPluginHostList::Get().TryLoadPlugin(TEXT("I:\\VST\\VST64\\Kontakt.dll"));
|
||||
// FPluginHostList::Get().RegisterInstrument(Kontakt);
|
||||
|
||||
|
||||
// MidiPattern->RequestCreateInstance(0, 0, 0, 0, MidiPattern->GetMidiLength(), 0, MidiPattern->GetSampleLength());
|
||||
// MidiSequencer.PatternSelector.SelectPattern(MidiPattern);
|
||||
// MidiSequencer.Playing = true;
|
||||
FPluginHostList::Get().TryLoadSampler(TEXT("F:\\FL垃圾桶\\Kawaii Anokoga Kiniiranai.mp3"));
|
||||
FPluginHostList::Get().TryLoadSampler(TEXT("F:\\Sample\\Cymatics - Empire Hip Hop Sample Pack\\Melody Loops\\Cymatics - Empire Melody Loop 1 - 100 BPM G Min.wav"));
|
||||
}
|
||||
4
Source/Arona/Test.h
Normal file
4
Source/Arona/Test.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
void AronaTest();
|
||||
11
Source/Arona/UI/AronaModuleCommands.cpp
Normal file
11
Source/Arona/UI/AronaModuleCommands.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaModuleCommands.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FAronaModuleModule"
|
||||
|
||||
void FAronaModuleCommands::RegisterCommands()
|
||||
{
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
20
Source/Arona/UI/AronaModuleCommands.h
Normal file
20
Source/Arona/UI/AronaModuleCommands.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Framework/Commands/Commands.h"
|
||||
#include "UI/Style/AronaStyle.h"
|
||||
|
||||
class ARONA_API FAronaModuleCommands : public TCommands<FAronaModuleCommands>
|
||||
{
|
||||
public:
|
||||
|
||||
FAronaModuleCommands()
|
||||
: TCommands<FAronaModuleCommands>(TEXT("AronaModule"), NSLOCTEXT("Contexts", "AronaModule", "AronaModule application"), NAME_None, FAronaStyle::Get().GetStyleSetName())
|
||||
{
|
||||
}
|
||||
|
||||
// TCommands<> interface
|
||||
virtual void RegisterCommands() override;
|
||||
};
|
||||
153
Source/Arona/UI/Style/AronaStyle.cpp
Normal file
153
Source/Arona/UI/Style/AronaStyle.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AronaStyle.h"
|
||||
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Misc/AronaConfig.h"
|
||||
|
||||
TSharedPtr< FAronaStyle > FAronaStyle::StyleInstance = NULL;
|
||||
|
||||
void FAronaStyle::Initialize()
|
||||
{
|
||||
if (!StyleInstance.IsValid())
|
||||
{
|
||||
StyleInstance = Create();
|
||||
StyleInstance->LoadConfig();
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void FAronaStyle::Shutdown()
|
||||
{
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
|
||||
ensure(StyleInstance.IsUnique());
|
||||
StyleInstance.Reset();
|
||||
}
|
||||
|
||||
const FButtonStyle* FAronaStyle::GetButtonStyle(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return &StyleInstance->GetWidgetStyle<FButtonStyle>(PropertyName, Specifier, nullptr);
|
||||
}
|
||||
|
||||
const FSlateBrush* FAronaStyle::GetSlateBrush(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetBrush(PropertyName, Specifier, nullptr);
|
||||
}
|
||||
|
||||
FSlateFontInfo FAronaStyle::GetFontInfo(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetFontStyle(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||
#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||
#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
|
||||
#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
|
||||
|
||||
#define MakeImageBrush(Name, Path, Size) \
|
||||
FSlateImageBrush Name = IMAGE_BRUSH(TEXT(Path), Size); \
|
||||
Name.Tiling = ESlateBrushTileType::NoTile; \
|
||||
Name.ImageSize = Size; \
|
||||
Name.DrawAs = ESlateBrushDrawType::Image; \
|
||||
Name.ImageType = ESlateBrushImageType::FullColor; \
|
||||
|
||||
|
||||
void FAronaStyle::LoadConfig()
|
||||
{
|
||||
TSharedRef<FAronaStyle> Style = StyleInstance.ToSharedRef();
|
||||
|
||||
AddButtonStyle(CloseButtonStyleName);
|
||||
AddFontInfo(DefaultFontName);
|
||||
AddFloatValue(VolumeMeterBarSpace);
|
||||
AddImageBrush(PianoRollBackground);
|
||||
AddImageBrush(VolumeMeterBar);
|
||||
AddImageBrush(WhiteBrush);
|
||||
AddImageBrush(BlackKey);
|
||||
AddImageBrush(WhiteKey);
|
||||
}
|
||||
|
||||
void FAronaStyle::AddImageBrush(FName Key)
|
||||
{
|
||||
TOptional<FString> ImageFileName = GetConfigValue<FString>(Key);
|
||||
if (!ImageFileName)
|
||||
return;
|
||||
TOptional<FVector2f> ImageSize = GetConfigValue<FVector2f>(FName(Key.ToString() + "Size"));
|
||||
|
||||
const FString ImageFilePathName = StyleInstance->RootToContentDir(ImageFileName.GetValue(), TEXT(".png"));
|
||||
StyleInstance->Set(Key, new FSlateImageBrush(ImageFilePathName, ImageSize.Get(FVector2f(16))));
|
||||
}
|
||||
|
||||
void FAronaStyle::AddFloatValue(FName Key)
|
||||
{
|
||||
TOptional<float> Value = GetConfigValue<float>(Key);
|
||||
if (!Value)
|
||||
return;
|
||||
StyleInstance->Set(Key, Value.GetValue());
|
||||
}
|
||||
|
||||
void FAronaStyle::AddFontInfo(FName Key)
|
||||
{
|
||||
const TOptional<FString> FontName = GetConfigValue<FString>(Key);
|
||||
if (!FontName)
|
||||
return;
|
||||
const TOptional<float> FontSize = GetConfigValue<float>(FName(Key.ToString() + "Size"));
|
||||
StyleInstance->Set(Key, FSlateFontInfo(StyleInstance->RootToContentDir(FontName.GetValue(), TEXT(".ttf")), FontSize.Get(12)));
|
||||
}
|
||||
|
||||
void FAronaStyle::AddButtonStyle(FName Key)
|
||||
{
|
||||
const FString ButtonNormalName = Key.ToString() + "Normal";
|
||||
const FString ButtonHoveredName = Key.ToString() + "Hovered";
|
||||
const FString ButtonPressedName = Key.ToString() + "Pressed";
|
||||
const TOptional<FString> ButtonNormal = GetConfigValue<FString>(FName(ButtonNormalName));
|
||||
const TOptional<FString> ButtonHovered = GetConfigValue<FString>(FName(ButtonHoveredName));
|
||||
const TOptional<FString> ButtonPressed = GetConfigValue<FString>(FName(ButtonPressedName));
|
||||
const TOptional<FVector2f> Size = GetConfigValue<FVector2f>(FName(Key.ToString() + "Size"));
|
||||
|
||||
FButtonStyle ButtonStyle;
|
||||
const FSlateImageBrush ButtonNormalBrush = FSlateImageBrush(StyleInstance->RootToContentDir(ButtonNormal.Get(""), TEXT(".png")), Size.Get(FVector2f(16)));
|
||||
const FSlateImageBrush ButtonHoveredBrush = FSlateImageBrush(StyleInstance->RootToContentDir(ButtonHovered.Get(""), TEXT(".png")), Size.Get(FVector2f(16)));
|
||||
const FSlateImageBrush ButtonPressedBrush = FSlateImageBrush(StyleInstance->RootToContentDir(ButtonPressed.Get(""), TEXT(".png")), Size.Get(FVector2f(16)));
|
||||
ButtonStyle.SetNormal(ButtonNormalBrush).SetHovered(ButtonHoveredBrush).SetPressed(ButtonPressedBrush);
|
||||
StyleInstance->Set(CloseButtonStyleName, ButtonStyle);
|
||||
}
|
||||
|
||||
TSharedRef< FAronaStyle > FAronaStyle::Create()
|
||||
{
|
||||
TSharedRef<FAronaStyle> Style = MakeShareable(new FAronaStyle("AronaStyle"));
|
||||
|
||||
FString SkinName;
|
||||
FAronaConfig::GetValue("Skin", "Skin", SkinName);
|
||||
Style->SkinDir = FPaths::ProjectContentDir() / TEXT("Skin") / SkinName;
|
||||
Style->Config.Read(Style->SkinDir / TEXT("Skin.ini"));
|
||||
|
||||
Style->SetContentRoot(Style->SkinDir);
|
||||
return Style;
|
||||
}
|
||||
|
||||
#undef IMAGE_BRUSH
|
||||
#undef BOX_BRUSH
|
||||
#undef BORDER_BRUSH
|
||||
#undef TTF_FONT
|
||||
#undef OTF_FONT
|
||||
|
||||
void FAronaStyle::ReloadTextures()
|
||||
{
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
|
||||
}
|
||||
}
|
||||
|
||||
const ISlateStyle& FAronaStyle::Get()
|
||||
{
|
||||
return *StyleInstance;
|
||||
}
|
||||
|
||||
const FName& FAronaStyle::GetStyleSetName() const
|
||||
{
|
||||
static FName Name(TEXT("AronaModuleStyle"));
|
||||
return Name;
|
||||
}
|
||||
146
Source/Arona/UI/Style/AronaStyle.h
Normal file
146
Source/Arona/UI/Style/AronaStyle.h
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ConfigCacheIni.h"
|
||||
#include "SlateTypes.h"
|
||||
#include "Styling/SlateStyle.h"
|
||||
|
||||
inline FName CloseButtonStyleName("CloseButton");
|
||||
inline FName VolumeMeterBar("VolumeMeter");
|
||||
inline FName VolumeMeterBarSpace("VolumeMeterSpace");
|
||||
inline FName DefaultFontName("DefaultFont");
|
||||
inline FName PianoRollBackground("PianoRollBackground");
|
||||
inline FName WhiteBrush("WhiteBrush");
|
||||
inline FName WhiteKey("WhiteKey");
|
||||
inline FName BlackKey("BlackKey");
|
||||
|
||||
/** */
|
||||
class FAronaStyle : public FSlateStyleSet
|
||||
{
|
||||
public:
|
||||
FAronaStyle(const FName& InStyleSetName)
|
||||
: FSlateStyleSet(InStyleSetName)
|
||||
{
|
||||
}
|
||||
|
||||
static void Initialize();
|
||||
|
||||
static void Shutdown();
|
||||
|
||||
/** reloads textures used by slate renderer */
|
||||
static void ReloadTextures();
|
||||
|
||||
/** @return The Slate style set for the Shooter game */
|
||||
static const ISlateStyle& Get();
|
||||
|
||||
virtual const FName& GetStyleSetName() const override;
|
||||
|
||||
static const FButtonStyle* GetButtonStyle(const FName& PropertyName, const ANSICHAR* Specifier = nullptr);
|
||||
static const FSlateBrush* GetSlateBrush(const FName& PropertyName, const ANSICHAR* Specifier = nullptr);
|
||||
static FSlateFontInfo GetFontInfo(const FName& PropertyName, const ANSICHAR* Specifier = nullptr);
|
||||
|
||||
template<typename T>
|
||||
static T GetValue(const FName& PropertyName, const ANSICHAR* Specifier = nullptr);
|
||||
|
||||
FString SkinDir;
|
||||
FConfigFile Config;
|
||||
private:
|
||||
void LoadConfig();
|
||||
template<typename T>
|
||||
TOptional<T> GetConfigValue(const FName& Key, FName FindSection = FName("Resource"));
|
||||
|
||||
void AddImageBrush(FName Key);
|
||||
void AddFloatValue(FName Key);
|
||||
void AddFontInfo(FName Key);
|
||||
void AddButtonStyle(FName Key);
|
||||
|
||||
// static FConfigFile Config;
|
||||
static TSharedRef<FAronaStyle> Create();
|
||||
static TSharedPtr<FAronaStyle> StyleInstance;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline float FAronaStyle::GetValue(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetFloat(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline FVector2D FAronaStyle::GetValue(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetVector(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline const FLinearColor& FAronaStyle::GetValue(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetColor(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline const FSlateColor FAronaStyle::GetValue(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetSlateColor(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline const FMargin& FAronaStyle::GetValue(const FName& PropertyName, const ANSICHAR* Specifier)
|
||||
{
|
||||
return StyleInstance->GetMargin(PropertyName, Specifier);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TOptional<FString> FAronaStyle::GetConfigValue(const FName& Key, FName FindSection)
|
||||
{
|
||||
TSharedRef<FAronaStyle> Style = StaticCastSharedRef<FAronaStyle>(StyleInstance.ToSharedRef());
|
||||
if (!Style->Config.Contains(FindSection.ToString()))
|
||||
return TOptional<FString>();
|
||||
const FConfigSection* Section = Style->Config.Find(FindSection.ToString());
|
||||
if (Section == nullptr)
|
||||
return TOptional<FString>();
|
||||
const FConfigValue* ConfigValue = Section->Find(Key);
|
||||
if (ConfigValue == nullptr)
|
||||
return TOptional<FString>();
|
||||
return TOptional<FString>(ConfigValue->GetValue());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TOptional<FVector2f> FAronaStyle::GetConfigValue(const FName& Key, FName FindSection)
|
||||
{
|
||||
TSharedRef<FAronaStyle> Style = StaticCastSharedRef<FAronaStyle>(StyleInstance.ToSharedRef());
|
||||
if (!Style->Config.Contains(FindSection.ToString()))
|
||||
return TOptional<FVector2f>();
|
||||
const FConfigSection* Section = Style->Config.Find(FindSection.ToString());
|
||||
if (Section == nullptr)
|
||||
return TOptional<FVector2f>();
|
||||
const FConfigValue* ConfigValue = Section->Find(Key);
|
||||
if (ConfigValue == nullptr)
|
||||
return TOptional<FVector2f>();
|
||||
FVector2f Value;
|
||||
Value.InitFromString(ConfigValue->GetValue());
|
||||
return TOptional<FVector2f>(Value);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TOptional<float> FAronaStyle::GetConfigValue(const FName& Key, FName FindSection)
|
||||
{
|
||||
TSharedRef<FAronaStyle> Style = StaticCastSharedRef<FAronaStyle>(StyleInstance.ToSharedRef());
|
||||
if (!Style->Config.Contains(FindSection.ToString()))
|
||||
return TOptional<float>();
|
||||
const FConfigSection* Section = Style->Config.Find(FindSection.ToString());
|
||||
if (Section == nullptr)
|
||||
return TOptional<float>();
|
||||
const FConfigValue* ConfigValue = Section->Find(Key);
|
||||
if (ConfigValue == nullptr)
|
||||
return TOptional<float>();
|
||||
FString String = ConfigValue->GetValue();
|
||||
String = String.Replace(TEXT("x"), TEXT("X"));
|
||||
String = String.Replace(TEXT("y"), TEXT("Y"));
|
||||
String = String.Replace(TEXT(","), TEXT(""));
|
||||
String = String.Replace(TEXT(","), TEXT(""));
|
||||
return TOptional<float>(FCString::Atof(*String));
|
||||
}
|
||||
|
||||
|
||||
172
Source/Arona/UI/Widget/ChannelInterface/SChannelNode.cpp
Normal file
172
Source/Arona/UI/Widget/ChannelInterface/SChannelNode.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SChannelNode.h"
|
||||
|
||||
#include "MultiBoxBuilder.h"
|
||||
#include "SButton.h"
|
||||
#include "SChannelNodeButton.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SMenuAnchor.h"
|
||||
#include "SOverlay.h"
|
||||
#include "SSplitter.h"
|
||||
#include "STextBlock.h"
|
||||
#include "Singleton/MixerList.h"
|
||||
#include "Mixer/MixerTrack.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SChannelNode::Construct(const FArguments& InArgs, FChannelNode* InChannelNode, FChannelInterface* InNodeOwner, int32 NodeNameIndex)
|
||||
{
|
||||
ChannelNode = InChannelNode;
|
||||
ChannelNodeType = InArgs._ChannelNodeType;
|
||||
NodeOwner = InNodeOwner;
|
||||
NodeIndex = NodeNameIndex;
|
||||
|
||||
|
||||
FText PortName = FText::FromString(GetNodeName(NodeNameIndex));
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1)
|
||||
[
|
||||
SAssignNew(PortTextBlock, STextBlock)
|
||||
.Text(PortName)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SAssignNew(ChangeNodeButton, SButton)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
ShowNodeSelectMenu();
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SAssignNew(NodeNameTextBlock, STextBlock)
|
||||
.Text(FText::FromString(ChannelNode->GetName()))
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void SChannelNode::ShowNodeSelectMenu()
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
MenuBuilder.AddWidget(
|
||||
CreateNodeSelectMenu(),
|
||||
FText()
|
||||
);
|
||||
auto Pos = ChangeNodeButton->GetTickSpaceGeometry().GetAbsolutePosition();
|
||||
Pos.Y += ChangeNodeButton->GetTickSpaceGeometry().GetAbsoluteSize().Y;
|
||||
FSlateApplication::Get().PushMenu(SharedThis(this), FWidgetPath(), MenuBuilder.MakeWidget(), Pos, FPopupTransitionEffect::ComboButton);
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SChannelNode::CreateNodeSelectMenu()
|
||||
{
|
||||
TSharedRef<SVerticalBox> MenuBox = SNew(SVerticalBox);
|
||||
|
||||
MenuBox->AddSlot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SChannelNodeButton, FNullChannelNode::Get())
|
||||
.OnClickedDelegate(this, &SChannelNode::OnMenuNodeSelected)
|
||||
];
|
||||
|
||||
FMixerList& MixerList = FMixerList::Get();
|
||||
for (const FMixerTrack* Mixer : MixerList)
|
||||
{
|
||||
MenuBox->AddSlot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(Mixer->GetName()))
|
||||
];
|
||||
const TArray<FChannelNode*>& MixerOutputNodes = Mixer->GetChannelInterface()->OutputChannelNodes;
|
||||
for (FChannelNode* MixerChannelNode : MixerOutputNodes)
|
||||
{
|
||||
MenuBox->AddSlot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SChannelNodeButton, MixerChannelNode)
|
||||
.OnClickedDelegate(this, &SChannelNode::OnMenuNodeSelected)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return MenuBox;
|
||||
}
|
||||
|
||||
void SChannelNode::OnMenuNodeSelected(FChannelNode* Node)
|
||||
{
|
||||
check(NodeOwner);
|
||||
|
||||
switch (ChannelNodeType)
|
||||
{
|
||||
case EChannelNodeType::InputNode:
|
||||
{
|
||||
NodeIndex = NodeOwner->GetInputNodeIndex(ChannelNode);
|
||||
NodeOwner->SetInputChannel(NodeIndex, Node);
|
||||
break;
|
||||
}
|
||||
case EChannelNodeType::OutputNode:
|
||||
{
|
||||
NodeIndex = NodeOwner->GetOutputNodeIndex(ChannelNode);
|
||||
NodeOwner->SetOutputChannel(NodeIndex, Node);
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
}
|
||||
|
||||
ChannelNode = Node;
|
||||
NodeNameTextBlock->SetText(FText::FromString(Node->GetName()));
|
||||
}
|
||||
|
||||
FString SChannelNode::GetNodeName(int32 NodeNameIndex)
|
||||
{
|
||||
FString PortName;
|
||||
|
||||
TMap<int32, FName> PortNames;
|
||||
|
||||
switch (ChannelNodeType)
|
||||
{
|
||||
case EChannelNodeType::InputNode:
|
||||
PortNames = NodeOwner->GetInputChannelNodeName(ChannelNode);
|
||||
break;
|
||||
case EChannelNodeType::OutputNode:
|
||||
PortNames = NodeOwner->GetOutputChannelNodeName(ChannelNode);
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
|
||||
const FName* NameA = PortNames.Find(NodeNameIndex);
|
||||
const FName* NameB = PortNames.Find(NodeNameIndex + 1);
|
||||
if (NameA && NameB)
|
||||
{
|
||||
if (*NameA == *NameB)
|
||||
PortName = NameA->ToString();
|
||||
else
|
||||
PortName = FString::Printf(TEXT("%s-%s"), *NameA->ToString(), *NameB->ToString());
|
||||
}
|
||||
else if (NameA)
|
||||
{
|
||||
PortName = NameA->ToString();
|
||||
}
|
||||
else if (NameB)
|
||||
{
|
||||
PortName = NameB->ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
PortName = TEXT("Null");
|
||||
}
|
||||
|
||||
return PortName;
|
||||
}
|
||||
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
49
Source/Arona/UI/Widget/ChannelInterface/SChannelNode.h
Normal file
49
Source/Arona/UI/Widget/ChannelInterface/SChannelNode.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "AudioBuffer/ChannelNode.h"
|
||||
|
||||
class SButton;
|
||||
class STextBlock;
|
||||
class SMenuAnchor;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SChannelNode : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
enum class EChannelNodeType
|
||||
{
|
||||
InputNode,
|
||||
OutputNode,
|
||||
};
|
||||
|
||||
SLATE_BEGIN_ARGS(SChannelNode)
|
||||
{
|
||||
}
|
||||
SLATE_ARGUMENT(EChannelNodeType, ChannelNodeType)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FChannelNode* InChannelNode, FChannelInterface* InNodeOwner, int32 NodeNameIndex);
|
||||
|
||||
FChannelNode::FChannelNodeDelegate OnRightClickedDelegate;
|
||||
private:
|
||||
void ShowNodeSelectMenu();
|
||||
TSharedRef<SWidget> CreateNodeSelectMenu();
|
||||
void OnMenuNodeSelected(FChannelNode* Node);
|
||||
FString GetNodeName(int32 NodeNameIndex = -1);
|
||||
|
||||
FChannelInterface* NodeOwner = nullptr;
|
||||
FChannelNode* ChannelNode = nullptr;
|
||||
EChannelNodeType ChannelNodeType = EChannelNodeType::InputNode;
|
||||
|
||||
TSharedPtr<SButton> ChangeNodeButton;
|
||||
TSharedPtr<STextBlock> NodeNameTextBlock;
|
||||
TSharedPtr<STextBlock> PortTextBlock;
|
||||
int32 NodeIndex = -1;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SChannelNodeButton.h"
|
||||
|
||||
#include "SButton.h"
|
||||
#include "SlateOptMacros.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
void SChannelNodeButton::Construct(const FArguments& InArgs, FChannelNode* InChannelNode)
|
||||
{
|
||||
ChannelNode = InChannelNode;
|
||||
OnClickedDelegate = InArgs._OnClickedDelegate;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString(ChannelNode->GetName()))
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
OnClickedDelegate.ExecuteIfBound(ChannelNode);
|
||||
return FReply::Handled();
|
||||
})
|
||||
];
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
28
Source/Arona/UI/Widget/ChannelInterface/SChannelNodeButton.h
Normal file
28
Source/Arona/UI/Widget/ChannelInterface/SChannelNodeButton.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "AudioBuffer/ChannelNode.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SChannelNodeButton : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SChannelNodeButton)
|
||||
{}
|
||||
|
||||
SLATE_EVENT(FChannelNode::FChannelNodeDelegate, OnClickedDelegate)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FChannelNode* InChannelNode);
|
||||
|
||||
FChannelNode::FChannelNodeDelegate OnClickedDelegate;
|
||||
private:
|
||||
FChannelNode* ChannelNode = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPluginHostChannelInterface.h"
|
||||
|
||||
#include "SChannelNode.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "SSpacer.h"
|
||||
#include "STextBlock.h"
|
||||
#include "AudioBuffer/ChannelInterface.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SPluginHostChannelInterface::Construct(const FArguments& InArgs, FPluginHost* InPluginHost)
|
||||
{
|
||||
PluginHost = InPluginHost;
|
||||
const FChannelInterface* ChannelInterface = PluginHost->ChannelInterface;
|
||||
|
||||
InPluginHost->UpdateChannelNodeName();
|
||||
|
||||
const auto& InputNodes = ChannelInterface->InputChannelNodes;
|
||||
const auto& OutputNodes = ChannelInterface->OutputChannelNodes;
|
||||
|
||||
TSharedPtr<SVerticalBox> InputNodesBox;
|
||||
TSharedPtr<SVerticalBox> OutputNodesBox;
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SScrollBox)
|
||||
+SScrollBox::Slot()
|
||||
.AutoSize()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString("Input: "))
|
||||
]
|
||||
+SScrollBox::Slot()
|
||||
.AutoSize()
|
||||
[
|
||||
SAssignNew(InputNodesBox, SVerticalBox)
|
||||
]
|
||||
+SScrollBox::Slot()
|
||||
.AutoSize()
|
||||
[
|
||||
SNew(SSpacer)
|
||||
.Size(FVector2D(1, 3))
|
||||
]
|
||||
+SScrollBox::Slot()
|
||||
.AutoSize()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString("Output: "))
|
||||
]
|
||||
+SScrollBox::Slot()
|
||||
.AutoSize()
|
||||
[
|
||||
SAssignNew(OutputNodesBox, SVerticalBox)
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
if (InputNodes.Num() > 0)
|
||||
{
|
||||
const TSharedRef<SChannelNode> DefaultInputNodeWidget = BuildChannelNode(InputNodes[0], SChannelNode::EChannelNodeType::InputNode, 0).ToSharedRef();
|
||||
DefaultInputNodeWidget->SetEnabled(false);
|
||||
InputNodesBox->AddSlot()
|
||||
[
|
||||
DefaultInputNodeWidget
|
||||
];
|
||||
}
|
||||
for (int32 i = 1; i < InputNodes.Num(); ++i)
|
||||
{
|
||||
InputNodesBox->AddSlot()
|
||||
[
|
||||
BuildChannelNode(InputNodes[i], SChannelNode::EChannelNodeType::InputNode, i * 2).ToSharedRef()
|
||||
];
|
||||
}
|
||||
|
||||
if (OutputNodes.Num() > 0)
|
||||
{
|
||||
TSharedRef<SChannelNode> DefaultOutputNodeWidget = BuildChannelNode(OutputNodes[0], SChannelNode::EChannelNodeType::OutputNode, 0).ToSharedRef();
|
||||
DefaultOutputNodeWidget->SetEnabled(false);
|
||||
OutputNodesBox->AddSlot()
|
||||
[
|
||||
DefaultOutputNodeWidget
|
||||
];
|
||||
}
|
||||
for (int32 i = 1; i < OutputNodes.Num(); ++i)
|
||||
{
|
||||
OutputNodesBox->AddSlot()
|
||||
[
|
||||
BuildChannelNode(OutputNodes[i], SChannelNode::EChannelNodeType::OutputNode, i * 2).ToSharedRef()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<SChannelNode> SPluginHostChannelInterface::BuildChannelNode(FChannelNode* ChannelNode, SChannelNode::EChannelNodeType ChannelNodeType, int32 NodeNameIndex)
|
||||
{
|
||||
return SNew(SChannelNode, ChannelNode, PluginHost->ChannelInterface, NodeNameIndex)
|
||||
.ChannelNodeType(ChannelNodeType);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@@ -0,0 +1,31 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SChannelNode.h"
|
||||
#include "SCompoundWidget.h"
|
||||
|
||||
class FPluginHost;
|
||||
class FChannelNode;
|
||||
class SChannelNode;
|
||||
class FChannelInterface;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPluginHostChannelInterface : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPluginHostChannelInterface)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPluginHost* InPluginHost);
|
||||
private:
|
||||
TSharedPtr<SChannelNode> BuildChannelNode(FChannelNode* ChannelNode, SChannelNode::EChannelNodeType ChannelNodeType, int32 NodeNameIndex);
|
||||
|
||||
FPluginHost* PluginHost = nullptr;
|
||||
};
|
||||
105
Source/Arona/UI/Widget/ChannelRack/ChannelRack.cpp
Normal file
105
Source/Arona/UI/Widget/ChannelRack/ChannelRack.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "ChannelRack.h"
|
||||
|
||||
#include "ChannelRackItem.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "Pattern/MidiPattern.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SChannelRack::Construct(const FArguments& InArgs)
|
||||
{
|
||||
FPluginHostList::Get().OnInstrumentHostCreated.AddRaw(this, &SChannelRack::OnInstrumentHostCreated);
|
||||
FPluginHostList::Get().OnPluginHostRemoved.AddRaw(this, &SChannelRack::OnInstrumentHostRemoved);
|
||||
FPatternSelector::Get().OnPatternSelected.AddRaw(this, &SChannelRack::OnSelectPattern);
|
||||
|
||||
for (FPluginHost* PluginHost : FPluginHostList::Get().Instruments)
|
||||
{
|
||||
OnInstrumentHostCreated(PluginHost);
|
||||
}
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(ListView, SScrollBox)
|
||||
.AnimateWheelScrolling(true)
|
||||
.ScrollBarThickness(FVector2D(2, 2))
|
||||
];
|
||||
|
||||
OnSelectPattern(FPatternSelector::Get().GetSelectedPattern());
|
||||
}
|
||||
|
||||
void SChannelRack::SelectPattern(FMidiPattern* Pattern)
|
||||
{
|
||||
TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
auto& Children = ScrollPanel->Children;
|
||||
for (int i = 0; i < Children.Num(); ++i)
|
||||
{
|
||||
TSharedRef<SChannelRackItem, ESPMode::ThreadSafe> Item = StaticCastSharedRef<SChannelRackItem>(Children.GetChildAt(i));
|
||||
Item->SetPattern(Pattern);
|
||||
}
|
||||
}
|
||||
|
||||
void SChannelRack::OnSelectPattern(FPattern* Pattern)
|
||||
{
|
||||
if (!Pattern)
|
||||
return;
|
||||
if (Pattern->Type != EPatternType::Midi)
|
||||
return;
|
||||
SelectPattern((FMidiPattern*)Pattern);
|
||||
}
|
||||
|
||||
void SChannelRack::UpdateSplitterAPosition(float A)
|
||||
{
|
||||
TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
auto& Children = ScrollPanel->Children;
|
||||
for (int i = 0; i < Children.Num(); ++i)
|
||||
{
|
||||
TSharedPtr<SChannelRackItem, ESPMode::ThreadSafe> Item = StaticCastSharedRef<SChannelRackItem>(Children.GetChildAt(i));
|
||||
Item->SetSplliterASize(A);
|
||||
}
|
||||
}
|
||||
|
||||
void SChannelRack::UpdateSplitterBPosition(float B)
|
||||
{
|
||||
TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
auto& Children = ScrollPanel->Children;
|
||||
for (int i = 0; i < Children.Num(); ++i)
|
||||
{
|
||||
TSharedPtr<SChannelRackItem, ESPMode::ThreadSafe> Item = StaticCastSharedRef<SChannelRackItem>(Children.GetChildAt(i));
|
||||
Item->SetSplliterBSize(B);
|
||||
}
|
||||
}
|
||||
|
||||
void SChannelRack::OnInstrumentHostCreated(FPluginHost* PluginHost)
|
||||
{
|
||||
ListView->AddSlot()
|
||||
.AutoSize()
|
||||
.Padding(0, 0, 0, 10)
|
||||
[
|
||||
SNew(SChannelRackItem, PluginHost)
|
||||
.OnSplliterAPositionChanged(this, &SChannelRack::UpdateSplitterAPosition)
|
||||
.OnSplliterBPositionChanged(this, &SChannelRack::UpdateSplitterBPosition)
|
||||
];
|
||||
}
|
||||
|
||||
void SChannelRack::OnInstrumentHostRemoved(FPluginHost* PluginHost)
|
||||
{
|
||||
TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
FChildren* Children = ScrollPanel->GetChildren();
|
||||
for (int i = 0; i < Children->Num(); ++i)
|
||||
{
|
||||
const TSharedRef<SChannelRackItem> ChannelRackItem = StaticCastSharedRef<SChannelRackItem>(Children->GetChildAt(i));
|
||||
if (ChannelRackItem->GetPluginHost() == PluginHost)
|
||||
{
|
||||
ListView->RemoveSlot(ChannelRackItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
43
Source/Arona/UI/Widget/ChannelRack/ChannelRack.h
Normal file
43
Source/Arona/UI/Widget/ChannelRack/ChannelRack.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ChannelRackItem.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SListView.h"
|
||||
#include "UI/Widget/IChildWindow.h"
|
||||
|
||||
class FPattern;
|
||||
class FMidiPattern;
|
||||
class SScrollBox;
|
||||
class FPluginHost;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SChannelRack : public SCompoundWidget, public IChildWindow
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SChannelRack)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
virtual TSharedRef<SWidget> GetWidget() override { return AsShared(); }
|
||||
|
||||
void SelectPattern(FMidiPattern* Pattern);
|
||||
private:
|
||||
void OnSelectPattern(FPattern* Pattern);
|
||||
void UpdateSplitterAPosition(float A);
|
||||
void UpdateSplitterBPosition(float B);
|
||||
|
||||
void OnInstrumentHostCreated(FPluginHost* PluginHost);
|
||||
void OnInstrumentHostRemoved(FPluginHost* PluginHost);
|
||||
|
||||
TSharedPtr<SScrollBox> ListView;
|
||||
};
|
||||
143
Source/Arona/UI/Widget/ChannelRack/ChannelRackItem.cpp
Normal file
143
Source/Arona/UI/Widget/ChannelRack/ChannelRackItem.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "ChannelRackItem.h"
|
||||
|
||||
#include "ChannelRackMidiThumbnail.h"
|
||||
#include "MultiBoxBuilder.h"
|
||||
#include "SBox.h"
|
||||
#include "SButton.h"
|
||||
#include "SImage.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SSplitter.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "UI/Widget/WindowManager.h"
|
||||
#include "UI/Widget/ChannelInterface/SPluginHostChannelInterface.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FChannelRack"
|
||||
|
||||
void SChannelRackItem::Construct(const FArguments& InArgs, FPluginHost* InPluginHost)
|
||||
{
|
||||
PluginHost = InPluginHost;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(Splitter, SSplitter)
|
||||
.ResizeMode(ESplitterResizeMode::FixedSize)
|
||||
|
||||
+SSplitter::Slot()
|
||||
.OnSlotResized(InArgs._OnSplliterAPositionChanged)
|
||||
.MinSize(32)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(PluginHost->IsEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
||||
.OnCheckStateChanged(this, &SChannelRackItem::SetPluginHostEnabled)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.f)
|
||||
.VAlign(VAlign_Top)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString(PluginHost->Name))
|
||||
.OnClicked(this, &SChannelRackItem::TogglePluginHostEditor)
|
||||
]
|
||||
]
|
||||
|
||||
+SSplitter::Slot()
|
||||
.OnSlotResized(InArgs._OnSplliterBPositionChanged)
|
||||
[
|
||||
SNew(SBox)
|
||||
.HeightOverride(64)
|
||||
[
|
||||
SAssignNew(MidiThumbnail, SChannelRackMidiThumbnail, PluginHost)
|
||||
.Pattern(nullptr)
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
FReply SChannelRackItem::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
ShowPluginHostMenu();
|
||||
return FReply::Handled();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
void SChannelRackItem::SetPattern(FMidiPattern* InPattern)
|
||||
{
|
||||
MidiThumbnail->SetPattern(InPattern);
|
||||
}
|
||||
|
||||
void SChannelRackItem::SetSplliterASize(float X)
|
||||
{
|
||||
Splitter->SlotAt(0).SetSizeValue(X);
|
||||
}
|
||||
|
||||
void SChannelRackItem::SetSplliterBSize(float X)
|
||||
{
|
||||
Splitter->SlotAt(1).SetSizeValue(X);
|
||||
}
|
||||
|
||||
void SChannelRackItem::ShowPluginHostMenu()
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
// MenuBuilder.BeginSection("ChannelRackItem", FText::FromString("ChannelRackItem"));
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("OpenChannelInterface", "打开通道面板"),
|
||||
LOCTEXT("OpenChannelInterface_ToolTip", "打开一个通道面板窗口"),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &SChannelRackItem::ShowPluginHostChannelInterface))
|
||||
);
|
||||
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("DeletePluginHost", "删除插件"),
|
||||
FText(),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &SChannelRackItem::DeletePluginHost))
|
||||
);
|
||||
|
||||
// MenuBuilder.EndSection();
|
||||
|
||||
FSlateApplication::Get().PushMenu(SharedThis(this), FWidgetPath(), MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect::ContextMenu);
|
||||
}
|
||||
|
||||
void SChannelRackItem::SetPluginHostEnabled(ECheckBoxState CheckBoxState)
|
||||
{
|
||||
PluginHost->SetEnabled(CheckBoxState == ECheckBoxState::Checked);
|
||||
}
|
||||
|
||||
FReply SChannelRackItem::TogglePluginHostEditor()
|
||||
{
|
||||
FWindowManager::Get().TogglePluginEditor(PluginHost);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
void SChannelRackItem::ShowPluginHostChannelInterface()
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
MenuBuilder.AddWidget(
|
||||
SNew(SPluginHostChannelInterface, PluginHost),
|
||||
FText()
|
||||
);
|
||||
FSlateApplication::Get().PushMenu(SharedThis(this), FWidgetPath(), MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect::ComboButton);
|
||||
}
|
||||
|
||||
void SChannelRackItem::DeletePluginHost()
|
||||
{
|
||||
FPluginHostList::Get().RemoveInstrument(PluginHost);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
55
Source/Arona/UI/Widget/ChannelRack/ChannelRackItem.h
Normal file
55
Source/Arona/UI/Widget/ChannelRack/ChannelRackItem.h
Normal file
@@ -0,0 +1,55 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CurveSequence.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SCheckBox.h"
|
||||
|
||||
class SSplitter;
|
||||
class SImage;
|
||||
class SChannelRackMidiThumbnail;
|
||||
class FMidiPattern;
|
||||
class FPluginHost;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SChannelRackItem : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
DECLARE_DELEGATE_OneParam(FSetter, float);
|
||||
|
||||
SLATE_BEGIN_ARGS(SChannelRackItem)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_EVENT(FSetter, OnSplliterAPositionChanged)
|
||||
SLATE_EVENT(FSetter, OnSplliterBPositionChanged)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPluginHost* InPluginHost);
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
void SetPattern(FMidiPattern* InPattern);
|
||||
FPluginHost* GetPluginHost() const { return PluginHost; }
|
||||
|
||||
void SetSplliterASize(float X);
|
||||
void SetSplliterBSize(float X);
|
||||
private:
|
||||
|
||||
void ShowPluginHostMenu();
|
||||
void SetPluginHostEnabled(ECheckBoxState CheckBoxState);
|
||||
FReply TogglePluginHostEditor();
|
||||
|
||||
void ShowPluginHostChannelInterface();
|
||||
void DeletePluginHost();
|
||||
|
||||
FPluginHost* PluginHost = nullptr;
|
||||
FMidiPattern* Pattern = nullptr;
|
||||
|
||||
TSharedPtr<SChannelRackMidiThumbnail> MidiThumbnail;
|
||||
TSharedPtr<SSplitter> Splitter;
|
||||
};
|
||||
124
Source/Arona/UI/Widget/ChannelRack/ChannelRackMidiThumbnail.cpp
Normal file
124
Source/Arona/UI/Widget/ChannelRack/ChannelRackMidiThumbnail.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "ChannelRackMidiThumbnail.h"
|
||||
|
||||
#include "SImage.h"
|
||||
#include "SInvalidationPanel.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "SViewport.h"
|
||||
#include "Midi/MidiMessageSequence.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "UI/Widget/MainWindow.h"
|
||||
#include "UI/Widget/SUpdatableImage.h"
|
||||
#include "UI/Widget/Thumbnail.h"
|
||||
#include "UI/Widget/WindowManager.h"
|
||||
#include "UI/Widget/PianoRoll/SPianoRoll.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
SChannelRackMidiThumbnail::~SChannelRackMidiThumbnail()
|
||||
{
|
||||
SetPattern(nullptr);
|
||||
}
|
||||
|
||||
void SChannelRackMidiThumbnail::SetPattern(FMidiPattern* InPattern)
|
||||
{
|
||||
if (CurrentPattern)
|
||||
{
|
||||
CurrentPattern->OnChanged.RemoveAll(this);
|
||||
CurrentPattern->OnPlay.RemoveAll(this);
|
||||
}
|
||||
if (!InPattern)
|
||||
return;
|
||||
CurrentPattern = InPattern;
|
||||
CurrentPattern->OnChanged_MainThread.AddRaw(this, &SChannelRackMidiThumbnail::OnChanged);
|
||||
CurrentPattern->OnPlay.AddRaw(this, &SChannelRackMidiThumbnail::OnPatternPlay);
|
||||
OnChanged(PluginHost, &InPattern->GetSequence(PluginHost));
|
||||
}
|
||||
|
||||
void SChannelRackMidiThumbnail::Construct(const FArguments& InArgs, FPluginHost* InPluginHost)
|
||||
{
|
||||
PluginHost = InPluginHost;
|
||||
SetPattern(InArgs._Pattern);
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SInvalidationPanel)
|
||||
[
|
||||
SAssignNew(UpdatableImage, SUpdatableImage)
|
||||
.OnPostResize(this, &SChannelRackMidiThumbnail::UpdateMidiThumbnail)
|
||||
.UpdateInterval(0)
|
||||
]
|
||||
];
|
||||
|
||||
if (InArgs._Pattern)
|
||||
OnChanged(PluginHost, &InArgs._Pattern->GetSequence(InPluginHost));
|
||||
}
|
||||
|
||||
FReply SChannelRackMidiThumbnail::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (!CurrentPattern)
|
||||
return FReply::Unhandled();
|
||||
FMidiMessageSequence* MidiSequence = &CurrentPattern->GetSequence(PluginHost);
|
||||
TSharedPtr<SPianoRoll> PianoRoll = SNew(SPianoRoll, MidiSequence, PluginHost);
|
||||
|
||||
|
||||
FWindowManager::Get().GetMainWindow()->CreateChildWindow(
|
||||
FVector2D(100, 100),
|
||||
TEXT(""),
|
||||
PianoRoll.ToSharedRef()
|
||||
);
|
||||
PianoRoll->InitScrollBar();
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
int32 SChannelRackMidiThumbnail::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
LayerId = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
||||
if (CurrentPattern)
|
||||
{
|
||||
const double EndTime = CurrentPattern->GetLength();
|
||||
const double CurrentPercent = LastPatternPos / EndTime * (double)AllottedGeometry.Size.X;
|
||||
TArray<FVector2f> Points;
|
||||
Points.Add(FVector2f(CurrentPercent, 0));
|
||||
Points.Add(FVector2f(CurrentPercent, AllottedGeometry.Size.Y));
|
||||
FSlateDrawElement::MakeLines(
|
||||
OutDrawElements,
|
||||
++LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(),
|
||||
Points,
|
||||
ESlateDrawEffect::None,
|
||||
FLinearColor::Red,
|
||||
true,
|
||||
1.f
|
||||
);
|
||||
}
|
||||
return LayerId;
|
||||
}
|
||||
|
||||
void SChannelRackMidiThumbnail::OnChanged(FPluginHost* Host, FMidiMessageSequence* Changed)
|
||||
{
|
||||
if (Host != PluginHost)
|
||||
return;
|
||||
UpdatableImage->NeedRedraw();
|
||||
}
|
||||
|
||||
void SChannelRackMidiThumbnail::OnPatternPlay(FPatternInstance* PatternInstance, AudioFrame PatternTick, uint32 Length)
|
||||
{
|
||||
LastPatternPos = PatternTick;
|
||||
}
|
||||
|
||||
void SChannelRackMidiThumbnail::UpdateMidiThumbnail(FImageData& ImageData)
|
||||
{
|
||||
if (!CurrentPattern)
|
||||
return;
|
||||
const FMidiMessageSequence* MidiSequence = &CurrentPattern->GetSequence(PluginHost);
|
||||
|
||||
ImageData.ClearColor(FColor::Black);
|
||||
Thumbnail::GenerateMidiThumbnail(MidiSequence, ImageData);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@@ -0,0 +1,48 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SLeafWidget.h"
|
||||
#include "Pattern/MidiPattern.h"
|
||||
#include "UI/Widget/SUpdatableImage.h"
|
||||
|
||||
class SUpdatableImage;
|
||||
class FPluginHost;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SChannelRackMidiThumbnail : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SChannelRackMidiThumbnail)
|
||||
{
|
||||
}
|
||||
SLATE_ARGUMENT(FMidiPattern*, Pattern)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
~SChannelRackMidiThumbnail();
|
||||
void SetPattern(FMidiPattern* InPattern);
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPluginHost* InPluginHost);
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
private:
|
||||
void OnChanged(FPluginHost* Host, FMidiMessageSequence* Changed);
|
||||
void OnPatternPlay(FPatternInstance* PatternInstance, AudioFrame PatternTick, uint32 Length);
|
||||
|
||||
void UpdateMidiThumbnail(FImageData& ImageData);
|
||||
|
||||
FPluginHost* PluginHost;
|
||||
FMidiPattern* CurrentPattern;
|
||||
|
||||
TSharedPtr<SUpdatableImage> UpdatableImage;
|
||||
|
||||
AudioFrame LastPatternPos;
|
||||
};
|
||||
|
||||
12
Source/Arona/UI/Widget/IChildWindow.h
Normal file
12
Source/Arona/UI/Widget/IChildWindow.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class SWidget;
|
||||
|
||||
class IChildWindow
|
||||
{
|
||||
public:
|
||||
virtual ~IChildWindow() = default;
|
||||
virtual TSharedPtr<SWidget> GetFocusWidget() { return nullptr; }
|
||||
virtual TSharedRef<SWidget> GetWidget() = 0;
|
||||
};
|
||||
198
Source/Arona/UI/Widget/MainWindow.cpp
Normal file
198
Source/Arona/UI/Widget/MainWindow.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include "IChildWindow.h"
|
||||
#include "SBackgroundBlur.h"
|
||||
#include "SChildWindow.h"
|
||||
#include "SMainWindowHeader.h"
|
||||
#include "SConstraintCanvas.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SMainWindow.h"
|
||||
#include "SWindow.h"
|
||||
#include "SWindowTitleBar.h"
|
||||
#include "WaveformViewer.h"
|
||||
#include "ChannelRack/ChannelRack.h"
|
||||
#include "Thread/MainThreadEventList.h"
|
||||
#include "Thread/ThreadMessage.h"
|
||||
#include "Mixer/SMixer.h"
|
||||
#include "PlayList/SPlayList.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Arona"
|
||||
|
||||
DECLARE_THREAD_MESSAGE(FMainThreadEventList, PostCreateChildWindow,
|
||||
FMainWindow* Window;
|
||||
TSharedPtr<SChildWindow> ChildWindow;
|
||||
)
|
||||
{
|
||||
Args.Window->FrontChildWindow(Args.ChildWindow);
|
||||
}
|
||||
|
||||
void FMainWindow::Init()
|
||||
{
|
||||
auto NewTab = SNew(SDockTab);
|
||||
TabManager = FGlobalTabmanager::Get()->NewTabManager(NewTab);
|
||||
TArray<FName> TabNames = {"Tab0", "Tab1", "Tab2"};
|
||||
auto Layout = FTabManager::NewLayout(TEXT("TestLayout"))
|
||||
->AddArea(
|
||||
FTabManager::NewPrimaryArea()
|
||||
->SetOrientation(Orient_Horizontal)
|
||||
->Split(
|
||||
FTabManager::NewStack()
|
||||
->AddTab(TabNames[0], ETabState::Type::OpenedTab)
|
||||
)
|
||||
->Split(
|
||||
FTabManager::NewStack()
|
||||
->AddTab(TabNames[1], ETabState::Type::OpenedTab)
|
||||
)
|
||||
);
|
||||
|
||||
TabManager->RegisterTabSpawner(TabNames[0], FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& Args) -> TSharedRef<SDockTab>
|
||||
{
|
||||
return SNew(SDockTab)
|
||||
.TabRole(ETabRole::NomadTab)
|
||||
[
|
||||
SNew(SButton)
|
||||
];
|
||||
}));
|
||||
|
||||
TabManager->RegisterTabSpawner(TabNames[1], FOnSpawnTab::CreateRaw(this, &FMainWindow::CreatePlayListTab));
|
||||
|
||||
|
||||
MainWindow = SNew(SMainWindow)
|
||||
.CreateTitleBar(false)
|
||||
.AutoCenter(EAutoCenter::PrimaryWorkArea)
|
||||
.ClientSize(FVector2D(1270, 720));
|
||||
|
||||
const auto WindowTitleBar = SNew(SWindowTitleBar, MainWindow.ToSharedRef(), nullptr, EHorizontalAlignment::HAlign_Fill)
|
||||
.ShowAppIcon(false);
|
||||
|
||||
MainWindow->SetTitleBar(WindowTitleBar);
|
||||
MainWindow->SetContent(
|
||||
SNew(SVerticalBox)
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
WindowTitleBar
|
||||
]
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SMainWindowHeader)
|
||||
.OnChannelRackClicked_Raw(this, &FMainWindow::ToggleChannelRack)
|
||||
.OnMixerClicked_Raw(this, &FMainWindow::ToggleMixer)
|
||||
]
|
||||
+SVerticalBox::Slot()
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
TabManager->RestoreFrom(Layout, MainWindow).ToSharedRef()
|
||||
]
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SAssignNew(MainWindowCanvas, SConstraintCanvas)
|
||||
]
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
[
|
||||
SNew(SWaveformViewer)
|
||||
.Visibility(EVisibility::SelfHitTestInvisible)
|
||||
].FillWidth(1.f)
|
||||
// +SHorizontalBox::Slot()
|
||||
// [
|
||||
// SNew(SWaveformViewer)
|
||||
// .Visibility(EVisibility::SelfHitTestInvisible)
|
||||
// ].FillWidth(1.f)
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
FSlateApplication::Get().AddWindow(MainWindow.ToSharedRef());
|
||||
|
||||
InitChildWindow();
|
||||
}
|
||||
|
||||
TSharedPtr<SChildWindow> FMainWindow::CreateChildWindow(FVector2D Size, FString Title, TSharedPtr<IChildWindow> Content, bool IsSingletonWindow)
|
||||
{
|
||||
auto* PanelChildren = static_cast<TPanelChildren<SConstraintCanvas::FSlot>*>(MainWindowCanvas->GetChildren());
|
||||
|
||||
TSharedPtr<SChildWindow> Out;
|
||||
auto Slot = MainWindowCanvas->AddSlot();
|
||||
Slot.ZOrder(PanelChildren->Num() + 2);
|
||||
Slot.AttachWidget(SAssignNew(Out, SChildWindow, Slot.GetSlot())
|
||||
.IsSingletonWindow(IsSingletonWindow)
|
||||
.Title(Title)
|
||||
.ResizeHandleSize(FVector2D(16, 16))
|
||||
.Content(Content)
|
||||
);
|
||||
|
||||
Slot.Offset(FMargin(0, 0, Size.X, Size.Y));
|
||||
|
||||
PUSH_THREAD_EVENT(PostCreateChildWindow, this, Out);
|
||||
return Out;
|
||||
}
|
||||
|
||||
void FMainWindow::FrontChildWindow(TSharedPtr<SChildWindow> ChildWindow)
|
||||
{
|
||||
const auto& PanelChildren = MainWindowCanvas->GetPanelChildren();
|
||||
const TArray<TUniquePtr<SConstraintCanvas::FSlot>>& Children = PanelChildren.GetAllChildren();
|
||||
for (auto& Slot : Children)
|
||||
{
|
||||
Slot->SetZOrder(Slot->GetZOrder() - 1);
|
||||
}
|
||||
ChildWindow->GetSlot()->SetZOrder(PanelChildren.Num());
|
||||
FWidgetPath WidgetPath;
|
||||
|
||||
const TSharedPtr<SWidget> FocusWidget = ChildWindow->Content->GetFocusWidget();
|
||||
if (FocusWidget.IsValid())
|
||||
{
|
||||
FSlateApplication::Get().SetAllUserFocus(FocusWidget);
|
||||
FSlateApplication::Get().SetKeyboardFocus(FocusWidget);
|
||||
}
|
||||
else
|
||||
{
|
||||
FSlateApplication::Get().SetAllUserFocus(ChildWindow->Content->GetWidget());
|
||||
FSlateApplication::Get().SetKeyboardFocus(ChildWindow->Content->GetWidget());
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<SChannelRack> FMainWindow::GetChannelRack()
|
||||
{
|
||||
return ChannelRack;
|
||||
}
|
||||
|
||||
TSharedRef<SDockTab> FMainWindow::CreatePlayListTab(const FSpawnTabArgs& Args)
|
||||
{
|
||||
return SNew(SDockTab)
|
||||
.TabRole(ETabRole::NomadTab)
|
||||
.Label(LOCTEXT("PlayListTabTitle", "PlayList"))
|
||||
[
|
||||
SNew(SPlayList)
|
||||
];
|
||||
}
|
||||
|
||||
void FMainWindow::InitChildWindow()
|
||||
{
|
||||
ChannelRackWindow = CreateChildWindow(FVector2D(300, 600), "ChannelRack", SAssignNew(ChannelRack, SChannelRack), true);
|
||||
MixerWindow = CreateChildWindow(FVector2D(600, 400), "Mixer", SNew(SMixer), true);
|
||||
}
|
||||
|
||||
void FMainWindow::ToggleChildWindow(TSharedPtr<SChildWindow> ChildWindow)
|
||||
{
|
||||
if (ChildWindow->GetVisibility() != EVisibility::Visible)
|
||||
ChildWindow->SetVisibility(EVisibility::Visible);
|
||||
else
|
||||
ChildWindow->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
|
||||
void FMainWindow::ToggleChannelRack()
|
||||
{
|
||||
ToggleChildWindow(ChannelRackWindow);
|
||||
}
|
||||
|
||||
void FMainWindow::ToggleMixer()
|
||||
{
|
||||
ToggleChildWindow(MixerWindow);
|
||||
}
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
38
Source/Arona/UI/Widget/MainWindow.h
Normal file
38
Source/Arona/UI/Widget/MainWindow.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "Events.h"
|
||||
#include "WidgetPath.h"
|
||||
|
||||
class IChildWindow;
|
||||
class SChannelRack;
|
||||
class SChildWindow;
|
||||
class SConstraintCanvas;
|
||||
class SWindow;
|
||||
class SWidget;
|
||||
|
||||
class FMainWindow
|
||||
{
|
||||
public:
|
||||
void Init();
|
||||
TSharedPtr<SChildWindow> CreateChildWindow(FVector2D Size, FString Title, TSharedPtr<IChildWindow> Content, bool IsSingletonWindow = false);
|
||||
void FrontChildWindow(TSharedPtr<SChildWindow> ChildWindow);
|
||||
|
||||
TSharedPtr<SWindow> MainWindow;
|
||||
TSharedPtr<SConstraintCanvas> MainWindowCanvas;
|
||||
|
||||
TSharedPtr<SChannelRack> GetChannelRack();
|
||||
private:
|
||||
TSharedRef<SDockTab> CreatePlayListTab(const FSpawnTabArgs& Args);
|
||||
|
||||
void InitChildWindow();
|
||||
|
||||
void ToggleChildWindow(TSharedPtr<SChildWindow> ChildWindow);
|
||||
|
||||
void ToggleChannelRack();
|
||||
void ToggleMixer();
|
||||
|
||||
TSharedPtr<SChildWindow> ChannelRackWindow;
|
||||
TSharedPtr<SChannelRack> ChannelRack;
|
||||
|
||||
TSharedPtr<SChildWindow> MixerWindow;
|
||||
TSharedPtr<FTabManager> TabManager;
|
||||
};
|
||||
264
Source/Arona/UI/Widget/Mixer/SMixer.cpp
Normal file
264
Source/Arona/UI/Widget/Mixer/SMixer.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SMixer.h"
|
||||
|
||||
#include "MultiBoxBuilder.h"
|
||||
#include "SMixerTrack.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SListView.h"
|
||||
#include "SMixerEffectList.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "Singleton/MixerList.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SMixer"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void FMixerSelector::AddTrack(FMixerTrack* MixerTrack)
|
||||
{
|
||||
if (SelectedTracks.Contains(MixerTrack))
|
||||
return;
|
||||
SelectedTracks.Add(MixerTrack);
|
||||
OnMixerTrackSelected.Broadcast(MixerTrack);
|
||||
}
|
||||
|
||||
bool FMixerSelector::RemoveTrack(FMixerTrack* MixerTrack)
|
||||
{
|
||||
if (!SelectedTracks.Contains(MixerTrack))
|
||||
return false;
|
||||
SelectedTracks.Remove(MixerTrack);
|
||||
OnMixerTrackDeselected.Broadcast(MixerTrack);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FMixerSelector::UnselectAll()
|
||||
{
|
||||
for (FMixerTrack* MixerTrack : SelectedTracks)
|
||||
{
|
||||
OnMixerTrackDeselected.Broadcast(MixerTrack);
|
||||
}
|
||||
SelectedTracks.Empty();
|
||||
}
|
||||
|
||||
SMixer::~SMixer()
|
||||
{
|
||||
FMixerList& MixerList = FMixerList::Get();
|
||||
MixerList.OnMixerTrackCreated.RemoveAll(this);
|
||||
MixerList.OnMixerTrackRemoved.RemoveAll(this);
|
||||
}
|
||||
|
||||
void SMixer::Construct(const FArguments& InArgs)
|
||||
{
|
||||
FMixerList& MixerList = FMixerList::Get();
|
||||
|
||||
MixerList.OnMixerTrackCreated.AddRaw(this, &SMixer::OnMixerTrackCreated);
|
||||
MixerList.OnMixerTrackRemoved.AddRaw(this, &SMixer::OnMixerTrackRemoved);
|
||||
|
||||
Selector.OnMixerTrackSelected.AddRaw(this, &SMixer::OnMixerTrackSelected);
|
||||
Selector.OnMixerTrackDeselected.AddRaw(this, &SMixer::OnMixerTrackDeselected);
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.f)
|
||||
[
|
||||
SAssignNew(ListView, SScrollBox)
|
||||
.AnimateWheelScrolling(true)
|
||||
.Orientation(EOrientation::Orient_Horizontal)
|
||||
.ScrollBarThickness(FVector2D(2))
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SBox)
|
||||
.WidthOverride(200)
|
||||
[
|
||||
SAssignNew(EffectList, SMixerEffectList)
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
for (FMixerTrack* Mixer : MixerList)
|
||||
{
|
||||
OnMixerTrackCreated(Mixer);
|
||||
}
|
||||
}
|
||||
|
||||
FReply SMixer::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("AddDummyMixerTrack", "创建傀儡轨道"),
|
||||
LOCTEXT("AddDummyMixerTrack_Tooltip", "创建一个新的傀儡轨道"),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateLambda([this]()
|
||||
{
|
||||
FMixerList& MixerList = FMixerList::Get();
|
||||
MixerList.CreateDummyTrack("Dummy");
|
||||
}))
|
||||
);
|
||||
|
||||
FSlateApplication::Get().PushMenu(SharedThis(this), FWidgetPath(), MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect::ContextMenu);
|
||||
}
|
||||
else if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
Selector.UnselectAll();
|
||||
EffectList->SetMixerTrack(nullptr);
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SMixer::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
||||
{
|
||||
if (InKeyEvent.GetKey() == EKeys::LeftShift || InKeyEvent.GetKey() == EKeys::RightShift)
|
||||
{
|
||||
bShiftDown = true;
|
||||
}
|
||||
|
||||
if (InKeyEvent.GetKey() == EKeys::Delete)
|
||||
{
|
||||
for (FMixerTrack* MixerTrack : Selector.GetSelectedTracks())
|
||||
{
|
||||
FMixerList::Get().RemoveTrack(MixerTrack);
|
||||
}
|
||||
Selector.UnselectAll();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SMixer::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
||||
{
|
||||
if (InKeyEvent.GetKey() == EKeys::LeftShift || InKeyEvent.GetKey() == EKeys::RightShift)
|
||||
{
|
||||
bShiftDown = false;
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
void SMixer::OnMixerTrackCreated(FMixerTrack* MixerTrack)
|
||||
{
|
||||
ListView->AddSlot()
|
||||
[
|
||||
SNew(SMixerTrack)
|
||||
.MixerTrack(MixerTrack)
|
||||
.OnClicked(this, &SMixer::OnMixerTrackClicked)
|
||||
];
|
||||
}
|
||||
|
||||
void SMixer::OnMixerTrackRemoved(FMixerTrack* MixerTrack)
|
||||
{
|
||||
if (Selector.RemoveTrack(MixerTrack))
|
||||
{
|
||||
Selector.AddTrack(FMixerList::Get().GetMaster());
|
||||
}
|
||||
|
||||
ForeachMixerTrackWidget([&, this](TSharedRef<SMixerTrack> MixerTrackWidget)
|
||||
{
|
||||
if (MixerTrackWidget->GetMixerTrack() == MixerTrack)
|
||||
{
|
||||
ListView->RemoveSlot(MixerTrackWidget);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SMixer::OnMixerTrackClicked(FMixerTrack* InMixerTrack)
|
||||
{
|
||||
Selector.UnselectAll();
|
||||
|
||||
if (bShiftDown)
|
||||
{
|
||||
const TArray<FMixerTrack*>& Selected = Selector.GetSelectedTracks();
|
||||
if (Selected.Num() > 0)
|
||||
{
|
||||
const TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
FChildren* Children = ScrollPanel->GetChildren();
|
||||
for (int32 i = 0; i < Children->Num(); i++)
|
||||
{
|
||||
TSharedRef<SWidget> Child = Children->GetChildAt(i);
|
||||
const TSharedRef<SMixerTrack> MixerTrackWidget = StaticCastSharedRef<SMixerTrack>(Child);
|
||||
if (MixerTrackWidget->GetMixerTrack() == Selected[0])
|
||||
{
|
||||
int32 StartIndex = i;
|
||||
for (int32 j = 0; j < Children->Num(); j++)
|
||||
{
|
||||
TSharedRef<SWidget> Child2 = Children->GetChildAt(j);
|
||||
const TSharedRef<SMixerTrack> MixerTrackWidget2 = StaticCastSharedRef<SMixerTrack>(Child2);
|
||||
if (MixerTrackWidget2->GetMixerTrack() == InMixerTrack)
|
||||
{
|
||||
int32 EndIndex = j;
|
||||
if (StartIndex > EndIndex)
|
||||
{
|
||||
const int32 Temp = StartIndex;
|
||||
StartIndex = EndIndex;
|
||||
EndIndex = Temp;
|
||||
}
|
||||
for (int32 k = StartIndex; k <= EndIndex; k++)
|
||||
{
|
||||
TSharedRef<SWidget> Child3 = Children->GetChildAt(k);
|
||||
const TSharedRef<SMixerTrack> MixerTrackWidget3 = StaticCastSharedRef<SMixerTrack>(Child3);
|
||||
Selector.AddTrack(MixerTrackWidget3->GetMixerTrack());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Selector.AddTrack(InMixerTrack);
|
||||
EffectList->SetMixerTrack(InMixerTrack);
|
||||
}
|
||||
}
|
||||
|
||||
void SMixer::OnMixerTrackSelected(FMixerTrack* InMixerTrack)
|
||||
{
|
||||
ForeachMixerTrackWidget([&, this](TSharedRef<SMixerTrack> MixerTrackWidget)
|
||||
{
|
||||
if (MixerTrackWidget->GetMixerTrack() == InMixerTrack)
|
||||
{
|
||||
MixerTrackWidget->SetSelected(true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void SMixer::OnMixerTrackDeselected(FMixerTrack* InMixerTrack)
|
||||
{
|
||||
ForeachMixerTrackWidget([&, this](TSharedRef<SMixerTrack> MixerTrackWidget)
|
||||
{
|
||||
if (MixerTrackWidget->GetMixerTrack() == InMixerTrack)
|
||||
{
|
||||
MixerTrackWidget->SetSelected(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void SMixer::ForeachMixerTrackWidget(TFunction<bool(TSharedRef<SMixerTrack>)> InFunction)
|
||||
{
|
||||
const TSharedPtr<SScrollPanel> ScrollPanel = ListView->GetScrollPanel();
|
||||
FChildren* Children = ScrollPanel->GetChildren();
|
||||
for (int32 i = 0; i < Children->Num(); i++)
|
||||
{
|
||||
TSharedRef<SWidget> Child = Children->GetChildAt(i);
|
||||
const TSharedRef<SMixerTrack> MixerTrackWidget = StaticCastSharedRef<SMixerTrack>(Child);
|
||||
if (!InFunction(MixerTrackWidget))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
65
Source/Arona/UI/Widget/Mixer/SMixer.h
Normal file
65
Source/Arona/UI/Widget/Mixer/SMixer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SMixerTrack.h"
|
||||
#include "Mixer/MixerTrack.h"
|
||||
#include "UI/Widget/IChildWindow.h"
|
||||
|
||||
class SScrollBox;
|
||||
class SMixerEffectList;
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FMixerTrackDelegate, FMixerTrack*)
|
||||
|
||||
class FMixerSelector
|
||||
{
|
||||
public:
|
||||
void AddTrack(FMixerTrack* MixerTrack);
|
||||
bool RemoveTrack(FMixerTrack* MixerTrack);
|
||||
void UnselectAll();
|
||||
|
||||
const TArray<FMixerTrack*>& GetSelectedTracks() const { return SelectedTracks; }
|
||||
|
||||
FMixerTrackDelegate OnMixerTrackSelected;
|
||||
FMixerTrackDelegate OnMixerTrackDeselected;
|
||||
private:
|
||||
TArray<FMixerTrack*> SelectedTracks;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SMixer : public SCompoundWidget, public IChildWindow
|
||||
{
|
||||
public:
|
||||
~SMixer();
|
||||
SLATE_BEGIN_ARGS(SMixer)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
|
||||
virtual TSharedRef<SWidget> GetWidget() override { return AsShared(); }
|
||||
protected:
|
||||
void OnMixerTrackCreated(FMixerTrack* MixerTrack);
|
||||
void OnMixerTrackRemoved(FMixerTrack* MixerTrack);
|
||||
void OnMixerTrackClicked(FMixerTrack* InMixerTrack);
|
||||
|
||||
void OnMixerTrackSelected(FMixerTrack* InMixerTrack);
|
||||
void OnMixerTrackDeselected(FMixerTrack* InMixerTrack);
|
||||
|
||||
void ForeachMixerTrackWidget(TFunction<bool(TSharedRef<SMixerTrack>)> InFunction);
|
||||
private:
|
||||
TSharedPtr<SScrollBox> ListView;
|
||||
TSharedPtr<SMixerEffectList> EffectList;
|
||||
FMixerSelector Selector;
|
||||
bool bShiftDown = false;
|
||||
};
|
||||
99
Source/Arona/UI/Widget/Mixer/SMixerEffectItem.cpp
Normal file
99
Source/Arona/UI/Widget/Mixer/SMixerEffectItem.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SMixerEffectItem.h"
|
||||
|
||||
#include "MultiBoxBuilder.h"
|
||||
#include "SButton.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "UI/Widget/WindowManager.h"
|
||||
#include "UI/Widget/ChannelInterface/SPluginHostChannelInterface.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SMixer"
|
||||
|
||||
void SMixerEffectItem::Construct(const FArguments& InArgs, FPluginHost* InPluginHost)
|
||||
{
|
||||
PluginHost = InPluginHost;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString(PluginHost->Name))
|
||||
.OnClicked(this, &SMixerEffectItem::OnButtonClicked)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SAssignNew(MenuAnchor, SMenuAnchor)
|
||||
.OnGetMenuContent(this, &SMixerEffectItem::CreateChannelInterfaceMenu)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString("X"))
|
||||
.OnClicked(this, &SMixerEffectItem::OnChannelInterfaceClicked)
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
FReply SMixerEffectItem::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
ShowMenu();
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SMixerEffectItem::OnButtonClicked()
|
||||
{
|
||||
FWindowManager::Get().TogglePluginEditor(PluginHost);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SMixerEffectItem::OnChannelInterfaceClicked()
|
||||
{
|
||||
MenuAnchor->SetIsOpen(true);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SMixerEffectItem::CreateChannelInterfaceMenu()
|
||||
{
|
||||
return SNew(SPluginHostChannelInterface, PluginHost);
|
||||
}
|
||||
|
||||
void SMixerEffectItem::ShowMenu()
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
// Delete effect
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("DeleteEffect", "删除效果器"),
|
||||
LOCTEXT("DeleteEffectTooltip", "从混音轨道中删除该效果。"),
|
||||
FSlateIcon(),
|
||||
FUIAction(
|
||||
FExecuteAction::CreateLambda([this]()
|
||||
{
|
||||
FPluginHostList::Get().RemovePluginHost(PluginHost);
|
||||
})
|
||||
)
|
||||
);
|
||||
FSlateApplication::Get().PushMenu(
|
||||
AsShared(),
|
||||
FWidgetPath(),
|
||||
MenuBuilder.MakeWidget(),
|
||||
FSlateApplication::Get().GetCursorPos(),
|
||||
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
|
||||
);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
33
Source/Arona/UI/Widget/Mixer/SMixerEffectItem.h
Normal file
33
Source/Arona/UI/Widget/Mixer/SMixerEffectItem.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
|
||||
class SMenuAnchor;
|
||||
class FPluginHost;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SMixerEffectItem : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMixerEffectItem)
|
||||
{}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPluginHost* InPluginHost);
|
||||
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
FPluginHost* PluginHost;
|
||||
private:
|
||||
FReply OnButtonClicked();
|
||||
FReply OnChannelInterfaceClicked();
|
||||
TSharedRef<SWidget> CreateChannelInterfaceMenu();
|
||||
|
||||
void ShowMenu();
|
||||
|
||||
TSharedPtr<SMenuAnchor> MenuAnchor;
|
||||
};
|
||||
123
Source/Arona/UI/Widget/Mixer/SMixerEffectList.cpp
Normal file
123
Source/Arona/UI/Widget/Mixer/SMixerEffectList.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SMixerEffectList.h"
|
||||
|
||||
#include "DesktopPlatformModule.h"
|
||||
#include "SButton.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SMixerEffectItem.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "Singleton/MixerList.h"
|
||||
#include "Mixer/MixerTrack.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
SMixerEffectList::~SMixerEffectList()
|
||||
{
|
||||
FMixerList::Get().OnMixerTrackEffectAdded.RemoveAll(this);
|
||||
// FMixerList::Get().OnMixerTrackEffectRemoved.RemoveAll(this);
|
||||
FMixerList::Get().OnMixerTrackRemoved.RemoveAll(this);
|
||||
}
|
||||
|
||||
void SMixerEffectList::Construct(const FArguments& InArgs)
|
||||
{
|
||||
FMixerList::Get().OnMixerTrackRemoved.AddRaw(this, &SMixerEffectList::OnMixerTrackRemoved);
|
||||
FMixerList::Get().OnMixerTrackEffectAdded.AddRaw(this, &SMixerEffectList::OnMixerTrackEffectAdded);
|
||||
// FMixerList::Get().OnMixerTrackEffectRemoved.AddRaw(this, &SMixerEffectList::OnMixerTrackEffectRemoved);
|
||||
FPluginHostList::Get().OnPluginHostRemoved.AddRaw(this, &SMixerEffectList::OnMixerTrackEffectRemoved);
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(ScrollBox, SScrollBox)
|
||||
.Orientation(Orient_Vertical)
|
||||
.AnimateWheelScrolling(true)
|
||||
.Visibility(EVisibility::SelfHitTestInvisible)
|
||||
+SScrollBox::Slot()
|
||||
[
|
||||
SAssignNew(VerticalBox, SVerticalBox)
|
||||
]
|
||||
+SScrollBox::Slot()
|
||||
[
|
||||
SAssignNew(AddEffectButton, SButton)
|
||||
.Text(FText::FromString("Add Effect"))
|
||||
.OnClicked(this, &SMixerEffectList::AddNewEffect)
|
||||
.IsEnabled(false)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void SMixerEffectList::SetMixerTrack(FMixerTrack* InMixerTrack)
|
||||
{
|
||||
if (MixerTrack == InMixerTrack)
|
||||
return;
|
||||
VerticalBox->ClearChildren();
|
||||
MixerTrack = InMixerTrack;
|
||||
if (!MixerTrack)
|
||||
{
|
||||
AddEffectButton->SetEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
AddEffectButton->SetEnabled(true);
|
||||
for (FPluginHost* Effect : MixerTrack->Effects)
|
||||
{
|
||||
AddEffectItemWidget(Effect);
|
||||
}
|
||||
}
|
||||
|
||||
void SMixerEffectList::OnMixerTrackEffectAdded(FMixerTrack* InMixerTrack, FPluginHost* InPluginHost)
|
||||
{
|
||||
if (MixerTrack != InMixerTrack)
|
||||
return;
|
||||
AddEffectItemWidget(InPluginHost);
|
||||
}
|
||||
|
||||
void SMixerEffectList::OnMixerTrackEffectRemoved(FPluginHost* InPluginHost)
|
||||
{
|
||||
const int32 NumSlots = VerticalBox->NumSlots();
|
||||
for (int i = 0; i < NumSlots; ++i)
|
||||
{
|
||||
SVerticalBox::FSlot& Slot = VerticalBox->GetSlot(i);
|
||||
const TSharedRef<SWidget>& Widget = Slot.GetWidget();
|
||||
TSharedRef<SMixerEffectItem> EffectItem = StaticCastSharedRef<SMixerEffectItem>(Widget);
|
||||
if (EffectItem->PluginHost == InPluginHost)
|
||||
{
|
||||
VerticalBox->RemoveSlot(EffectItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SMixerEffectList::OnMixerTrackRemoved(FMixerTrack* InMixerTrack)
|
||||
{
|
||||
if (MixerTrack != InMixerTrack)
|
||||
return;
|
||||
VerticalBox->ClearChildren();
|
||||
}
|
||||
|
||||
void SMixerEffectList::AddEffectItemWidget(FPluginHost* InPluginHost)
|
||||
{
|
||||
VerticalBox->AddSlot()
|
||||
[
|
||||
SNew(SMixerEffectItem, InPluginHost)
|
||||
];
|
||||
}
|
||||
|
||||
FReply SMixerEffectList::AddNewEffect()
|
||||
{
|
||||
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
||||
FString FileTypes = TEXT("Dynamic Link Library (*.dll)|*.dll");
|
||||
TArray<FString> OutFiles;
|
||||
DesktopPlatform->OpenFileDialog(nullptr, TEXT("Choose a DLL"), TEXT(""), TEXT(""), FileTypes, EFileDialogFlags::None, OutFiles);
|
||||
if (OutFiles.Num() == 0)
|
||||
return FReply::Handled();
|
||||
FPluginHost* EffectPlugin = FPluginHostList::Get().TryLoadPlugin(OutFiles[0]);
|
||||
if (!EffectPlugin)
|
||||
return FReply::Handled();
|
||||
MixerTrack->AddEffect(EffectPlugin);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
44
Source/Arona/UI/Widget/Mixer/SMixerEffectList.h
Normal file
44
Source/Arona/UI/Widget/Mixer/SMixerEffectList.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
|
||||
class SButton;
|
||||
class SVerticalBox;
|
||||
class SScrollBox;
|
||||
class FMixerTrack;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SMixerEffectList : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
~SMixerEffectList();
|
||||
SLATE_BEGIN_ARGS(SMixerEffectList)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
void SetMixerTrack(FMixerTrack* InMixerTrack);
|
||||
private:
|
||||
void OnMixerTrackEffectAdded(FMixerTrack* InMixerTrack, FPluginHost* InPluginHost);
|
||||
void OnMixerTrackEffectRemoved(FPluginHost* InPluginHost);
|
||||
void OnMixerTrackRemoved(FMixerTrack* InMixerTrack);
|
||||
void AddEffectItemWidget(FPluginHost* InPluginHost);
|
||||
|
||||
FReply AddNewEffect();
|
||||
|
||||
TSharedPtr<SScrollBox> ScrollBox;
|
||||
TSharedPtr<SVerticalBox> VerticalBox;
|
||||
TSharedPtr<SButton> AddEffectButton;
|
||||
FMixerTrack* MixerTrack;
|
||||
};
|
||||
158
Source/Arona/UI/Widget/Mixer/SMixerTrack.cpp
Normal file
158
Source/Arona/UI/Widget/Mixer/SMixerTrack.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SMixerTrack.h"
|
||||
|
||||
#include "MultiBoxBuilder.h"
|
||||
#include "SBorder.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
|
||||
#include "Mixer/MixerTrack.h"
|
||||
|
||||
#include "SSlider.h"
|
||||
#include "STextBlock.h"
|
||||
#include "Singleton/MixerList.h"
|
||||
#include "Singleton/PluginHostList.h"
|
||||
#include "UI/Widget/SVolumeMeterBar.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
|
||||
#include "DSP/Dsp.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SMixerTrack"
|
||||
|
||||
void SMixerTrack::Construct(const FArguments& InArgs)
|
||||
{
|
||||
MixerTrack = InArgs._MixerTrack;
|
||||
OnClicked = InArgs._OnClicked;
|
||||
FText Name = FText::FromString(MixerTrack->GetName());
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SAssignNew(Border, SBorder)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.BorderImage(&White)
|
||||
]
|
||||
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(0.3f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.OverflowPolicy(ETextOverflowPolicy::Ellipsis)
|
||||
.Text(Name)
|
||||
.WrapTextAt(1)
|
||||
]
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(1.f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
[
|
||||
SNew(SVolumeMeterBar)
|
||||
.Width(5)
|
||||
.MinValue(-96.f)
|
||||
.MaxValue(6.f)
|
||||
.MeterValue(this, &SMixerTrack::MeterValue)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
[
|
||||
SNew(SSlider)
|
||||
.Orientation(EOrientation::Orient_Vertical)
|
||||
.MinValue(-96.f)
|
||||
.MaxValue(10.f)
|
||||
.Value(Audio::ConvertToDecibels(MixerTrack->Gain))
|
||||
.OnValueChanged(this, &SMixerTrack::OnVolumeChanged)
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
FReply SMixerTrack::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
OnClicked.ExecuteIfBound(MixerTrack);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
ShowMenu();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
void SMixerTrack::SetSelected(bool bInSelected)
|
||||
{
|
||||
Border->SetVisibility(bInSelected ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed);
|
||||
}
|
||||
|
||||
void SMixerTrack::ShowMenu()
|
||||
{
|
||||
FMenuBuilder MenuBuilder(true, nullptr);
|
||||
|
||||
if (MixerTrack->Type == EMixerTrackType::Dummy)
|
||||
{
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("DeleteTrack", "删除轨道"),
|
||||
LOCTEXT("DeleteTrack", "删除轨道"),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &SMixerTrack::DeleteTrack))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuBuilder.AddMenuEntry(
|
||||
LOCTEXT("DeleteTrack", "删除乐器轨道"),
|
||||
LOCTEXT("DeleteTrack", "删除乐器轨道(这将会导致删除乐器)"),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &SMixerTrack::DeleteInstrument))
|
||||
);
|
||||
}
|
||||
|
||||
FSlateApplication::Get().PushMenu(
|
||||
AsShared(),
|
||||
FWidgetPath(),
|
||||
MenuBuilder.MakeWidget(),
|
||||
FSlateApplication::Get().GetCursorPos(),
|
||||
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
|
||||
);
|
||||
}
|
||||
|
||||
void SMixerTrack::DeleteTrack()
|
||||
{
|
||||
FMixerList::Get().RemoveTrack(MixerTrack);
|
||||
}
|
||||
|
||||
void SMixerTrack::DeleteInstrument()
|
||||
{
|
||||
FInstrumentMixerTrack* InstrumentMixerTrack = static_cast<FInstrumentMixerTrack*>(MixerTrack);
|
||||
FPluginHostList::Get().RemoveInstrument(InstrumentMixerTrack->GetHost());
|
||||
}
|
||||
|
||||
TArray<float> SMixerTrack::MeterValue() const
|
||||
{
|
||||
TArray<float> Out;
|
||||
Out.Reset(2);
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
Out.Add(MixerTrack->GetPeak(i));
|
||||
}
|
||||
return Out;
|
||||
}
|
||||
|
||||
void SMixerTrack::OnVolumeChanged(float X)
|
||||
{
|
||||
MixerTrack->Gain = Audio::ConvertToLinear(X);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
51
Source/Arona/UI/Widget/Mixer/SMixerTrack.h
Normal file
51
Source/Arona/UI/Widget/Mixer/SMixerTrack.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
|
||||
class SBorder;
|
||||
class SMixer;
|
||||
class FMixerTrack;
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FMixerTrackEvent, FMixerTrack*)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SMixerTrack : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMixerTrack)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_ARGUMENT(FMixerTrack*, MixerTrack)
|
||||
|
||||
SLATE_EVENT(FMixerTrackEvent, OnClicked)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
FMixerTrack* GetMixerTrack() const { return MixerTrack; }
|
||||
|
||||
void SetSelected(bool bInSelected);
|
||||
private:
|
||||
void ShowMenu();
|
||||
void DeleteTrack();
|
||||
void DeleteInstrument();
|
||||
|
||||
TArray<float> MeterValue() const;
|
||||
void OnVolumeChanged(float X);
|
||||
|
||||
bool LeftMouseButtonDown = false;
|
||||
FMixerTrackEvent OnClicked;
|
||||
FMixerTrack* MixerTrack = nullptr;
|
||||
TSharedPtr<SBorder> Border;
|
||||
FSlateColorBrush White = FSlateColorBrush(FColor::White);
|
||||
};
|
||||
160
Source/Arona/UI/Widget/Pattern/SPatternInstance.cpp
Normal file
160
Source/Arona/UI/Widget/Pattern/SPatternInstance.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPatternInstance.h"
|
||||
|
||||
#include "SButton.h"
|
||||
#include "SInvalidationPanel.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "STextBlock.h"
|
||||
#include "SViewport.h"
|
||||
#include "Pattern/PatternInstance.h"
|
||||
#include "Render/UpdatableTexture.h"
|
||||
#include "UI/Widget/SUpdatableTexture.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SPatternInstance::Construct(const FArguments& InArgs)
|
||||
{
|
||||
PatternInstance = InArgs._PatternInstance;
|
||||
FrameToPixel = InArgs._FrameToPixel;
|
||||
SnapFrame = InArgs._SnapFrame;
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderBackgroundColor(FLinearColor(0, 0, 0, 0.3f))
|
||||
]
|
||||
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString("X"))
|
||||
.OnClicked(this, &SPatternInstance::OpenPatternMenu)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(PatternInstance->GetOwner()->Name))
|
||||
.OverflowPolicy(ETextOverflowPolicy::Ellipsis)
|
||||
.Justification(ETextJustify::Left)
|
||||
]
|
||||
]
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(1.f)
|
||||
[
|
||||
InArgs._View
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
FReply SPatternInstance::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (ResizeDirection != ResizeDir::None)
|
||||
{
|
||||
FPatternSelector::Get().SelectPatternInstance(PatternInstance);
|
||||
const TRange<AudioFrame>& Range = PatternInstance->TimeRange;
|
||||
BeginPatternStart = Range.GetLowerBoundValue();
|
||||
BeginPatternEnd = Range.GetUpperBoundValue();
|
||||
MouseDownPos = MouseEvent.GetScreenSpacePosition();
|
||||
BeginPatternPos = PatternInstance->Pos;
|
||||
return FReply::Handled().CaptureMouse(AsShared());
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SPatternInstance::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (ResizeDirection != ResizeDir::None)
|
||||
{
|
||||
PatternInstance->RequestEndSetting();
|
||||
return FReply::Handled().ReleaseMouseCapture();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SPatternInstance::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
const FVector2f LocalMousePos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
||||
|
||||
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
|
||||
{
|
||||
const float MouseDelta = (MouseEvent.GetScreenSpacePosition().X - MouseDownPos.X) / MyGeometry.Scale;
|
||||
const float Scaler = FrameToPixel.Get();
|
||||
const bool NoSnap = MouseEvent.GetModifierKeys().IsAltDown();
|
||||
double Delta = MouseDelta / Scaler;
|
||||
if (!NoSnap)
|
||||
Delta = FMath::GridSnap(Delta, SnapFrame.Get());
|
||||
|
||||
switch (ResizeDirection)
|
||||
{
|
||||
case ResizeDir::None: break;
|
||||
case ResizeDir::Left:
|
||||
{
|
||||
AudioFrame NewRangeStart = BeginPatternStart + Delta;
|
||||
AudioFrame NewPos = BeginPatternPos + Delta;
|
||||
if (NewRangeStart.Pos < 0)
|
||||
{
|
||||
NewPos -= NewRangeStart;
|
||||
NewRangeStart = 0;
|
||||
}
|
||||
if (NewPos.Pos < 0)
|
||||
{
|
||||
NewRangeStart -= NewPos;
|
||||
NewPos = 0;
|
||||
}
|
||||
PatternInstance->TimeRange.SetLowerBoundValue(NewRangeStart);
|
||||
PatternInstance->SetMidiPos(NewPos.Ticks());
|
||||
return FReply::Handled();
|
||||
}
|
||||
case ResizeDir::Right:
|
||||
{
|
||||
const AudioFrame NewRangeEnd = FMath::Max<AudioFrame>(0, BeginPatternEnd + Delta);
|
||||
PatternInstance->TimeRange.SetUpperBoundValue(NewRangeEnd);
|
||||
return FReply::Handled();
|
||||
}
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LocalMousePos.X <= ResizeHandleSize)
|
||||
{
|
||||
ResizeDirection = ResizeDir::Left;
|
||||
}
|
||||
else if (LocalMousePos.X >= MyGeometry.GetLocalSize().X - ResizeHandleSize)
|
||||
{
|
||||
ResizeDirection = ResizeDir::Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResizeDirection = ResizeDir::None;
|
||||
}
|
||||
switch (ResizeDirection)
|
||||
{
|
||||
case ResizeDir::None: SetCursor(EMouseCursor::Default); break;
|
||||
case ResizeDir::Left:
|
||||
case ResizeDir::Right: SetCursor(EMouseCursor::ResizeLeftRight); break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
71
Source/Arona/UI/Widget/Pattern/SPatternInstance.h
Normal file
71
Source/Arona/UI/Widget/Pattern/SPatternInstance.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
|
||||
class SUpdatableTexture;
|
||||
class FPatternInstance;
|
||||
class FUpdatableTexture;
|
||||
|
||||
struct FPatternInstanceClickData
|
||||
{
|
||||
FPatternInstance* PatternInstance;
|
||||
double MidiTimestampOffset;
|
||||
};
|
||||
|
||||
DECLARE_DELEGATE_RetVal_OneParam(FReply, FPatternInstanceClickDelegate, FPatternInstanceClickData)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPatternInstance : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
|
||||
enum class ResizeDir
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
SLATE_BEGIN_ARGS(SPatternInstance)
|
||||
{}
|
||||
|
||||
SLATE_ARGUMENT(FPatternInstance*, PatternInstance)
|
||||
SLATE_ATTRIBUTE(float, FrameToPixel)
|
||||
|
||||
SLATE_ATTRIBUTE(AudioFrame, SnapFrame)
|
||||
SLATE_ARGUMENT(TSharedRef<SWidget>, View)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
FPatternInstance* GetPatternInstance() const { return PatternInstance; }
|
||||
|
||||
float ResizeHandleSize = 5.f;
|
||||
protected:
|
||||
virtual FReply OpenPatternMenu() { return FReply::Unhandled(); }
|
||||
|
||||
ResizeDir ResizeDirection = ResizeDir::None;
|
||||
|
||||
FPatternInstance* PatternInstance = nullptr;
|
||||
|
||||
TAttribute<float> FrameToPixel;
|
||||
TAttribute<AudioFrame> SnapFrame;
|
||||
|
||||
FVector2f MouseDownPos; // 屏幕坐标
|
||||
AudioFrame BeginPatternEnd;
|
||||
AudioFrame BeginPatternStart;
|
||||
AudioFrame BeginPatternPos;
|
||||
|
||||
mutable FVector2f LastSize;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
#include "SWaveformPatternInstance.h"
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "SPatternInstance.h"
|
||||
|
||||
class ARONA_API SWaveformPatternInstance : public SPatternInstance
|
||||
{
|
||||
public:
|
||||
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SMidiPatternThumbnail.h"
|
||||
|
||||
#include "SInvalidationPanel.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "Render/UpdatableTexture.h"
|
||||
#include "Pattern/MidiPattern.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "UI/Widget/SUpdatableImage.h"
|
||||
#include "UI/Widget/Thumbnail.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
|
||||
SMidiPatternThumbnail::~SMidiPatternThumbnail()
|
||||
{
|
||||
if (FMidiPattern* Pattern = (FMidiPattern*)GetPattern())
|
||||
Pattern->OnChanged.RemoveAll(this);
|
||||
}
|
||||
|
||||
void SMidiPatternThumbnail::Construct(const FArguments& InArgs)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SInvalidationPanel)
|
||||
[
|
||||
SAssignNew(UpdatableImage, SUpdatableImage)
|
||||
.OnPostResize(this, &SMidiPatternThumbnail::UpdateMidiThumbnail)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void SMidiPatternThumbnail::Redraw()
|
||||
{
|
||||
OnChanged(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void SMidiPatternThumbnail::OnPatternChanged(FPattern* OldPattern, FPattern* NewPattern)
|
||||
{
|
||||
FMidiPattern* OldMidiPattern = (FMidiPattern*)OldPattern;
|
||||
FMidiPattern* NewMidiPattern = (FMidiPattern*)NewPattern;
|
||||
check(NewPattern->Type == EPatternType::Midi)
|
||||
|
||||
if (OldMidiPattern)
|
||||
OldMidiPattern->OnChanged.RemoveAll(this);
|
||||
|
||||
if (!NewMidiPattern)
|
||||
return;
|
||||
NewMidiPattern->OnChanged_MainThread.AddRaw(this, &SMidiPatternThumbnail::OnChanged);
|
||||
}
|
||||
|
||||
void SMidiPatternThumbnail::OnChanged(FPluginHost* Host, FMidiMessageSequence* Changed)
|
||||
{
|
||||
if (!GetPattern())
|
||||
return;
|
||||
UpdatableImage->NeedRedraw();
|
||||
}
|
||||
|
||||
void SMidiPatternThumbnail::UpdateMidiThumbnail(FImageData& ImageData)
|
||||
{
|
||||
if (!GetPattern())
|
||||
return;
|
||||
|
||||
ImageData.ClearColor(FColor::Black);
|
||||
FMidiPattern* Pattern = (FMidiPattern*)GetPattern();
|
||||
Thumbnail::GenerateMidiPatternThumbnail(Pattern, ImageData, FColor::White);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@@ -0,0 +1,32 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SPatternThumbnail.h"
|
||||
|
||||
class SUpdatableImage;
|
||||
struct FImageData;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SMidiPatternThumbnail : public SPatternThumbnail
|
||||
{
|
||||
public:
|
||||
~SMidiPatternThumbnail();
|
||||
SLATE_BEGIN_ARGS(SMidiPatternThumbnail)
|
||||
{
|
||||
}
|
||||
SLATE_END_ARGS()
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual void Redraw() override;
|
||||
protected:
|
||||
void OnPatternChanged(FPattern* OldPattern, FPattern* NewPattern);
|
||||
private:
|
||||
void OnChanged(FPluginHost* Host, FMidiMessageSequence* Changed);
|
||||
void UpdateMidiThumbnail(FImageData& ImageData);
|
||||
|
||||
TSharedPtr<SUpdatableImage> UpdatableImage;
|
||||
};
|
||||
93
Source/Arona/UI/Widget/PatternThumbnail/SPatternSelector.cpp
Normal file
93
Source/Arona/UI/Widget/PatternThumbnail/SPatternSelector.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPatternSelector.h"
|
||||
|
||||
#include "SPatternThumbnail.h"
|
||||
#include "SInvalidationPanel.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SMenuAnchor.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "UI/Widget/ChannelRack/ChannelRackMidiThumbnail.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SPatternSelector::Construct(const FArguments& InArgs)
|
||||
{
|
||||
FPatternSelector::Get().OnPatternSelected.AddRaw(this, &SPatternSelector::OnPatternSelected);
|
||||
FPatternSelector::Get().OnPatternDeselected.AddRaw(this, &SPatternSelector::OnPatternDeselected);
|
||||
PatternList = SNew(SScrollBox);
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(MenuAnchor, SMenuAnchor)
|
||||
.OnGetMenuContent(this, &SPatternSelector::CreatePatternList)
|
||||
.Placement(EMenuPlacement::MenuPlacement_ComboBox)
|
||||
[
|
||||
SAssignNew(SelectPatternName, STextBlock)
|
||||
]
|
||||
];
|
||||
|
||||
FMidiSequencer::Get().OnNewPattern.AddRaw(this, &SPatternSelector::OnNewPattern);
|
||||
FMidiSequencer::Get().OnDeletePattern.AddRaw(this, &SPatternSelector::OnDeletePattern);
|
||||
|
||||
for (FPattern* MidiPattern : FMidiSequencer::Get().GetPattern(EPatternType::Midi))
|
||||
{
|
||||
OnNewPattern(MidiPattern);
|
||||
}
|
||||
}
|
||||
|
||||
FReply SPatternSelector::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
MenuAnchor->SetIsOpen(true);
|
||||
}
|
||||
return SCompoundWidget::OnMouseButtonDown(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
void SPatternSelector::OnPatternSelected(FPattern* Pattern)
|
||||
{
|
||||
SelectPatternName->SetText(FText::FromString(Pattern->Name));
|
||||
MenuAnchor->SetIsOpen(false);
|
||||
FPatternSelector::Get().SelectPattern(Pattern);
|
||||
}
|
||||
|
||||
void SPatternSelector::OnPatternDeselected(FPattern* Pattern)
|
||||
{
|
||||
SelectPatternName->SetText(FText::FromString("无"));
|
||||
}
|
||||
|
||||
void SPatternSelector::OnNewPattern(FPattern* Pattern)
|
||||
{
|
||||
const TSharedRef<SAutoPatternThumbnail> PatternThumbnail = SNew(SAutoPatternThumbnail, Pattern);
|
||||
PatternThumbnail->OnPatternClicked.AddRaw(this, &SPatternSelector::OnPatternSelected);
|
||||
PatternList->AddSlot()
|
||||
.HAlign(HAlign_Fill)
|
||||
[
|
||||
PatternThumbnail
|
||||
];
|
||||
}
|
||||
|
||||
void SPatternSelector::OnDeletePattern(FPattern* Pattern)
|
||||
{
|
||||
const TSharedPtr<SScrollPanel>& ScrollPanel = PatternList->GetScrollPanel();
|
||||
FChildren* Children = ScrollPanel->GetChildren();
|
||||
for (int32 i = 0; i < Children->Num(); ++i)
|
||||
{
|
||||
TSharedRef<SWidget> Child = Children->GetChildAt(i);
|
||||
TSharedRef<SAutoPatternThumbnail> ThumbnailWidget = StaticCastSharedRef<SAutoPatternThumbnail>(Child);
|
||||
if (ThumbnailWidget->GetPattern() == Pattern)
|
||||
{
|
||||
PatternList->RemoveSlot(Child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SPatternSelector::CreatePatternList()
|
||||
{
|
||||
return PatternList.ToSharedRef();
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
45
Source/Arona/UI/Widget/PatternThumbnail/SPatternSelector.h
Normal file
45
Source/Arona/UI/Widget/PatternThumbnail/SPatternSelector.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
|
||||
class STextBlock;
|
||||
class SAutoPatternThumbnail;
|
||||
class SPatternThumbnail;
|
||||
class SMidiPatternThumbnail;
|
||||
class SScrollBox;
|
||||
class SMenuAnchor;
|
||||
class FPattern;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPatternSelector : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPatternSelector)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
private:
|
||||
void OnPatternSelected(FPattern* Pattern);
|
||||
void OnPatternDeselected(FPattern* Pattern);
|
||||
|
||||
void OnNewPattern(FPattern* Pattern);
|
||||
void OnDeletePattern(FPattern* Pattern);
|
||||
|
||||
TSharedRef<SWidget> CreatePatternList();
|
||||
|
||||
TSharedPtr<SMenuAnchor> MenuAnchor;
|
||||
TSharedPtr<SScrollBox> PatternList;
|
||||
TSharedPtr<STextBlock> SelectPatternName;
|
||||
|
||||
TSharedPtr<SAutoPatternThumbnail> Thumbnail;
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPatternThumbnail.h"
|
||||
|
||||
#include "SInvalidationPanel.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SMidiPatternThumbnail.h"
|
||||
#include "SSamplePatternThumbnail.h"
|
||||
#include "STextBlock.h"
|
||||
#include "Pattern/Pattern.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SNullPatternThumbnail::Construct(const FArguments& InArgs, FText InText)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Justification(ETextJustify::Center)
|
||||
.Text(InText)
|
||||
];
|
||||
}
|
||||
|
||||
TSharedRef<SPatternThumbnail> SNullPatternThumbnail::Create(FText InText)
|
||||
{
|
||||
return SNew(SNullPatternThumbnail, InText);
|
||||
}
|
||||
|
||||
void SAutoPatternThumbnail::Construct(const FArguments& InArgs, FPattern* InPattern)
|
||||
{
|
||||
SetPattern(InPattern);
|
||||
}
|
||||
|
||||
void SAutoPatternThumbnail::SetPattern(FPattern* InPattern)
|
||||
{
|
||||
Pattern = InPattern;
|
||||
ChildSlot.DetachWidget();
|
||||
Thumbnail = CreateThumbnail(InPattern);
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SInvalidationPanel)
|
||||
[
|
||||
Thumbnail.ToSharedRef()
|
||||
]
|
||||
];
|
||||
Thumbnail->Redraw();
|
||||
}
|
||||
|
||||
FReply SAutoPatternThumbnail::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && Thumbnail.IsValid())
|
||||
{
|
||||
if (Thumbnail->GetPattern())
|
||||
{
|
||||
OnPatternClicked.Broadcast(Thumbnail->GetPattern());
|
||||
}
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FVector2D SAutoPatternThumbnail::ComputeDesiredSize(float LayoutScaleMultiplier) const
|
||||
{
|
||||
return FVector2D(0, 36) * LayoutScaleMultiplier;
|
||||
}
|
||||
|
||||
TSharedPtr<SPatternThumbnail> SAutoPatternThumbnail::CreateThumbnail(FPattern* InPattern)
|
||||
{
|
||||
TSharedPtr<SPatternThumbnail> Out;
|
||||
if (!InPattern)
|
||||
{
|
||||
Out = SNullPatternThumbnail::Create(FText::FromString(TEXT("Null Pattern")));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Pattern->Type)
|
||||
{
|
||||
case EPatternType::Midi:
|
||||
SAssignNew(Out, SMidiPatternThumbnail);
|
||||
break;
|
||||
case EPatternType::Automation:
|
||||
break;
|
||||
case EPatternType::Sample:
|
||||
SAssignNew(Out, SSamplePatternThumbnail, static_cast<FSamplePattern*>(InPattern)->GetSampler());
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
Out->SetPattern(InPattern);
|
||||
return Out;
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
68
Source/Arona/UI/Widget/PatternThumbnail/SPatternThumbnail.h
Normal file
68
Source/Arona/UI/Widget/PatternThumbnail/SPatternThumbnail.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
|
||||
class FMidiMessageSequence;
|
||||
class FPluginHost;
|
||||
class FPattern;
|
||||
|
||||
class ARONA_API SPatternThumbnail : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
void SetPattern(FPattern* InPattern) { OnPatternChanged(CurrentPattern, InPattern); CurrentPattern = InPattern; }
|
||||
FPattern* GetPattern() const { return CurrentPattern; }
|
||||
virtual void Redraw() {}
|
||||
|
||||
TRange<MidiTick> MidiTickRange;
|
||||
protected:
|
||||
virtual void OnPatternChanged(FPattern* OldPattern, FPattern* NewPattern) {}
|
||||
private:
|
||||
FPattern* CurrentPattern = nullptr;
|
||||
};
|
||||
|
||||
class ARONA_API SNullPatternThumbnail : public SPatternThumbnail
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SNullPatternThumbnail)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FText InText);
|
||||
|
||||
static TSharedRef<SPatternThumbnail> Create(FText InText);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SAutoPatternThumbnail : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SAutoPatternThumbnail)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPattern* InPattern);
|
||||
|
||||
void SetPattern(FPattern* InPattern);
|
||||
FPattern* GetPattern() const { return Pattern; }
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
FPatternDelegate OnPatternClicked;
|
||||
private:
|
||||
TSharedPtr<SPatternThumbnail> CreateThumbnail(FPattern* InPattern);
|
||||
|
||||
TSharedPtr<SPatternThumbnail> Thumbnail;
|
||||
FPattern* Pattern = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SSamplePatternThumbnail.h"
|
||||
|
||||
#include "Async.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "ExecutionTime.h"
|
||||
#include "Pattern/SamplePatternInstance.h"
|
||||
#include "PluginHost/Sampler.h"
|
||||
#include "UI/Widget/SUpdatableImage.h"
|
||||
#include "UI/Widget/Thumbnail.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
TArray<float> FSampleWaveformHandle::GetWaveform(int32 SizeX) const
|
||||
{
|
||||
|
||||
const FSampler* Sampler = SampleInstance->GetInstanceOwner()->GetSampler();
|
||||
TArray<TArray64<float>> Copy = Sampler->GetSampleBuffer(); // 拷贝以防在渲染时被修改
|
||||
uint32 Count = Sampler->GetFrameCount();
|
||||
TRange<uint32> Range = SampleInstance->GetRange();
|
||||
return Thumbnail::GenerateWaveformData(SizeX, Copy, Count, Range);
|
||||
}
|
||||
|
||||
void SSamplePatternThumbnail::Construct(const FArguments& InArgs, FSampler* InSampler)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
void SSamplePatternThumbnail::Redraw()
|
||||
{
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@@ -0,0 +1,37 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SPatternThumbnail.h"
|
||||
#include "Render/UpdatableTexture.h"
|
||||
#include "UI/Widget/WaveformViewer.h"
|
||||
|
||||
class FSampler;
|
||||
|
||||
|
||||
class FSampleWaveformHandle : public IWaveformHandle
|
||||
{
|
||||
public:
|
||||
FSamplePatternInstance* SampleInstance = nullptr;
|
||||
virtual TArray<float> GetWaveform(int32 SizeX) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SSamplePatternThumbnail : public SPatternThumbnail
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SSamplePatternThumbnail)
|
||||
{}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FSampler* InSampler);
|
||||
|
||||
virtual void Redraw() override;
|
||||
private:
|
||||
FSampleWaveformHandle* WaveformHandle = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "PianoRollEditTool.h"
|
||||
|
||||
#include "PluginHost/PluginHost.h"
|
||||
|
||||
void FPianoRollEditTool::OnActive(FPianoRollEdit& Edit)
|
||||
{
|
||||
PluginHost = Edit.GetPluginHost();
|
||||
}
|
||||
|
||||
void FPianoRollEditTool::Clear(FPianoRollEdit& Edit)
|
||||
{
|
||||
PluginHost = nullptr;
|
||||
}
|
||||
|
||||
void FPianoRollEditTool::PlaySelected(FPianoRollEdit& Edit)
|
||||
{
|
||||
if (!PluginHost)
|
||||
return;
|
||||
|
||||
TArray<int32> Notes;
|
||||
for (const FMidiMessageSequence::MidiEventHolder* Selected : Edit.Selector.GetSelectedMidiMessages())
|
||||
{
|
||||
if (!Selected->noteOffObject)
|
||||
continue;
|
||||
if (Notes.Contains(Selected->message.getNoteNumber()))
|
||||
continue;
|
||||
|
||||
FMidiMessage NoteOn = Selected->message;
|
||||
FMidiMessage NoteOff = Selected->noteOffObject->message;
|
||||
Notes.Add(NoteOn.getNoteNumber());
|
||||
|
||||
PluginHost->IncomingMidi.addEvent(NoteOn, 0);
|
||||
PluginHost->IncomingMidi.addEvent(NoteOff, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEditTool::StopSelected(FPianoRollEdit& Edit)
|
||||
{
|
||||
if (!PluginHost)
|
||||
return;
|
||||
|
||||
TArray<int32> Notes;
|
||||
for (const FMidiMessageSequence::MidiEventHolder* Selected : Edit.Selector.GetSelectedMidiMessages())
|
||||
{
|
||||
if (!Selected->noteOffObject)
|
||||
continue;
|
||||
if (Notes.Contains(Selected->message.getNoteNumber()))
|
||||
continue;
|
||||
|
||||
FMidiMessage NoteOff = Selected->noteOffObject->message;
|
||||
Notes.Add(Selected->message.getNoteNumber());
|
||||
|
||||
PluginHost->IncomingMidi.addEvent(NoteOff, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "Reply.h"
|
||||
#include "WidgetStyle.h"
|
||||
#include "UI/Widget/PianoRoll/PianoRollPanel/PianoRollEdit.h"
|
||||
|
||||
class FPianoRollEditTool
|
||||
{
|
||||
public:
|
||||
FPianoRollEditTool(EPianoRollToolType InToolType) : PluginHost(nullptr), ToolType(InToolType)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~FPianoRollEditTool() = default;
|
||||
|
||||
virtual void OnActive(FPianoRollEdit& Edit);
|
||||
virtual void Clear(FPianoRollEdit& Edit);
|
||||
|
||||
virtual void OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { }
|
||||
virtual void OnMouseButtonUp(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { }
|
||||
virtual void OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { }
|
||||
|
||||
virtual void OnBeginDrag(FPianoRollEdit& Edit, FVector2f InMouseStartPos) { }
|
||||
virtual void OnEndDrag(FPianoRollEdit& Edit, FVector2f InMouseEndPos) { }
|
||||
|
||||
virtual int32 OnPaint(const FPianoRollEdit& Edit, const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { return LayerId; }
|
||||
|
||||
EPianoRollToolType GetToolType() const { return ToolType; }
|
||||
FPluginHost* GetPluginHost() const { return PluginHost; }
|
||||
protected:
|
||||
void PlaySelected(FPianoRollEdit& Edit);
|
||||
void StopSelected(FPianoRollEdit& Edit);
|
||||
private:
|
||||
FPluginHost* PluginHost;
|
||||
EPianoRollToolType ToolType;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "PianoRollEditTool_DragView.h"
|
||||
|
||||
#include "SlateApplication.h"
|
||||
#include "UI/Widget/PianoRoll/PianoRollPanel/SPianoRollPanel.h"
|
||||
|
||||
void FPianoRollEditTool_DragView::OnActive(FPianoRollEdit& Edit)
|
||||
{
|
||||
FPianoRollEditTool::OnActive(Edit);
|
||||
const TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
OriginalRange = Panel->TimeRange;
|
||||
OriginalMousePos = FSlateApplication::Get().GetCursorPos();
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_DragView::OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry,
|
||||
const FPointerEvent& MouseEvent)
|
||||
{
|
||||
OriginalMousePos = MouseEvent.GetScreenSpacePosition();
|
||||
const TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
OriginalRange = Panel->TimeRange;
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_DragView::OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry,
|
||||
const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (!MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton))
|
||||
return;
|
||||
|
||||
const FVector2f MouseCurrentPos = MouseEvent.GetScreenSpacePosition();
|
||||
TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
const FVector2f Delta = MouseCurrentPos - OriginalMousePos;
|
||||
const double TickDelta = Delta.X / Panel->FrameToPixel / MyGeometry.Scale;
|
||||
|
||||
const AudioFrame Lower = FMath::Max<AudioFrame>(0, OriginalRange.GetLowerBoundValue() - TickDelta);
|
||||
const AudioFrame Upper = FMath::Max<AudioFrame>(OriginalRange.Size<double>(), OriginalRange.GetUpperBoundValue() - TickDelta);
|
||||
Panel->OnTargetRangeChanged.ExecuteIfBound(TRange<AudioFrame>(Lower, Upper));
|
||||
Panel->OnYRequestUpdate.ExecuteIfBound(-MouseEvent.GetCursorDelta().Y / MyGeometry.Scale);
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_DragView::OnBeginDrag(FPianoRollEdit& Edit, FVector2f InMouseStartPos)
|
||||
{
|
||||
OriginalMousePos = FSlateApplication::Get().GetCursorPos();
|
||||
const TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
OriginalRange = Panel->TimeRange;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "PianoRollEditTool.h"
|
||||
|
||||
class FPianoRollEditTool_DragView : public FPianoRollEditTool
|
||||
{
|
||||
public:
|
||||
FPianoRollEditTool_DragView() : FPianoRollEditTool(EPianoRollToolType::DragView)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void OnActive(FPianoRollEdit& Edit) override;
|
||||
virtual void OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnBeginDrag(FPianoRollEdit& Edit, FVector2f InMouseStartPos) override;
|
||||
private:
|
||||
TRange<AudioFrame> OriginalRange;
|
||||
FVector2f OriginalMousePos;
|
||||
float OriginalYOffset = 0;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "PianoRollEditTool_Select.h"
|
||||
|
||||
#include "SlateApplication.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "UI/Widget/PianoRoll/PianoRollPanel/SPianoRollPanel.h"
|
||||
|
||||
void FPianoRollEditTool_Select::Clear(FPianoRollEdit& Edit)
|
||||
{
|
||||
FPianoRollEditTool::Clear(Edit);
|
||||
RectSelecting = false;
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Select::OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry,
|
||||
const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
RectSelecting = true;
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Select::OnMouseButtonUp(FPianoRollEdit& Edit, const FGeometry& MyGeometry,
|
||||
const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
RectSelecting = false;
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Select::OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry,
|
||||
const FPointerEvent& MouseEvent)
|
||||
{
|
||||
MouseCurrentPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Select::OnEndDrag(FPianoRollEdit& Edit, FVector2f InMouseEndPos)
|
||||
{
|
||||
Edit.Selector.UnSelect();
|
||||
|
||||
const TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
const FVector2f& MouseDownPos = Edit.GetMouseDownPos();
|
||||
|
||||
TArray<FMidiMessageSequence::MidiEventHolder*> InRectMidiMessages;
|
||||
const int MouseDownNote = FMath::Abs(Panel->HeightToNoteNumber(MouseDownPos.Y));
|
||||
const int MouseUpNote = FMath::Abs(Panel->HeightToNoteNumber(InMouseEndPos.Y));
|
||||
|
||||
const TRange<int> NoteRange(MouseUpNote, MouseDownNote);
|
||||
const TRange<AudioFrame> MouseMidiTickRange(Panel->PixelToFrame(MouseDownPos.X), Panel->PixelToFrame(InMouseEndPos.X));
|
||||
|
||||
FMidiMessageSequence* MidiEventHolders = Edit.GetMidiMessageSequence();
|
||||
for (FMidiMessageSequence::MidiEventHolder* MidiMessage : *MidiEventHolders)
|
||||
{
|
||||
if (!MidiMessage->message.isNoteOn())
|
||||
continue;
|
||||
|
||||
// 如果MidiMessage的时间戳大于选择框的结束时间,那么就不需要再遍历了
|
||||
if (MidiMessage->message.getTimeStamp() > MouseMidiTickRange.GetUpperBoundValue())
|
||||
break;
|
||||
|
||||
const double NoteOnTime = MidiMessage->message.getTimeStamp();
|
||||
const double Duration = MidiMessage->getDuration();
|
||||
|
||||
if (!MouseMidiTickRange.Contains(NoteOnTime) && !MouseMidiTickRange.Contains(NoteOnTime + Duration))
|
||||
continue;
|
||||
|
||||
const int NoteNumber = MidiMessage->message.getNoteNumber();
|
||||
if (!NoteRange.Contains(NoteNumber))
|
||||
continue;
|
||||
|
||||
InRectMidiMessages.Add(MidiMessage);
|
||||
}
|
||||
|
||||
Edit.Selector.SelectMidiMessage(InRectMidiMessages);
|
||||
PlaySelected(Edit);
|
||||
}
|
||||
|
||||
int32 FPianoRollEditTool_Select::OnPaint(const FPianoRollEdit& Edit, const FPaintArgs& Args, const FGeometry& AllottedGeometry,
|
||||
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId,
|
||||
const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
if (!RectSelecting)
|
||||
return LayerId;
|
||||
// 绘制选择框
|
||||
const FVector2f& MouseDownPos = Edit.GetMouseDownPos();
|
||||
const FVector2f RectStart = FVector2f(MouseDownPos.X, MouseDownPos.Y);
|
||||
const FVector2f RectEnd = FVector2f(MouseCurrentPos.X, MouseCurrentPos.Y);
|
||||
|
||||
// Draw Line
|
||||
TArray<FVector2f> Points;
|
||||
Points.Add(RectStart);
|
||||
Points.Add(FVector2f(RectEnd.X, RectStart.Y));
|
||||
Points.Add(RectEnd);
|
||||
Points.Add(FVector2f(RectStart.X, RectEnd.Y));
|
||||
Points.Add(RectStart);
|
||||
FSlateDrawElement::MakeLines(
|
||||
OutDrawElements,
|
||||
++LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(),
|
||||
Points,
|
||||
ESlateDrawEffect::None,
|
||||
FLinearColor::Red,
|
||||
true,
|
||||
1
|
||||
);
|
||||
|
||||
return LayerId;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "PianoRollEditTool.h"
|
||||
|
||||
class FPianoRollEditTool_Select : public FPianoRollEditTool
|
||||
{
|
||||
public:
|
||||
FPianoRollEditTool_Select() : FPianoRollEditTool(EPianoRollToolType::Select)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void Clear(FPianoRollEdit& Edit) override;
|
||||
virtual void OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnMouseButtonUp(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnEndDrag(FPianoRollEdit& Edit, FVector2f InMouseEndPos) override;
|
||||
|
||||
virtual int32 OnPaint(const FPianoRollEdit& Edit, const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
private:
|
||||
bool RectSelecting = false;
|
||||
FVector2f MouseCurrentPos;
|
||||
};
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "PianoRollEditTool_Write.h"
|
||||
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "UI/Widget/PianoRoll/PianoRollPanel/SPianoRollPanel.h"
|
||||
|
||||
FPianoRollEditTool_Write::FPianoRollEditTool_Write(): FPianoRollEditTool(EPianoRollToolType::Write), Channel(1),
|
||||
Velocity(1), NoteLength(FMidiSequencer::Get().GetBeat())
|
||||
{
|
||||
TempSelector.OnMidiMessageSelected.AddRaw(this, &FPianoRollEditTool_Write::OnMidiMessageSelected);
|
||||
TempSelector.OnMidiMessageUnSelected.AddRaw(this, &FPianoRollEditTool_Write::OnMidiMessageUnSelected);
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnActive(FPianoRollEdit& Edit)
|
||||
{
|
||||
FPianoRollEditTool::OnActive(Edit);
|
||||
Edit.Selector.OnMidiMessageSelected.AddRaw(this, &FPianoRollEditTool_Write::OnMidiMessageSelected);
|
||||
Edit.Selector.OnMidiMessageUnSelected.AddRaw(this, &FPianoRollEditTool_Write::OnMidiMessageUnSelected);
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::Clear(FPianoRollEdit& Edit)
|
||||
{
|
||||
FPianoRollEditTool::Clear(Edit);
|
||||
HasDeleteNote = false;
|
||||
TempSelector.UnSelect();
|
||||
|
||||
Edit.Selector.OnMidiMessageSelected.RemoveAll(this);
|
||||
Edit.Selector.OnMidiMessageUnSelected.RemoveAll(this);
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
HasDeleteNote = false;
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
LeftMouseButton(Edit);
|
||||
}
|
||||
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
RightMouseButton(Edit);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnMouseButtonUp(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && !HasDeleteNote)
|
||||
Edit.Selector.UnSelect();
|
||||
TempSelector.UnSelect();
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
const FGeometry& TickSpaceGeometry = MyGeometry;
|
||||
const auto& CursorPos = MouseEvent.GetScreenSpacePosition();
|
||||
const auto& LocalPos = TickSpaceGeometry.AbsoluteToLocal(CursorPos);
|
||||
const TSharedPtr<SPianoRollPanel> Panel = Edit.PianoRollPanel.Pin();
|
||||
|
||||
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
|
||||
{
|
||||
const int MouseNote = Panel->HeightToNoteNumber(LocalPos.Y);
|
||||
const AudioFrame& NewFrame = Panel->PixelToFrame(LocalPos.X);
|
||||
UpdateTempSelector(Edit, MouseNote, NewFrame.Ticks());
|
||||
UpdateSelector(Edit, MouseNote, NewFrame.Ticks());
|
||||
}
|
||||
|
||||
if (MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton))
|
||||
{
|
||||
RightMouseButton(Edit);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnMidiMessageSelected(FMidiMessageSequence::MidiEventHolder* MidiEventHolder)
|
||||
{
|
||||
PlayNote(MidiEventHolder->message.getNoteNumber());
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::OnMidiMessageUnSelected(FMidiMessageSequence::MidiEventHolder* MidiEventHolder)
|
||||
{
|
||||
StopNote(MidiEventHolder->message.getNoteNumber());
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::PlayNote(int32 Note)
|
||||
{
|
||||
if (GetPluginHost())
|
||||
GetPluginHost()->IncomingMidi.addEvent(FMidiMessage::noteOn(Channel, Note, Velocity));
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::StopNote(int32 Note)
|
||||
{
|
||||
if (GetPluginHost())
|
||||
GetPluginHost()->IncomingMidi.addEvent(FMidiMessage::noteOff(Channel, Note, Velocity));
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::LeftMouseButton(FPianoRollEdit& Edit)
|
||||
{
|
||||
FPianoRollSelector& Selector = Edit.Selector;
|
||||
const MidiTick SnapMidiTimestamp = Edit.GetSnapFrame();
|
||||
|
||||
if (const FNoteSelectData& SelectedNote = Edit.GetMidiMessageUnderMouse())
|
||||
{
|
||||
FMidiMessageSequence::MidiEventHolder* MidiEventHolder = SelectedNote.MidiMessage;
|
||||
const int BasePitch = MidiEventHolder->message.getNoteNumber();
|
||||
const double MidiTimestampDelta = SelectedNote.MidiTimestampOffset;
|
||||
const double BaseMidiTimestamp = MidiEventHolder->message.getTimeStamp();
|
||||
Channel = MidiEventHolder->message.getChannel();
|
||||
Velocity = MidiEventHolder->message.getFloatVelocity();
|
||||
NoteLength = MidiEventHolder->noteOffObject ? MidiEventHolder->noteOffObject->message.getTimeStamp() - BaseMidiTimestamp : 0;
|
||||
|
||||
if (Selector.IsSelected(MidiEventHolder))
|
||||
{
|
||||
Selector.BeginDrag(BasePitch, MidiTimestampDelta, SnapMidiTimestamp, BaseMidiTimestamp);
|
||||
Selector.ResetAnchorPoint();
|
||||
}
|
||||
else
|
||||
{
|
||||
Selector.UnSelect();
|
||||
TempSelector.UnSelect();
|
||||
TempSelector.AddMidiMessage(MidiEventHolder);
|
||||
TempSelector.BeginDrag(BasePitch, MidiTimestampDelta, SnapMidiTimestamp, BaseMidiTimestamp);
|
||||
TempSelector.ResetAnchorPoint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Selector.UnSelect();
|
||||
|
||||
const int32 NoteNumberUnderMouse = Edit.GetNoteNumberUnderMouse();
|
||||
const MidiTick MidiTickUnderMouse = Edit.GetAudioFrameUnderMouse();
|
||||
const MidiTick TimestampDelta = MidiTickUnderMouse % SnapMidiTimestamp;
|
||||
const MidiTick SnapMidiTick = MidiTickUnderMouse - TimestampDelta;
|
||||
|
||||
const FMidiMessage& NoteOn = FMidiMessage::noteOn(Channel, NoteNumberUnderMouse, Velocity);
|
||||
FMidiMessageSequence::MidiEventHolder* MidiEventHolder = Edit.GetMidiMessageSequence()->addEvent(NoteOn, SnapMidiTick);
|
||||
if (NoteLength > 0)
|
||||
MidiEventHolder->noteOffObject = Edit.GetMidiMessageSequence()->addEvent(FMidiMessage::noteOff(Channel, NoteNumberUnderMouse, Velocity), SnapMidiTick + NoteLength);
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::RightMouseButton(FPianoRollEdit& Edit)
|
||||
{
|
||||
if (const FNoteSelectData& SelectedNote = Edit.GetMidiMessageUnderMouse())
|
||||
{
|
||||
Edit.Selector.UnSelect(SelectedNote.MidiMessage);
|
||||
Edit.SetModified(true);
|
||||
FMidiMessageSequence* Sequence = Edit.GetMidiMessageSequence();
|
||||
const int NoteIndex = Sequence->getIndexOf(SelectedNote.MidiMessage);
|
||||
Sequence->deleteEvent(NoteIndex, true);
|
||||
HasDeleteNote = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::UpdateTempSelector(FPianoRollEdit& Edit, int32 NewNote, MidiTick NewTick)
|
||||
{
|
||||
if (TempSelector.Num() == 0)
|
||||
return;
|
||||
|
||||
const int32 LastNote = TempSelector.GetSelectedMidiMessages()[0]->message.getNoteNumber();
|
||||
bool IsModify = TempSelector.SetPitch(NewNote);
|
||||
if (IsModify)
|
||||
{
|
||||
StopNote(LastNote);
|
||||
PlayNote(NewNote);
|
||||
}
|
||||
|
||||
IsModify |= TempSelector.SetMidiTimestamp(NewTick);
|
||||
if (IsModify)
|
||||
{
|
||||
Edit.SetModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEditTool_Write::UpdateSelector(FPianoRollEdit& Edit, int32 NewNote, MidiTick NewTick)
|
||||
{
|
||||
if (Edit.Selector.Num() == 0)
|
||||
return;
|
||||
|
||||
bool IsModify = Edit.Selector.SetPitch(NewNote);
|
||||
IsModify |= Edit.Selector.SetMidiTimestamp(NewTick);
|
||||
|
||||
if (IsModify)
|
||||
{
|
||||
Edit.SetModified(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "PianoRollEditTool.h"
|
||||
|
||||
class FPianoRollEditTool_Write : public FPianoRollEditTool
|
||||
{
|
||||
public:
|
||||
FPianoRollEditTool_Write();
|
||||
|
||||
virtual void OnActive(FPianoRollEdit& Edit) override;
|
||||
virtual void Clear(FPianoRollEdit& Edit) override;
|
||||
|
||||
virtual void OnMouseButtonDown(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnMouseButtonUp(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual void OnMouseMove(FPianoRollEdit& Edit, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
int32 Channel;
|
||||
float Velocity;
|
||||
int64 NoteLength;
|
||||
private:
|
||||
void OnMidiMessageSelected(FMidiMessageSequence::MidiEventHolder* MidiEventHolder);
|
||||
void OnMidiMessageUnSelected(FMidiMessageSequence::MidiEventHolder* MidiEventHolder);
|
||||
|
||||
void PlayNote(int32 Note);
|
||||
void StopNote(int32 Note);
|
||||
|
||||
void LeftMouseButton(FPianoRollEdit& Edit);
|
||||
void RightMouseButton(FPianoRollEdit& Edit);
|
||||
|
||||
void UpdateTempSelector(FPianoRollEdit& Edit, int32 NewNote, MidiTick NewTick);
|
||||
void UpdateSelector(FPianoRollEdit& Edit, int32 NewNote, MidiTick NewTick);
|
||||
|
||||
bool HasDeleteNote = false;
|
||||
|
||||
FPianoRollSelector TempSelector;
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
#include "PianoRollEdit.h"
|
||||
|
||||
#include "SlateApplication.h"
|
||||
#include "SPianoRollPanel.h"
|
||||
#include "Pattern/MidiPattern.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "EditTool/PianoRollEditTool.h"
|
||||
#include "EditTool/PianoRollEditTool_DragView.h"
|
||||
#include "EditTool/PianoRollEditTool_Select.h"
|
||||
#include "EditTool/PianoRollEditTool_Write.h"
|
||||
|
||||
FPianoRollEdit::FPianoRollEdit(TSharedRef<SPianoRollPanel> InPianoRollPanel)
|
||||
{
|
||||
PianoRollPanel = InPianoRollPanel;
|
||||
|
||||
EditToolMap.Add(EPianoRollToolType::Write, new FPianoRollEditTool_Write());
|
||||
EditToolMap.Add(EPianoRollToolType::Select, new FPianoRollEditTool_Select());
|
||||
EditToolMap.Add(EPianoRollToolType::DragView, new FPianoRollEditTool_DragView());
|
||||
|
||||
|
||||
ToolKeyMap.Add(EPianoRollToolType::Select, {EKeys::LeftControl, EKeys::LeftMouseButton});
|
||||
ToolKeyMap.Add(EPianoRollToolType::DragView, {EKeys::LeftShift, EKeys::RightMouseButton});
|
||||
|
||||
|
||||
CurrentEditTool = EditToolMap[EPianoRollToolType::Write];
|
||||
CurrentEditTool->OnActive(*this);
|
||||
TempTool = nullptr;
|
||||
}
|
||||
|
||||
FPianoRollEdit::~FPianoRollEdit()
|
||||
{
|
||||
for (const auto& Tuple : EditToolMap)
|
||||
{
|
||||
delete Tuple.Value;
|
||||
}
|
||||
EditToolMap.Empty();
|
||||
}
|
||||
|
||||
FNoteSelectData FPianoRollEdit::GetMidiMessageByPos(FVector2D Pos) const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
FNoteSelectData OutData;
|
||||
|
||||
const int Note = Panel->HeightToNoteNumber(Pos.Y);
|
||||
const float& FrameToPixel = Panel->FrameToPixel;
|
||||
const TRange<AudioFrame>& FrameRange = Panel->TimeRange;
|
||||
FMidiMessageSequence* MidiEventHolders = Panel->MidiMessageSequence;
|
||||
|
||||
// UE_LOG(LogTemp, Log, TEXT("Find Note: %s"), *FMidiMessage::getMidiNoteName(Note, true, true, 3));
|
||||
AudioFrame Time = Pos.X / FrameToPixel;
|
||||
Time += FrameRange.GetLowerBoundValue();
|
||||
const MidiTick Ticks = Time.Ticks();
|
||||
for (FMidiMessageSequence::MidiEventHolder* Holder : *MidiEventHolders)
|
||||
{
|
||||
if (Holder->message.getNoteNumber() != Note)
|
||||
continue;
|
||||
MidiTick BeginMidiTick = FrameRange.GetLowerBoundValue().Ticks();
|
||||
if (Holder->message.getTimeStamp() > BeginMidiTick)
|
||||
break;
|
||||
if (!Holder->message.isNoteOn())
|
||||
continue;
|
||||
if (Ticks < Holder->message.getTimeStamp())
|
||||
continue;
|
||||
|
||||
const FMidiMessageSequence::MidiEventHolder* NoteOffHolder = Holder->noteOffObject;
|
||||
if (NoteOffHolder && Ticks > NoteOffHolder->message.getTimeStamp())
|
||||
continue;
|
||||
|
||||
OutData.MidiMessage = Holder;
|
||||
OutData.MidiTimestampOffset = Ticks - (int64)Holder->message.getTimeStamp();
|
||||
return OutData;
|
||||
}
|
||||
|
||||
return OutData;
|
||||
}
|
||||
|
||||
FNoteSelectData FPianoRollEdit::GetMidiMessageUnderMouse() const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
const FGeometry& TickSpaceGeometry = Panel->GetTickSpaceGeometry();
|
||||
const auto& LocalPos = TickSpaceGeometry.AbsoluteToLocal(FSlateApplication::Get().GetCursorPos());
|
||||
return GetMidiMessageByPos(LocalPos);
|
||||
}
|
||||
|
||||
FReply FPianoRollEdit::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
MouseDownPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
||||
TryEnableTempTool();
|
||||
|
||||
GetCurrentTool()->OnMouseButtonDown(*this, MyGeometry, MouseEvent);
|
||||
UpdateModified();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FPianoRollEdit::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
const FVector2f MouseUpLocalPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
||||
|
||||
// OnEndDrag需要在OnMouseUp之前调用,因为OnMouseUp可能会清除OnMouseDrag
|
||||
if (OnMouseDrag)
|
||||
{
|
||||
OnMouseDrag = false;
|
||||
GetCurrentTool()->OnEndDrag(*this, MouseUpLocalPos);
|
||||
UpdateModified();
|
||||
}
|
||||
|
||||
{
|
||||
GetCurrentTool()->OnMouseButtonUp(*this, MyGeometry, MouseEvent);
|
||||
UpdateModified();
|
||||
}
|
||||
|
||||
TryEnableTempTool();
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FPianoRollEdit::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
const FGeometry& TickSpaceGeometry = MyGeometry;
|
||||
const auto& CursorPos = MouseEvent.GetScreenSpacePosition();
|
||||
const auto& LocalPos = TickSpaceGeometry.AbsoluteToLocal(CursorPos);
|
||||
TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
|
||||
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && !OnMouseDrag)
|
||||
{
|
||||
OnMouseDrag = true;
|
||||
GetCurrentTool()->OnBeginDrag(*this, LocalPos);
|
||||
UpdateModified();
|
||||
}
|
||||
|
||||
GetCurrentTool()->OnMouseMove(*this, MyGeometry, MouseEvent);
|
||||
UpdateModified();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FPianoRollEdit::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
|
||||
{
|
||||
if (KeyEvent.IsRepeat())
|
||||
return FReply::Handled();
|
||||
PressedKeys.Add(KeyEvent.GetKey());
|
||||
TryEnableTempTool();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FPianoRollEdit::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
|
||||
{
|
||||
if (KeyEvent.IsRepeat())
|
||||
return FReply::Handled();
|
||||
PressedKeys.Remove(KeyEvent.GetKey());
|
||||
TryEnableTempTool();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
int32 FPianoRollEdit::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,
|
||||
const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId,
|
||||
const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
return GetCurrentTool()->OnPaint(*this, Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
||||
}
|
||||
|
||||
int32 FPianoRollEdit::GetNoteNumberByPos(float Height) const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
return Panel->HeightToNoteNumber(Height);
|
||||
}
|
||||
|
||||
int32 FPianoRollEdit::GetNoteNumberUnderMouse() const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
const FGeometry& TickSpaceGeometry = Panel->GetTickSpaceGeometry();
|
||||
const auto& LocalPos = TickSpaceGeometry.AbsoluteToLocal(FSlateApplication::Get().GetCursorPos());
|
||||
return Panel->HeightToNoteNumber(LocalPos.Y);
|
||||
}
|
||||
|
||||
AudioFrame FPianoRollEdit::GetAudioFrameByPos(float Width) const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
return Panel->PixelToFrame(Width);
|
||||
}
|
||||
|
||||
AudioFrame FPianoRollEdit::GetAudioFrameUnderMouse() const
|
||||
{
|
||||
const TSharedPtr<SPianoRollPanel> Panel = PianoRollPanel.Pin();
|
||||
const FGeometry& TickSpaceGeometry = Panel->GetTickSpaceGeometry();
|
||||
const auto& LocalPos = TickSpaceGeometry.AbsoluteToLocal(FSlateApplication::Get().GetCursorPos());
|
||||
return Panel->PixelToFrame(LocalPos.X);
|
||||
}
|
||||
|
||||
AudioFrame FPianoRollEdit::GetSnapFrame() const
|
||||
{
|
||||
return FMidiSequencer::Get().GetTicksPerQuarter();
|
||||
}
|
||||
|
||||
FMidiMessageSequence* FPianoRollEdit::GetMidiMessageSequence() const
|
||||
{
|
||||
return PianoRollPanel.Pin()->GetMidiMessageSequence();
|
||||
}
|
||||
|
||||
FPluginHost* FPianoRollEdit::GetPluginHost() const
|
||||
{
|
||||
FMidiMessageSequence* Sequence = GetMidiMessageSequence();
|
||||
if (!Sequence)
|
||||
return nullptr;
|
||||
return Sequence->pattern->GetPluginHost(Sequence);
|
||||
}
|
||||
|
||||
void FPianoRollEdit::SetTempTool(EPianoRollToolType InToolType)
|
||||
{
|
||||
CurrentEditTool->Clear(*this);
|
||||
TempTool = EditToolMap[InToolType];
|
||||
TempTool->OnActive(*this);
|
||||
}
|
||||
|
||||
void FPianoRollEdit::ClearTempTool()
|
||||
{
|
||||
if (TempTool)
|
||||
{
|
||||
TempTool->Clear(*this);
|
||||
TempTool = nullptr;
|
||||
CurrentEditTool->OnActive(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollEdit::UpdateModified()
|
||||
{
|
||||
if (GetModified())
|
||||
{
|
||||
GetMidiMessageSequence()->sortAudioThread();
|
||||
SetModified(false);
|
||||
}
|
||||
}
|
||||
|
||||
FPianoRollEditTool* FPianoRollEdit::TryEnableTempTool()
|
||||
{
|
||||
const TSet<FKey>& PressedMouseButtons = FSlateApplication::Get().GetPressedMouseButtons();
|
||||
for (const auto& Tuple : ToolKeyMap)
|
||||
{
|
||||
const EPianoRollToolType PianoRollTool = Tuple.Key;
|
||||
const TArray<FKey>& Keys = Tuple.Value;
|
||||
bool bAllPressed = true;
|
||||
for (const FKey& ToolKey : Keys)
|
||||
{
|
||||
if (ToolKey.IsMouseButton())
|
||||
bAllPressed = bAllPressed && PressedMouseButtons.Contains(ToolKey);
|
||||
else
|
||||
bAllPressed = bAllPressed && PressedKeys.Contains(ToolKey);
|
||||
if (!bAllPressed)
|
||||
break;
|
||||
}
|
||||
if (bAllPressed)
|
||||
{
|
||||
ClearTempTool();
|
||||
SetTempTool(PianoRollTool);
|
||||
return EditToolMap[PianoRollTool];
|
||||
}
|
||||
}
|
||||
ClearTempTool();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "Events.h"
|
||||
#include "Geometry.h"
|
||||
#include "PaintArgs.h"
|
||||
#include "PianoRollSelector.h"
|
||||
#include "Reply.h"
|
||||
#include "WidgetStyle.h"
|
||||
#include "Midi/MidiMessageSequence.h"
|
||||
#include "Midi/MidiType.h"
|
||||
#include "Midi/Time/TimePos.h"
|
||||
class FPluginHost;
|
||||
class FPianoRollEditTool;
|
||||
class SPianoRollPanel;
|
||||
|
||||
enum class EPianoRollToolType
|
||||
{
|
||||
Select,
|
||||
Write,
|
||||
DragView,
|
||||
Count
|
||||
};
|
||||
|
||||
struct FNoteSelectData
|
||||
{
|
||||
FMidiMessageSequence::MidiEventHolder* MidiMessage = nullptr;
|
||||
double MidiTimestampOffset = 0;
|
||||
operator bool() const
|
||||
{
|
||||
return MidiMessage != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class FPianoRollEdit
|
||||
{
|
||||
public:
|
||||
FPianoRollEdit(TSharedRef<SPianoRollPanel> InPianoRollPanel);
|
||||
~FPianoRollEdit();
|
||||
|
||||
FNoteSelectData GetMidiMessageByPos(FVector2D Pos) const;
|
||||
FNoteSelectData GetMidiMessageUnderMouse() const;
|
||||
|
||||
FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
|
||||
FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
|
||||
FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
|
||||
FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent);
|
||||
FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent);
|
||||
int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const;
|
||||
|
||||
int32 GetNoteNumberByPos(float Height) const;
|
||||
int32 GetNoteNumberUnderMouse() const;
|
||||
AudioFrame GetAudioFrameByPos(float Width) const;
|
||||
AudioFrame GetAudioFrameUnderMouse() const;
|
||||
|
||||
bool IsNoteSelected(FMidiMessageSequence::MidiEventHolder* MidiMessage) const { return Selector.IsSelected(MidiMessage); }
|
||||
bool IsMultiSelected() const { return Selector.Num() > 1; }
|
||||
FVector2f GetMouseDownPos() const { return MouseDownPos; }
|
||||
void SetModified(bool InHasModified) { HasModified = InHasModified; }
|
||||
bool GetModified() const { return HasModified; }
|
||||
bool IsMouseDragging() const { return OnMouseDrag; }
|
||||
AudioFrame GetSnapFrame() const;
|
||||
FMidiMessageSequence* GetMidiMessageSequence() const;
|
||||
FPluginHost* GetPluginHost() const;
|
||||
|
||||
void SetTempTool(EPianoRollToolType InToolType);
|
||||
void ClearTempTool();
|
||||
|
||||
FPianoRollSelector Selector;
|
||||
TWeakPtr<SPianoRollPanel> PianoRollPanel;
|
||||
protected:
|
||||
bool OnMouseDrag = false;
|
||||
private:
|
||||
void UpdateModified();
|
||||
FPianoRollEditTool* TryEnableTempTool();
|
||||
|
||||
FVector2f MouseDownPos;
|
||||
bool HasModified = false;
|
||||
|
||||
TArray<FKey> PressedKeys;
|
||||
|
||||
FPianoRollEditTool* GetCurrentTool() const { return TempTool ? TempTool : CurrentEditTool; }
|
||||
FPianoRollEditTool* TempTool = nullptr;
|
||||
FPianoRollEditTool* CurrentEditTool = nullptr;
|
||||
TMap<EPianoRollToolType, FPianoRollEditTool*> EditToolMap;
|
||||
TMap<EPianoRollToolType, TArray<FKey>> ToolKeyMap;
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
#include "PianoRollSelector.h"
|
||||
|
||||
void FPianoRollSelector::SelectMidiMessage(TArray<FMidiMessageSequence::MidiEventHolder*> InMidiMessages)
|
||||
{
|
||||
UnSelect();
|
||||
for (auto MidiMessage : InMidiMessages)
|
||||
{
|
||||
FData& D = SelectedMidiMessages.AddDefaulted_GetRef();
|
||||
D.MidiMessage = MidiMessage;
|
||||
D.OriginalPitch = MidiMessage->message.getNoteNumber();
|
||||
D.OriginalMidiTimestamp = MidiMessage->message.getTimeStamp();
|
||||
|
||||
OnMidiMessageSelected.Broadcast(MidiMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void FPianoRollSelector::AddMidiMessage(FMidiMessageSequence::MidiEventHolder* MidiMessage)
|
||||
{
|
||||
if (SelectedMidiMessages.Contains(MidiMessage))
|
||||
return;
|
||||
|
||||
FData& D = SelectedMidiMessages.AddDefaulted_GetRef();
|
||||
D.MidiMessage = MidiMessage;
|
||||
D.OriginalPitch = MidiMessage->message.getNoteNumber();
|
||||
D.OriginalMidiTimestamp = MidiMessage->message.getTimeStamp();
|
||||
|
||||
OnMidiMessageSelected.Broadcast(MidiMessage);
|
||||
}
|
||||
|
||||
void FPianoRollSelector::UnSelect()
|
||||
{
|
||||
for (const auto& D : SelectedMidiMessages)
|
||||
{
|
||||
OnMidiMessageUnSelected.Broadcast(D.MidiMessage);
|
||||
}
|
||||
SelectedMidiMessages.Reset();
|
||||
}
|
||||
|
||||
void FPianoRollSelector::UnSelect(FMidiMessageSequence::MidiEventHolder* MidiMessage)
|
||||
{
|
||||
if (SelectedMidiMessages.RemoveAll([&MidiMessage](const FData& Element) { return Element == MidiMessage; }) != 0)
|
||||
{
|
||||
OnMidiMessageUnSelected.Broadcast(MidiMessage);
|
||||
}
|
||||
}
|
||||
|
||||
bool FPianoRollSelector::SetPitch(int Pitch)
|
||||
{
|
||||
const int PitchDelta = Pitch - BasePitch;
|
||||
for (const FData& D : SelectedMidiMessages)
|
||||
{
|
||||
const int NewNoteNumber = D.OriginalPitch + PitchDelta;
|
||||
D.MidiMessage->message.setNoteNumber(NewNoteNumber);
|
||||
if (D.MidiMessage->noteOffObject)
|
||||
D.MidiMessage->noteOffObject->message.setNoteNumber(NewNoteNumber);
|
||||
}
|
||||
|
||||
if (LastPitch != Pitch)
|
||||
{
|
||||
LastPitch = Pitch;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FPianoRollSelector::SetMidiTimestamp(MidiTick InMidiTick)
|
||||
{
|
||||
MidiTick TimestampOffset = BaseMidiTimestamp - InMidiTick + MidiTimestampDelta; // 点击位置的MidiTick
|
||||
TimestampOffset = FMath::GridSnap<MidiTick>(TimestampOffset, SnapMidiTimestamp);
|
||||
|
||||
for (const FData& D : SelectedMidiMessages)
|
||||
{
|
||||
const double NewTimestamp = FMath::Max(0, D.OriginalMidiTimestamp - TimestampOffset);
|
||||
|
||||
const double Duration = D.MidiMessage->getDuration();
|
||||
D.MidiMessage->message.setTimeStamp(NewTimestamp);
|
||||
if (D.MidiMessage->noteOffObject)
|
||||
D.MidiMessage->noteOffObject->message.setTimeStamp(NewTimestamp + Duration);
|
||||
}
|
||||
|
||||
if (LastMidiTimestamp != TimestampOffset)
|
||||
{
|
||||
LastMidiTimestamp = TimestampOffset;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FPianoRollSelector::ResetAnchorPoint()
|
||||
{
|
||||
for (FData& D : SelectedMidiMessages)
|
||||
{
|
||||
D.OriginalPitch = D.MidiMessage->message.getNoteNumber();
|
||||
D.OriginalMidiTimestamp = D.MidiMessage->message.getTimeStamp();
|
||||
}
|
||||
}
|
||||
|
||||
bool FPianoRollSelector::IsSelected(FMidiMessageSequence::MidiEventHolder* MidiMessage) const
|
||||
{
|
||||
return SelectedMidiMessages.Contains(MidiMessage);
|
||||
}
|
||||
|
||||
TArray<FMidiMessageSequence::MidiEventHolder*> FPianoRollSelector::GetSelectedMidiMessages() const
|
||||
{
|
||||
TArray<FMidiMessageSequence::MidiEventHolder*> OutSelectedMidiMessages;
|
||||
for (const FData& D : SelectedMidiMessages)
|
||||
{
|
||||
OutSelectedMidiMessages.Add(D.MidiMessage);
|
||||
}
|
||||
return OutSelectedMidiMessages;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "Midi/MidiMessageSequence.h"
|
||||
#include "Midi/Time/TimePos.h"
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FMidiMessageDelegate, FMidiMessageSequence::MidiEventHolder*)
|
||||
|
||||
class FPianoRollSelector
|
||||
{
|
||||
struct FData
|
||||
{
|
||||
FMidiMessageSequence::MidiEventHolder* MidiMessage;
|
||||
int OriginalPitch;
|
||||
double OriginalMidiTimestamp;
|
||||
|
||||
bool operator==(FMidiMessageSequence::MidiEventHolder* In) const
|
||||
{
|
||||
return MidiMessage == In;
|
||||
}
|
||||
};
|
||||
public:
|
||||
void SelectMidiMessage(TArray<FMidiMessageSequence::MidiEventHolder*> InMidiMessages);
|
||||
void AddMidiMessage(FMidiMessageSequence::MidiEventHolder* MidiMessage);
|
||||
void UnSelect();
|
||||
void UnSelect(FMidiMessageSequence::MidiEventHolder* MidiMessage);
|
||||
|
||||
void BeginDrag(int InBasePitch, MidiTick InMidiTimestampDelta, MidiTick InSnapMidiTimestamp, MidiTick InBaseMidiTimestamp)
|
||||
{
|
||||
BasePitch = InBasePitch;
|
||||
MidiTimestampDelta = InMidiTimestampDelta;
|
||||
SnapMidiTimestamp = InSnapMidiTimestamp;
|
||||
BaseMidiTimestamp = InBaseMidiTimestamp;
|
||||
|
||||
LastPitch = InBasePitch;
|
||||
LastMidiTimestamp.Ticks = 0;
|
||||
}
|
||||
void ResetAnchorPoint();
|
||||
|
||||
bool SetPitch(int Pitch);
|
||||
bool SetMidiTimestamp(MidiTick InMidiTick);
|
||||
bool IsSelected(FMidiMessageSequence::MidiEventHolder* MidiMessage) const;
|
||||
|
||||
TArray<FMidiMessageSequence::MidiEventHolder*> GetSelectedMidiMessages() const;
|
||||
int32 Num() const { return SelectedMidiMessages.Num(); }
|
||||
|
||||
FMidiMessageDelegate OnMidiMessageSelected;
|
||||
FMidiMessageDelegate OnMidiMessageUnSelected;
|
||||
private:
|
||||
TArray<FData> SelectedMidiMessages;
|
||||
int32 BasePitch = 0;
|
||||
MidiTick MidiTimestampDelta;
|
||||
MidiTick BaseMidiTimestamp;
|
||||
MidiTick SnapMidiTimestamp;
|
||||
|
||||
int32 LastPitch = 0;
|
||||
MidiTick LastMidiTimestamp;
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPianoRollPanel.h"
|
||||
|
||||
#include "FontMeasure.h"
|
||||
#include "SImage.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SScaleBox.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SPianoRollPanel::Construct(const FArguments& InArgs, FMidiMessageSequence* InMidiMessageSequence)
|
||||
{
|
||||
FontInfo = InArgs._FontInfo;
|
||||
NoteImage = InArgs._NoteImage;
|
||||
MidiMessageSequence = InMidiMessageSequence;
|
||||
|
||||
const double MidiDuration = FMath::Min(FMidiSequencer::Get().GetBar() * 2 ,MidiMessageSequence->getEndTime());
|
||||
SequenceWidth = MidiDuration * FrameToPixel;
|
||||
TimeRange = TRange<AudioFrame>(0, MidiDuration);
|
||||
OnTargetRangeChanged = InArgs._OnTargetRangeChanged;
|
||||
OnYRequestUpdate = InArgs._OnYRequestUpdate;
|
||||
OnRequestHScroll = InArgs._OnRequestHScroll;
|
||||
|
||||
SpacingLineColor = InArgs._SpacingLineColor;
|
||||
NoteColor = InArgs._NoteColor;
|
||||
NoteSelectedColor = InArgs._NoteSelectedColor;
|
||||
NoteTextColor = InArgs._NoteTextColor;
|
||||
SpacingLineThickness = InArgs._SpacingLineThickness;
|
||||
Clipping = EWidgetClipping::ClipToBounds;
|
||||
PianoRollEdit = MakeShareable(new FPianoRollEdit(SharedThis(this)));
|
||||
}
|
||||
|
||||
int32 SPianoRollPanel::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle,
|
||||
bool bParentEnabled) const
|
||||
{
|
||||
const float RangeBegin = TimeRange.GetLowerBoundValue();
|
||||
const float RangeEnd = TimeRange.GetUpperBoundValue();
|
||||
if (FMath::IsNaN(RangeBegin) || FMath::IsNaN(RangeEnd))
|
||||
return LayerId;
|
||||
|
||||
const auto& Size = AllottedGeometry.Size;
|
||||
|
||||
const FMidiSequencer& MidiSequencer = FMidiSequencer::Get();
|
||||
const double CurrentMidiTick = MidiSequencer.FramePos.load().Ticks();
|
||||
const int32 TicksPerQuarter = MidiSequencer.GetTicksPerQuarter();
|
||||
int32 TicksPerBeat = MidiSequencer.GetBeat();
|
||||
int32 TicksPerBar = MidiSequencer.GetBar();
|
||||
|
||||
++LayerId;
|
||||
// 绘制Note间隔线
|
||||
for (int32 i = 0; i < FMidiMessage::MaxNoteNumber + 1; ++i)
|
||||
{
|
||||
TArray<FVector2f> Points;
|
||||
Points.Add(FVector2f(0, i * NoteHeight));
|
||||
Points.Add(FVector2f(SequenceWidth, i * NoteHeight));
|
||||
FSlateDrawElement::MakeLines(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(),
|
||||
Points,
|
||||
ESlateDrawEffect::None,
|
||||
SpacingLineColor,
|
||||
true,
|
||||
SpacingLineThickness
|
||||
);
|
||||
}
|
||||
|
||||
++LayerId;
|
||||
// 绘制节拍线
|
||||
const float Delta = FMath::Fmod(RangeBegin, (float)TicksPerQuarter);
|
||||
for (float i = RangeBegin - Delta; i < RangeEnd; i += TicksPerQuarter)
|
||||
{
|
||||
TArray<FVector2f> Points;
|
||||
Points.Add(FVector2f((i - RangeBegin) * FrameToPixel, 0));
|
||||
Points.Add(FVector2f((i - RangeBegin) * FrameToPixel, Size.Y));
|
||||
FSlateDrawElement::MakeLines(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(),
|
||||
Points,
|
||||
ESlateDrawEffect::None,
|
||||
SpacingLineColor,
|
||||
true,
|
||||
SpacingLineThickness
|
||||
);
|
||||
}
|
||||
|
||||
++LayerId;
|
||||
// 绘制Note
|
||||
{
|
||||
for (const auto& MidiMessage : *MidiMessageSequence)
|
||||
{
|
||||
if (!MidiMessage->message.isNoteOn())
|
||||
continue;
|
||||
double NoteOnTime = MidiMessage->message.getTimeStamp();
|
||||
if (RangeEnd < NoteOnTime)
|
||||
break;
|
||||
|
||||
float NoteOffRealTime;
|
||||
if (MidiMessage->noteOffObject)
|
||||
{
|
||||
NoteOffRealTime = MidiMessage->noteOffObject->message.getTimeStamp();
|
||||
if (RangeBegin > NoteOffRealTime)
|
||||
continue;
|
||||
NoteOffRealTime -= RangeBegin;
|
||||
}
|
||||
else
|
||||
{
|
||||
NoteOffRealTime = 0;
|
||||
if (RangeBegin > NoteOnTime)
|
||||
continue;
|
||||
}
|
||||
|
||||
NoteOnTime -= RangeBegin;
|
||||
|
||||
const int NoteNumber = MidiMessage->message.getNoteNumber();
|
||||
const float NoteOnX = NoteOnTime * FrameToPixel;
|
||||
const float NoteOffX = NoteOffRealTime * FrameToPixel;
|
||||
const float NoteY = (FMidiMessage::MaxNoteNumber - NoteNumber) * NoteHeight;
|
||||
const float NoteWidth = NoteOffX - NoteOnX;
|
||||
const FSlateLayoutTransform& InLayoutTransform = FSlateLayoutTransform(FVector2D(NoteOnX, NoteY));
|
||||
// 根据NoteHeight绘制矩形
|
||||
FSlateDrawElement::MakeBox(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2D(NoteWidth, NoteHeight), InLayoutTransform),
|
||||
NoteImage,
|
||||
ESlateDrawEffect::None,
|
||||
PianoRollEdit->IsNoteSelected(MidiMessage) ? NoteSelectedColor : NoteColor
|
||||
);
|
||||
|
||||
const FString& NoteName = FMidiMessage::getMidiNoteName(NoteNumber, true, true);
|
||||
|
||||
// 绘制Note名称
|
||||
FSlateDrawElement::MakeText(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2D(NoteWidth, NoteHeight), InLayoutTransform),
|
||||
NoteName,
|
||||
FontInfo,
|
||||
ESlateDrawEffect::None,
|
||||
NoteTextColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
++LayerId;
|
||||
// 绘制当前MidiTick
|
||||
{
|
||||
const float CurrentMidiTickX = (CurrentMidiTick - RangeBegin) * FrameToPixel;
|
||||
TArray<FVector2f> Points;
|
||||
Points.Add(FVector2f(CurrentMidiTickX, 0));
|
||||
Points.Add(FVector2f(CurrentMidiTickX, Size.Y));
|
||||
FSlateDrawElement::MakeLines(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(),
|
||||
Points,
|
||||
ESlateDrawEffect::None,
|
||||
FLinearColor::Red,
|
||||
true,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
LayerId = PianoRollEdit->OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
|
||||
|
||||
return LayerId;
|
||||
}
|
||||
|
||||
FReply SPianoRollPanel::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
return PianoRollEdit->OnMouseButtonDown(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
FReply SPianoRollPanel::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
return PianoRollEdit->OnMouseButtonUp(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
FReply SPianoRollPanel::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
return PianoRollEdit->OnMouseMove(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
FReply SPianoRollPanel::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
||||
{
|
||||
if (!InKeyEvent.IsRepeat() && InKeyEvent.GetKey() == EKeys::LeftShift)
|
||||
OnRequestHScroll.ExecuteIfBound(true);
|
||||
return PianoRollEdit->OnKeyDown(MyGeometry, InKeyEvent);
|
||||
}
|
||||
|
||||
FReply SPianoRollPanel::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
||||
{
|
||||
if (!InKeyEvent.IsRepeat() && InKeyEvent.GetKey() == EKeys::LeftShift)
|
||||
OnRequestHScroll.ExecuteIfBound(false);
|
||||
|
||||
return PianoRollEdit->OnKeyUp(MyGeometry, InKeyEvent);
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@@ -0,0 +1,90 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PianoRollEdit.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "SLeafWidget.h"
|
||||
#include "Midi/MidiMessageSequence.h"
|
||||
|
||||
class FMidiMessage;
|
||||
class FMidiMessageSequence;
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FPianoRollPanelTargetRangeChanged, TRange<AudioFrame>)
|
||||
DECLARE_DELEGATE_OneParam(FPianoRollPanelYRequestUpdate, float /*Delta*/)
|
||||
DECLARE_DELEGATE_OneParam(FPianoRollPanelRequestHScroll, bool /*Delta*/)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPianoRollPanel : public SLeafWidget
|
||||
{
|
||||
friend class FPianoRollEdit;
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPianoRollPanel):
|
||||
_SpacingLineColor(FLinearColor::Gray),
|
||||
_NoteColor(FLinearColor::White),
|
||||
_NoteSelectedColor(FLinearColor::Blue),
|
||||
_NoteTextColor(FLinearColor::Black)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_EVENT(FPianoRollPanelTargetRangeChanged, OnTargetRangeChanged)
|
||||
SLATE_EVENT(FPianoRollPanelYRequestUpdate, OnYRequestUpdate)
|
||||
SLATE_EVENT(FPianoRollPanelRequestHScroll, OnRequestHScroll)
|
||||
|
||||
SLATE_ARGUMENT(const FSlateBrush*, NoteImage)
|
||||
|
||||
SLATE_ARGUMENT(FLinearColor, SpacingLineColor)
|
||||
SLATE_ARGUMENT(FLinearColor, NoteColor)
|
||||
SLATE_ARGUMENT(FLinearColor, NoteSelectedColor)
|
||||
SLATE_ARGUMENT(FLinearColor, NoteTextColor)
|
||||
SLATE_ARGUMENT(FSlateFontInfo, FontInfo)
|
||||
|
||||
SLATE_ARGUMENT(float, SpacingLineThickness)
|
||||
SLATE_ARGUMENT(float, NoteHeight)
|
||||
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FMidiMessageSequence* InMidiMessageSequence);
|
||||
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override { return FVector2D(SequenceWidth, NoteHeight * 128); }
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
virtual bool SupportsKeyboardFocus() const override { return true; }
|
||||
|
||||
int HeightToNoteNumber(float Height) const { return FMidiMessage::MaxNoteNumber - Height / NoteHeight + 1; }
|
||||
// double PixelToMidiTick(float Pixel) const { return Pixel / FrameToPixel + TimeRange.GetLowerBoundValue(); }
|
||||
AudioFrame PixelToFrame(float Pixel) const { return Pixel / FrameToPixel + TimeRange.GetLowerBoundValue(); }
|
||||
|
||||
FMidiMessageSequence* GetMidiMessageSequence() const { return MidiMessageSequence; }
|
||||
|
||||
TRange<AudioFrame> TimeRange;
|
||||
|
||||
FPianoRollPanelTargetRangeChanged OnTargetRangeChanged;
|
||||
FPianoRollPanelYRequestUpdate OnYRequestUpdate;
|
||||
FPianoRollPanelRequestHScroll OnRequestHScroll;
|
||||
|
||||
FLinearColor SpacingLineColor;
|
||||
FLinearColor NoteColor;
|
||||
FLinearColor NoteSelectedColor;
|
||||
FLinearColor NoteTextColor;
|
||||
FSlateFontInfo FontInfo;
|
||||
TSharedPtr<FPianoRollEdit> PianoRollEdit;
|
||||
float SpacingLineThickness = 1.f;
|
||||
float NoteHeight = 20.f;
|
||||
float FrameToPixel = 0.2f;
|
||||
private:
|
||||
float SequenceWidth = 100.f;
|
||||
const FSlateBrush* BackgroundImage = nullptr;
|
||||
const FSlateBrush* NoteImage = nullptr;
|
||||
FMidiMessageSequence* MidiMessageSequence = nullptr;
|
||||
};
|
||||
161
Source/Arona/UI/Widget/PianoRoll/SPianoKey.cpp
Normal file
161
Source/Arona/UI/Widget/PianoRoll/SPianoKey.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPianoKey.h"
|
||||
|
||||
#include "FontMeasure.h"
|
||||
#include "SlateApplication.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "Midi/MidiMessage.h"
|
||||
#include "PluginHost/PluginHost.h"
|
||||
#include "UI/Style/AronaStyle.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
bool IsBetweenBlackKey(int32 NoteNumber)
|
||||
{
|
||||
return NoteNumber % 12 == 1 || NoteNumber % 12 == 3 || NoteNumber % 12 == 6 || NoteNumber % 12 == 8 || NoteNumber % 12 == 10;
|
||||
}
|
||||
|
||||
void SPianoKey::Construct(const FArguments& InArgs, FPluginHost* InPluginHost)
|
||||
{
|
||||
PluginHost = InPluginHost;
|
||||
KeyHeight = InArgs._KeyHeight;
|
||||
KeyWidth = InArgs._KeyWidth;
|
||||
}
|
||||
|
||||
int32 SPianoKey::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
const FVector2D Size = AllottedGeometry.GetLocalSize();
|
||||
const float BlackKeyHeight = KeyHeight.Get(); // 黑键始终为KeyHeight
|
||||
const float SemitoneHeight = BlackKeyHeight * 1.5f; // 半音高度
|
||||
const float WholeToneHeight = SemitoneHeight * 2; // 全音高度
|
||||
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
||||
|
||||
float CursorY = KeyHeight.Get() * FMidiMessage::MaxNoteNumber - (KeyHeight.Get() * 1.5);
|
||||
FSlateFontInfo SlateFontInfo = FAronaStyle::GetFontInfo(DefaultFontName);
|
||||
// 画白键
|
||||
++LayerId;
|
||||
for (int32 i = 0; i < FMidiMessage::MaxNoteNumber + 1; ++i)
|
||||
{
|
||||
if (FMidiMessage::isMidiNoteBlack(i))
|
||||
continue;
|
||||
const bool IsBetweenBlack = IsBetweenBlackKey(i);
|
||||
const float CurrentKeyHeight = IsBetweenBlack ? SemitoneHeight : WholeToneHeight;
|
||||
const FSlateLayoutTransform& InLayoutTransform = FSlateLayoutTransform(FVector2D(0, CursorY));
|
||||
|
||||
// 画白键
|
||||
FSlateDrawElement::MakeBox(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2f(Size.X, CurrentKeyHeight), InLayoutTransform),
|
||||
FAronaStyle::GetSlateBrush(WhiteKey)
|
||||
);
|
||||
CursorY -= CurrentKeyHeight;
|
||||
}
|
||||
|
||||
// 画黑键
|
||||
++LayerId;
|
||||
for (int32 i = 0; i < FMidiMessage::MaxNoteNumber + 1; ++i)
|
||||
{
|
||||
if (FMidiMessage::isMidiNoteWhite(i))
|
||||
continue;
|
||||
const FSlateLayoutTransform& InLayoutTransform = FSlateLayoutTransform(FVector2D(0, (FMidiMessage::MaxNoteNumber - i) * BlackKeyHeight));
|
||||
// AllottedGeometry.ToPaintGeometry(FVector2f(0, (FMidiMessage::MaxNoteNumber - i) * BlackKeyHeight), FVector2f(Size.X * 0.7, BlackKeyHeight))
|
||||
// 画黑键
|
||||
FSlateDrawElement::MakeBox(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2f(Size.X * 0.7, BlackKeyHeight), InLayoutTransform),
|
||||
FAronaStyle::GetSlateBrush(BlackKey)
|
||||
);
|
||||
}
|
||||
|
||||
++LayerId;
|
||||
// 画字
|
||||
for (int i = 0; i < FMidiMessage::MaxNoteNumber + 1; ++i)
|
||||
{
|
||||
FString MidiNoteName = FMidiMessage::getMidiNoteName(i, true, true);
|
||||
// 计算字体大小
|
||||
const float FontSize = FontMeasureService->Measure(MidiNoteName, SlateFontInfo).X;
|
||||
const FSlateLayoutTransform& InLayoutTransform = FSlateLayoutTransform(FVector2D(Size.X - FontSize, (FMidiMessage::MaxNoteNumber - i) * BlackKeyHeight));
|
||||
|
||||
// 画字
|
||||
FSlateDrawElement::MakeText(
|
||||
OutDrawElements,
|
||||
LayerId,
|
||||
AllottedGeometry.ToPaintGeometry(FVector2f(Size.X, BlackKeyHeight), InLayoutTransform),
|
||||
MidiNoteName,
|
||||
SlateFontInfo,
|
||||
ESlateDrawEffect::None,
|
||||
FLinearColor::Black
|
||||
);
|
||||
}
|
||||
return LayerId;
|
||||
}
|
||||
|
||||
FVector2D SPianoKey::ComputeDesiredSize(float LayoutScaleMultiplier) const
|
||||
{
|
||||
return FVector2D(KeyWidth.Get(), KeyHeight.Get() * FMidiMessage::MaxNoteNumber);
|
||||
}
|
||||
|
||||
FReply SPianoKey::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
const FVector2D LocalPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
||||
const int32 NoteNumber = HeightToNoteNumber(LocalPos.Y);
|
||||
if (NoteNumber >= 0 && NoteNumber <= FMidiMessage::MaxNoteNumber)
|
||||
{
|
||||
PluginHost->IncomingMidi.addEvent(FMidiMessage::noteOn(1, NoteNumber, WidthToVelocity(LocalPos.X)));
|
||||
LastNoteNumber = NoteNumber;
|
||||
}
|
||||
return FReply::Handled().CaptureMouse(AsShared());
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SPianoKey::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
||||
{
|
||||
if (LastNoteNumber >= 0 && LastNoteNumber <= FMidiMessage::MaxNoteNumber)
|
||||
{
|
||||
PluginHost->IncomingMidi.addEvent(FMidiMessage::noteOff(1, LastNoteNumber, 0.f));
|
||||
}
|
||||
LastNoteNumber = -1;
|
||||
return FReply::Handled().ReleaseMouseCapture();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SPianoKey::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
|
||||
{
|
||||
const int32 CurrentNoteNumber = HeightToNoteNumber(MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).Y);
|
||||
|
||||
if (LastNoteNumber != CurrentNoteNumber)
|
||||
{
|
||||
if (LastNoteNumber != -1)
|
||||
PluginHost->IncomingMidi.addEvent(FMidiMessage::noteOff(1, LastNoteNumber, 0.f));
|
||||
LastNoteNumber = CurrentNoteNumber;
|
||||
|
||||
const float Velocity = WidthToVelocity(MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X);
|
||||
PluginHost->IncomingMidi.addEvent(FMidiMessage::noteOn(1, CurrentNoteNumber, Velocity));
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
int32 SPianoKey::HeightToNoteNumber(float Height) const
|
||||
{
|
||||
return FMidiMessage::MaxNoteNumber - Height / KeyHeight.Get();
|
||||
}
|
||||
|
||||
float SPianoKey::WidthToVelocity(float Width) const
|
||||
{
|
||||
return FMath::Min(Width / KeyWidth.Get(), 1.f);
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
41
Source/Arona/UI/Widget/PianoRoll/SPianoKey.h
Normal file
41
Source/Arona/UI/Widget/PianoRoll/SPianoKey.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SLeafWidget.h"
|
||||
|
||||
class FPluginHost;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPianoKey : public SLeafWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPianoKey)
|
||||
{
|
||||
}
|
||||
SLATE_ATTRIBUTE(float, KeyHeight)
|
||||
SLATE_ATTRIBUTE(float, KeyWidth)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FPluginHost* InPluginHost);
|
||||
|
||||
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
private:
|
||||
int32 HeightToNoteNumber(float Height) const;
|
||||
float WidthToVelocity(float Width) const;
|
||||
|
||||
int32 LastNoteNumber = -1;
|
||||
FPluginHost* PluginHost = nullptr;
|
||||
TAttribute<float> KeyHeight; // 白键高度
|
||||
TAttribute<float> KeyWidth;
|
||||
};
|
||||
191
Source/Arona/UI/Widget/PianoRoll/SPianoRoll.cpp
Normal file
191
Source/Arona/UI/Widget/PianoRoll/SPianoRoll.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SPianoRoll.h"
|
||||
|
||||
#include "SBackgroundBlur.h"
|
||||
#include "SGridPanel.h"
|
||||
#include "SImage.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "SOverlay.h"
|
||||
#include "SPianoKey.h"
|
||||
#include "SScaleBox.h"
|
||||
#include "SScrollBar.h"
|
||||
#include "SScrollBox.h"
|
||||
#include "Midi/MidiMessageSequence.h"
|
||||
#include "Singleton/MidiSequencer.h"
|
||||
#include "PianoRollPanel/SPianoRollPanel.h"
|
||||
#include "UI/Style/AronaStyle.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SPianoRoll::Construct(const FArguments& InArgs, FMidiMessageSequence* InMidiMessageSequence, FPluginHost* InPluginHost)
|
||||
{
|
||||
MidiMessageSequence = InMidiMessageSequence;
|
||||
VerticalScrollBar = SNew(SScrollBar)
|
||||
.Orientation(Orient_Vertical)
|
||||
.AlwaysShowScrollbar(true)
|
||||
.AlwaysShowScrollbarTrack(true);
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SGridPanel)
|
||||
.FillColumn(0, 1)
|
||||
.FillRow(0, 1)
|
||||
|
||||
+SGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SNew(SScaleBox)
|
||||
.Stretch(EStretch::ScaleToFill)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SImage)
|
||||
.Image(FAronaStyle::GetSlateBrush(PianoRollBackground))
|
||||
]
|
||||
]
|
||||
+SOverlay::Slot()
|
||||
[
|
||||
SAssignNew(ScrollBox, SScrollBox)
|
||||
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
|
||||
.ExternalScrollbar(VerticalScrollBar)
|
||||
.AnimateWheelScrolling(false)
|
||||
.Visibility(EVisibility::SelfHitTestInvisible)
|
||||
+SScrollBox::Slot()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SPianoKey, InPluginHost)
|
||||
.KeyHeight(KeyHeight)
|
||||
.KeyWidth(100)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.f)
|
||||
[
|
||||
SAssignNew(PianoRollPanel, SPianoRollPanel, MidiMessageSequence)
|
||||
.FontInfo(FAronaStyle::GetFontInfo(DefaultFontName))
|
||||
.NoteImage(FAronaStyle::GetSlateBrush(VolumeMeterBar))
|
||||
.SpacingLineColor(FLinearColor::Green)
|
||||
.NoteHeight(KeyHeight)
|
||||
.OnTargetRangeChanged(this, &SPianoRoll::SetFrameRange)
|
||||
.OnYRequestUpdate(this, &SPianoRoll::AddYDelta)
|
||||
.OnRequestHScroll(this, &SPianoRoll::OnPanelRequestHScroll)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
+SGridPanel::Slot(1, 0)
|
||||
[
|
||||
VerticalScrollBar.ToSharedRef()
|
||||
]
|
||||
+SGridPanel::Slot(0, 1)
|
||||
[
|
||||
SAssignNew(HorizontalScrollBar, SScrollBar)
|
||||
.Orientation(Orient_Horizontal)
|
||||
.AlwaysShowScrollbar(true)
|
||||
.AlwaysShowScrollbarTrack(true)
|
||||
.OnUserScrolled(this, &SPianoRoll::OnHorizontalScrollBarChanged)
|
||||
]
|
||||
];
|
||||
|
||||
TargetMidiTickRange = PianoRollPanel->TimeRange;
|
||||
const double EndTime = MidiMessageSequence->getEndTime();
|
||||
HorizontalScrollBar->SetState(0, TargetMidiTickRange.Size<float>() / EndTime, false);
|
||||
}
|
||||
|
||||
void SPianoRoll::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
||||
{
|
||||
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
||||
if (AllottedGeometry.Size.X == 0)
|
||||
return;
|
||||
|
||||
// const double Lower = FMath::FInterpTo<double>(PianoRollPanel->TimeRange.GetLowerBoundValue(), TargetMidiTickRange.GetLowerBoundValue(), InDeltaTime, 15.f);
|
||||
// const double Upper = FMath::FInterpTo<double>(PianoRollPanel->TimeRange.GetUpperBoundValue(), TargetMidiTickRange.GetUpperBoundValue(), InDeltaTime, 15.f);
|
||||
const double Lower = TargetMidiTickRange.GetLowerBoundValue();
|
||||
const double Upper = TargetMidiTickRange.GetUpperBoundValue();
|
||||
PianoRollPanel->TimeRange.SetLowerBoundValue(Lower);
|
||||
PianoRollPanel->TimeRange.SetUpperBoundValue(Upper);
|
||||
|
||||
const float MidiTickDuration = PianoRollPanel->TimeRange.Size<float>();
|
||||
const double EndTime = MidiMessageSequence->getEndTime();
|
||||
HorizontalScrollBar->SetState(Lower / EndTime, MidiTickDuration / EndTime, false);
|
||||
}
|
||||
|
||||
FReply SPianoRoll::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (EnableHorizontalScroll)
|
||||
{
|
||||
AddFrameRangeDelta(MouseEvent.GetWheelDelta() * -FMidiSequencer::Get().GetTicksPerQuarter());
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
void SPianoRoll::AddFrameRangeDelta(AudioFrame InDelta)
|
||||
{
|
||||
double Lower = TargetMidiTickRange.GetLowerBoundValue() + InDelta;
|
||||
double Upper = TargetMidiTickRange.GetUpperBoundValue() + InDelta;
|
||||
if (Lower < 0)
|
||||
{
|
||||
Upper -= Lower;
|
||||
Lower = 0;
|
||||
}
|
||||
|
||||
TargetMidiTickRange = TRange<AudioFrame>(Lower, Upper);
|
||||
}
|
||||
|
||||
void SPianoRoll::SetFrameRange(TRange<AudioFrame> InMidiRange)
|
||||
{
|
||||
TargetMidiTickRange = InMidiRange;
|
||||
}
|
||||
|
||||
void SPianoRoll::AddYDelta(float InDelta)
|
||||
{
|
||||
ScrollBox->SetScrollOffset(ScrollBox->GetScrollOffset() + InDelta);
|
||||
}
|
||||
|
||||
void SPianoRoll::InitScrollBar()
|
||||
{
|
||||
OnHorizontalScrollBarChanged(0);
|
||||
}
|
||||
|
||||
TSharedPtr<SWidget> SPianoRoll::GetFocusWidget()
|
||||
{
|
||||
return PianoRollPanel;
|
||||
}
|
||||
|
||||
void SPianoRoll::OnHorizontalScrollBarChanged(float InScrollOffsetFraction)
|
||||
{
|
||||
const auto& PanelSize = PianoRollPanel->GetTickSpaceGeometry().Size;
|
||||
if (PanelSize.X == 0)
|
||||
return;
|
||||
|
||||
const MidiTick EndTime = MidiMessageSequence->getEndTime();
|
||||
const MidiTick MidiTickDuration = PianoRollPanel->TimeRange.Size<double>();
|
||||
const MidiTick StartTick = EndTime * InScrollOffsetFraction;
|
||||
|
||||
TargetMidiTickRange = TRange<AudioFrame>(StartTick.Frames(), StartTick.Frames() + MidiTickDuration.Frames());
|
||||
|
||||
// HorizontalScrollBar->SetState(InScrollOffsetFraction, MidiTickDuration / EndTime, false);
|
||||
}
|
||||
|
||||
void SPianoRoll::OnPanelRequestHScroll(bool InStatus)
|
||||
{
|
||||
if (InStatus)
|
||||
{
|
||||
EnableHorizontalScroll = true;
|
||||
ScrollBox->SetConsumeMouseWheel(EConsumeMouseWheel::Never);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableHorizontalScroll = false;
|
||||
ScrollBox->SetConsumeMouseWheel(EConsumeMouseWheel::Always);
|
||||
}
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
56
Source/Arona/UI/Widget/PianoRoll/SPianoRoll.h
Normal file
56
Source/Arona/UI/Widget/PianoRoll/SPianoRoll.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SCompoundWidget.h"
|
||||
#include "Midi/Time/TimePos.h"
|
||||
#include "UI/Widget/IChildWindow.h"
|
||||
|
||||
class FPluginHost;
|
||||
class SScrollBox;
|
||||
class FMidiMessageSequence;
|
||||
class SScrollBar;
|
||||
class SPianoRollPanel;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ARONA_API SPianoRoll : public SCompoundWidget, public IChildWindow
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SPianoRoll)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs, FMidiMessageSequence* InMidiMessageSequence, FPluginHost* InPluginHost);
|
||||
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
|
||||
|
||||
virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
void AddFrameRangeDelta(AudioFrame InDelta);
|
||||
void SetFrameRange(TRange<AudioFrame> InMidiRange);
|
||||
|
||||
void AddYDelta(float InDelta);
|
||||
|
||||
void InitScrollBar();
|
||||
|
||||
virtual TSharedRef<SWidget> GetWidget() override { return AsShared(); }
|
||||
virtual TSharedPtr<SWidget> GetFocusWidget() override;
|
||||
|
||||
private:
|
||||
void OnHorizontalScrollBarChanged(float InScrollOffsetFraction);
|
||||
void OnPanelRequestHScroll(bool InStatus);
|
||||
|
||||
float KeyHeight = 20.f;
|
||||
TSharedPtr<SPianoRollPanel> PianoRollPanel;
|
||||
TSharedPtr<SScrollBox> ScrollBox;
|
||||
TSharedPtr<SScrollBar> VerticalScrollBar;
|
||||
TSharedPtr<SScrollBar> HorizontalScrollBar;
|
||||
TRange<AudioFrame> TargetMidiTickRange;
|
||||
FMidiMessageSequence* MidiMessageSequence = nullptr;
|
||||
bool EnableHorizontalScroll = false;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "PlayListEditAction.h"
|
||||
|
||||
void FPlayListEditAction::OnActive(FPlayListEdit& Edit)
|
||||
{
|
||||
FPlayListEditTool::OnActive(Edit);
|
||||
Execute(Edit);
|
||||
Edit.ClearTempTool();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "PlayListEditTool.h"
|
||||
|
||||
class FPlayListEditAction : public FPlayListEditTool
|
||||
{
|
||||
public:
|
||||
FPlayListEditAction(EPlayListToolType InToolType) : FPlayListEditTool(InToolType)
|
||||
{
|
||||
}
|
||||
virtual void OnActive(FPlayListEdit& Edit) override;
|
||||
protected:
|
||||
virtual void Execute(FPlayListEdit& Edit) = 0;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user