This commit is contained in:
2024-01-25 11:21:15 +08:00
commit e79b8f3ed2
339 changed files with 116224 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/Binaries
/.vs
/.idea
/Intermediate
/Saved

13
.vsconfig Normal file
View 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"
]
}

1846
Arona.sln Normal file

File diff suppressed because it is too large Load Diff

View 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
View 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
View File

@@ -0,0 +1,2 @@
[Skin]
Skin=Default

View 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;
}

View 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;
}
}

View 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;
}

View 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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 );
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

View 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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

24
Source/Arona.Target.cs Normal file
View 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;
}
}

View 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"
}
);
}
}
}

View 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

View 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);

View 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)

View 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;
};

View 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]));
}
}

View 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);
}

View 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;
}

View 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;
}

View 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);
});
}

View 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;
};

View 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()
{
}

View 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
View 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
View File

@@ -0,0 +1,4 @@
#pragma once
#include "CoreMinimal.h"
void AronaTest();

View 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

View 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;
};

View 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;
}

View 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));
}

View 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

View 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;
};

View File

@@ -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

View 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;
};

View File

@@ -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

View File

@@ -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;
};

View 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

View 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;
};

View 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

View 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;
};

View 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

View File

@@ -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;
};

View 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;
};

View 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

View 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;
};

View 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

View 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;
};

View 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

View 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;
};

View 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

View 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;
};

View 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

View 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);
};

View 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

View 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;
};

View File

@@ -0,0 +1 @@
#include "SWaveformPatternInstance.h"

View File

@@ -0,0 +1,8 @@
#pragma once
#include "SPatternInstance.h"
class ARONA_API SWaveformPatternInstance : public SPatternInstance
{
public:
};

View File

@@ -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

View File

@@ -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;
};

View 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

View 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;
};

View File

@@ -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

View 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;
};

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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);
}
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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);
}
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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;
};

View 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

View 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;
};

View 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

View 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;
};

View File

@@ -0,0 +1,8 @@
#include "PlayListEditAction.h"
void FPlayListEditAction::OnActive(FPlayListEdit& Edit)
{
FPlayListEditTool::OnActive(Edit);
Execute(Edit);
Edit.ClearTempTool();
}

View File

@@ -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