2024-01-25 11:21:15 +08:00

162 lines
5.6 KiB
C++

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