mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
Dialog screens (tabbed, two-pane, simple and the others built on UIBaseDialogScreen) center their content at up to 1400 wide, instead of stretching settings rows and buttons across very wide windows. On large screens, those screens and the main screen also get an inset on all sides, since controls right at the edge are hard to see there. It grows with the screen's smaller dimension, from nothing at 600 to the full inset at 1000, and is always full on TVs. The game, popups, and the screens that position things to match the game's screen (display layout, touch control layout and tests, custom button and visual mapping) are left as they were. The root's bounds are computed by UIScreen::GetContentBounds, since UI::Margins can't hold widths this large. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
254 lines
6.7 KiB
C++
254 lines
6.7 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(), GetContentBounds(*screenManager()->getUIContext()), root_);
|
|
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());
|
|
}
|
|
|
|
// On TVs and big monitors, controls out at the edges are hard to see and far apart. Grows with the
|
|
// screen, from nothing at 600 high (or wide, if smaller) to the full inset at 1000, so resizing a window
|
|
// doesn't make it jump.
|
|
static float LargeScreenInset() {
|
|
const float fullInset = 40.0f;
|
|
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {
|
|
return fullInset;
|
|
}
|
|
const float size = (float)std::min(g_display.dp_xres, g_display.dp_yres);
|
|
return fullInset * std::clamp((size - 600.0f) / 400.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
Bounds UIScreen::GetContentBounds(const UIContext &dc) const {
|
|
const UI::Margins margins = RootMargins();
|
|
Bounds bounds = dc.GetLayoutBounds(LayoutMode(), UseImmersiveMode());
|
|
bounds.x += margins.left;
|
|
bounds.y += margins.top;
|
|
bounds.w -= margins.horiz();
|
|
bounds.h -= margins.vert();
|
|
if (!LimitContentArea()) {
|
|
return bounds;
|
|
}
|
|
|
|
const float inset = LargeScreenInset();
|
|
bounds.x += inset;
|
|
bounds.y += inset;
|
|
bounds.w -= inset * 2.0f;
|
|
bounds.h -= inset * 2.0f;
|
|
const float maxWidth = MaxContentWidth();
|
|
if (maxWidth > 0.0f && bounds.w > maxWidth) {
|
|
bounds.x += (bounds.w - maxWidth) * 0.5f;
|
|
bounds.w = maxWidth;
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
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, GetContentBounds(uiContext), root_);
|
|
}
|
|
|
|
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);
|
|
}
|