#include "MatrixInputWidget.h" #include "SlateOptMacros.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SScrollBox.h" #include "Styling/AppStyle.h" BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SMatrixInputWidget::Construct(const FArguments& InArgs) { Matrix = InArgs._Matrix; OnMatrixChanged = InArgs._OnMatrixChanged; Rows = InArgs._Rows; Columns = InArgs._Columns; // Create a grid panel for the matrix elements GridPanel = SNew(SGridPanel); // Create numeric entry boxes for each matrix element NumericEntryBoxes.SetNum(Rows * Columns); // Allocate numeric entry boxes based on rows and columns // Add labels for rows and columns for (int32 Col = 0; Col < Columns; ++Col) { FString ColLabel; switch (Col) { case 0: ColLabel = "X"; break; case 1: ColLabel = "Y"; break; case 2: ColLabel = "Z"; break; default: ColLabel = FString::Printf(TEXT("Column %d"), Col); } GridPanel->AddSlot(Col + 1, 0) [ SNew(STextBlock) .Text(FText::FromString(ColLabel)) .Margin(FMargin(5.0f)) ]; } for (int32 Row = 0; Row < Rows; ++Row) { FString RowLabel; switch (Row) { case 0: RowLabel = "Right"; break; case 1: RowLabel = "Forward"; break; case 2: RowLabel = "Up"; break; case 3: RowLabel = "Position"; break; default: RowLabel = FString::Printf(TEXT("Row %d"), Row); } GridPanel->AddSlot(0, Row + 1) [ SNew(STextBlock) .Text(FText::FromString(RowLabel)) .Margin(FMargin(5.0f)) ]; } // Add numeric entry boxes for each matrix element for (int32 Row = 0; Row < Rows; ++Row) { for (int32 Col = 0; Col < Columns; ++Col) { TSharedPtr> NumericEntryBox = CreateMatrixElementWidget(Row, Col); NumericEntryBoxes[Row * Columns + Col] = NumericEntryBox; GridPanel->AddSlot(Col + 1, Row + 1) [ SNew(SBox) .WidthOverride(80.0f) .Padding(FMargin(2.0f)) [ NumericEntryBox.ToSharedRef() ] ]; } } // Create buttons for common operations TSharedPtr ButtonPanel = SNew(SUniformGridPanel) .SlotPadding(FMargin(2.0f)); ButtonPanel->AddSlot(0, 0) [ SNew(SButton) .Text(FText::FromString("Reset to Identity")) .ToolTipText(FText::FromString("Reset the matrix to identity")) .OnClicked(FOnClicked::CreateLambda([this]() { ResetToIdentity(); return FReply::Handled(); })) ]; ButtonPanel->AddSlot(1, 0) [ SNew(SButton) .Text(FText::FromString("Set from Rotation")) .ToolTipText(FText::FromString("Set the matrix from rotation values (degrees)")) .OnClicked(FOnClicked::CreateLambda([this]() { SetFromEulerAngles(0.0f, 0.0f, 0.0f); return FReply::Handled(); })) ]; ButtonPanel->AddSlot(2, 0) [ SNew(SButton) .Text(FText::FromString("Set from Translation")) .ToolTipText(FText::FromString("Set the matrix from translation values")) .OnClicked(FOnClicked::CreateLambda([this]() { SetFromTranslation(FVector::ZeroVector); return FReply::Handled(); })) ]; // Add preset cutting buttons ButtonPanel->AddSlot(0, 1) [ SNew(SButton) .Text(FText::FromString("Horizontal Cut")) .ToolTipText(FText::FromString("Set up a horizontal cutting plane")) .OnClicked(FOnClicked::CreateLambda([this]() { // Set horizontal cutting plane FMatrix HorizontalCutMatrix = FMatrix::Identity; HorizontalCutMatrix.SetOrigin(FVector(0.0f, 0.0f, 0.0f)); SetMatrix(HorizontalCutMatrix); return FReply::Handled(); })) ]; ButtonPanel->AddSlot(1, 1) [ SNew(SButton) .Text(FText::FromString("Vertical Cut")) .ToolTipText(FText::FromString("Set up a vertical cutting plane")) .OnClicked(FOnClicked::CreateLambda([this]() { // Set vertical cutting plane FMatrix VerticalCutMatrix = FMatrix::Identity; VerticalCutMatrix.SetOrigin(FVector(0.0f, 0.0f, 0.0f)); // Rotate 90 degrees to make it vertical FRotator Rotator(0.0f, 0.0f, 90.0f); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); SetMatrix(VerticalCutMatrix * RotationMatrix); return FReply::Handled(); })) ]; ButtonPanel->AddSlot(2, 1) [ SNew(SButton) .Text(FText::FromString("Diagonal Cut")) .ToolTipText(FText::FromString("Set up a diagonal cutting plane")) .OnClicked(FOnClicked::CreateLambda([this]() { // Set diagonal cutting plane FMatrix DiagonalCutMatrix = FMatrix::Identity; DiagonalCutMatrix.SetOrigin(FVector(0.0f, 0.0f, 0.0f)); // Rotate 45 degrees FRotator Rotator(0.0f, 0.0f, 45.0f); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); SetMatrix(DiagonalCutMatrix * RotationMatrix); return FReply::Handled(); })) ]; // Add apply cut button TSharedPtr ApplyCutButton = SNew(SButton) .Text(FText::FromString("Apply Cut")) .ButtonStyle(FAppStyle::Get(), "FlatButton.Success") .ContentPadding(FMargin(8.0f, 4.0f)) .HAlign(HAlign_Center) .ToolTipText(FText::FromString("Apply the current cutting plane to the model")) .OnClicked(FOnClicked::CreateLambda([this]() { // Apply cutting if (OnMatrixChanged.IsBound()) { OnMatrixChanged.Execute(Matrix); } return FReply::Handled(); })); // Create rotation controls TSharedPtr RotationControls = SNew(SVerticalBox); // Add X-axis rotation RotationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("X Rotation:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-180.0f) .MaxValue(180.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply X-axis rotation FRotator Rotator(NewValue, 0.0f, 0.0f); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); FMatrix NewMatrix = Matrix * RotationMatrix; SetMatrix(NewMatrix); }) ] ]; // Add Y-axis rotation RotationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("Y Rotation:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-180.0f) .MaxValue(180.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply Y-axis rotation FRotator Rotator(0.0f, NewValue, 0.0f); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); FMatrix NewMatrix = Matrix * RotationMatrix; SetMatrix(NewMatrix); }) ] ]; // Add Z-axis rotation RotationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("Z Rotation:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-180.0f) .MaxValue(180.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply Z-axis rotation FRotator Rotator(0.0f, 0.0f, NewValue); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); FMatrix NewMatrix = Matrix * RotationMatrix; SetMatrix(NewMatrix); }) ] ]; // Create translation controls TSharedPtr TranslationControls = SNew(SVerticalBox); // Add X-axis translation TranslationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("X Position:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-1000.0f) .MaxValue(1000.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply X-axis translation FVector Translation = Matrix.GetOrigin(); Translation.X = NewValue; FMatrix NewMatrix = Matrix; NewMatrix.SetOrigin(Translation); SetMatrix(NewMatrix); }) ] ]; // Add Y-axis translation TranslationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("Y Position:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-1000.0f) .MaxValue(1000.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply Y-axis translation FVector Translation = Matrix.GetOrigin(); Translation.Y = NewValue; FMatrix NewMatrix = Matrix; NewMatrix.SetOrigin(Translation); SetMatrix(NewMatrix); }) ] ]; // Add Z-axis translation TranslationControls->AddSlot() .AutoHeight() .Padding(0.0f, 4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString("Z Position:")) .MinDesiredWidth(80.0f) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) .MinValue(-1000.0f) .MaxValue(1000.0f) .Delta(1.0f) .Value(0.0f) .OnValueChanged_Lambda([this](float NewValue) { // Apply Z-axis translation FVector Translation = Matrix.GetOrigin(); Translation.Z = NewValue; FMatrix NewMatrix = Matrix; NewMatrix.SetOrigin(Translation); SetMatrix(NewMatrix); }) ] ]; // Create main layout ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 8.0f) [ SNew(STextBlock) .Text(FText::FromString("Cutting Plane Transformation Matrix")) .Font(FAppStyle::GetFontStyle("HeadingFont")) ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(4.0f) [ GridPanel.ToSharedRef() ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 8.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(4.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromString("Rotation Controls")) .Font(FAppStyle::GetFontStyle("NormalFontBold")) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 4.0f) [ RotationControls.ToSharedRef() ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 8.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(4.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromString("Translation Controls")) .Font(FAppStyle::GetFontStyle("NormalFontBold")) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 4.0f) [ TranslationControls.ToSharedRef() ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 8.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(4.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromString("Presets")) .Font(FAppStyle::GetFontStyle("NormalFontBold")) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 4.0f) [ ButtonPanel.ToSharedRef() ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 8.0f) .HAlign(HAlign_Center) [ ApplyCutButton.ToSharedRef() ] ]; // Initialize the UI UpdateUI(); } void SMatrixInputWidget::SetMatrix(const FMatrix& InMatrix) { Matrix = InMatrix; UpdateUI(); // Notify listeners of the change if (OnMatrixChanged.IsBound()) { OnMatrixChanged.Execute(Matrix); } } FMatrix SMatrixInputWidget::GetMatrix() const { return Matrix; } void SMatrixInputWidget::ResetToIdentity() { SetMatrix(FMatrix::Identity); } void SMatrixInputWidget::SetFromEulerAngles(float Roll, float Pitch, float Yaw) { FRotator Rotator(Pitch, Yaw, Roll); FMatrix RotationMatrix = FRotationMatrix::Make(Rotator); SetMatrix(RotationMatrix); } void SMatrixInputWidget::SetFromTranslation(const FVector& Translation) { FMatrix TranslationMatrix = FMatrix::Identity; TranslationMatrix.SetOrigin(Translation); SetMatrix(TranslationMatrix); } void SMatrixInputWidget::SetFromTransform(const FTransform& Transform) { SetMatrix(Transform.ToMatrixWithScale()); } TSharedPtr> SMatrixInputWidget::CreateMatrixElementWidget(int32 Row, int32 Col) { return SNew(SNumericEntryBox) .Value_Lambda([this, Row, Col]() -> float { return Matrix.M[Row][Col]; }) .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateSP(this, &SMatrixInputWidget::OnMatrixElementChanged, Row, Col)) .AllowSpin(true) .Delta(0.1f) .MinValue(-10000.0f) .MaxValue(10000.0f) .MinSliderValue(-10.0f) .MaxSliderValue(10.0f); } void SMatrixInputWidget::OnMatrixElementChanged(float NewValue, int32 Row, int32 Col) { // Update the matrix element Matrix.M[Row][Col] = NewValue; // Notify listeners of the change if (OnMatrixChanged.IsBound()) { OnMatrixChanged.Execute(Matrix); } } void SMatrixInputWidget::UpdateUI() { // Update each numeric entry box with the current matrix values for (int32 Row = 0; Row < Rows; ++Row) { for (int32 Col = 0; Col < Columns; ++Col) { int32 Index = Row * Columns + Col; if (NumericEntryBoxes.IsValidIndex(Index) && NumericEntryBoxes[Index].IsValid()) { // In UE5.5.4, we need to rebuild the grid panel to update the value NumericEntryBoxes[Index] = CreateMatrixElementWidget(Row, Col); } } } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION