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