You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
It was the initial bug, but while looking at it, I noticed that the MultilineEditableTextBox was containing both a Font via FEditableTextBoxStyle, and another one via FTextBlockStyle, thus being error prone / inconsistent.
In order to fix the underlying issue (in addition to fix the initial bug), I removed the Font from FEditableTextBoxStyle, and moved the FTextBlockStyle from MultilineEditableTextBox to FEditableTextBoxStyle.
It solves the duplication issue and so make it clear where the Font should be set/read from.
However, as the text block style is now embedded in the editable text box style, it cannot be initialized the exact same way, and I had to do some changes to ensure there was no regression, by configuring various FEditableTextBoxStyle in some style files. I also change the default value for TextBlockStyle to better match our default theme.
-Default font is not set for text widgets.
EditableWidget: ensure to have a default font, and to set the style when calling SynchronizeProperties to ensure it reacts directly without having to force a refresh (by moving the widget for instance)
Bonus:
-Move to cpp some private methods that where 'forced' inline (and we were using function pointer on them). It will avoid some noise in public interface and speed up iteration / compile time when playing with them.
#jira UE-96464
#jira UE-137126
[RN] MultilineEditableTextBox was containing both a Font via FEditableTextBoxStyle, and another one via FTextBlockStyle, thus being error prone / inconsistent.The Font from FEditableTextBoxStyle has been removed, and the FTextBlockStyle moved from MultilineEditableTextBox to FEditableTextBoxStyle. It solves the duplication issue and so make it clear where the Font should be set/read from.
However, as the FTextBlockStyle is now embedded in the FEditableTextBoxStyle, it cannot be initialized the exact same way, and you can now configure the FTextBlockStyle of FEditableTextBoxStyle when creating one from scratch, by calling SetTextStyle on it.
Test
- created a Widget blueprint with different editable types combination: multiline or single line, box or no box.
-Validated that everything was reacting live as expected now.
-Created a blue print to set the text style and validated it was working.
-Create data with old version, then open it with updated version to validate that the visual was still the same and deprecation of style working as expected.
-checked different places in the editor using variation of editable text to ensure they were behaving as before (detail view, console command entry, comment on blueprint node).
#preflight 63344b9f110bb3721ef8aa77
[CL 22232366 by yohann dossantos in ue5-main branch]
404 lines
12 KiB
C++
404 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SDeviceOutputLog.h"
|
|
#include "Framework/Text/TextLayout.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Widgets/Input/SMultiLineEditableTextBox.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "Interfaces/ITargetPlatformManagerModule.h"
|
|
#include "PlatformInfo.h"
|
|
#include "OutputLogStyle.h"
|
|
#include "SSimpleComboButton.h"
|
|
|
|
static bool IsSupportedPlatform(ITargetPlatform* Platform)
|
|
{
|
|
check(Platform);
|
|
return Platform->SupportsFeature( ETargetPlatformFeatures::DeviceOutputLog );
|
|
}
|
|
|
|
|
|
void SDeviceOutputLog::Construct( const FArguments& InArgs )
|
|
{
|
|
bAutoSelectDevice = InArgs._AutoSelectDevice;
|
|
|
|
MessagesTextMarshaller = FOutputLogTextLayoutMarshaller::Create(TArray<TSharedPtr<FOutputLogMessage>>(), &Filter);
|
|
|
|
MessagesTextBox = SNew(SMultiLineEditableTextBox)
|
|
.Style(FOutputLogStyle::Get(), "Log.TextBox")
|
|
.ForegroundColor(FLinearColor::Gray)
|
|
.Marshaller(MessagesTextMarshaller)
|
|
.IsReadOnly(true)
|
|
.AlwaysShowScrollbars(true)
|
|
.OnVScrollBarUserScrolled(this, &SDeviceOutputLog::OnUserScrolled)
|
|
.ContextMenuExtender(this, &SDeviceOutputLog::ExtendTextBoxMenu);
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Output log area
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
MessagesTextBox.ToSharedRef()
|
|
]
|
|
// The console input box
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(0.0f, 2.0f, 0.0f, 0.0f))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SAssignNew(TargetDeviceComboButton, SComboButton)
|
|
.ForegroundColor(FSlateColor::UseForeground())
|
|
.OnGetMenuContent(this, &SDeviceOutputLog::MakeDeviceComboButtonMenu)
|
|
.ContentPadding(FMargin(4.0f, 0.0f))
|
|
.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(16)
|
|
.HeightOverride(16)
|
|
[
|
|
SNew(SImage).Image(this, &SDeviceOutputLog::GetSelectedTargetDeviceBrush)
|
|
]
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(FOutputLogStyle::Get().GetFontStyle("NormalFontBold"))
|
|
.Text(this, &SDeviceOutputLog::GetSelectedTargetDeviceText)
|
|
]
|
|
]
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
|
|
.FillWidth(1)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SConsoleInputBox)
|
|
.ConsoleCommandCustomExec(this, &SDeviceOutputLog::ExecuteConsoleCommand)
|
|
.OnConsoleCommandExecuted(this, &SDeviceOutputLog::OnConsoleCommandExecuted)
|
|
// Always place suggestions above the input line for the output log widget
|
|
.SuggestionListPlacement( MenuPlacement_AboveAnchor )
|
|
]
|
|
]
|
|
];
|
|
|
|
bIsUserScrolled = false;
|
|
RequestForceScroll();
|
|
|
|
ITargetPlatform::OnDeviceDiscovered().AddRaw(this, &SDeviceOutputLog::HandleTargetPlatformDeviceDiscovered);
|
|
ITargetPlatform::OnDeviceLost().AddRaw(this, &SDeviceOutputLog::HandleTargetPlatformDeviceLost);
|
|
|
|
// Get list of available devices
|
|
for (ITargetPlatform* Platform : GetTargetPlatformManager()->GetTargetPlatforms())
|
|
{
|
|
if (IsSupportedPlatform(Platform))
|
|
{
|
|
TArray<ITargetDevicePtr> TargetDevices;
|
|
Platform->GetAllDevices(TargetDevices);
|
|
|
|
for (const ITargetDevicePtr& Device : TargetDevices)
|
|
{
|
|
if (Device.IsValid())
|
|
{
|
|
AddDeviceEntry(Device.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SDeviceOutputLog::~SDeviceOutputLog()
|
|
{
|
|
ITargetPlatform::OnDeviceDiscovered().RemoveAll(this);
|
|
ITargetPlatform::OnDeviceLost().RemoveAll(this);
|
|
|
|
// Clearing the pointer manually to ensure that when the pointed device output object is destroyed
|
|
// SDeviceOutputLog is still in a valid state in case CurrentDeviceOutputPtr wanted to dereference it.
|
|
CurrentDeviceOutputPtr.Reset();
|
|
}
|
|
|
|
void SDeviceOutputLog::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
// If auto-select is enabled request connecting to the default device and select it
|
|
if (!CurrentDevicePtr.IsValid() && bAutoSelectDevice)
|
|
{
|
|
int32 DefaultDeviceEntryIdx = DeviceList.IndexOfByPredicate([this](const TSharedPtr<FTargetDeviceEntry>& Other) {
|
|
ITargetDevicePtr PinnedPtr = Other->DeviceWeakPtr.Pin();
|
|
return PinnedPtr->IsDefault();
|
|
});
|
|
|
|
if (DeviceList.IsValidIndex(DefaultDeviceEntryIdx))
|
|
{
|
|
ITargetDevicePtr PinnedPtr = DeviceList[DefaultDeviceEntryIdx]->DeviceWeakPtr.Pin();
|
|
PinnedPtr->Connect();
|
|
OnDeviceSelectionChanged(DeviceList[DefaultDeviceEntryIdx]);
|
|
}
|
|
}
|
|
|
|
// If the device is selected but was not yet connected then the output router would not have been registered
|
|
if (CurrentDevicePtr.IsValid() && !CurrentDeviceOutputPtr.IsValid())
|
|
{
|
|
ITargetDevicePtr PinnedPtr = CurrentDevicePtr->DeviceWeakPtr.Pin();
|
|
if (PinnedPtr.IsValid() && PinnedPtr->IsConnected())
|
|
{
|
|
// It is now connected so register the output router
|
|
CurrentDeviceOutputPtr = PinnedPtr->CreateDeviceOutputRouter(this);
|
|
}
|
|
}
|
|
|
|
FScopeLock ScopeLock(&BufferedLinesSynch);
|
|
if (BufferedLines.Num() > 0)
|
|
{
|
|
for (const FBufferedLine& Line : BufferedLines)
|
|
{
|
|
MessagesTextMarshaller->AppendPendingMessage(Line.Data, Line.Verbosity, Line.Category);
|
|
}
|
|
BufferedLines.Empty(32);
|
|
}
|
|
|
|
SOutputLog::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
|
}
|
|
|
|
void SDeviceOutputLog::Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category)
|
|
{
|
|
FScopeLock ScopeLock(&BufferedLinesSynch);
|
|
BufferedLines.Emplace(V, Category, Verbosity);
|
|
}
|
|
|
|
bool SDeviceOutputLog::CanBeUsedOnAnyThread() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void SDeviceOutputLog::ExecuteConsoleCommand(const FString& ExecCommand)
|
|
{
|
|
if (CurrentDevicePtr.IsValid())
|
|
{
|
|
ITargetDevicePtr PinnedPtr = CurrentDevicePtr->DeviceWeakPtr.Pin();
|
|
if (PinnedPtr.IsValid())
|
|
{
|
|
PinnedPtr->ExecuteConsoleCommand(ExecCommand);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDeviceOutputLog::HandleTargetPlatformDeviceLost(ITargetDeviceRef LostDevice)
|
|
{
|
|
FTargetDeviceId LostDeviceId = LostDevice->GetId();
|
|
|
|
if (CurrentDevicePtr.IsValid() && CurrentDevicePtr->DeviceId.GetDeviceName() == LostDeviceId.GetDeviceName())
|
|
{
|
|
// Kill device output object, but do not clean up output in the window
|
|
CurrentDeviceOutputPtr.Reset();
|
|
}
|
|
|
|
// Should not do it, but what if someone somewhere holds strong reference to a lost device?
|
|
for (const TSharedPtr<FTargetDeviceEntry>& EntryPtr : DeviceList)
|
|
{
|
|
if (EntryPtr->DeviceId.GetDeviceName() == LostDeviceId.GetDeviceName())
|
|
{
|
|
EntryPtr->DeviceWeakPtr = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDeviceOutputLog::HandleTargetPlatformDeviceDiscovered(ITargetDeviceRef DiscoveredDevice)
|
|
{
|
|
FTargetDeviceId DiscoveredDeviceId = DiscoveredDevice->GetId();
|
|
|
|
int32 ExistingEntryIdx = DeviceList.IndexOfByPredicate([&](const TSharedPtr<FTargetDeviceEntry>& Other) {
|
|
return (Other->DeviceId.GetDeviceName() == DiscoveredDeviceId.GetDeviceName());
|
|
});
|
|
|
|
if (DeviceList.IsValidIndex(ExistingEntryIdx))
|
|
{
|
|
DeviceList[ExistingEntryIdx]->DeviceWeakPtr = DiscoveredDevice;
|
|
if (CurrentDevicePtr == DeviceList[ExistingEntryIdx])
|
|
{
|
|
if (!CurrentDeviceOutputPtr.IsValid())
|
|
{
|
|
if (CurrentDevicePtr.IsValid())
|
|
{
|
|
ITargetDevicePtr PinnedPtr = CurrentDevicePtr->DeviceWeakPtr.Pin();
|
|
if (PinnedPtr.IsValid() && PinnedPtr->IsConnected())
|
|
{
|
|
CurrentDeviceOutputPtr = PinnedPtr->CreateDeviceOutputRouter(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddDeviceEntry(DiscoveredDevice);
|
|
}
|
|
}
|
|
|
|
ITargetDevicePtr SDeviceOutputLog::GetSelectedTargetDevice() const
|
|
{
|
|
ITargetDevicePtr PinnedPtr = nullptr;
|
|
if (CurrentDevicePtr.IsValid())
|
|
{
|
|
PinnedPtr = CurrentDevicePtr->DeviceWeakPtr.Pin();
|
|
}
|
|
return PinnedPtr;
|
|
}
|
|
|
|
void SDeviceOutputLog::AddDeviceEntry(ITargetDeviceRef TargetDevice)
|
|
{
|
|
if (FindDeviceEntry(TargetDevice->GetId()))
|
|
{
|
|
return;
|
|
}
|
|
const FString DummyIOSDeviceName(FString::Printf(TEXT("All_iOS_On_%s"), FPlatformProcess::ComputerName()));
|
|
const FString DummyTVOSDeviceName(FString::Printf(TEXT("All_tvOS_On_%s"), FPlatformProcess::ComputerName()));
|
|
if (TargetDevice->GetId().GetDeviceName().Equals(DummyIOSDeviceName, ESearchCase::IgnoreCase) ||
|
|
TargetDevice->GetId().GetDeviceName().Equals(DummyTVOSDeviceName, ESearchCase::IgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
using namespace PlatformInfo;
|
|
FName DeviceIconStyleName = TargetDevice->GetTargetPlatform().GetPlatformInfo().GetIconStyleName(EPlatformIconSize::Normal);
|
|
|
|
TSharedPtr<FTargetDeviceEntry> DeviceEntry = MakeShareable(new FTargetDeviceEntry());
|
|
|
|
DeviceEntry->DeviceId = TargetDevice->GetId();
|
|
DeviceEntry->DeviceIconBrush = FOutputLogStyle::Get().GetBrush(DeviceIconStyleName);
|
|
DeviceEntry->DeviceWeakPtr = TargetDevice;
|
|
|
|
DeviceList.Add(DeviceEntry);
|
|
}
|
|
|
|
bool SDeviceOutputLog::FindDeviceEntry(FTargetDeviceId InDeviceId)
|
|
{
|
|
// Should not do it, but what if someone somewhere holds strong reference to a lost device?
|
|
for (const TSharedPtr<FTargetDeviceEntry>& EntryPtr : DeviceList)
|
|
{
|
|
if (EntryPtr->DeviceId.GetDeviceName() == InDeviceId.GetDeviceName())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SDeviceOutputLog::OnDeviceSelectionChanged(FTargetDeviceEntryPtr DeviceEntry)
|
|
{
|
|
CurrentDeviceOutputPtr.Reset();
|
|
OnClearLog();
|
|
CurrentDevicePtr = DeviceEntry;
|
|
|
|
if (DeviceEntry.IsValid())
|
|
{
|
|
ITargetDevicePtr PinnedPtr = DeviceEntry->DeviceWeakPtr.Pin();
|
|
if (PinnedPtr.IsValid() && PinnedPtr->IsConnected())
|
|
{
|
|
CurrentDeviceOutputPtr = PinnedPtr->CreateDeviceOutputRouter(this);
|
|
}
|
|
OnSelectedDeviceChangedDelegate.ExecuteIfBound(GetSelectedTargetDevice());
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SDeviceOutputLog::MakeDeviceComboButtonMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
|
|
for (const FTargetDeviceEntryPtr& TargetDeviceEntryPtr : DeviceList)
|
|
{
|
|
TSharedRef<SWidget> MenuEntryWidget = GenerateWidgetForDeviceComboBox(TargetDeviceEntryPtr);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FUIAction(FExecuteAction::CreateSP(this, &SDeviceOutputLog::OnDeviceSelectionChanged, TargetDeviceEntryPtr)),
|
|
MenuEntryWidget
|
|
);
|
|
}
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
TSharedRef<SWidget> SDeviceOutputLog::GenerateWidgetForDeviceComboBox(const FTargetDeviceEntryPtr& DeviceEntry) const
|
|
{
|
|
return
|
|
SNew(SBox)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(24)
|
|
.HeightOverride(24)
|
|
[
|
|
SNew(SImage).Image(GetTargetDeviceBrush(DeviceEntry))
|
|
]
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(FMargin(4.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock).Text(this, &SDeviceOutputLog::GetTargetDeviceText, DeviceEntry)
|
|
]
|
|
];
|
|
}
|
|
|
|
const FSlateBrush* SDeviceOutputLog::GetTargetDeviceBrush(FTargetDeviceEntryPtr DeviceEntry) const
|
|
{
|
|
if (DeviceEntry.IsValid())
|
|
{
|
|
return DeviceEntry->DeviceIconBrush;
|
|
}
|
|
else
|
|
{
|
|
return FOutputLogStyle::Get().GetBrush("Launcher.Instance_Unknown");
|
|
}
|
|
}
|
|
|
|
const FSlateBrush* SDeviceOutputLog::GetSelectedTargetDeviceBrush() const
|
|
{
|
|
return GetTargetDeviceBrush(CurrentDevicePtr);
|
|
}
|
|
|
|
FText SDeviceOutputLog::GetTargetDeviceText(FTargetDeviceEntryPtr DeviceEntry) const
|
|
{
|
|
if (DeviceEntry.IsValid())
|
|
{
|
|
ITargetDevicePtr PinnedPtr = DeviceEntry->DeviceWeakPtr.Pin();
|
|
if (PinnedPtr.IsValid() && PinnedPtr->IsConnected())
|
|
{
|
|
FString DeviceName = PinnedPtr->GetName();
|
|
return FText::FromString(DeviceName);
|
|
}
|
|
else
|
|
{
|
|
FString DeviceName = DeviceEntry->DeviceId.GetDeviceName();
|
|
return FText::Format(NSLOCTEXT("OutputLog", "TargetDeviceOffline", "{0} (Offline)"), FText::FromString(DeviceName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NSLOCTEXT("OutputLog", "UnknownTargetDevice", "<Unknown device>");
|
|
}
|
|
}
|
|
|
|
FText SDeviceOutputLog::GetSelectedTargetDeviceText() const
|
|
{
|
|
return GetTargetDeviceText(CurrentDevicePtr);
|
|
}
|