Files
ppsspp/Common/UI/UIScreen.cpp
T
Henrik RydgårdandClaude Opus 5.5 27899cc875 UI: Give views in lazily created tabs the views requester token
GetRequesterToken() only returns the views token inside CreateViews(),
but tabbed screens create every tab other than the first on demand, and
the persist restore after CreateViews() can create one too. So text
inputs and file choosers there (Networking tab nickname, search filter,
RemoteISO and achievement settings) registered their [this] callbacks
under the screen token, which a recreate doesn't forget. Open the native
input box, rotate, press OK, and the callback wrote through the deleted
view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:32:49 +02:00

218 lines
5.5 KiB
C++

#include <algorithm>
#include "Common/Log.h"
#include "Common/System/Display.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/Context.h"
#include "Common/UI/ScreenManager.h"
#include "Common/UI/Root.h"
#include "Common/Render/DrawBuffer.h"
static constexpr bool ClickDebug = false;
UIScreen::UIScreen() : Screen() {
lastOrientation_ = GetDeviceOrientation();
}
UIScreen::~UIScreen() {
delete root_;
}
// This is the source of truth for orientation for configuration and rendering.
DeviceOrientation UIScreen::GetDeviceOrientation() const {
// TODO: On some platforms, we can do a more sophisticated check.
return g_display.GetDeviceOrientation();
}
void UIScreen::DoRecreateViews() {
if (!recreateViews_) {
return;
}
UI::PersistMap persisted;
bool persisting = root_ != nullptr;
if (persisting) {
root_->PersistData(UI::PERSIST_SAVE, "root", persisted);
}
// Pending requests from the views (like an open text input dialog) would call back into deleted views.
ForgetViewRequests();
delete root_;
root_ = nullptr;
SetCreatingViews(true);
CreateViews();
UI::View *defaultView = root_ ? root_->GetDefaultFocusView() : nullptr;
if (defaultView && defaultView->GetVisibility() == UI::V_VISIBLE) {
defaultView->SetFocus(UI::FocusFlags::CAUSE_OTHER);
}
recreateViews_ = false;
if (persisting && root_ != nullptr) {
// Still creating views: restoring a TabHolder's current tab can create the tab.
root_->PersistData(UI::PERSIST_RESTORE, "root", persisted);
// Update layout and refocus so things scroll into view.
// This is for resizing down, when focused on something now offscreen.
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), RootMargins(), root_, LayoutMode(), UseImmersiveMode());
UI::View *focused = UI::GetFocusedView();
if (focused) {
root_->SubviewFocused(focused);
}
}
SetCreatingViews(false);
}
bool UIScreen::touch(const TouchInput &touch) {
if (ClickDebug && root_ && (touch.flags & TouchInputFlags::DOWN)) {
INFO_LOG(Log::System, "Touch down!");
std::vector<UI::View *> views;
root_->Query(touch.x, touch.y, views);
for (auto view : views) {
INFO_LOG(Log::System, "%s", view->DescribeLog().c_str());
}
}
if (!ignoreInput_ && root_) {
UI::TouchEvent(touch, root_);
}
return true;
}
void UIScreen::axis(const AxisInput &axis) {
if (!ignoreInput_ && root_) {
UI::AxisEvent(axis, root_);
}
}
bool UIScreen::key(const KeyInput &key) {
if (!ignoreInput_ && root_) {
return UI::KeyEvent(key, root_);
} else {
return false;
}
}
void UIScreen::update() {
DeviceOrientation orientation = GetDeviceOrientation();
if (orientation != lastOrientation_) {
RecreateViews();
lastOrientation_ = orientation;
}
DoRecreateViews();
if (root_) {
DialogResult result = UpdateViewHierarchy(root_, AllowFocusMovement());
if (result != DR_NONE) {
TriggerFinish(result);
}
}
}
void UIScreen::deviceLost() {
if (root_)
root_->DeviceLost();
}
void UIScreen::deviceRestored(Draw::DrawContext *draw) {
if (root_)
root_->DeviceRestored(draw);
}
Bounds UIScreen::GetLayoutBounds(UIContext &dc) const {
return dc.GetLayoutBounds(LayoutMode(), UseImmersiveMode());
}
ScreenRenderFlags UIScreen::render(ScreenRenderMode mode) {
// A screen rendered under a popup must keep its views: the popup's callbacks point into them (the choice
// that opened it, the value it writes to). update() recreates them once the screen is on top again.
if (!root_ || !screenManager()->HasScreenAbove(this)) {
DoRecreateViews();
}
UIContext &uiContext = *screenManager()->getUIContext();
if (root_) {
UI::LayoutViewHierarchy(uiContext, RootMargins(), root_, LayoutMode(), UseImmersiveMode());
}
uiContext.PushTransform({translation_, scale_, alpha_});
uiContext.Begin();
DrawBackground(uiContext);
if (root_) {
root_->Draw(uiContext);
}
uiContext.Flush();
DrawForeground(uiContext);
uiContext.Flush();
uiContext.PopTransform();
return ScreenRenderFlags::NONE;
}
TouchInput UIScreen::transformTouch(const TouchInput &touch) {
TouchInput updated = touch;
float x = touch.x - translation_.x;
float y = touch.y - translation_.y;
// Scale around the center as the origin.
updated.x = (x - g_display.dp_xres * 0.5f) / scale_.x + g_display.dp_xres * 0.5f;
updated.y = (y - g_display.dp_yres * 0.5f) / scale_.y + g_display.dp_yres * 0.5f;
return updated;
}
void UIScreen::TriggerFinish(DialogResult result) {
// From here on, this dialog cannot receive input.
ignoreInput_ = true;
screenManager()->finishDialog(this, result);
}
bool UIDialogScreen::key(const KeyInput &key) {
bool retval = UIScreen::key(key);
if (!retval && (key.flags & KeyInputFlags::DOWN) && UI::IsEscapeKey(key)) {
if (finished_) {
ERROR_LOG(Log::System, "Screen already finished");
} else if (!firstFrame_) {
finished_ = true;
TriggerFinish(DR_BACK);
UI::PlayUISound(UI::UISound::BACK);
}
return true;
}
return retval;
}
void UIDialogScreen::sendMessage(UIMessage message, const char *value) {
Screen *screen = screenManager()->dialogParent(this);
if (screen) {
screen->sendMessage(message, value);
}
}
UIDialogScreen::~UIDialogScreen() {
System_NotifyUIEvent(UIEventNotification::DIALOG_CLOSED);
}
bool UIScreen::IsOnTop() const {
return screenManager()->topScreen() == this;
}
void UIScreen::OnBack(UI::EventParams &e) {
TriggerFinish(DR_BACK);
}
void UIScreen::OnOK(UI::EventParams &e) {
TriggerFinish(DR_OK);
}
void UIScreen::OnCancel(UI::EventParams &e) {
TriggerFinish(DR_CANCEL);
}