Select Git revision
custom_config.h
SDasherWidget.cpp 19.11 KiB
#include "SDasherWidget.h"
#include "SlateOptMacros.h"
#include "Styling/CoreStyle.h"
#include "Brushes/SlateColorBrush.h"
#include "Rendering/DrawElements.h"
#include "DasherInterface.h"
#include "IDisplayCluster.h"
#include "Cluster/DisplayClusterClusterEvent.h"
#include "Cluster/IDisplayClusterClusterManager.h"
#include "Utility/RWTHVRUtilities.h"
#include "Components/SlateWrapperTypes.h"
#include "Framework/Application/SlateApplication.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
// ++ This is needed in order to use the localization macro LOCTEXT
#define LOCTEXT_NAMESPACE "SStandardSlateWidget"
//Set the state if we're in the editor or not.
void SDasherWidget::SetEditor(bool EditorState)
{
IsEditor = EditorState;
}
//Pause the input
void SDasherWidget::PauseInput()
{
if (!URWTHVRUtilities::IsPrimaryNode()) return;
InputPaused = true;
}
//Unpause the input
void SDasherWidget::UnpauseInput()
{
if (!URWTHVRUtilities::IsPrimaryNode()) return;
InputPaused = false;
}
//Event Handlers
//Mouse position saved for mouse Input
FReply SDasherWidget::HandleMouseMoveEvent(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if (!InputPaused)
{
if (CurrentlyUsingVectorInput) return FReply::Unhandled();
//The mouse event only contains the Screen Space Position
const FVector2D newMousePosition = Geometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
NewMousePosition.x = newMousePosition.X;
NewMousePosition.y = newMousePosition.Y;
}
return FReply::Handled();
}
FReply SDasherWidget::HandleMouseDownEvent(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if (!InputPaused)
{
if(URWTHVRUtilities::IsRoomMountedMode())
{
FDisplayClusterClusterEventBinary ClusterEvent;
ClusterEvent.EventId = DasherEventID + static_cast<uint8>(DasherEventType::Input_MouseDown);
IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventBinary(ClusterEvent, false);
UE_LOG(LogTemp, Log, TEXT("MouseDownSend"));
return FReply::Handled().LockMouseToWidget(AsShared());
}
if (CurrentlyUsingVectorInput)
{
CurrentlyUsingVectorInput = false;
return FReply::Handled().LockMouseToWidget(AsShared());
}
DasherMainInterface->KeyDown(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
MouseDownListeners.ExecuteIfBound();
}
return FReply::Handled().LockMouseToWidget(AsShared());
}
FReply SDasherWidget::HandleMouseUpEvent(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if(URWTHVRUtilities::IsRoomMountedMode())
{
FDisplayClusterClusterEventBinary ClusterEvent;
ClusterEvent.EventId = ClusterEvent.EventId = DasherEventID + static_cast<uint8>(DasherEventType::Input_MouseUp);
IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventBinary(ClusterEvent, false);
return FReply::Handled().ReleaseMouseLock();
}
if (CurrentlyUsingVectorInput)
{
CurrentlyUsingVectorInput = false;
return FReply::Handled().ReleaseMouseLock();
}
DasherMainInterface->KeyUp(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
MouseUpListeners.ExecuteIfBound();
return FReply::Handled().ReleaseMouseLock();
}
FReply SDasherWidget::HandleMouseDoubleClickEvent(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if (!InputPaused)
{
if(URWTHVRUtilities::IsRoomMountedMode())
{
FDisplayClusterClusterEventBinary ClusterEvent;
ClusterEvent.EventId = ClusterEvent.EventId = DasherEventID + static_cast<uint8>(DasherEventType::Input_MouseDown);
IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventBinary(ClusterEvent, false);
return FReply::Handled();
}
if (CurrentlyUsingVectorInput)
{
CurrentlyUsingVectorInput = false;
return FReply::Handled();
}
DasherMainInterface->KeyUp(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
}
return FReply::Handled();
}
//Set in the HandleMouseMoveEvent or via Set InputVector
//See tick for geometry things that could be use to modify this.
FVector2D SDasherWidget::GetCursorPosition() const
{
return FVector2D(CursorPosition.x, CursorPosition.y);
}
bool SDasherWidget::GetScreenCoords(screenint& iX, screenint& iY, Dasher::CDasherView* pView)
{
const FVector2D Position = GetCursorPosition();
iX = Position.X;
iY = Position.Y;
return true;
}
void SDasherWidget::HandleClusterEvent(const FDisplayClusterClusterEventBinary& Event)
{
switch(static_cast<DasherEventType>(Event.EventId - DasherEventID))
{
case DasherEventType::Input_MouseDown:
DasherMainInterface->KeyDown(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
MouseDownListeners.ExecuteIfBound();
UE_LOG(LogTemp, Log, TEXT("MouseDownReceived"));
break;
case DasherEventType::Input_MouseUp:
DasherMainInterface->KeyUp(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
MouseUpListeners.ExecuteIfBound();
break;
case DasherEventType::Tick:
double currentTime = 0;
FMemory::Memcpy(¤tTime, Event.EventData.GetData(), sizeof(currentTime));
FMemory::Memcpy(&CursorPosition.x, Event.EventData.GetData() + sizeof(currentTime), sizeof(CursorPosition.x));
FMemory::Memcpy(&CursorPosition.y, Event.EventData.GetData() + sizeof(currentTime) + sizeof(CursorPosition.x), sizeof(CursorPosition.y));
DasherMainInterface->Tick(static_cast<unsigned long>(currentTime * 1000.0));
break;
}
}
void SDasherWidget::InputVector(const FVector2D InputVector)
{
if (!InputPaused) {
if (!CurrentlyUsingVectorInput) return;
const FVector2D pos = DasherMainInterface->ConvertDasher2Screen(Dasher::CDasherModel::ORIGIN_X, Dasher::CDasherModel::ORIGIN_Y)
+ InputVector * FVector2D(1.0f, -1.0f) * (GetHeight() / 2);
CursorPosition.x = pos.X;
CursorPosition.y = pos.Y;
}
}
void SDasherWidget::InputButton(bool Pressed)
{
if (!InputPaused) {
CurrentlyUsingVectorInput = true;
if (Pressed)
{
DasherMainInterface->KeyDown(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
}
else {
DasherMainInterface->KeyUp(FDateTime::Now().GetSecond() + FDateTime::Now().GetMillisecond(), Dasher::Keys::Primary_Input);
}
}
}
//The construction of the Dasher Widget, Dasher parameters are set here, a function to set them outside is not provided, but can be easily implemented
void SDasherWidget::Construct(const FArguments& InArgs)
{
//Initial resize, needed for setup
Width = InArgs._width;
Height = InArgs._height;
resize(Width, Height);
//initialize the font measuring service.
FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
//Setting up Dasher
static Dasher::XMLErrorDisplay display;
Dasher::XmlSettingsStore* Settings = new Dasher::XmlSettingsStore("Settings.xml", &display); //Gets deleted somewhere else
Settings->Load();
Settings->Save();
DasherMainInterface = MakeShared<Dasher::DasherInterface>(Settings);
DasherMainInterface->GetModuleManager()->RegisterInputDeviceModule(this, true);
DasherMainInterface->SetScreen(this);
DasherMainInterface->SetBuffer(0);
DasherMainInterface->SetCharEnteredCallback([this](FString Char, FString Buffer) {CharacterEnteredFlag = true; AlteredChar = Char; });
DasherMainInterface->SetCharDeletedCallback([this](FString Char, FString Buffer) {CharacterDeletedFlag = true; AlteredChar = Char; });
// Bind the cluster events that manage the door state.
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
if (URWTHVRUtilities::IsRoomMountedMode() && ClusterManager && !ClusterEventListener.IsBound())
{
ClusterEventListener =
FOnClusterEventBinaryListener::CreateSP(this, &SDasherWidget::HandleClusterEvent);
ClusterManager->AddClusterEventBinaryListener(ClusterEventListener);
}
if (URWTHVRUtilities::IsPrimaryNode())
{
//Set up the Event Handlers for Mouse Movement etc.
SetOnMouseMove(FPointerEventHandler::CreateLambda([this](const FGeometry& Geometry, const FPointerEvent& MouseEvent) {return HandleMouseMoveEvent(Geometry, MouseEvent); }));
SetOnMouseButtonDown(FPointerEventHandler::CreateLambda([this](const FGeometry& Geometry, const FPointerEvent& MouseEvent) {return HandleMouseDownEvent(Geometry, MouseEvent); }));
SetOnMouseButtonUp(FPointerEventHandler::CreateLambda([this](const FGeometry& Geometry, const FPointerEvent& MouseEvent) {return HandleMouseUpEvent(Geometry, MouseEvent); }));
SetOnMouseDoubleClick(FPointerEventHandler::CreateLambda([this](const FGeometry& Geometry, const FPointerEvent& MouseEvent) {return HandleMouseDoubleClickEvent(Geometry, MouseEvent); })); //We treat a double click of the mouse as a lift of the mouse button.
}
}
void SDasherWidget::SetParameter(FString& ParameterName, bool Value) {
DasherMainInterface->SetBoolParameter(Dasher::Settings::GetParameter(TCHAR_TO_UTF8(*ParameterName)).first, Value);
}
void SDasherWidget::SetParameter(FString& ParameterName, int64 Value) {
DasherMainInterface->SetLongParameter(Dasher::Settings::GetParameter(TCHAR_TO_UTF8(*ParameterName)).first, Value);
}
void SDasherWidget::SetParameter(FString& ParameterName, FString Value) {
DasherMainInterface->SetStringParameter(Dasher::Settings::GetParameter(TCHAR_TO_UTF8(*ParameterName)).first, TCHAR_TO_UTF8(*Value));
}
//paints our stuff, then lets compoundwidget (super::) draw its stuff
int32 SDasherWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
////this doesn't draw anything red, we just need a brush. Could probably find a better "empty" brush
auto MyBrush = FSlateColorBrush(FColor::Red);
FFilledRect* RectObject;
FWriting* WritingObject;
FPolyLine* LineObject;
FVector2D Size;
FString Text;
FSlateFontInfo Font;
TArray<TUniquePtr<DasherDrawGeometry>>& GeometryBuffer = *FrontBuffer;
for (TUniquePtr<DasherDrawGeometry>& GeneralObject : GeometryBuffer) {
switch (GeneralObject->Type)
{
case GeometryType::Rectangle:
RectObject = static_cast<FFilledRect*>(GeneralObject.Get());
Size = { FMath::Abs(RectObject->top.X - RectObject->bottom.X), FMath::Abs(RectObject->top.Y - RectObject->bottom.Y) };
FSlateDrawElement::MakeBox(OutDrawElements, LayerId++, AllottedGeometry.ToPaintGeometry(Size, FSlateLayoutTransform(1, RectObject->top)), &MyBrush, ESlateDrawEffect::None, RectObject->color); //actually adds the box to the geometry
break;
case GeometryType::Writing:
WritingObject = static_cast<FWriting*>(GeneralObject.Get());
Font = FCoreStyle::GetDefaultFontStyle("Roboto", WritingObject->size, FFontOutlineSettings::NoOutline); //get the font
FSlateDrawElement::MakeText(OutDrawElements, LayerId++, AllottedGeometry.ToPaintGeometry(FSlateLayoutTransform(1, FVector2D(WritingObject->pos.X, WritingObject->pos.Y))), WritingObject->label, Font, ESlateDrawEffect::None, WritingObject->color);
break;
case GeometryType::PolyLine:
LineObject = static_cast<FPolyLine*>(GeneralObject.Get());
FSlateDrawElement::MakeLines(OutDrawElements, LayerId++, AllottedGeometry.ToPaintGeometry(), LineObject->points, ESlateDrawEffect::None, LineObject->color, LineObject->AntiAliasing, LineObject->linewidth);
break;
default: break;
}
}
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); //call the parent onPaint
}
//The tick function, we tick dasher in it and update the screen size for dasher
void SDasherWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) {
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
//don't tick in the editor
if (IsEditor || InputPaused) return;
//Needs to be updated in Tick due to event order in cluster mode
CursorPosition = NewMousePosition;
if(!URWTHVRUtilities::IsRoomMountedMode()){
DasherMainInterface->Tick(static_cast<unsigned long>(InCurrentTime * 1000.0)); //we need to provide ticks in milliseconds
}
else
if(URWTHVRUtilities::IsPrimaryNode())
{
FDisplayClusterClusterEventBinary ClusterEvent;
ClusterEvent.EventId = ClusterEvent.EventId = DasherEventID + static_cast<uint8>(DasherEventType::Tick);
ClusterEvent.EventData.SetNumUninitialized(sizeof(InCurrentTime) + sizeof(CursorPosition.x) + sizeof(CursorPosition.y));
FMemory::Memcpy(ClusterEvent.EventData.GetData(), &InCurrentTime, sizeof(InCurrentTime));
FMemory::Memcpy(ClusterEvent.EventData.GetData() + sizeof(InCurrentTime), &CursorPosition.x, sizeof(CursorPosition.x));
FMemory::Memcpy(ClusterEvent.EventData.GetData() + sizeof(InCurrentTime) + sizeof(CursorPosition.x), &CursorPosition.y, sizeof(CursorPosition.y));
IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventBinary(ClusterEvent, false);
}
//This probably doesn't have to be done every tick, but it does not seem to have a huge hit on performance
const FGeometry Geometry = GetTickSpaceGeometry();
const FIntPoint AbsoluteSize = Geometry.Size.IntPoint();
if (Width != AbsoluteSize.X || Height != AbsoluteSize.Y) {
Width = AbsoluteSize.X;
Height = AbsoluteSize.Y;
resize(Width, Height);
//tell dasher we resized the screen, but only if there's actually a screen
if (Width && Height) DasherMainInterface->ScreenResized(this);
}
}
std::pair<Dasher::screenint, Dasher::screenint> SDasherWidget::TextSize(Label* Label, unsigned Size)
{
const FSlateFontInfo Font = FCoreStyle::GetDefaultFontStyle("Roboto", Size, FFontOutlineSettings::NoOutline); //get the font
const FVector2D TextSize = FontMeasureService->Measure(FString(UTF8_TO_TCHAR(Label->m_strText.c_str())), Font, 1); //get the real size of the text, using the fontmeasuring service
return { static_cast<screenint>(TextSize.X), static_cast<screenint>(TextSize.Y) };
}
//Double Buffers are rotated here.
void SDasherWidget::Display() {
std::swap(FrontBuffer, BackBuffer);
BackBuffer->Empty();
if (CharacterEnteredFlag && CharacterDeletedFlag) {
CharacterSwitched.ExecuteIfBound(AlteredChar, GetBuffer());
}
else if (CharacterEnteredFlag) {
CharacterEntered.ExecuteIfBound(AlteredChar, GetBuffer());
}
else if (CharacterDeletedFlag) {
CharacterDeleted.ExecuteIfBound(AlteredChar, GetBuffer());
}
CharacterEnteredFlag = false;
CharacterDeletedFlag = false;
}
//Functions for Drawing
void SDasherWidget::DrawRectangle(Dasher::screenint x1, Dasher::screenint y1, Dasher::screenint x2, Dasher::screenint y2, const Dasher::ColorPalette::Color& color, const Dasher::ColorPalette::Color& outlineColor, int iThickness)
{
if (outlineColor == Dasher::ColorPalette::noColor) iThickness = 0; // Draw till brim if no outline color is given
if (color != Dasher::ColorPalette::noColor && !color.isFullyTransparent())
{
BackBuffer->Add(MakeUnique<FFilledRect>(FVector2D(x1 + iThickness, y1 + iThickness), FVector2D(x2 - iThickness, y2 - iThickness), FLinearColor(color.Red / 255.0f, color.Green / 255.0f, color.Blue / 255.0f, color.Alpha / 255.0f)));
}
if (iThickness && outlineColor != Dasher::ColorPalette::noColor && !outlineColor.isFullyTransparent())
{
const float hThickness = iThickness / 2.0f;
const FVector2D CornerMin = FVector2D(x1 + hThickness, y1 + hThickness);
const FVector2D CornerMax = FVector2D(x2 - hThickness, y2 - hThickness);
const FLinearColor oColor = FLinearColor(outlineColor.Red / 255.0f, outlineColor.Green / 255.0f, outlineColor.Blue / 255.0f, outlineColor.Alpha / 255.0f);
BackBuffer->Add(MakeUnique<FPolyLine>(TArray({ FVector2D(CornerMin.X, CornerMin.Y - hThickness),FVector2D(CornerMin.X, CornerMax.Y + hThickness) }), static_cast<float>(iThickness), oColor, false));
BackBuffer->Add(MakeUnique<FPolyLine>(TArray({ FVector2D(CornerMin.X - hThickness, CornerMax.Y),FVector2D(CornerMax.X + hThickness, CornerMax.Y) }), static_cast<float>(iThickness), oColor, false));
BackBuffer->Add(MakeUnique<FPolyLine>(TArray({ FVector2D(CornerMax.X, CornerMax.Y + hThickness),FVector2D(CornerMax.X, CornerMin.Y - hThickness) }), static_cast<float>(iThickness), oColor, false));
BackBuffer->Add(MakeUnique<FPolyLine>(TArray({ FVector2D(CornerMax.X + hThickness, CornerMin.Y),FVector2D(CornerMin.X - hThickness, CornerMin.Y) }), static_cast<float>(iThickness), oColor, false));
}
}
void SDasherWidget::DrawString(CDasherScreen::Label* lab, screenint x1, screenint y1, unsigned int iSize, const Dasher::ColorPalette::Color& color) {
BackBuffer->Add(MakeUnique<FWriting>(UTF8_TO_TCHAR(lab->m_strText.c_str()), FVector2D(x1, y1), static_cast<int>(iSize), FLinearColor(color.Red / 255.0f, color.Green / 255.0f, color.Blue / 255.0f, color.Alpha / 255.0f)));
}
void SDasherWidget::Polyline(CDasherScreen::point* points, int number, int iwidth, const Dasher::ColorPalette::Color& color) {
TArray<FVector2D> PointArray;
for (int i = 0; i < number; i++) {
FVector2D Point(points[i].x, points[i].y);
PointArray.Add(Point);
}
BackBuffer->Add(MakeUnique<FPolyLine>(PointArray, static_cast<float>(iwidth), FLinearColor(color.Red / 255.0f, color.Green / 255.0f, color.Blue / 255.0f, color.Alpha / 255.0f), true));
}
//techincally polygons are just multiple polylines. Dasher doesn't actually draw polygons in our case.
void SDasherWidget::Polygon(CDasherScreen::point* points, int number, const Dasher::ColorPalette::Color& fillcolor, const Dasher::ColorPalette::Color& outlinecolor, int iwidth) {
TArray<FVector2D> PointArray;
for (int i = 0; i < number; i++) {
FVector2D Point(points[i].x, points[i].y);
PointArray.Add(Point);
}
PointArray.Add(FVector2D(points[0].x, points[0].y));
BackBuffer->Add(MakeUnique<FPolyLine>(PointArray, static_cast<float>(iwidth), FLinearColor(outlinecolor.Red / 255.0f, outlinecolor.Green / 255.0f, outlinecolor.Blue / 255.0f, outlinecolor.Alpha / 255.0f), true));
}
//We pass through the contents of the dasher buffer
FString SDasherWidget::GetBuffer() const
{
return DasherMainInterface->GetBuffer();
}
void SDasherWidget::ResetBuffer()
{
DasherMainInterface->ResetBuffer();
}
void SDasherWidget::StartTraining(FString PathToTextFile)
{
DasherMainInterface->ImportTrainingFile(TCHAR_TO_UTF8(*FPaths::ConvertRelativePathToFull(PathToTextFile)));
}
// ++ We need to undefine this namespace after we finish creating the Slate widget
#undef LOCTEXT_NAMESPACE
END_SLATE_FUNCTION_BUILD_OPTIMIZATION